From 3a40664bc73892c54955ce1f2c11e6596bdf07f9 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 7 Jul 2023 22:38:10 -0600 Subject: [PATCH 0001/1204] initial draft of appsi interface to wntr --- pyomo/contrib/appsi/base.py | 2 +- pyomo/contrib/appsi/solvers/__init__.py | 1 + pyomo/contrib/appsi/solvers/wntr.py | 471 ++++++++++++++++++++++++ 3 files changed, 473 insertions(+), 1 deletion(-) create mode 100644 pyomo/contrib/appsi/solvers/wntr.py diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index ca7255d5628..00c945235e5 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -933,7 +933,7 @@ def update_config(self, val: UpdateConfig): def set_instance(self, model): saved_update_config = self.update_config - self.__init__() + self.__init__(only_child_vars=self._only_child_vars) self.update_config = saved_update_config self._model = model if self.use_extensions and cmodel_available: diff --git a/pyomo/contrib/appsi/solvers/__init__.py b/pyomo/contrib/appsi/solvers/__init__.py index df58a0cb245..20755d1eb07 100644 --- a/pyomo/contrib/appsi/solvers/__init__.py +++ b/pyomo/contrib/appsi/solvers/__init__.py @@ -3,3 +3,4 @@ from .cbc import Cbc from .cplex import Cplex from .highs import Highs +from .wntr import Wntr, WntrResults diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py new file mode 100644 index 00000000000..655a620077c --- /dev/null +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -0,0 +1,471 @@ +from pyomo.contrib.appsi.base import ( + PersistentBase, + PersistentSolver, + SolverConfig, + Results, + TerminationCondition, + PersistentSolutionLoader +) +from pyomo.core.expr.numeric_expr import ( + ProductExpression, + DivisionExpression, + PowExpression, + SumExpression, + MonomialTermExpression, + NegationExpression, + UnaryFunctionExpression, + LinearExpression, + AbsExpression, + NPV_ProductExpression, + NPV_DivisionExpression, + NPV_PowExpression, + NPV_SumExpression, + NPV_NegationExpression, + NPV_UnaryFunctionExpression, + NPV_AbsExpression, +) +from pyomo.common.errors import PyomoException +from pyomo.common.collections import ComponentMap +from pyomo.core.expr.numvalue import native_numeric_types +from typing import Dict, Optional, List +from pyomo.core.base.block import _BlockData +from pyomo.core.base.var import _GeneralVarData +from pyomo.core.base.param import _ParamData +from pyomo.core.base.constraint import _GeneralConstraintData +from pyomo.common.timing import HierarchicalTimer +from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler +from pyomo.common.dependencies import attempt_import +from pyomo.core.staleflag import StaleFlagManager +from pyomo.contrib.appsi.cmodel import cmodel, cmodel_available +wntr, wntr_available = attempt_import('wntr') +import wntr +import logging +import time +from pyomo.core.expr.visitor import ExpressionValueVisitor + + +logger = logging.getLogger(__name__) + + +class WntrConfig(SolverConfig): + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + super().__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, + ) + + +class WntrResults(Results): + def __init__(self, solver): + super().__init__() + self.wallclock_time = None + self.solution_loader = PersistentSolutionLoader(solver=solver) + + +class Wntr(PersistentBase, PersistentSolver): + def __init__(self, only_child_vars=True): + super().__init__(only_child_vars=only_child_vars) + self._config = WntrConfig() + self._solver_options = dict() + self._solver_model = None + self._symbol_map = SymbolMap() + self._labeler = None + self._pyomo_var_to_solver_var_map = dict() + self._pyomo_con_to_solver_con_map = dict() + self._pyomo_param_to_solver_param_map = dict() + self._needs_updated = True + self._last_results_object: Optional[WntrResults] = None + self._pyomo_to_wntr_visitor = PyomoToWntrVisitor( + self._pyomo_var_to_solver_var_map, + self._pyomo_param_to_solver_param_map + ) + + def available(self): + if wntr_available: + return self.Availability.FullLicense + else: + return self.Availability.NotFound + + def version(self): + return tuple(int(i) for i in wntr.__version__.split('.')) + + @property + def config(self) -> WntrConfig: + return self._config + + @config.setter + def config(self, val: WntrConfig): + self._config = val + + @property + def wntr_options(self): + return self._solver_options + + @wntr_options.setter + def wntr_options(self, val: Dict): + self._solver_options = val + + @property + def symbol_map(self): + return self._symbol_map + + def _solve(self, timer: HierarchicalTimer): + t0 = time.time() + opt = wntr.sim.solvers.NewtonSolver(self.wntr_options) + if self._needs_updated: + timer.start('set_structure') + self._solver_model.set_structure() + timer.stop('set_structure') + self._needs_updated = False + timer.start('newton solve') + status, msg, num_iter = opt.solve(self._solver_model) + timer.stop('newton solve') + tf = time.time() + + results = WntrResults(self) + results.wallclock_time = tf - t0 + if status == wntr.sim.solvers.SolverStatus.converged: + results.termination_condition = TerminationCondition.optimal + else: + results.termination_condition = TerminationCondition.error + results.best_feasible_objective = None + results.best_objective_bound = None + + if self.config.load_solution: + if status == wntr.sim.solvers.SolverStatus.converged: + timer.start('load solution') + self.load_vars() + timer.stop('load solution') + else: + raise RuntimeError( + 'A feasible solution was not found, so no solution can be loaded.' + 'Please set opt.config.load_solution=False and check ' + 'results.termination_condition and ' + 'results.best_feasible_objective before loading a solution.' + ) + return results + + def solve(self, model: _BlockData, timer: HierarchicalTimer = None) -> Results: + StaleFlagManager.mark_all_as_stale() + if self._last_results_object is not None: + self._last_results_object.solution_loader.invalidate() + if timer is None: + timer = HierarchicalTimer() + if model is not self._model: + timer.start('set_instance') + self.set_instance(model) + timer.stop('set_instance') + else: + timer.start('update') + self.update(timer=timer) + timer.stop('update') + res = self._solve(timer) + self._last_results_object = res + if self.config.report_timing: + logger.info('\n' + str(timer)) + return res + + def _reinit(self): + saved_config = self.config + saved_options = self.wntr_options + saved_update_config = self.update_config + self.__init__(only_child_vars=self._only_child_vars) + self.config = saved_config + self.wntr_options = saved_options + self.update_config = saved_update_config + + def set_instance(self, model): + if self._last_results_object is not None: + self._last_results_object.solution_loader.invalidate() + if not self.available(): + c = self.__class__ + raise PyomoException( + f'Solver {c.__module__}.{c.__qualname__} is not available ' + f'({self.available()}).' + ) + self._reinit() + self._model = model + if self.use_extensions and cmodel_available: + self._expr_types = cmodel.PyomoExprTypes() + + if self.config.symbolic_solver_labels: + self._labeler = TextLabeler() + else: + self._labeler = NumericLabeler('x') + + self._solver_model = wntr.sim.aml.aml.Model() + + self.add_block(model) + + def _add_variables(self, variables: List[_GeneralVarData]): + aml = wntr.sim.aml.aml + for var in variables: + varname = self._symbol_map.getSymbol(var, self._labeler) + _v, _lb, _ub, _fixed, _domain_interval, _value = self._vars[id(var)] + lb, ub, step = _domain_interval + if ( + _lb is not None + or _ub is not None + or lb is not None + or ub is not None + or step != 0 + ): + raise ValueError(f"WNTR's newton solver only supports continuous variables without bounds: {var.name}") + if _value is None: + _value = 0 + wntr_var = aml.Var(_value) + setattr(self._solver_model, varname, wntr_var) + self._pyomo_var_to_solver_var_map[id(var)] = wntr_var + if _fixed: + self._solver_model._wntr_fixed_var_params[id(var)] = param = aml.Param(_value) + wntr_expr = self._pyomo_to_wntr_visitor.dfs_postorder_stack(var - param) + self._solver_model._wntr_fixed_var_cons[id(var)] = aml.Constraint(wntr_expr) + self._needs_updated = True + + def _add_params(self, params: List[_ParamData]): + aml = wntr.sim.aml.aml + for p in params: + pname = self._symbol_map.getSymbol(p, self._labeler) + wntr_p = aml.Param(p.value) + setattr(self._solver_model, pname, wntr_p) + self._pyomo_param_to_solver_param_map[id(p)] = wntr_p + + def _add_constraints(self, cons: List[_GeneralConstraintData]): + aml = wntr.sim.aml.aml + for con in cons: + if not con.equality: + raise ValueError(f"WNTR's newtwon solver only supports equality constraints: {con.name}") + conname = self._symbol_map.getSymbol(con, self._labeler) + wntr_expr = self._pyomo_to_wntr_visitor.dfs_postorder_stack(con.body - con.upper) + wntr_con = aml.Constraint(wntr_expr) + setattr(self._solver_model, conname, wntr_con) + self._pyomo_con_to_solver_con_map[con] = wntr_con + self._needs_updated = True + + def _remove_constraints(self, cons: List[_GeneralConstraintData]): + for con in cons: + solver_con = self._pyomo_con_to_solver_con_map[con] + delattr(self._solver_model, solver_con.name) + self._symbol_map.removeSymbol(con) + del self._pyomo_con_to_solver_con_map[con] + self._needs_updated = True + + def _remove_variables(self, variables: List[_GeneralVarData]): + for var in variables: + v_id = id(var) + solver_var = self._pyomo_var_to_solver_var_map[v_id] + delattr(self._solver_model, solver_var.name) + self._symbol_map.removeSymbol(var) + del self._pyomo_var_to_solver_var_map[v_id] + if v_id in self._solver_model._wntr_fixed_var_params: + del self._solver_model._wntr_fixed_var_params[v_id] + del self._solver_model._wntr_fixed_var_cons[v_id] + self._needs_updated = True + + def _remove_params(self, params: List[_ParamData]): + for p in params: + p_id = id(p) + solver_param = self._pyomo_param_to_solver_param_map[p_id] + delattr(self._solver_model, solver_param.name) + self._symbol_map.removeSymbol(p) + del self._pyomo_param_to_solver_param_map[p_id] + + def _update_variables(self, variables: List[_GeneralVarData]): + for var in variables: + v_id = id(var) + solver_var = self._pyomo_var_to_solver_var_map[v_id] + _v, _lb, _ub, _fixed, _domain_interval, _value = self._vars[v_id] + lb, ub, step = _domain_interval + if ( + _lb is not None + or _ub is not None + or lb is not None + or ub is not None + or step != 0 + ): + raise ValueError(f"WNTR's newton solver only supports continuous variables without bounds: {var.name}") + if _value is None: + _value = 0 + solver_var.value = _value + if _fixed: + if v_id not in self._solver_model._wntr_fixed_var_params: + self._solver_model._wntr_fixed_var_params[v_id] = param = aml.Param(_value) + wntr_expr = self._pyomo_to_wntr_visitor.dfs_postorder_stack(var - param) + self._solver_model._wntr_fixed_var_cons[v_id] = aml.Constraint(wntr_expr) + self._needs_updated = True + else: + self._solver_model._wntr_fixed_var_params[v_id].value = _value + else: + if v_id in self._solver_model._wntr_fixed_var_params: + del self._solver_model._wntr_fixed_var_params[v_id] + del self._solver_model._wntr_fixed_var_cons[v_id] + self._needs_updated = True + + def update_params(self): + for p_id, solver_p in self._pyomo_param_to_solver_param_map.items(): + p = self._params[p_id] + solver_p.value = p.value + + def _set_objective(self, obj): + raise NotImplementedError(f"WNTR's newton solver can only solve square problems") + + def load_vars(self, vars_to_load=None): + if vars_to_load is None: + vars_to_load = [i[0] for i in self._vars.values()] + for v in vars_to_load: + v_id = id(v) + solver_v = self._pyomo_var_to_solver_var_map[v_id] + v.value = solver_v.value + + def get_primals(self, vars_to_load=None): + if vars_to_load is None: + vars_to_load = [i[0] for i in self._vars.values()] + res = ComponentMap() + for v in vars_to_load: + v_id = id(v) + solver_v = self._pyomo_var_to_solver_var_map[v_id] + res[v] = solver_v.value + + def _add_sos_constraints(self, cons): + if len(cons) > 0: + raise NotImplementedError(f"WNTR's newton solver does not support SOS constraints") + + def _remove_sos_constraints(self, cons): + if len(cons) > 0: + raise NotImplementedError(f"WNTR's newton solver does not support SOS constraints") + + +def _handle_product_expression(node, values): + arg1, arg2 = values + return arg1 * arg2 + + +def _handle_sum_expression(node, values): + return sum(values) + + +def _handle_division_expression(node, values): + arg1, arg2 = values + return arg1 / arg2 + + +def _handle_pow_expression(node, values): + arg1, arg2 = values + return arg1 ** arg2 + + +def _handle_negation_expression(node, values): + return -values[0] + + +def _handle_exp_expression(node, values): + return wntr.sim.aml.exp(values[0]) + + +def _handle_log_expression(node, values): + return wntr.sim.aml.log(values[0]) + + +def _handle_sin_expression(node, values): + return wntr.sim.aml.sin(values[0]) + + +def _handle_cos_expression(node, values): + return wntr.sim.aml.cos(values[0]) + + +def _handle_tan_expression(node, values): + return wntr.sim.aml.tan(values[0]) + + +def _handle_asin_expression(node, values): + return wntr.sim.aml.asin(values[0]) + + +def _handle_acos_expression(node, values): + return wntr.sim.aml.acos(values[0]) + + +def _handle_atan_expression(node, values): + return wntr.sim.aml.atan(values[0]) + + +def _handle_sqrt_expression(node, values): + return (values[0])**0.5 + + +def _handle_abs_expression(node, values): + return wntr.sim.aml.abs(values[0]) + + +_unary_handler_map = dict() +_unary_handler_map['exp'] = _handle_exp_expression +_unary_handler_map['log'] = _handle_log_expression +_unary_handler_map['sin'] = _handle_sin_expression +_unary_handler_map['cos'] = _handle_cos_expression +_unary_handler_map['tan'] = _handle_tan_expression +_unary_handler_map['asin'] = _handle_asin_expression +_unary_handler_map['acos'] = _handle_acos_expression +_unary_handler_map['atan'] = _handle_atan_expression +_unary_handler_map['sqrt'] = _handle_sqrt_expression +_unary_handler_map['abs'] = _handle_abs_expression + + +def _handle_unary_function_expression(node, values): + if node.getname() in _unary_handler_map: + return _unary_handler_map[node.getname()](node, values) + else: + raise NotImplementedError(f'Unrecognized unary function expression: {node.getname()}') + + +_handler_map = dict() +_handler_map[ProductExpression] = _handle_product_expression +_handler_map[DivisionExpression] = _handle_division_expression +_handler_map[PowExpression] = _handle_pow_expression +_handler_map[SumExpression] = _handle_sum_expression +_handler_map[MonomialTermExpression] = _handle_product_expression +_handler_map[NegationExpression] = _handle_negation_expression +_handler_map[UnaryFunctionExpression] = _handle_unary_function_expression +_handler_map[LinearExpression] = _handle_sum_expression +_handler_map[AbsExpression] = _handle_abs_expression +_handler_map[NPV_ProductExpression] = _handle_product_expression +_handler_map[NPV_DivisionExpression] = _handle_division_expression +_handler_map[NPV_PowExpression] = _handle_pow_expression +_handler_map[NPV_SumExpression] = _handle_sum_expression +_handler_map[NPV_NegationExpression] = _handle_negation_expression +_handler_map[NPV_UnaryFunctionExpression] = _handle_unary_function_expression +_handler_map[NPV_AbsExpression] = _handle_abs_expression + + +class PyomoToWntrVisitor(ExpressionValueVisitor): + def __init__(self, var_map, param_map): + self.var_map = var_map + self.param_map = param_map + + def visit(self, node, values): + if node.__class__ in _handler_map: + return _handler_map[node.__class__](node, values) + else: + raise NotImplementedError(f'Unrecognized expression type: {node.__class__}') + + def visiting_potential_leaf(self, node): + if node.__class__ in native_numeric_types: + return True, node + + if node.is_variable_type(): + return True, self.var_map[id(node)] + + if node.is_parameter_type(): + return True, self.param_map[id(node)] + + return False, None From a5b1eb35c52982161400445e1875279e2d2a3c7b Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Sun, 9 Jul 2023 20:19:05 -0600 Subject: [PATCH 0002/1204] tests for persistent interface to wntr --- .../solvers/tests/test_wntr_persistent.py | 184 ++++++++++++++++++ pyomo/contrib/appsi/solvers/wntr.py | 30 ++- 2 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py diff --git a/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py new file mode 100644 index 00000000000..b172a9204e8 --- /dev/null +++ b/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py @@ -0,0 +1,184 @@ +import pyomo.environ as pe +import pyomo.common.unittest as unittest +from pyomo.contrib.appsi.base import TerminationCondition, Results, PersistentSolver +from pyomo.contrib.appsi.solvers.wntr import Wntr, wntr_available +import math + + +_default_wntr_options = dict( + TOL=1e-8, +) + + +@unittest.skipUnless(wntr_available, 'wntr is not available') +class TestWntrPersistent(unittest.TestCase): + def test_param_updates(self): + m = pe.ConcreteModel() + m.x = pe.Var() + m.p = pe.Param(initialize=1, mutable=True) + m.c = pe.Constraint(expr=m.x == m.p) + opt = Wntr() + opt.wntr_options.update(_default_wntr_options) + res = opt.solve(m) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(m.x.value, 1) + + m.p.value = 2 + res = opt.solve(m) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(m.x.value, 2) + + def test_remove_add_constraint(self): + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.c1 = pe.Constraint(expr=m.y == (m.x - 1)**2) + m.c2 = pe.Constraint(expr=m.y == pe.exp(m.x)) + opt = Wntr() + opt.config.symbolic_solver_labels = True + opt.wntr_options.update(_default_wntr_options) + res = opt.solve(m) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(m.x.value, 0) + self.assertAlmostEqual(m.y.value, 1) + + del m.c2 + m.c2 = pe.Constraint(expr=m.y == pe.log(m.x)) + m.x.value = 0.5 + m.y.value = 0.5 + res = opt.solve(m) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(m.x.value, 1) + self.assertAlmostEqual(m.y.value, 0) + + def test_fixed_var(self): + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.c1 = pe.Constraint(expr=m.y == (m.x - 1)**2) + m.x.fix(0.5) + opt = Wntr() + opt.wntr_options.update(_default_wntr_options) + res = opt.solve(m) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(m.x.value, 0.5) + self.assertAlmostEqual(m.y.value, 0.25) + + m.x.unfix() + m.c2 = pe.Constraint(expr=m.y == pe.exp(m.x)) + res = opt.solve(m) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(m.x.value, 0) + self.assertAlmostEqual(m.y.value, 1) + + m.x.fix(0.5) + del m.c2 + res = opt.solve(m) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(m.x.value, 0.5) + self.assertAlmostEqual(m.y.value, 0.25) + + def test_remove_variables_params(self): + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.z = pe.Var() + m.z.fix(0) + m.px = pe.Param(mutable=True, initialize=1) + m.py = pe.Param(mutable=True, initialize=1) + m.c1 = pe.Constraint(expr=m.x == m.px) + m.c2 = pe.Constraint(expr=m.y == m.py) + opt = Wntr() + opt.wntr_options.update(_default_wntr_options) + res = opt.solve(m) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(m.x.value, 1) + self.assertAlmostEqual(m.y.value, 1) + self.assertAlmostEqual(m.z.value, 0) + + del m.c2 + del m.y + del m.py + m.z.value = 2 + m.px.value = 2 + res = opt.solve(m) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(m.x.value, 2) + self.assertAlmostEqual(m.z.value, 2) + + del m.z + m.px.value = 3 + res = opt.solve(m) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(m.x.value, 3) + + def test_get_primals(self): + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.c1 = pe.Constraint(expr=m.y == (m.x - 1)**2) + m.c2 = pe.Constraint(expr=m.y == pe.exp(m.x)) + opt = Wntr() + opt.config.load_solution = False + opt.wntr_options.update(_default_wntr_options) + res = opt.solve(m) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(m.x.value, None) + self.assertAlmostEqual(m.y.value, None) + primals = opt.get_primals() + self.assertAlmostEqual(primals[m.x], 0) + self.assertAlmostEqual(primals[m.y], 1) + + def test_operators(self): + m = pe.ConcreteModel() + m.x = pe.Var(initialize=1) + m.c1 = pe.Constraint(expr=2/m.x == 1) + opt = Wntr() + opt.wntr_options.update(_default_wntr_options) + res = opt.solve(m) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(m.x.value, 2) + + del m.c1 + m.x.value = 0 + m.c1 = pe.Constraint(expr=pe.sin(m.x) == math.sin(math.pi/4)) + res = opt.solve(m) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(m.x.value, math.pi/4) + + del m.c1 + m.c1 = pe.Constraint(expr=pe.cos(m.x) == 0) + res = opt.solve(m) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(m.x.value, math.pi/2) + + del m.c1 + m.c1 = pe.Constraint(expr=pe.tan(m.x) == 1) + m.x.value = 0 + res = opt.solve(m) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(m.x.value, math.pi/4) + + del m.c1 + m.c1 = pe.Constraint(expr=pe.asin(m.x) == math.asin(0.5)) + res = opt.solve(m) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(m.x.value, 0.5) + + del m.c1 + m.c1 = pe.Constraint(expr=pe.acos(m.x) == math.acos(0.6)) + res = opt.solve(m) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(m.x.value, 0.6) + + del m.c1 + m.c1 = pe.Constraint(expr=pe.atan(m.x) == math.atan(0.5)) + res = opt.solve(m) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(m.x.value, 0.5) + + del m.c1 + m.c1 = pe.Constraint(expr=pe.sqrt(m.x) == math.sqrt(0.6)) + res = opt.solve(m) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(m.x.value, 0.6) diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py index 655a620077c..1e0952cd867 100644 --- a/pyomo/contrib/appsi/solvers/wntr.py +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -41,6 +41,7 @@ import wntr import logging import time +import sys from pyomo.core.expr.visitor import ExpressionValueVisitor @@ -120,15 +121,25 @@ def symbol_map(self): return self._symbol_map def _solve(self, timer: HierarchicalTimer): + options = dict() + if self.config.time_limit is not None: + options['TIME_LIMIT'] = self.config.time_limit + options.update(self.wntr_options) + opt = wntr.sim.solvers.NewtonSolver(options) + + if self.config.stream_solver: + ostream = sys.stdout + else: + ostream = None + t0 = time.time() - opt = wntr.sim.solvers.NewtonSolver(self.wntr_options) if self._needs_updated: timer.start('set_structure') self._solver_model.set_structure() timer.stop('set_structure') self._needs_updated = False timer.start('newton solve') - status, msg, num_iter = opt.solve(self._solver_model) + status, msg, num_iter = opt.solve(self._solver_model, ostream) timer.stop('newton solve') tf = time.time() @@ -168,6 +179,13 @@ def solve(self, model: _BlockData, timer: HierarchicalTimer = None) -> Results: else: timer.start('update') self.update(timer=timer) + timer.start('initial values') + for v_id, solver_v in self._pyomo_var_to_solver_var_map.items(): + pyomo_v = self._vars[v_id][0] + val = pyomo_v.value + if val is not None: + solver_v.value = val + timer.stop('initial values') timer.stop('update') res = self._solve(timer) self._last_results_object = res @@ -204,6 +222,8 @@ def set_instance(self, model): self._labeler = NumericLabeler('x') self._solver_model = wntr.sim.aml.aml.Model() + self._solver_model._wntr_fixed_var_params = wntr.sim.aml.aml.ParamDict() + self._solver_model._wntr_fixed_var_cons = wntr.sim.aml.aml.ConstraintDict() self.add_block(model) @@ -228,7 +248,7 @@ def _add_variables(self, variables: List[_GeneralVarData]): self._pyomo_var_to_solver_var_map[id(var)] = wntr_var if _fixed: self._solver_model._wntr_fixed_var_params[id(var)] = param = aml.Param(_value) - wntr_expr = self._pyomo_to_wntr_visitor.dfs_postorder_stack(var - param) + wntr_expr = wntr_var - param self._solver_model._wntr_fixed_var_cons[id(var)] = aml.Constraint(wntr_expr) self._needs_updated = True @@ -281,6 +301,7 @@ def _remove_params(self, params: List[_ParamData]): del self._pyomo_param_to_solver_param_map[p_id] def _update_variables(self, variables: List[_GeneralVarData]): + aml = wntr.sim.aml.aml for var in variables: v_id = id(var) solver_var = self._pyomo_var_to_solver_var_map[v_id] @@ -300,7 +321,7 @@ def _update_variables(self, variables: List[_GeneralVarData]): if _fixed: if v_id not in self._solver_model._wntr_fixed_var_params: self._solver_model._wntr_fixed_var_params[v_id] = param = aml.Param(_value) - wntr_expr = self._pyomo_to_wntr_visitor.dfs_postorder_stack(var - param) + wntr_expr = solver_var - param self._solver_model._wntr_fixed_var_cons[v_id] = aml.Constraint(wntr_expr) self._needs_updated = True else: @@ -335,6 +356,7 @@ def get_primals(self, vars_to_load=None): v_id = id(v) solver_v = self._pyomo_var_to_solver_var_map[v_id] res[v] = solver_v.value + return res def _add_sos_constraints(self, cons): if len(cons) > 0: From 797c918e9c01ae07b2ad239673200df40cc61e95 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Sun, 9 Jul 2023 20:19:53 -0600 Subject: [PATCH 0003/1204] tests for persistent interface to wntr --- pyomo/contrib/appsi/solvers/wntr.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py index 1e0952cd867..09fe53dbb98 100644 --- a/pyomo/contrib/appsi/solvers/wntr.py +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -38,7 +38,6 @@ from pyomo.core.staleflag import StaleFlagManager from pyomo.contrib.appsi.cmodel import cmodel, cmodel_available wntr, wntr_available = attempt_import('wntr') -import wntr import logging import time import sys From 7065274ec8c0265b198395e0e0bb9e9b6f7b6162 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 10 Jul 2023 05:47:48 -0600 Subject: [PATCH 0004/1204] add wntr to GHA --- .github/workflows/test_branches.yml | 2 ++ .github/workflows/test_pr_and_main.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 48feb7a4465..d6de4022f4c 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -280,6 +280,8 @@ jobs: || echo "WARNING: Gurobi is not available" python -m pip install --cache-dir cache/pip xpress \ || echo "WARNING: Xpress Community Edition is not available" + python -m pip install --cache-dir cache/pip wntr \ + || echo "WARNING: WNTR is not available" fi python -c 'import sys; print("PYTHON_EXE=%s" \ % (sys.executable,))' >> $GITHUB_ENV diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index fd52c9610a8..5d3503f17c7 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -298,6 +298,8 @@ jobs: || echo "WARNING: Gurobi is not available" python -m pip install --cache-dir cache/pip xpress \ || echo "WARNING: Xpress Community Edition is not available" + python -m pip install --cache-dir cache/pip wntr \ + || echo "WARNING: WNTR is not available" fi python -c 'import sys; print("PYTHON_EXE=%s" \ % (sys.executable,))' >> $GITHUB_ENV From b7c54f3ebd9cdd2a431e59f37024a7d39888f9fc Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 10 Jul 2023 05:57:03 -0600 Subject: [PATCH 0005/1204] run black --- .../solvers/tests/test_wntr_persistent.py | 20 +++-- pyomo/contrib/appsi/solvers/wntr.py | 78 ++++++++++++------- 2 files changed, 60 insertions(+), 38 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py index b172a9204e8..d250923f104 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py @@ -5,9 +5,7 @@ import math -_default_wntr_options = dict( - TOL=1e-8, -) +_default_wntr_options = dict(TOL=1e-8) @unittest.skipUnless(wntr_available, 'wntr is not available') @@ -32,7 +30,7 @@ def test_remove_add_constraint(self): m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() - m.c1 = pe.Constraint(expr=m.y == (m.x - 1)**2) + m.c1 = pe.Constraint(expr=m.y == (m.x - 1) ** 2) m.c2 = pe.Constraint(expr=m.y == pe.exp(m.x)) opt = Wntr() opt.config.symbolic_solver_labels = True @@ -55,7 +53,7 @@ def test_fixed_var(self): m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() - m.c1 = pe.Constraint(expr=m.y == (m.x - 1)**2) + m.c1 = pe.Constraint(expr=m.y == (m.x - 1) ** 2) m.x.fix(0.5) opt = Wntr() opt.wntr_options.update(_default_wntr_options) @@ -116,7 +114,7 @@ def test_get_primals(self): m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() - m.c1 = pe.Constraint(expr=m.y == (m.x - 1)**2) + m.c1 = pe.Constraint(expr=m.y == (m.x - 1) ** 2) m.c2 = pe.Constraint(expr=m.y == pe.exp(m.x)) opt = Wntr() opt.config.load_solution = False @@ -132,7 +130,7 @@ def test_get_primals(self): def test_operators(self): m = pe.ConcreteModel() m.x = pe.Var(initialize=1) - m.c1 = pe.Constraint(expr=2/m.x == 1) + m.c1 = pe.Constraint(expr=2 / m.x == 1) opt = Wntr() opt.wntr_options.update(_default_wntr_options) res = opt.solve(m) @@ -141,23 +139,23 @@ def test_operators(self): del m.c1 m.x.value = 0 - m.c1 = pe.Constraint(expr=pe.sin(m.x) == math.sin(math.pi/4)) + m.c1 = pe.Constraint(expr=pe.sin(m.x) == math.sin(math.pi / 4)) res = opt.solve(m) self.assertEqual(res.termination_condition, TerminationCondition.optimal) - self.assertAlmostEqual(m.x.value, math.pi/4) + self.assertAlmostEqual(m.x.value, math.pi / 4) del m.c1 m.c1 = pe.Constraint(expr=pe.cos(m.x) == 0) res = opt.solve(m) self.assertEqual(res.termination_condition, TerminationCondition.optimal) - self.assertAlmostEqual(m.x.value, math.pi/2) + self.assertAlmostEqual(m.x.value, math.pi / 2) del m.c1 m.c1 = pe.Constraint(expr=pe.tan(m.x) == 1) m.x.value = 0 res = opt.solve(m) self.assertEqual(res.termination_condition, TerminationCondition.optimal) - self.assertAlmostEqual(m.x.value, math.pi/4) + self.assertAlmostEqual(m.x.value, math.pi / 4) del m.c1 m.c1 = pe.Constraint(expr=pe.asin(m.x) == math.asin(0.5)) diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py index 09fe53dbb98..0a358c6aedf 100644 --- a/pyomo/contrib/appsi/solvers/wntr.py +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -4,7 +4,7 @@ SolverConfig, Results, TerminationCondition, - PersistentSolutionLoader + PersistentSolutionLoader, ) from pyomo.core.expr.numeric_expr import ( ProductExpression, @@ -37,6 +37,7 @@ from pyomo.common.dependencies import attempt_import from pyomo.core.staleflag import StaleFlagManager from pyomo.contrib.appsi.cmodel import cmodel, cmodel_available + wntr, wntr_available = attempt_import('wntr') import logging import time @@ -86,8 +87,7 @@ def __init__(self, only_child_vars=True): self._needs_updated = True self._last_results_object: Optional[WntrResults] = None self._pyomo_to_wntr_visitor = PyomoToWntrVisitor( - self._pyomo_var_to_solver_var_map, - self._pyomo_param_to_solver_param_map + self._pyomo_var_to_solver_var_map, self._pyomo_param_to_solver_param_map ) def available(self): @@ -233,22 +233,28 @@ def _add_variables(self, variables: List[_GeneralVarData]): _v, _lb, _ub, _fixed, _domain_interval, _value = self._vars[id(var)] lb, ub, step = _domain_interval if ( - _lb is not None - or _ub is not None - or lb is not None - or ub is not None - or step != 0 + _lb is not None + or _ub is not None + or lb is not None + or ub is not None + or step != 0 ): - raise ValueError(f"WNTR's newton solver only supports continuous variables without bounds: {var.name}") + raise ValueError( + f"WNTR's newton solver only supports continuous variables without bounds: {var.name}" + ) if _value is None: _value = 0 wntr_var = aml.Var(_value) setattr(self._solver_model, varname, wntr_var) self._pyomo_var_to_solver_var_map[id(var)] = wntr_var if _fixed: - self._solver_model._wntr_fixed_var_params[id(var)] = param = aml.Param(_value) + self._solver_model._wntr_fixed_var_params[id(var)] = param = aml.Param( + _value + ) wntr_expr = wntr_var - param - self._solver_model._wntr_fixed_var_cons[id(var)] = aml.Constraint(wntr_expr) + self._solver_model._wntr_fixed_var_cons[id(var)] = aml.Constraint( + wntr_expr + ) self._needs_updated = True def _add_params(self, params: List[_ParamData]): @@ -263,9 +269,13 @@ def _add_constraints(self, cons: List[_GeneralConstraintData]): aml = wntr.sim.aml.aml for con in cons: if not con.equality: - raise ValueError(f"WNTR's newtwon solver only supports equality constraints: {con.name}") + raise ValueError( + f"WNTR's newtwon solver only supports equality constraints: {con.name}" + ) conname = self._symbol_map.getSymbol(con, self._labeler) - wntr_expr = self._pyomo_to_wntr_visitor.dfs_postorder_stack(con.body - con.upper) + wntr_expr = self._pyomo_to_wntr_visitor.dfs_postorder_stack( + con.body - con.upper + ) wntr_con = aml.Constraint(wntr_expr) setattr(self._solver_model, conname, wntr_con) self._pyomo_con_to_solver_con_map[con] = wntr_con @@ -307,21 +317,27 @@ def _update_variables(self, variables: List[_GeneralVarData]): _v, _lb, _ub, _fixed, _domain_interval, _value = self._vars[v_id] lb, ub, step = _domain_interval if ( - _lb is not None - or _ub is not None - or lb is not None - or ub is not None - or step != 0 + _lb is not None + or _ub is not None + or lb is not None + or ub is not None + or step != 0 ): - raise ValueError(f"WNTR's newton solver only supports continuous variables without bounds: {var.name}") + raise ValueError( + f"WNTR's newton solver only supports continuous variables without bounds: {var.name}" + ) if _value is None: _value = 0 solver_var.value = _value if _fixed: if v_id not in self._solver_model._wntr_fixed_var_params: - self._solver_model._wntr_fixed_var_params[v_id] = param = aml.Param(_value) + self._solver_model._wntr_fixed_var_params[v_id] = param = aml.Param( + _value + ) wntr_expr = solver_var - param - self._solver_model._wntr_fixed_var_cons[v_id] = aml.Constraint(wntr_expr) + self._solver_model._wntr_fixed_var_cons[v_id] = aml.Constraint( + wntr_expr + ) self._needs_updated = True else: self._solver_model._wntr_fixed_var_params[v_id].value = _value @@ -337,7 +353,9 @@ def update_params(self): solver_p.value = p.value def _set_objective(self, obj): - raise NotImplementedError(f"WNTR's newton solver can only solve square problems") + raise NotImplementedError( + f"WNTR's newton solver can only solve square problems" + ) def load_vars(self, vars_to_load=None): if vars_to_load is None: @@ -359,11 +377,15 @@ def get_primals(self, vars_to_load=None): def _add_sos_constraints(self, cons): if len(cons) > 0: - raise NotImplementedError(f"WNTR's newton solver does not support SOS constraints") + raise NotImplementedError( + f"WNTR's newton solver does not support SOS constraints" + ) def _remove_sos_constraints(self, cons): if len(cons) > 0: - raise NotImplementedError(f"WNTR's newton solver does not support SOS constraints") + raise NotImplementedError( + f"WNTR's newton solver does not support SOS constraints" + ) def _handle_product_expression(node, values): @@ -382,7 +404,7 @@ def _handle_division_expression(node, values): def _handle_pow_expression(node, values): arg1, arg2 = values - return arg1 ** arg2 + return arg1**arg2 def _handle_negation_expression(node, values): @@ -422,7 +444,7 @@ def _handle_atan_expression(node, values): def _handle_sqrt_expression(node, values): - return (values[0])**0.5 + return (values[0]) ** 0.5 def _handle_abs_expression(node, values): @@ -446,7 +468,9 @@ def _handle_unary_function_expression(node, values): if node.getname() in _unary_handler_map: return _unary_handler_map[node.getname()](node, values) else: - raise NotImplementedError(f'Unrecognized unary function expression: {node.getname()}') + raise NotImplementedError( + f'Unrecognized unary function expression: {node.getname()}' + ) _handler_map = dict() From 2451c444c3f923d83131974e6048d1188f85a894 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 10 Jul 2023 07:19:48 -0600 Subject: [PATCH 0006/1204] add wntr to GHA --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index d6de4022f4c..69028e55a17 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -280,7 +280,7 @@ jobs: || echo "WARNING: Gurobi is not available" python -m pip install --cache-dir cache/pip xpress \ || echo "WARNING: Xpress Community Edition is not available" - python -m pip install --cache-dir cache/pip wntr \ + python -m pip install git+https://github.com/michaelbynum/wntr.git@working \ || echo "WARNING: WNTR is not available" fi python -c 'import sys; print("PYTHON_EXE=%s" \ diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 5d3503f17c7..357a2fe866e 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -298,7 +298,7 @@ jobs: || echo "WARNING: Gurobi is not available" python -m pip install --cache-dir cache/pip xpress \ || echo "WARNING: Xpress Community Edition is not available" - python -m pip install --cache-dir cache/pip wntr \ + python -m pip install git+https://github.com/michaelbynum/wntr.git@working \ || echo "WARNING: WNTR is not available" fi python -c 'import sys; print("PYTHON_EXE=%s" \ From b03bb7d465db22b3c37d4d5a589a3c0f7b992f3a Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Tue, 25 Jul 2023 12:27:31 -0600 Subject: [PATCH 0007/1204] catch PyNumeroEvaluationErrors and raise CyIpoptEvaluationErrors --- .../pynumero/interfaces/cyipopt_interface.py | 58 ++++++++++++++----- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py index 19e74625d03..89ce6683f4d 100644 --- a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py +++ b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py @@ -23,6 +23,7 @@ import abc from pyomo.common.dependencies import attempt_import, numpy as np, numpy_available +from pyomo.contrib.pynumero.exceptions import PyNumeroEvaluationError def _cyipopt_importer(): @@ -328,24 +329,46 @@ def scaling_factors(self): return obj_scaling, x_scaling, g_scaling def objective(self, x): - self._set_primals_if_necessary(x) - return self._nlp.evaluate_objective() + try: + self._set_primals_if_necessary(x) + return self._nlp.evaluate_objective() + except PyNumeroEvaluationError: + # TODO: halt_on_evaluation_error option. If set, we re-raise the + # original exception. + raise cyipopt.CyIpoptEvaluationError( + "Error in objective function evaluation" + ) def gradient(self, x): - self._set_primals_if_necessary(x) - return self._nlp.evaluate_grad_objective() + try: + self._set_primals_if_necessary(x) + return self._nlp.evaluate_grad_objective() + except PyNumeroEvaluationError: + raise cyipopt.CyIpoptEvaluationError( + "Error in objective gradient evaluation" + ) def constraints(self, x): - self._set_primals_if_necessary(x) - return self._nlp.evaluate_constraints() + try: + self._set_primals_if_necessary(x) + return self._nlp.evaluate_constraints() + except PyNumeroEvaluationError: + raise cyipopt.CyIpoptEvaluationError( + "Error in constraint evaluation" + ) def jacobianstructure(self): return self._jac_g.row, self._jac_g.col def jacobian(self, x): - self._set_primals_if_necessary(x) - self._nlp.evaluate_jacobian(out=self._jac_g) - return self._jac_g.data + try: + self._set_primals_if_necessary(x) + self._nlp.evaluate_jacobian(out=self._jac_g) + return self._jac_g.data + except PyNumeroEvaluationError: + raise cyipopt.CyIpoptEvaluationError( + "Error in constraint Jacobian evaluation" + ) def hessianstructure(self): if not self._hessian_available: @@ -359,12 +382,17 @@ def hessian(self, x, y, obj_factor): if not self._hessian_available: raise ValueError("Hessian requested, but not supported by the NLP") - self._set_primals_if_necessary(x) - self._set_duals_if_necessary(y) - self._set_obj_factor_if_necessary(obj_factor) - self._nlp.evaluate_hessian_lag(out=self._hess_lag) - data = np.compress(self._hess_lower_mask, self._hess_lag.data) - return data + try: + self._set_primals_if_necessary(x) + self._set_duals_if_necessary(y) + self._set_obj_factor_if_necessary(obj_factor) + self._nlp.evaluate_hessian_lag(out=self._hess_lag) + data = np.compress(self._hess_lower_mask, self._hess_lag.data) + return data + except PyNumeroEvaluationError: + raise cyipopt.CyIpoptEvaluationError( + "Error in Lagrangian Hessian evaluation" + ) def intermediate( self, From 8dac4abb67420578fcd8fa83b06d071beed44eb8 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Tue, 25 Jul 2023 12:28:19 -0600 Subject: [PATCH 0008/1204] test solving a model that raises an evaluation error --- .../solvers/tests/test_cyipopt_solver.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py index 2a7edb430d4..e3596993082 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py @@ -155,6 +155,32 @@ def f(model): return model +def make_hs071_model(): + # This is a model that is mathematically equivalent to the Hock-Schittkowski + # test problem 071, but that will trigger an evaluation error if x[0] goes + # above 1.1. + m = pyo.ConcreteModel() + m.x = pyo.Var([0, 1, 2, 3], bounds=(1.0, 5.0)) + m.x[0] = 1.0 + m.x[1] = 5.0 + m.x[2] = 5.0 + m.x[3] = 1.0 + m.obj = pyo.Objective(expr=m.x[0] * m.x[3] * (m.x[0] + m.x[1] + m.x[2]) + m.x[2]) + # This expression evaluates to zero, but is not well defined when x[0] > 1.1 + trivial_expr_with_eval_error = ( + # 0.0 + (pyo.sqrt(1.1 - m.x[0])) ** 2 + m.x[0] - 1.1 + ) + m.ineq1 = pyo.Constraint(expr=m.x[0] * m.x[1] * m.x[2] * m.x[3] >= 25.0) + m.eq1 = pyo.Constraint( + expr=( + m.x[0] ** 2 + m.x[1] ** 2 + m.x[2] ** 2 + m.x[3] ** 2 + == 40.0 + trivial_expr_with_eval_error + ) + ) + return m + + @unittest.skipIf(cyipopt_available, "cyipopt is available") class TestCyIpoptNotAvailable(unittest.TestCase): def test_not_available_exception(self): @@ -257,3 +283,12 @@ def test_options(self): x, info = solver.solve(tee=False) nlp.set_primals(x) self.assertAlmostEqual(nlp.evaluate_objective(), -5.0879028e02, places=5) + + def test_hs071_evalerror(self): + m = make_hs071_model() + solver = pyo.SolverFactory("cyipopt") + res = solver.solve(m, tee=True) + + x = list(m.x[:].value) + expected_x = np.array([1.0, 4.74299964, 3.82114998, 1.37940829]) + np.testing.assert_allclose(x, expected_x) From ddc60d76881c31807929def48eda33ebc715f554 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Tue, 25 Jul 2023 13:14:00 -0600 Subject: [PATCH 0009/1204] test raising CyIpoptEvaluationError from CyIpoptNLP --- .../tests/test_cyipopt_interface.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_cyipopt_interface.py b/pyomo/contrib/pynumero/interfaces/tests/test_cyipopt_interface.py index 2c5d8ff7e4e..dbff12121b0 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_cyipopt_interface.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_cyipopt_interface.py @@ -10,6 +10,7 @@ # ___________________________________________________________________________ import pyomo.common.unittest as unittest +import pyomo.environ as pyo from pyomo.contrib.pynumero.dependencies import ( numpy as np, @@ -25,14 +26,18 @@ if not AmplInterface.available(): raise unittest.SkipTest("Pynumero needs the ASL extension to run CyIpopt tests") +from pyomo.contrib.pynumero.interfaces.pyomo_nlp import PyomoNLP from pyomo.contrib.pynumero.interfaces.cyipopt_interface import ( cyipopt_available, CyIpoptProblemInterface, + CyIpoptNLP, ) if not cyipopt_available: raise unittest.SkipTest("CyIpopt is not available") +import cyipopt + class TestSubclassCyIpoptInterface(unittest.TestCase): def test_subclass_no_init(self): @@ -88,5 +93,49 @@ def hessian(self, x, y, obj_factor): problem.solve(x0) +class TestCyIpoptEvaluationErrors(unittest.TestCase): + def _get_model_nlp_interface(self): + m = pyo.ConcreteModel() + m.x = pyo.Var([1, 2, 3], initialize=1.0) + m.obj = pyo.Objective(expr=m.x[1] * pyo.sqrt(m.x[2]) + m.x[1] * m.x[3]) + m.eq1 = pyo.Constraint(expr=m.x[1] * pyo.sqrt(m.x[2]) == 1.0) + nlp = PyomoNLP(m) + interface = CyIpoptNLP(nlp) + bad_primals = np.array([1.0, -2.0, 3.0]) + indices = nlp.get_primal_indices([m.x[1], m.x[2], m.x[3]]) + bad_primals = bad_primals[indices] + return m, nlp, interface, bad_primals + + def test_error_in_objective(self): + m, nlp, interface, bad_x = self._get_model_nlp_interface() + msg = "Error in objective function" + with self.assertRaisesRegex(cyipopt.CyIpoptEvaluationError, msg): + interface.objective(bad_x) + + def test_error_in_gradient(self): + m, nlp, interface, bad_x = self._get_model_nlp_interface() + msg = "Error in objective gradient" + with self.assertRaisesRegex(cyipopt.CyIpoptEvaluationError, msg): + interface.gradient(bad_x) + + def test_error_in_constraints(self): + m, nlp, interface, bad_x = self._get_model_nlp_interface() + msg = "Error in constraint evaluation" + with self.assertRaisesRegex(cyipopt.CyIpoptEvaluationError, msg): + interface.constraints(bad_x) + + def test_error_in_jacobian(self): + m, nlp, interface, bad_x = self._get_model_nlp_interface() + msg = "Error in constraint Jacobian" + with self.assertRaisesRegex(cyipopt.CyIpoptEvaluationError, msg): + interface.jacobian(bad_x) + + def test_error_in_hessian(self): + m, nlp, interface, bad_x = self._get_model_nlp_interface() + msg = "Error in Lagrangian Hessian" + with self.assertRaisesRegex(cyipopt.CyIpoptEvaluationError, msg): + interface.hessian(bad_x, [1.0], 0.0) + + if __name__ == "__main__": unittest.main() From 168d7beef2b686a2c10d7abcac78184ac0b4786c Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 28 Jul 2023 14:42:14 -0600 Subject: [PATCH 0010/1204] Design discussion: Solver refactor - APPSI review --- pyomo/contrib/appsi/base.py | 167 ++++++++++++++++++++++++++++++------ 1 file changed, 140 insertions(+), 27 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index ca7255d5628..00f8982349c 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -42,14 +42,42 @@ from pyomo.core.expr.numvalue import NumericConstant +# # TerminationCondition + +# We currently have: Termination condition, solver status, and solution status. +# LL: Michael was trying to go for simplicity. All three conditions can be confusing. +# It is likely okay to have termination condition and solver status. + +# ## Open Questions (User Perspective) +# - Did I (the user) get a reasonable answer back from the solver? +# - If the answer is not reasonable, can I figure out why? + +# ## Our Goal +# Solvers normally tell you what they did and hope the users understand that. +# *We* want to try to return that information but also _help_ the user. + +# ## Proposals +# PROPOSAL 1: PyomoCondition and SolverCondition +# - SolverCondition: what the solver said +# - PyomoCondition: what we interpret that the solver said + +# PROPOSAL 2: TerminationCondition contains... +# - Some finite list of conditions +# - Two flags: why did it exit (TerminationCondition)? how do we interpret the result (SolutionStatus)? +# - Replace `optimal` with `normal` or `ok` for the termination flag; `optimal` can be used differently for the solver flag +# - You can use something else like `local`, `global`, `feasible` for solution status + class TerminationCondition(enum.Enum): """ An enumeration for checking the termination condition of solvers """ - unknown = 0 + unknown = 42 """unknown serves as both a default value, and it is used when no other enum member makes sense""" + ok = 0 + """The solver exited with the optimal solution""" + maxTimeLimit = 1 """The solver exited due to a time limit""" @@ -62,46 +90,78 @@ class TerminationCondition(enum.Enum): minStepLength = 4 """The solver exited due to a minimum step length""" - optimal = 5 - """The solver exited with the optimal solution""" - - unbounded = 8 + unbounded = 5 """The solver exited because the problem is unbounded""" - infeasible = 9 + infeasible = 6 """The solver exited because the problem is infeasible""" - infeasibleOrUnbounded = 10 + infeasibleOrUnbounded = 7 """The solver exited because the problem is either infeasible or unbounded""" - error = 11 + error = 8 """The solver exited due to an error""" - interrupted = 12 + interrupted = 9 """The solver exited because it was interrupted""" - licensingProblems = 13 + licensingProblems = 10 """The solver exited due to licensing problems""" -class SolverConfig(ConfigDict): +class SolutionStatus(enum.Enum): + # We may want to not use enum.Enum; we may want to use the flavor that allows sets + noSolution = 0 + locallyOptimal = 1 + globallyOptimal = 2 + feasible = 3 + + +# # SolverConfig + +# The idea here (currently / in theory) is that a call to solve will have a keyword argument `solver_config`: +# ``` +# solve(model, solver_config=...) +# config = self.config(solver_config) +# ``` + +# We have several flavors of options: +# - Solver options +# - Standardized options +# - Wrapper options +# - Interface options +# - potentially... more? + +# ## The Options + +# There are three basic structures: flat, doubly-nested, separate dicts. +# We need to pick between these three structures (and stick with it). + +# **Flat: Clear interface; ambiguous about what goes where; better solve interface.** <- WINNER +# Doubly: More obscure interface; less ambiguity; better programmatic interface. +# SepDicts: Clear delineation; **kwargs becomes confusing (what maps to what?) (NOT HAPPENING) + + +class InterfaceConfig(ConfigDict): """ Attributes ---------- - time_limit: float + time_limit: float - sent to solver Time limit for the solver - stream_solver: bool + stream_solver: bool - wrapper If True, then the solver log goes to stdout - load_solution: bool + load_solution: bool - wrapper If False, then the values of the primal variables will not be loaded into the model - symbolic_solver_labels: bool + symbolic_solver_labels: bool - sent to solver If True, the names given to the solver will reflect the names of the pyomo components. Cannot be changed after set_instance is called. - report_timing: bool + report_timing: bool - wrapper If True, then some timing information will be printed at the end of the solve. + solver_options: ConfigDict or dict + The "raw" solver options to be passed to the solver. """ def __init__( @@ -112,7 +172,7 @@ def __init__( implicit_domain=None, visibility=0, ): - super(SolverConfig, self).__init__( + super(InterfaceConfig, self).__init__( description=description, doc=doc, implicit=implicit, @@ -120,20 +180,19 @@ def __init__( visibility=visibility, ) - self.declare('time_limit', ConfigValue(domain=NonNegativeFloat)) self.declare('stream_solver', ConfigValue(domain=bool)) self.declare('load_solution', ConfigValue(domain=bool)) self.declare('symbolic_solver_labels', ConfigValue(domain=bool)) self.declare('report_timing', ConfigValue(domain=bool)) - self.time_limit: Optional[float] = None + self.time_limit: Optional[float] = self.declare('time_limit', ConfigValue(domain=NonNegativeFloat)) self.stream_solver: bool = False self.load_solution: bool = True self.symbolic_solver_labels: bool = False self.report_timing: bool = False -class MIPSolverConfig(SolverConfig): +class MIPSolverConfig(InterfaceConfig): """ Attributes ---------- @@ -167,6 +226,21 @@ def __init__( self.relax_integrality: bool = False +# # SolutionLoaderBase + +# This is an attempt to answer the issue of persistent/non-persistent solution +# loading. This is an attribute of the results object (not the solver). + +# You wouldn't ask the solver to load a solution into a model. You would +# ask the result to load the solution - into the model you solved. +# The results object points to relevant elements; elements do NOT point to +# the results object. + +# Per Michael: This may be a bit clunky; but it works. +# Per Siirola: We may want to rethink `load_vars` and `get_primals`. In particular, +# this is for efficiency - don't create a dictionary you don't need to. And what is +# the client use-case for `get_primals`? + class SolutionLoaderBase(abc.ABC): def load_vars( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None @@ -584,6 +658,46 @@ def __init__( self.treat_fixed_vars_as_params: bool = True +# # Solver + +# ## Open Question: What does 'solve' look like? + +# We may want to use the 80/20 rule here - we support 80% of the cases; anything +# fancier than that is going to require "writing code." The 80% would be offerings +# that are supported as part of the `pyomo` script. + +# ## Configs + +# We will likely have two configs for `solve`: standardized config (processes `**kwargs`) +# and implicit ConfigDict with some specialized options. + +# These have to be separated because there is a set that need to be passed +# directly to the solver. The other is Pyomo options / our standardized options +# (a few of which might be passed directly to solver, e.g., time_limit). + +# ## Contained Methods + +# We do not like `symbol_map`; it's keyed towards file-based interfaces. That +# is the `lp` writer; the `nl` writer doesn't need that (and in fact, it's +# obnoxious). The new `nl` writer returns back more meaningful things to the `nl` +# interface. + +# If the writer needs a symbol map, it will return it. But it is _not_ a +# solver thing. So it does not need to continue to exist in the solver interface. + +# All other options are reasonable. + +# ## Other (maybe should be contained) Methods + +# There are other methods in other solvers such as `warmstart`, `sos`; do we +# want to continue to support and/or offer those features? + +# The solver interface is not responsible for telling the client what +# it can do, e.g., `supports_sos2`. This is actually a contract between +# the solver and its writer. + +# End game: we are not supporting a `has_Xcapability` interface (CHECK BOOK). + class Solver(abc.ABC): class Availability(enum.IntEnum): NotFound = 0 @@ -610,7 +724,7 @@ def __str__(self): return self.name @abc.abstractmethod - def solve(self, model: _BlockData, timer: HierarchicalTimer = None) -> Results: + def solve(self, model: _BlockData, tee = False, timer: HierarchicalTimer = None, **kwargs) -> Results: """ Solve a Pyomo model. @@ -618,8 +732,12 @@ def solve(self, model: _BlockData, timer: HierarchicalTimer = None) -> Results: ---------- model: _BlockData The Pyomo model to be solved + tee: bool + Show solver output in the terminal timer: HierarchicalTimer An option timer for reporting timing + **kwargs + Additional keyword arguments (including solver_options - passthrough options; delivered directly to the solver (with no validation)) Returns ------- @@ -672,17 +790,12 @@ def config(self): Returns ------- - SolverConfig + InterfaceConfig An object for configuring pyomo solve options such as the time limit. These options are mostly independent of the solver. """ pass - @property - @abc.abstractmethod - def symbol_map(self): - pass - def is_persistent(self): """ Returns From 55ee816fc8247750ee049a8c5ddec91aa47f204b Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 1 Aug 2023 10:44:30 -0600 Subject: [PATCH 0011/1204] Finish conversion from optimal to ok --- pyomo/contrib/appsi/base.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index 00f8982349c..9ab4d5020a7 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -14,7 +14,7 @@ from pyomo.core.base.sos import _SOSConstraintData, SOSConstraint from pyomo.core.base.var import _GeneralVarData, Var from pyomo.core.base.param import _ParamData, Param -from pyomo.core.base.block import _BlockData, Block +from pyomo.core.base.block import _BlockData from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.common.collections import ComponentMap from .utils.get_objective import get_objective @@ -36,7 +36,6 @@ ) from pyomo.core.kernel.objective import minimize from pyomo.core.base import SymbolMap -import weakref from .cmodel import cmodel, cmodel_available from pyomo.core.staleflag import StaleFlagManager from pyomo.core.expr.numvalue import NumericConstant @@ -460,7 +459,7 @@ class Results(object): >>> opt = appsi.solvers.Ipopt() >>> opt.config.load_solution = False >>> results = opt.solve(m) #doctest:+SKIP - >>> if results.termination_condition == appsi.base.TerminationCondition.optimal: #doctest:+SKIP + >>> if results.termination_condition == appsi.base.TerminationCondition.ok: #doctest:+SKIP ... print('optimal solution found: ', results.best_feasible_objective) #doctest:+SKIP ... results.solution_loader.load_vars() #doctest:+SKIP ... print('the optimal value of x is ', m.x.value) #doctest:+SKIP @@ -1583,7 +1582,7 @@ def update(self, timer: HierarchicalTimer = None): TerminationCondition.maxIterations: LegacyTerminationCondition.maxIterations, TerminationCondition.objectiveLimit: LegacyTerminationCondition.minFunctionValue, TerminationCondition.minStepLength: LegacyTerminationCondition.minStepLength, - TerminationCondition.optimal: LegacyTerminationCondition.optimal, + TerminationCondition.ok: LegacyTerminationCondition.optimal, TerminationCondition.unbounded: LegacyTerminationCondition.unbounded, TerminationCondition.infeasible: LegacyTerminationCondition.infeasible, TerminationCondition.infeasibleOrUnbounded: LegacyTerminationCondition.infeasibleOrUnbounded, @@ -1599,7 +1598,7 @@ def update(self, timer: HierarchicalTimer = None): TerminationCondition.maxIterations: LegacySolverStatus.aborted, TerminationCondition.objectiveLimit: LegacySolverStatus.aborted, TerminationCondition.minStepLength: LegacySolverStatus.error, - TerminationCondition.optimal: LegacySolverStatus.ok, + TerminationCondition.ok: LegacySolverStatus.ok, TerminationCondition.unbounded: LegacySolverStatus.error, TerminationCondition.infeasible: LegacySolverStatus.error, TerminationCondition.infeasibleOrUnbounded: LegacySolverStatus.error, @@ -1615,7 +1614,7 @@ def update(self, timer: HierarchicalTimer = None): TerminationCondition.maxIterations: LegacySolutionStatus.stoppedByLimit, TerminationCondition.objectiveLimit: LegacySolutionStatus.stoppedByLimit, TerminationCondition.minStepLength: LegacySolutionStatus.error, - TerminationCondition.optimal: LegacySolutionStatus.optimal, + TerminationCondition.ok: LegacySolutionStatus.optimal, TerminationCondition.unbounded: LegacySolutionStatus.unbounded, TerminationCondition.infeasible: LegacySolutionStatus.infeasible, TerminationCondition.infeasibleOrUnbounded: LegacySolutionStatus.unsure, From a30477ce113b4fe9d87c4fd29b797542da14d8fe Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 1 Aug 2023 10:58:15 -0600 Subject: [PATCH 0012/1204] Change call to InterfaceConfig --- pyomo/contrib/appsi/base.py | 6 +++--- pyomo/contrib/appsi/solvers/cbc.py | 4 ++-- pyomo/contrib/appsi/solvers/cplex.py | 4 ++-- pyomo/contrib/appsi/solvers/gurobi.py | 4 ++-- pyomo/contrib/appsi/solvers/highs.py | 4 ++-- pyomo/contrib/appsi/solvers/ipopt.py | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index 9ab4d5020a7..630aefbbd29 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -116,7 +116,7 @@ class SolutionStatus(enum.Enum): feasible = 3 -# # SolverConfig +# # InterfaceConfig # The idea here (currently / in theory) is that a call to solve will have a keyword argument `solver_config`: # ``` @@ -191,7 +191,7 @@ def __init__( self.report_timing: bool = False -class MIPSolverConfig(InterfaceConfig): +class MIPInterfaceConfig(InterfaceConfig): """ Attributes ---------- @@ -210,7 +210,7 @@ def __init__( implicit_domain=None, visibility=0, ): - super(MIPSolverConfig, self).__init__( + super(MIPInterfaceConfig, self).__init__( description=description, doc=doc, implicit=implicit, diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index b31a96dbf8a..833ef54b2cf 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -4,7 +4,7 @@ PersistentSolver, Results, TerminationCondition, - SolverConfig, + InterfaceConfig, PersistentSolutionLoader, ) from pyomo.contrib.appsi.writers import LPWriter @@ -33,7 +33,7 @@ logger = logging.getLogger(__name__) -class CbcConfig(SolverConfig): +class CbcConfig(InterfaceConfig): def __init__( self, description=None, diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 6c5e281ffac..7b51d6611c2 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -3,7 +3,7 @@ PersistentSolver, Results, TerminationCondition, - MIPSolverConfig, + MIPInterfaceConfig, PersistentSolutionLoader, ) from pyomo.contrib.appsi.writers import LPWriter @@ -29,7 +29,7 @@ logger = logging.getLogger(__name__) -class CplexConfig(MIPSolverConfig): +class CplexConfig(MIPInterfaceConfig): def __init__( self, description=None, diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index 2362612e9ee..0d99089fbab 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -23,7 +23,7 @@ PersistentSolver, Results, TerminationCondition, - MIPSolverConfig, + MIPInterfaceConfig, PersistentBase, PersistentSolutionLoader, ) @@ -53,7 +53,7 @@ class DegreeError(PyomoException): pass -class GurobiConfig(MIPSolverConfig): +class GurobiConfig(MIPInterfaceConfig): def __init__( self, description=None, diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 9de5accfb91..63c799b0f61 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -20,7 +20,7 @@ PersistentSolver, Results, TerminationCondition, - MIPSolverConfig, + MIPInterfaceConfig, PersistentBase, PersistentSolutionLoader, ) @@ -38,7 +38,7 @@ class DegreeError(PyomoException): pass -class HighsConfig(MIPSolverConfig): +class HighsConfig(MIPInterfaceConfig): def __init__( self, description=None, diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index fde4c55073d..047ce09a533 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -4,7 +4,7 @@ PersistentSolver, Results, TerminationCondition, - SolverConfig, + InterfaceConfig, PersistentSolutionLoader, ) from pyomo.contrib.appsi.writers import NLWriter @@ -36,7 +36,7 @@ logger = logging.getLogger(__name__) -class IpoptConfig(SolverConfig): +class IpoptConfig(InterfaceConfig): def __init__( self, description=None, From 75cc8c4c654d0c15a7d0920c9b990eece9f99f6f Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 1 Aug 2023 11:24:57 -0600 Subject: [PATCH 0013/1204] Change test checks --- pyomo/contrib/appsi/base.py | 15 ++++-- .../contrib/appsi/examples/getting_started.py | 2 +- pyomo/contrib/appsi/solvers/cbc.py | 10 ++-- pyomo/contrib/appsi/solvers/cplex.py | 4 +- pyomo/contrib/appsi/solvers/gurobi.py | 4 +- pyomo/contrib/appsi/solvers/highs.py | 6 +-- pyomo/contrib/appsi/solvers/ipopt.py | 10 ++-- .../solvers/tests/test_gurobi_persistent.py | 2 +- .../solvers/tests/test_persistent_solvers.py | 50 +++++++++---------- 9 files changed, 55 insertions(+), 48 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index 630aefbbd29..fe0b3ee2999 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -62,10 +62,11 @@ # PROPOSAL 2: TerminationCondition contains... # - Some finite list of conditions -# - Two flags: why did it exit (TerminationCondition)? how do we interpret the result (SolutionStatus)? +# - Two flags: why did it exit (TerminationCondition)? how do we interpret the result (SolutionStatus)? # - Replace `optimal` with `normal` or `ok` for the termination flag; `optimal` can be used differently for the solver flag # - You can use something else like `local`, `global`, `feasible` for solution status + class TerminationCondition(enum.Enum): """ An enumeration for checking the termination condition of solvers @@ -184,7 +185,9 @@ def __init__( self.declare('symbolic_solver_labels', ConfigValue(domain=bool)) self.declare('report_timing', ConfigValue(domain=bool)) - self.time_limit: Optional[float] = self.declare('time_limit', ConfigValue(domain=NonNegativeFloat)) + self.time_limit: Optional[float] = self.declare( + 'time_limit', ConfigValue(domain=NonNegativeFloat) + ) self.stream_solver: bool = False self.load_solution: bool = True self.symbolic_solver_labels: bool = False @@ -240,6 +243,7 @@ def __init__( # this is for efficiency - don't create a dictionary you don't need to. And what is # the client use-case for `get_primals`? + class SolutionLoaderBase(abc.ABC): def load_vars( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None @@ -689,7 +693,7 @@ def __init__( # ## Other (maybe should be contained) Methods # There are other methods in other solvers such as `warmstart`, `sos`; do we -# want to continue to support and/or offer those features? +# want to continue to support and/or offer those features? # The solver interface is not responsible for telling the client what # it can do, e.g., `supports_sos2`. This is actually a contract between @@ -697,6 +701,7 @@ def __init__( # End game: we are not supporting a `has_Xcapability` interface (CHECK BOOK). + class Solver(abc.ABC): class Availability(enum.IntEnum): NotFound = 0 @@ -723,7 +728,9 @@ def __str__(self): return self.name @abc.abstractmethod - def solve(self, model: _BlockData, tee = False, timer: HierarchicalTimer = None, **kwargs) -> Results: + def solve( + self, model: _BlockData, tee=False, timer: HierarchicalTimer = None, **kwargs + ) -> Results: """ Solve a Pyomo model. diff --git a/pyomo/contrib/appsi/examples/getting_started.py b/pyomo/contrib/appsi/examples/getting_started.py index de22d28e0a4..5cbac7c81e3 100644 --- a/pyomo/contrib/appsi/examples/getting_started.py +++ b/pyomo/contrib/appsi/examples/getting_started.py @@ -31,7 +31,7 @@ def main(plot=True, n_points=200): for p_val in p_values: m.p.value = p_val res = opt.solve(m, timer=timer) - assert res.termination_condition == appsi.base.TerminationCondition.optimal + assert res.termination_condition == appsi.base.TerminationCondition.ok obj_values.append(res.best_feasible_objective) opt.load_vars([m.x]) x_values.append(m.x.value) diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index 833ef54b2cf..641a90c3ae7 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -232,7 +232,7 @@ def _parse_soln(self): termination_line = all_lines[0].lower() obj_val = None if termination_line.startswith('optimal'): - results.termination_condition = TerminationCondition.optimal + results.termination_condition = TerminationCondition.ok obj_val = float(termination_line.split()[-1]) elif 'infeasible' in termination_line: results.termination_condition = TerminationCondition.infeasible @@ -307,7 +307,7 @@ def _parse_soln(self): self._reduced_costs[v_id] = (v, -rc_val) if ( - results.termination_condition == TerminationCondition.optimal + results.termination_condition == TerminationCondition.ok and self.config.load_solution ): for v_id, (v, val) in self._primal_sol.items(): @@ -316,7 +316,7 @@ def _parse_soln(self): results.best_feasible_objective = None else: results.best_feasible_objective = obj_val - elif results.termination_condition == TerminationCondition.optimal: + elif results.termination_condition == TerminationCondition.ok: if self._writer.get_active_objective() is None: results.best_feasible_objective = None else: @@ -451,7 +451,7 @@ def get_duals(self, cons_to_load=None): if ( self._last_results_object is None or self._last_results_object.termination_condition - != TerminationCondition.optimal + != TerminationCondition.ok ): raise RuntimeError( 'Solver does not currently have valid duals. Please ' @@ -469,7 +469,7 @@ def get_reduced_costs( if ( self._last_results_object is None or self._last_results_object.termination_condition - != TerminationCondition.optimal + != TerminationCondition.ok ): raise RuntimeError( 'Solver does not currently have valid reduced costs. Please ' diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 7b51d6611c2..2ea051c58b4 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -284,7 +284,7 @@ def _postsolve(self, timer: HierarchicalTimer, solve_time): status = cpxprob.solution.get_status() if status in [1, 101, 102]: - results.termination_condition = TerminationCondition.optimal + results.termination_condition = TerminationCondition.ok elif status in [2, 40, 118, 133, 134]: results.termination_condition = TerminationCondition.unbounded elif status in [4, 119, 134]: @@ -336,7 +336,7 @@ def _postsolve(self, timer: HierarchicalTimer, solve_time): 'results.best_feasible_objective before loading a solution.' ) else: - if results.termination_condition != TerminationCondition.optimal: + if results.termination_condition != TerminationCondition.ok: logger.warning( 'Loading a feasible but suboptimal solution. ' 'Please set load_solution=False and check ' diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index 0d99089fbab..af17a398845 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -874,7 +874,7 @@ def _postsolve(self, timer: HierarchicalTimer): if status == grb.LOADED: # problem is loaded, but no solution results.termination_condition = TerminationCondition.unknown elif status == grb.OPTIMAL: # optimal - results.termination_condition = TerminationCondition.optimal + results.termination_condition = TerminationCondition.ok elif status == grb.INFEASIBLE: results.termination_condition = TerminationCondition.infeasible elif status == grb.INF_OR_UNBD: @@ -925,7 +925,7 @@ def _postsolve(self, timer: HierarchicalTimer): timer.start('load solution') if config.load_solution: if gprob.SolCount > 0: - if results.termination_condition != TerminationCondition.optimal: + if results.termination_condition != TerminationCondition.ok: logger.warning( 'Loading a feasible but suboptimal solution. ' 'Please set load_solution=False and check ' diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 63c799b0f61..8de873635a5 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -610,7 +610,7 @@ def _postsolve(self, timer: HierarchicalTimer): elif status == highspy.HighsModelStatus.kModelEmpty: results.termination_condition = TerminationCondition.unknown elif status == highspy.HighsModelStatus.kOptimal: - results.termination_condition = TerminationCondition.optimal + results.termination_condition = TerminationCondition.ok elif status == highspy.HighsModelStatus.kInfeasible: results.termination_condition = TerminationCondition.infeasible elif status == highspy.HighsModelStatus.kUnboundedOrInfeasible: @@ -633,7 +633,7 @@ def _postsolve(self, timer: HierarchicalTimer): timer.start('load solution') self._sol = highs.getSolution() has_feasible_solution = False - if results.termination_condition == TerminationCondition.optimal: + if results.termination_condition == TerminationCondition.ok: has_feasible_solution = True elif results.termination_condition in { TerminationCondition.objectiveLimit, @@ -645,7 +645,7 @@ def _postsolve(self, timer: HierarchicalTimer): if config.load_solution: if has_feasible_solution: - if results.termination_condition != TerminationCondition.optimal: + if results.termination_condition != TerminationCondition.ok: logger.warning( 'Loading a feasible but suboptimal solution. ' 'Please set load_solution=False and check ' diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index 047ce09a533..2d8cfe40b32 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -303,7 +303,7 @@ def _parse_sol(self): termination_line = all_lines[1] if 'Optimal Solution Found' in termination_line: - results.termination_condition = TerminationCondition.optimal + results.termination_condition = TerminationCondition.ok elif 'Problem may be infeasible' in termination_line: results.termination_condition = TerminationCondition.infeasible elif 'problem might be unbounded' in termination_line: @@ -384,7 +384,7 @@ def _parse_sol(self): self._reduced_costs[var] = 0 if ( - results.termination_condition == TerminationCondition.optimal + results.termination_condition == TerminationCondition.ok and self.config.load_solution ): for v, val in self._primal_sol.items(): @@ -395,7 +395,7 @@ def _parse_sol(self): results.best_feasible_objective = value( self._writer.get_active_objective().expr ) - elif results.termination_condition == TerminationCondition.optimal: + elif results.termination_condition == TerminationCondition.ok: if self._writer.get_active_objective() is None: results.best_feasible_objective = None else: @@ -526,7 +526,7 @@ def get_duals( if ( self._last_results_object is None or self._last_results_object.termination_condition - != TerminationCondition.optimal + != TerminationCondition.ok ): raise RuntimeError( 'Solver does not currently have valid duals. Please ' @@ -544,7 +544,7 @@ def get_reduced_costs( if ( self._last_results_object is None or self._last_results_object.termination_condition - != TerminationCondition.optimal + != TerminationCondition.ok ): raise RuntimeError( 'Solver does not currently have valid reduced costs. Please ' diff --git a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py index 6366077642d..03042bbb5f4 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py @@ -160,7 +160,7 @@ def test_lp(self): res = opt.solve(self.m) self.assertAlmostEqual(x + y, res.best_feasible_objective) self.assertAlmostEqual(x + y, res.best_objective_bound) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertTrue(res.best_feasible_objective is not None) self.assertAlmostEqual(x, self.m.x.value) self.assertAlmostEqual(y, self.m.y.value) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index bafccb3527c..88a278bfe3b 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -122,13 +122,13 @@ def test_range_constraint(self, name: str, opt_class: Type[PersistentSolver]): m.obj = pe.Objective(expr=m.x) m.c = pe.Constraint(expr=(-1, m.x, 1)) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(m.x.value, -1) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c], 1) m.obj.sense = pe.maximize res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(m.x.value, 1) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c], 1) @@ -143,7 +143,7 @@ def test_reduced_costs(self, name: str, opt_class: Type[PersistentSolver]): m.y = pe.Var(bounds=(-2, 2)) m.obj = pe.Objective(expr=3 * m.x + 4 * m.y) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(m.x.value, -1) self.assertAlmostEqual(m.y.value, -2) rc = opt.get_reduced_costs() @@ -159,13 +159,13 @@ def test_reduced_costs2(self, name: str, opt_class: Type[PersistentSolver]): m.x = pe.Var(bounds=(-1, 1)) m.obj = pe.Objective(expr=m.x) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(m.x.value, -1) rc = opt.get_reduced_costs() self.assertAlmostEqual(rc[m.x], 1) m.obj.sense = pe.maximize res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(m.x.value, 1) rc = opt.get_reduced_costs() self.assertAlmostEqual(rc[m.x], 1) @@ -193,7 +193,7 @@ def test_param_changes(self, name: str, opt_class: Type[PersistentSolver]): m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) @@ -229,7 +229,7 @@ def test_immutable_param(self, name: str, opt_class: Type[PersistentSolver]): m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) @@ -261,7 +261,7 @@ def test_equality(self, name: str, opt_class: Type[PersistentSolver]): m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) @@ -299,7 +299,7 @@ def test_linear_expression(self, name: str, opt_class: Type[PersistentSolver]): m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) self.assertTrue(res.best_objective_bound <= m.y.value) @@ -327,7 +327,7 @@ def test_no_objective(self, name: str, opt_class: Type[PersistentSolver]): m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertEqual(res.best_feasible_objective, None) @@ -354,7 +354,7 @@ def test_add_remove_cons(self, name: str, opt_class: Type[PersistentSolver]): m.c1 = pe.Constraint(expr=m.y >= a1 * m.x + b1) m.c2 = pe.Constraint(expr=m.y >= a2 * m.x + b2) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) @@ -365,7 +365,7 @@ def test_add_remove_cons(self, name: str, opt_class: Type[PersistentSolver]): m.c3 = pe.Constraint(expr=m.y >= a3 * m.x + b3) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(m.x.value, (b3 - b1) / (a1 - a3)) self.assertAlmostEqual(m.y.value, a1 * (b3 - b1) / (a1 - a3) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) @@ -377,7 +377,7 @@ def test_add_remove_cons(self, name: str, opt_class: Type[PersistentSolver]): del m.c3 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) @@ -401,7 +401,7 @@ def test_results_infeasible(self, name: str, opt_class: Type[PersistentSolver]): res = opt.solve(m) opt.config.load_solution = False res = opt.solve(m) - self.assertNotEqual(res.termination_condition, TerminationCondition.optimal) + self.assertNotEqual(res.termination_condition, TerminationCondition.ok) if opt_class is Ipopt: acceptable_termination_conditions = { TerminationCondition.infeasible, @@ -685,7 +685,7 @@ def test_mutable_param_with_range( m.c2.value = float(c2) m.obj.sense = sense res: Results = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) if sense is pe.minimize: self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2), 6) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1, 6) @@ -720,7 +720,7 @@ def test_add_and_remove_vars(self, name: str, opt_class: Type[PersistentSolver]) opt.update_config.check_for_new_or_removed_vars = False opt.config.load_solution = False res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) opt.load_vars() self.assertAlmostEqual(m.y.value, -1) m.x = pe.Var() @@ -733,7 +733,7 @@ def test_add_and_remove_vars(self, name: str, opt_class: Type[PersistentSolver]) opt.add_variables([m.x]) opt.add_constraints([m.c1, m.c2]) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) opt.load_vars() self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) @@ -741,7 +741,7 @@ def test_add_and_remove_vars(self, name: str, opt_class: Type[PersistentSolver]) opt.remove_variables([m.x]) m.x.value = None res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) opt.load_vars() self.assertEqual(m.x.value, None) self.assertAlmostEqual(m.y.value, -1) @@ -800,7 +800,7 @@ def test_with_numpy(self, name: str, opt_class: Type[PersistentSolver]): ) ) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) @@ -1126,14 +1126,14 @@ def test_variables_elsewhere(self, name: str, opt_class: Type[PersistentSolver]) m.b.c2 = pe.Constraint(expr=m.y >= -m.x) res = opt.solve(m.b) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(res.best_feasible_objective, 1) self.assertAlmostEqual(m.x.value, -1) self.assertAlmostEqual(m.y.value, 1) m.x.setlb(0) res = opt.solve(m.b) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(res.best_feasible_objective, 2) self.assertAlmostEqual(m.x.value, 0) self.assertAlmostEqual(m.y.value, 2) @@ -1156,7 +1156,7 @@ def test_variables_elsewhere2(self, name: str, opt_class: Type[PersistentSolver] m.c4 = pe.Constraint(expr=m.y >= -m.z + 1) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(res.best_feasible_objective, 1) sol = res.solution_loader.get_primals() self.assertIn(m.x, sol) @@ -1166,7 +1166,7 @@ def test_variables_elsewhere2(self, name: str, opt_class: Type[PersistentSolver] del m.c3 del m.c4 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(res.best_feasible_objective, 0) sol = res.solution_loader.get_primals() self.assertIn(m.x, sol) @@ -1188,12 +1188,12 @@ def test_bug_1(self, name: str, opt_class: Type[PersistentSolver]): m.c = pe.Constraint(expr=m.y >= m.p * m.x) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(res.best_feasible_objective, 0) m.p.value = 1 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(res.best_feasible_objective, 3) From e4b9313f417d177125b66a9ff0576ac0a70e334a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 1 Aug 2023 13:59:12 -0600 Subject: [PATCH 0014/1204] Update docs --- pyomo/contrib/appsi/base.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index fe0b3ee2999..e4e6a915cbd 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -110,10 +110,22 @@ class TerminationCondition(enum.Enum): class SolutionStatus(enum.Enum): - # We may want to not use enum.Enum; we may want to use the flavor that allows sets + """ + An enumeration for interpreting the result of a termination + + TODO: We may want to not use enum.Enum; we may want to use the flavor that allows sets + """ + + """No solution found""" noSolution = 0 + + """Locally optimal solution identified""" locallyOptimal = 1 + + """Globally optimal solution identified""" globallyOptimal = 2 + + """Feasible solution identified""" feasible = 3 @@ -160,8 +172,6 @@ class InterfaceConfig(ConfigDict): report_timing: bool - wrapper If True, then some timing information will be printed at the end of the solve. - solver_options: ConfigDict or dict - The "raw" solver options to be passed to the solver. """ def __init__( From 953607aa7c78cfd1e1f1a27dbe1fca4229a0d00a Mon Sep 17 00:00:00 2001 From: gomesraphael7d Date: Fri, 11 Aug 2023 08:34:52 -0300 Subject: [PATCH 0015/1204] Adjusting mps writer to the correct structure regarding binary variables declaration --- pyomo/repn/plugins/mps.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyomo/repn/plugins/mps.py b/pyomo/repn/plugins/mps.py index 89420929778..5186f077153 100644 --- a/pyomo/repn/plugins/mps.py +++ b/pyomo/repn/plugins/mps.py @@ -515,10 +515,18 @@ def yield_all_constraints(): column_template = " %s %s %" + self._precision_string + "\n" output_file.write("COLUMNS\n") cnt = 0 + set_integer = False for vardata in variable_list: col_entries = column_data[variable_to_column[vardata]] cnt += 1 if len(col_entries) > 0: + if vardata.is_binary() and not set_integer: + set_integer = True + output_file.write(" %s %s %s\n" % ("INT", "'MARKER'", "'INTORG'")) + if not vardata.is_binary() and set_integer: + set_integer = False + output_file.write(" %s %s %s\n" % ("INTEND", "'MARKER'", "'INTEND'")) + var_label = variable_symbol_dictionary[id(vardata)] for i, (row_label, coef) in enumerate(col_entries): output_file.write( From 9ee408f26a54bb2bf8f50870f075943c285fd62c Mon Sep 17 00:00:00 2001 From: gomesraphael7d Date: Fri, 11 Aug 2023 09:09:08 -0300 Subject: [PATCH 0016/1204] Changing is_binary() method to is_integer() to contemplate all integer variables --- pyomo/repn/plugins/mps.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/repn/plugins/mps.py b/pyomo/repn/plugins/mps.py index 5186f077153..d3a324e2f50 100644 --- a/pyomo/repn/plugins/mps.py +++ b/pyomo/repn/plugins/mps.py @@ -520,10 +520,10 @@ def yield_all_constraints(): col_entries = column_data[variable_to_column[vardata]] cnt += 1 if len(col_entries) > 0: - if vardata.is_binary() and not set_integer: + if vardata.is_integer() and not set_integer: set_integer = True output_file.write(" %s %s %s\n" % ("INT", "'MARKER'", "'INTORG'")) - if not vardata.is_binary() and set_integer: + if not vardata.is_integer() and set_integer: set_integer = False output_file.write(" %s %s %s\n" % ("INTEND", "'MARKER'", "'INTEND'")) From 4c9af82c7dd2226412de5ca50ed8c85be66cc67f Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 11 Aug 2023 10:12:40 -0600 Subject: [PATCH 0017/1204] Adjust tests to check for 'ok' instead of 'optimal' --- pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 4a5c816394f..ec9f397bdc4 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -91,7 +91,7 @@ def test_remove_variable_and_objective( m.x = pe.Var(bounds=(2, None)) m.obj = pe.Objective(expr=m.x) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(m.x.value, 2) del m.x @@ -99,7 +99,7 @@ def test_remove_variable_and_objective( m.x = pe.Var(bounds=(2, None)) m.obj = pe.Objective(expr=m.x) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.ok) self.assertAlmostEqual(m.x.value, 2) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) From a0b7840382e5db881e8a5ef8364967b5b2497616 Mon Sep 17 00:00:00 2001 From: gomesraphael7d Date: Fri, 11 Aug 2023 15:44:09 -0300 Subject: [PATCH 0018/1204] Formatting code to black standard indentation --- pyomo/repn/plugins/mps.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyomo/repn/plugins/mps.py b/pyomo/repn/plugins/mps.py index d3a324e2f50..8ebd45f2327 100644 --- a/pyomo/repn/plugins/mps.py +++ b/pyomo/repn/plugins/mps.py @@ -522,10 +522,14 @@ def yield_all_constraints(): if len(col_entries) > 0: if vardata.is_integer() and not set_integer: set_integer = True - output_file.write(" %s %s %s\n" % ("INT", "'MARKER'", "'INTORG'")) + output_file.write( + " %s %s %s\n" % ("INT", "'MARKER'", "'INTORG'") + ) if not vardata.is_integer() and set_integer: set_integer = False - output_file.write(" %s %s %s\n" % ("INTEND", "'MARKER'", "'INTEND'")) + output_file.write( + " %s %s %s\n" % ("INTEND", "'MARKER'", "'INTEND'") + ) var_label = variable_symbol_dictionary[id(vardata)] for i, (row_label, coef) in enumerate(col_entries): From cdcfeff95efc8881376986c031bb7b0357b3c3ef Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 11 Aug 2023 12:55:34 -0600 Subject: [PATCH 0019/1204] Much discussion with @jsiirola resulted in a small change --- pyomo/contrib/appsi/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index fd8f432a7fa..a715fabf1f6 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -75,7 +75,7 @@ class TerminationCondition(enum.Enum): unknown = 42 """unknown serves as both a default value, and it is used when no other enum member makes sense""" - ok = 0 + convergenceCriteriaSatisfied = 0 """The solver exited with the optimal solution""" maxTimeLimit = 1 From 92480c386a23e692eaac9bee3097ce9fce1fdd3b Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 15 Aug 2023 11:13:38 -0600 Subject: [PATCH 0020/1204] Small refactor in APPSI to improve performance; small rework of TerminationCondition/SolverStatus --- pyomo/contrib/appsi/base.py | 200 ++++++++---------- pyomo/contrib/appsi/build.py | 4 +- .../contrib/appsi/examples/getting_started.py | 4 +- .../appsi/examples/tests/test_examples.py | 2 +- pyomo/contrib/appsi/fbbt.py | 16 +- pyomo/contrib/appsi/solvers/cbc.py | 18 +- pyomo/contrib/appsi/solvers/cplex.py | 10 +- pyomo/contrib/appsi/solvers/gurobi.py | 66 +++--- pyomo/contrib/appsi/solvers/highs.py | 70 +++--- pyomo/contrib/appsi/solvers/ipopt.py | 8 +- .../solvers/tests/test_gurobi_persistent.py | 2 +- .../solvers/tests/test_ipopt_persistent.py | 2 +- .../solvers/tests/test_persistent_solvers.py | 8 +- pyomo/contrib/appsi/tests/test_base.py | 8 +- pyomo/contrib/appsi/tests/test_interval.py | 4 +- .../utils/collect_vars_and_named_exprs.py | 8 +- pyomo/contrib/appsi/writers/config.py | 2 +- pyomo/contrib/appsi/writers/lp_writer.py | 14 +- pyomo/contrib/appsi/writers/nl_writer.py | 18 +- .../appsi/writers/tests/test_nl_writer.py | 2 +- 20 files changed, 227 insertions(+), 239 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index a715fabf1f6..805e96dacf1 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -72,61 +72,66 @@ class TerminationCondition(enum.Enum): An enumeration for checking the termination condition of solvers """ - unknown = 42 """unknown serves as both a default value, and it is used when no other enum member makes sense""" + unknown = 42 + """The solver exited because the convergence criteria were satisfied""" convergenceCriteriaSatisfied = 0 - """The solver exited with the optimal solution""" - maxTimeLimit = 1 """The solver exited due to a time limit""" + maxTimeLimit = 1 - maxIterations = 2 - """The solver exited due to an iteration limit """ + """The solver exited due to an iteration limit""" + iterationLimit = 2 - objectiveLimit = 3 """The solver exited due to an objective limit""" + objectiveLimit = 3 - minStepLength = 4 """The solver exited due to a minimum step length""" + minStepLength = 4 - unbounded = 5 """The solver exited because the problem is unbounded""" + unbounded = 5 - infeasible = 6 - """The solver exited because the problem is infeasible""" + """The solver exited because the problem is proven infeasible""" + provenInfeasible = 6 + + """The solver exited because the problem was found to be locally infeasible""" + locallyInfeasible = 7 - infeasibleOrUnbounded = 7 """The solver exited because the problem is either infeasible or unbounded""" + infeasibleOrUnbounded = 8 - error = 8 """The solver exited due to an error""" + error = 9 - interrupted = 9 """The solver exited because it was interrupted""" + interrupted = 10 - licensingProblems = 10 """The solver exited due to licensing problems""" + licensingProblems = 11 -class SolutionStatus(enum.Enum): +class SolutionStatus(enum.IntEnum): """ - An enumeration for interpreting the result of a termination - - TODO: We may want to not use enum.Enum; we may want to use the flavor that allows sets + An enumeration for interpreting the result of a termination. This describes the designated + status by the solver to be loaded back into the model. + + For now, we are choosing to use IntEnum such that return values are numerically + assigned in increasing order. """ - """No solution found""" + """No (single) solution found; possible that a population of solutions was returned""" noSolution = 0 - """Locally optimal solution identified""" - locallyOptimal = 1 - - """Globally optimal solution identified""" - globallyOptimal = 2 + """Solution point does not satisfy some domains and/or constraints""" + infeasible = 10 """Feasible solution identified""" - feasible = 3 + feasible = 20 + + """Optimal solution identified""" + optimal = 30 # # InterfaceConfig @@ -182,7 +187,7 @@ def __init__( implicit_domain=None, visibility=0, ): - super(InterfaceConfig, self).__init__( + super().__init__( description=description, doc=doc, implicit=implicit, @@ -223,7 +228,7 @@ def __init__( implicit_domain=None, visibility=0, ): - super(MIPInterfaceConfig, self).__init__( + super().__init__( description=description, doc=doc, implicit=implicit, @@ -404,9 +409,9 @@ def get_duals( 'for the given problem type.' ) if cons_to_load is None: - duals = dict(self._duals) + duals = {self._duals} else: - duals = dict() + duals = {} for c in cons_to_load: duals[c] = self._duals[c] return duals @@ -421,9 +426,9 @@ def get_slacks( 'for the given problem type.' ) if cons_to_load is None: - slacks = dict(self._slacks) + slacks = {self._slacks} else: - slacks = dict() + slacks = {} for c in cons_to_load: slacks[c] = self._slacks[c] return slacks @@ -446,7 +451,7 @@ def get_reduced_costs( return rc -class Results(object): +class Results(): """ Attributes ---------- @@ -526,7 +531,7 @@ def __init__( ): if doc is None: doc = 'Configuration options to detect changes in model between solves' - super(UpdateConfig, self).__init__( + super().__init__( description=description, doc=doc, implicit=implicit, @@ -1031,23 +1036,23 @@ def invalidate(self): class PersistentBase(abc.ABC): def __init__(self, only_child_vars=False): self._model = None - self._active_constraints = dict() # maps constraint to (lower, body, upper) - self._vars = dict() # maps var id to (var, lb, ub, fixed, domain, value) - self._params = dict() # maps param id to param + self._active_constraints = {} # maps constraint to (lower, body, upper) + self._vars = {} # maps var id to (var, lb, ub, fixed, domain, value) + self._params = {} # maps param id to param self._objective = None self._objective_expr = None self._objective_sense = None self._named_expressions = ( - dict() + {} ) # maps constraint to list of tuples (named_expr, named_expr.expr) self._external_functions = ComponentMap() - self._obj_named_expressions = list() + self._obj_named_expressions = [] self._update_config = UpdateConfig() self._referenced_variables = ( - dict() + {} ) # var_id: [dict[constraints, None], dict[sos constraints, None], None or objective] - self._vars_referenced_by_con = dict() - self._vars_referenced_by_obj = list() + self._vars_referenced_by_con = {} + self._vars_referenced_by_obj = [] self._expr_types = None self.use_extensions = False self._only_child_vars = only_child_vars @@ -1081,7 +1086,7 @@ def add_variables(self, variables: List[_GeneralVarData]): raise ValueError( 'variable {name} has already been added'.format(name=v.name) ) - self._referenced_variables[id(v)] = [dict(), dict(), None] + self._referenced_variables[id(v)] = [{}, {}, None] self._vars[id(v)] = ( v, v._lb, @@ -1106,24 +1111,24 @@ def _add_constraints(self, cons: List[_GeneralConstraintData]): pass def _check_for_new_vars(self, variables: List[_GeneralVarData]): - new_vars = dict() + new_vars = {} for v in variables: v_id = id(v) if v_id not in self._referenced_variables: new_vars[v_id] = v - self.add_variables(list(new_vars.values())) + self.add_variables([new_vars.values()]) def _check_to_remove_vars(self, variables: List[_GeneralVarData]): - vars_to_remove = dict() + vars_to_remove = {} for v in variables: v_id = id(v) ref_cons, ref_sos, ref_obj = self._referenced_variables[v_id] if len(ref_cons) == 0 and len(ref_sos) == 0 and ref_obj is None: vars_to_remove[v_id] = v - self.remove_variables(list(vars_to_remove.values())) + self.remove_variables([vars_to_remove.values()]) def add_constraints(self, cons: List[_GeneralConstraintData]): - all_fixed_vars = dict() + all_fixed_vars = {} for con in cons: if con in self._named_expressions: raise ValueError( @@ -1165,7 +1170,7 @@ def add_sos_constraints(self, cons: List[_SOSConstraintData]): variables = con.get_variables() if not self._only_child_vars: self._check_for_new_vars(variables) - self._named_expressions[con] = list() + self._named_expressions[con] = [] self._vars_referenced_by_con[con] = variables for v in variables: self._referenced_variables[id(v)][1][con] = None @@ -1206,20 +1211,20 @@ def set_objective(self, obj: _GeneralObjectiveData): for v in fixed_vars: v.fix() else: - self._vars_referenced_by_obj = list() + self._vars_referenced_by_obj = [] self._objective = None self._objective_expr = None self._objective_sense = None - self._obj_named_expressions = list() + self._obj_named_expressions = [] self._set_objective(obj) def add_block(self, block): - param_dict = dict() + param_dict = {} for p in block.component_objects(Param, descend_into=True): if p.mutable: for _p in p.values(): param_dict[id(_p)] = _p - self.add_params(list(param_dict.values())) + self.add_params([param_dict.values()]) if self._only_child_vars: self.add_variables( list( @@ -1230,20 +1235,10 @@ def add_block(self, block): ) ) self.add_constraints( - [ - con - for con in block.component_data_objects( - Constraint, descend_into=True, active=True - ) - ] + list(block.component_data_objects(Constraint, descend_into=True, active=True)) ) self.add_sos_constraints( - [ - con - for con in block.component_data_objects( - SOSConstraint, descend_into=True, active=True - ) - ] + list(block.component_data_objects(SOSConstraint, descend_into=True, active=True)) ) obj = get_objective(block) if obj is not None: @@ -1326,20 +1321,10 @@ def remove_params(self, params: List[_ParamData]): def remove_block(self, block): self.remove_constraints( - [ - con - for con in block.component_data_objects( - ctype=Constraint, descend_into=True, active=True - ) - ] + list(block.component_data_objects(ctype=Constraint, descend_into=True, active=True)) ) self.remove_sos_constraints( - [ - con - for con in block.component_data_objects( - ctype=SOSConstraint, descend_into=True, active=True - ) - ] + list(block.component_data_objects(ctype=SOSConstraint, descend_into=True, active=True)) ) if self._only_child_vars: self.remove_variables( @@ -1387,17 +1372,17 @@ def update(self, timer: HierarchicalTimer = None): if timer is None: timer = HierarchicalTimer() config = self.update_config - new_vars = list() - old_vars = list() - new_params = list() - old_params = list() - new_cons = list() - old_cons = list() - old_sos = list() - new_sos = list() - current_vars_dict = dict() - current_cons_dict = dict() - current_sos_dict = dict() + new_vars = [] + old_vars = [] + new_params = [] + old_params = [] + new_cons = [] + old_cons = [] + old_sos = [] + new_sos = [] + current_vars_dict = {} + current_cons_dict = {} + current_sos_dict = {} timer.start('vars') if self._only_child_vars and ( config.check_for_new_or_removed_vars or config.update_vars @@ -1417,7 +1402,7 @@ def update(self, timer: HierarchicalTimer = None): timer.stop('vars') timer.start('params') if config.check_for_new_or_removed_params: - current_params_dict = dict() + current_params_dict = {} for p in self._model.component_objects(Param, descend_into=True): if p.mutable: for _p in p.values(): @@ -1482,11 +1467,11 @@ def update(self, timer: HierarchicalTimer = None): new_cons_set = set(new_cons) new_sos_set = set(new_sos) new_vars_set = set(id(v) for v in new_vars) - cons_to_remove_and_add = dict() + cons_to_remove_and_add = {} need_to_set_objective = False if config.update_constraints: - cons_to_update = list() - sos_to_update = list() + cons_to_update = [] + sos_to_update = [] for c in current_cons_dict.keys(): if c not in new_cons_set: cons_to_update.append(c) @@ -1524,7 +1509,7 @@ def update(self, timer: HierarchicalTimer = None): timer.stop('cons') timer.start('vars') if self._only_child_vars and config.update_vars: - vars_to_check = list() + vars_to_check = [] for v_id, v in current_vars_dict.items(): if v_id not in new_vars_set: vars_to_check.append(v) @@ -1532,7 +1517,7 @@ def update(self, timer: HierarchicalTimer = None): end_vars = {v_id: v_tuple[0] for v_id, v_tuple in self._vars.items()} vars_to_check = [v for v_id, v in end_vars.items() if v_id in start_vars] if config.update_vars: - vars_to_update = list() + vars_to_update = [] for v in vars_to_check: _v, lb, ub, fixed, domain_interval, value = self._vars[id(v)] if lb is not v._lb: @@ -1557,7 +1542,7 @@ def update(self, timer: HierarchicalTimer = None): timer.stop('cons') timer.start('named expressions') if config.update_named_expressions: - cons_to_update = list() + cons_to_update = [] for c, expr_list in self._named_expressions.items(): if c in new_cons_set: continue @@ -1599,12 +1584,13 @@ def update(self, timer: HierarchicalTimer = None): legacy_termination_condition_map = { TerminationCondition.unknown: LegacyTerminationCondition.unknown, TerminationCondition.maxTimeLimit: LegacyTerminationCondition.maxTimeLimit, - TerminationCondition.maxIterations: LegacyTerminationCondition.maxIterations, + TerminationCondition.iterationLimit: LegacyTerminationCondition.maxIterations, TerminationCondition.objectiveLimit: LegacyTerminationCondition.minFunctionValue, TerminationCondition.minStepLength: LegacyTerminationCondition.minStepLength, - TerminationCondition.ok: LegacyTerminationCondition.optimal, + TerminationCondition.convergenceCriteriaSatisfied: LegacyTerminationCondition.optimal, TerminationCondition.unbounded: LegacyTerminationCondition.unbounded, - TerminationCondition.infeasible: LegacyTerminationCondition.infeasible, + TerminationCondition.provenInfeasible: LegacyTerminationCondition.infeasible, + TerminationCondition.locallyInfeasible: LegacyTerminationCondition.infeasible, TerminationCondition.infeasibleOrUnbounded: LegacyTerminationCondition.infeasibleOrUnbounded, TerminationCondition.error: LegacyTerminationCondition.error, TerminationCondition.interrupted: LegacyTerminationCondition.resourceInterrupt, @@ -1615,12 +1601,13 @@ def update(self, timer: HierarchicalTimer = None): legacy_solver_status_map = { TerminationCondition.unknown: LegacySolverStatus.unknown, TerminationCondition.maxTimeLimit: LegacySolverStatus.aborted, - TerminationCondition.maxIterations: LegacySolverStatus.aborted, + TerminationCondition.iterationLimit: LegacySolverStatus.aborted, TerminationCondition.objectiveLimit: LegacySolverStatus.aborted, TerminationCondition.minStepLength: LegacySolverStatus.error, - TerminationCondition.ok: LegacySolverStatus.ok, + TerminationCondition.convergenceCriteriaSatisfied: LegacySolverStatus.ok, TerminationCondition.unbounded: LegacySolverStatus.error, - TerminationCondition.infeasible: LegacySolverStatus.error, + TerminationCondition.provenInfeasible: LegacySolverStatus.error, + TerminationCondition.locallyInfeasible: LegacySolverStatus.error, TerminationCondition.infeasibleOrUnbounded: LegacySolverStatus.error, TerminationCondition.error: LegacySolverStatus.error, TerminationCondition.interrupted: LegacySolverStatus.aborted, @@ -1631,12 +1618,13 @@ def update(self, timer: HierarchicalTimer = None): legacy_solution_status_map = { TerminationCondition.unknown: LegacySolutionStatus.unknown, TerminationCondition.maxTimeLimit: LegacySolutionStatus.stoppedByLimit, - TerminationCondition.maxIterations: LegacySolutionStatus.stoppedByLimit, + TerminationCondition.iterationLimit: LegacySolutionStatus.stoppedByLimit, TerminationCondition.objectiveLimit: LegacySolutionStatus.stoppedByLimit, TerminationCondition.minStepLength: LegacySolutionStatus.error, - TerminationCondition.ok: LegacySolutionStatus.optimal, + TerminationCondition.convergenceCriteriaSatisfied: LegacySolutionStatus.optimal, TerminationCondition.unbounded: LegacySolutionStatus.unbounded, - TerminationCondition.infeasible: LegacySolutionStatus.infeasible, + TerminationCondition.provenInfeasible: LegacySolutionStatus.infeasible, + TerminationCondition.locallyInfeasible: LegacySolutionStatus.infeasible, TerminationCondition.infeasibleOrUnbounded: LegacySolutionStatus.unsure, TerminationCondition.error: LegacySolutionStatus.error, TerminationCondition.interrupted: LegacySolutionStatus.error, @@ -1644,7 +1632,7 @@ def update(self, timer: HierarchicalTimer = None): } -class LegacySolverInterface(object): +class LegacySolverInterface(): def solve( self, model: _BlockData, @@ -1683,7 +1671,7 @@ def solve( if options is not None: self.options = options - results: Results = super(LegacySolverInterface, self).solve(model) + results: Results = super().solve(model) legacy_results = LegacySolverResults() legacy_soln = LegacySolution() @@ -1760,7 +1748,7 @@ def solve( return legacy_results def available(self, exception_flag=True): - ans = super(LegacySolverInterface, self).available() + ans = super().available() if exception_flag and not ans: raise ApplicationError(f'Solver {self.__class__} is not available ({ans}).') return bool(ans) diff --git a/pyomo/contrib/appsi/build.py b/pyomo/contrib/appsi/build.py index 2a4e7bb785e..6146272978c 100644 --- a/pyomo/contrib/appsi/build.py +++ b/pyomo/contrib/appsi/build.py @@ -80,7 +80,7 @@ def run(self): print("Building in '%s'" % tmpdir) os.chdir(tmpdir) try: - super(appsi_build_ext, self).run() + super().run() if not self.inplace: library = glob.glob("build/*/appsi_cmodel.*")[0] target = os.path.join( @@ -117,7 +117,7 @@ def run(self): pybind11.setup_helpers.MACOS = original_pybind11_setup_helpers_macos -class AppsiBuilder(object): +class AppsiBuilder(): def __call__(self, parallel): return build_appsi() diff --git a/pyomo/contrib/appsi/examples/getting_started.py b/pyomo/contrib/appsi/examples/getting_started.py index 5cbac7c81e3..6d2cce76925 100644 --- a/pyomo/contrib/appsi/examples/getting_started.py +++ b/pyomo/contrib/appsi/examples/getting_started.py @@ -24,8 +24,8 @@ def main(plot=True, n_points=200): # write a for loop to vary the value of parameter p from 1 to 10 p_values = [float(i) for i in np.linspace(1, 10, n_points)] - obj_values = list() - x_values = list() + obj_values = [] + x_values = [] timer = HierarchicalTimer() # create a timer for some basic profiling timer.start('p loop') for p_val in p_values: diff --git a/pyomo/contrib/appsi/examples/tests/test_examples.py b/pyomo/contrib/appsi/examples/tests/test_examples.py index d2c88224a7d..ffcecaf0c5f 100644 --- a/pyomo/contrib/appsi/examples/tests/test_examples.py +++ b/pyomo/contrib/appsi/examples/tests/test_examples.py @@ -1,5 +1,5 @@ from pyomo.contrib.appsi.examples import getting_started -import pyomo.common.unittest as unittest +from pyomo.common import unittest import pyomo.environ as pe from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.contrib import appsi diff --git a/pyomo/contrib/appsi/fbbt.py b/pyomo/contrib/appsi/fbbt.py index 92a0e0c8cbc..22badd83d12 100644 --- a/pyomo/contrib/appsi/fbbt.py +++ b/pyomo/contrib/appsi/fbbt.py @@ -35,7 +35,7 @@ def __init__( implicit_domain=None, visibility=0, ): - super(IntervalConfig, self).__init__( + super().__init__( description=description, doc=doc, implicit=implicit, @@ -62,14 +62,14 @@ def __init__( class IntervalTightener(PersistentBase): def __init__(self): - super(IntervalTightener, self).__init__() + super().__init__() self._config = IntervalConfig() self._cmodel = None - self._var_map = dict() - self._con_map = dict() - self._param_map = dict() - self._rvar_map = dict() - self._rcon_map = dict() + self._var_map = {} + self._con_map = {} + self._param_map = {} + self._rvar_map = {} + self._rcon_map = {} self._pyomo_expr_types = cmodel.PyomoExprTypes() self._symbolic_solver_labels: bool = False self._symbol_map = SymbolMap() @@ -254,7 +254,7 @@ def _update_pyomo_var_bounds(self): self._vars[v_id] = (_v, _lb, cv_ub, _fixed, _domain, _value) def _deactivate_satisfied_cons(self): - cons_to_deactivate = list() + cons_to_deactivate = [] if self.config.deactivate_satisfied_constraints: for c, cc in self._con_map.items(): if not cc.active: diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index 6fd01fb9149..84a38ec3cdb 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -42,7 +42,7 @@ def __init__( implicit_domain=None, visibility=0, ): - super(CbcConfig, self).__init__( + super().__init__( description=description, doc=doc, implicit=implicit, @@ -66,12 +66,12 @@ def __init__( class Cbc(PersistentSolver): def __init__(self, only_child_vars=False): self._config = CbcConfig() - self._solver_options = dict() + self._solver_options = {} self._writer = LPWriter(only_child_vars=only_child_vars) self._filename = None - self._dual_sol = dict() - self._primal_sol = dict() - self._reduced_costs = dict() + self._dual_sol = {} + self._primal_sol = {} + self._reduced_costs = {} self._last_results_object: Optional[Results] = None def available(self): @@ -261,9 +261,9 @@ def _parse_soln(self): first_var_line = ndx last_var_line = len(all_lines) - 1 - self._dual_sol = dict() - self._primal_sol = dict() - self._reduced_costs = dict() + self._dual_sol = {} + self._primal_sol = {} + self._reduced_costs = {} symbol_map = self._writer.symbol_map @@ -362,7 +362,7 @@ def _check_and_escape_options(): yield tmp_k, tmp_v cmd = [str(config.executable)] - action_options = list() + action_options = [] if config.time_limit is not None: cmd.extend(['-sec', str(config.time_limit)]) cmd.extend(['-timeMode', 'elapsed']) diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index f007573639b..47042586d0b 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -38,7 +38,7 @@ def __init__( implicit_domain=None, visibility=0, ): - super(CplexConfig, self).__init__( + super().__init__( description=description, doc=doc, implicit=implicit, @@ -59,7 +59,7 @@ def __init__( class CplexResults(Results): def __init__(self, solver): - super(CplexResults, self).__init__() + super().__init__() self.wallclock_time = None self.solution_loader = PersistentSolutionLoader(solver=solver) @@ -69,7 +69,7 @@ class Cplex(PersistentSolver): def __init__(self, only_child_vars=False): self._config = CplexConfig() - self._solver_options = dict() + self._solver_options = {} self._writer = LPWriter(only_child_vars=only_child_vars) self._filename = None self._last_results_object: Optional[CplexResults] = None @@ -400,7 +400,7 @@ def get_duals( con_names = self._cplex_model.linear_constraints.get_names() dual_values = self._cplex_model.solution.get_dual_values() else: - con_names = list() + con_names = [] for con in cons_to_load: orig_name = symbol_map.byObject[id(con)] if con.equality: @@ -412,7 +412,7 @@ def get_duals( con_names.append(orig_name + '_ub') dual_values = self._cplex_model.solution.get_dual_values(con_names) - res = dict() + res = {} for name, val in zip(con_names, dual_values): orig_name = name[:-3] if orig_name == 'obj_const_con': diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index 999b542ad70..23a87e06f1c 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -62,7 +62,7 @@ def __init__( implicit_domain=None, visibility=0, ): - super(GurobiConfig, self).__init__( + super().__init__( description=description, doc=doc, implicit=implicit, @@ -95,12 +95,12 @@ def get_primals(self, vars_to_load=None, solution_number=0): class GurobiResults(Results): def __init__(self, solver): - super(GurobiResults, self).__init__() + super().__init__() self.wallclock_time = None self.solution_loader = GurobiSolutionLoader(solver=solver) -class _MutableLowerBound(object): +class _MutableLowerBound(): def __init__(self, expr): self.var = None self.expr = expr @@ -109,7 +109,7 @@ def update(self): self.var.setAttr('lb', value(self.expr)) -class _MutableUpperBound(object): +class _MutableUpperBound(): def __init__(self, expr): self.var = None self.expr = expr @@ -118,7 +118,7 @@ def update(self): self.var.setAttr('ub', value(self.expr)) -class _MutableLinearCoefficient(object): +class _MutableLinearCoefficient(): def __init__(self): self.expr = None self.var = None @@ -129,7 +129,7 @@ def update(self): self.gurobi_model.chgCoeff(self.con, self.var, value(self.expr)) -class _MutableRangeConstant(object): +class _MutableRangeConstant(): def __init__(self): self.lhs_expr = None self.rhs_expr = None @@ -145,7 +145,7 @@ def update(self): slack.ub = rhs_val - lhs_val -class _MutableConstant(object): +class _MutableConstant(): def __init__(self): self.expr = None self.con = None @@ -154,7 +154,7 @@ def update(self): self.con.rhs = value(self.expr) -class _MutableQuadraticConstraint(object): +class _MutableQuadraticConstraint(): def __init__( self, gurobi_model, gurobi_con, constant, linear_coefs, quadratic_coefs ): @@ -189,7 +189,7 @@ def get_updated_rhs(self): return value(self.constant.expr) -class _MutableObjective(object): +class _MutableObjective(): def __init__(self, gurobi_model, constant, linear_coefs, quadratic_coefs): self.gurobi_model = gurobi_model self.constant = constant @@ -217,7 +217,7 @@ def get_updated_expression(self): return gurobi_expr -class _MutableQuadraticCoefficient(object): +class _MutableQuadraticCoefficient(): def __init__(self): self.expr = None self.var1 = None @@ -233,21 +233,21 @@ class Gurobi(PersistentBase, PersistentSolver): _num_instances = 0 def __init__(self, only_child_vars=False): - super(Gurobi, self).__init__(only_child_vars=only_child_vars) + super().__init__(only_child_vars=only_child_vars) self._num_instances += 1 self._config = GurobiConfig() - self._solver_options = dict() + self._solver_options = {} self._solver_model = None self._symbol_map = SymbolMap() self._labeler = None - self._pyomo_var_to_solver_var_map = dict() - self._pyomo_con_to_solver_con_map = dict() - self._solver_con_to_pyomo_con_map = dict() - self._pyomo_sos_to_solver_sos_map = dict() + self._pyomo_var_to_solver_var_map = {} + self._pyomo_con_to_solver_con_map = {} + self._solver_con_to_pyomo_con_map = {} + self._pyomo_sos_to_solver_sos_map = {} self._range_constraints = OrderedSet() - self._mutable_helpers = dict() - self._mutable_bounds = dict() - self._mutable_quadratic_helpers = dict() + self._mutable_helpers = {} + self._mutable_bounds = {} + self._mutable_quadratic_helpers = {} self._mutable_objective = None self._needs_updated = True self._callback = None @@ -448,12 +448,12 @@ def _process_domain_and_bounds( return lb, ub, vtype def _add_variables(self, variables: List[_GeneralVarData]): - var_names = list() - vtypes = list() - lbs = list() - ubs = list() - mutable_lbs = dict() - mutable_ubs = dict() + var_names = [] + vtypes = [] + lbs = [] + ubs = [] + mutable_lbs = {} + mutable_ubs = {} for ndx, var in enumerate(variables): varname = self._symbol_map.getSymbol(var, self._labeler) lb, ub, vtype = self._process_domain_and_bounds( @@ -519,8 +519,8 @@ def set_instance(self, model): self.set_objective(None) def _get_expr_from_pyomo_expr(self, expr): - mutable_linear_coefficients = list() - mutable_quadratic_coefficients = list() + mutable_linear_coefficients = [] + mutable_quadratic_coefficients = [] repn = generate_standard_repn(expr, quadratic=True, compute_values=False) degree = repn.polynomial_degree() @@ -530,7 +530,7 @@ def _get_expr_from_pyomo_expr(self, expr): ) if len(repn.linear_vars) > 0: - linear_coef_vals = list() + linear_coef_vals = [] for ndx, coef in enumerate(repn.linear_coefs): if not is_constant(coef): mutable_linear_coefficient = _MutableLinearCoefficient() @@ -824,8 +824,8 @@ def _set_objective(self, obj): sense = gurobipy.GRB.MINIMIZE gurobi_expr = 0 repn_constant = 0 - mutable_linear_coefficients = list() - mutable_quadratic_coefficients = list() + mutable_linear_coefficients = [] + mutable_quadratic_coefficients = [] else: if obj.sense == minimize: sense = gurobipy.GRB.MINIMIZE @@ -1047,7 +1047,7 @@ def get_duals(self, cons_to_load=None): con_map = self._pyomo_con_to_solver_con_map reverse_con_map = self._solver_con_to_pyomo_con_map - dual = dict() + dual = {} if cons_to_load is None: linear_cons_to_load = self._solver_model.getConstrs() @@ -1090,7 +1090,7 @@ def get_slacks(self, cons_to_load=None): con_map = self._pyomo_con_to_solver_con_map reverse_con_map = self._solver_con_to_pyomo_con_map - slack = dict() + slack = {} gurobi_range_con_vars = OrderedSet(self._solver_model.getVars()) - OrderedSet( self._pyomo_var_to_solver_var_map.values() @@ -1140,7 +1140,7 @@ def get_slacks(self, cons_to_load=None): def update(self, timer: HierarchicalTimer = None): if self._needs_updated: self._update_gurobi_model() - super(Gurobi, self).update(timer=timer) + super().update(timer=timer) self._update_gurobi_model() def _update_gurobi_model(self): diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 23f49a057a7..528fb2f3087 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -47,7 +47,7 @@ def __init__( implicit_domain=None, visibility=0, ): - super(HighsConfig, self).__init__( + super().__init__( description=description, doc=doc, implicit=implicit, @@ -71,7 +71,7 @@ def __init__(self, solver): self.solution_loader = PersistentSolutionLoader(solver=solver) -class _MutableVarBounds(object): +class _MutableVarBounds(): def __init__(self, lower_expr, upper_expr, pyomo_var_id, var_map, highs): self.pyomo_var_id = pyomo_var_id self.lower_expr = lower_expr @@ -86,7 +86,7 @@ def update(self): self.highs.changeColBounds(col_ndx, lb, ub) -class _MutableLinearCoefficient(object): +class _MutableLinearCoefficient(): def __init__(self, pyomo_con, pyomo_var_id, con_map, var_map, expr, highs): self.expr = expr self.highs = highs @@ -101,7 +101,7 @@ def update(self): self.highs.changeCoeff(row_ndx, col_ndx, value(self.expr)) -class _MutableObjectiveCoefficient(object): +class _MutableObjectiveCoefficient(): def __init__(self, pyomo_var_id, var_map, expr, highs): self.expr = expr self.highs = highs @@ -113,7 +113,7 @@ def update(self): self.highs.changeColCost(col_ndx, value(self.expr)) -class _MutableObjectiveOffset(object): +class _MutableObjectiveOffset(): def __init__(self, expr, highs): self.expr = expr self.highs = highs @@ -122,7 +122,7 @@ def update(self): self.highs.changeObjectiveOffset(value(self.expr)) -class _MutableConstraintBounds(object): +class _MutableConstraintBounds(): def __init__(self, lower_expr, upper_expr, pyomo_con, con_map, highs): self.lower_expr = lower_expr self.upper_expr = upper_expr @@ -147,14 +147,14 @@ class Highs(PersistentBase, PersistentSolver): def __init__(self, only_child_vars=False): super().__init__(only_child_vars=only_child_vars) self._config = HighsConfig() - self._solver_options = dict() + self._solver_options = {} self._solver_model = None - self._pyomo_var_to_solver_var_map = dict() - self._pyomo_con_to_solver_con_map = dict() - self._solver_con_to_pyomo_con_map = dict() - self._mutable_helpers = dict() - self._mutable_bounds = dict() - self._objective_helpers = list() + self._pyomo_var_to_solver_var_map = {} + self._pyomo_con_to_solver_con_map = {} + self._solver_con_to_pyomo_con_map = {} + self._mutable_helpers = {} + self._mutable_bounds = {} + self._objective_helpers = [] self._last_results_object: Optional[HighsResults] = None self._sol = None @@ -301,10 +301,10 @@ def _add_variables(self, variables: List[_GeneralVarData]): self._sol = None if self._last_results_object is not None: self._last_results_object.solution_loader.invalidate() - lbs = list() - ubs = list() - indices = list() - vtypes = list() + lbs = [] + ubs = [] + indices = [] + vtypes = [] current_num_vars = len(self._pyomo_var_to_solver_var_map) for v in variables: @@ -360,11 +360,11 @@ def _add_constraints(self, cons: List[_GeneralConstraintData]): if self._last_results_object is not None: self._last_results_object.solution_loader.invalidate() current_num_cons = len(self._pyomo_con_to_solver_con_map) - lbs = list() - ubs = list() - starts = list() - var_indices = list() - coef_values = list() + lbs = [] + ubs = [] + starts = [] + var_indices = [] + coef_values = [] for con in cons: repn = generate_standard_repn( @@ -390,7 +390,7 @@ def _add_constraints(self, cons: List[_GeneralConstraintData]): highs=self._solver_model, ) if con not in self._mutable_helpers: - self._mutable_helpers[con] = list() + self._mutable_helpers[con] = [] self._mutable_helpers[con].append(mutable_linear_coefficient) if coef_val == 0: continue @@ -445,7 +445,7 @@ def _remove_constraints(self, cons: List[_GeneralConstraintData]): self._sol = None if self._last_results_object is not None: self._last_results_object.solution_loader.invalidate() - indices_to_remove = list() + indices_to_remove = [] for con in cons: con_ndx = self._pyomo_con_to_solver_con_map.pop(con) del self._solver_con_to_pyomo_con_map[con_ndx] @@ -455,7 +455,7 @@ def _remove_constraints(self, cons: List[_GeneralConstraintData]): len(indices_to_remove), np.array(indices_to_remove) ) con_ndx = 0 - new_con_map = dict() + new_con_map = {} for c in self._pyomo_con_to_solver_con_map.keys(): new_con_map[c] = con_ndx con_ndx += 1 @@ -474,7 +474,7 @@ def _remove_variables(self, variables: List[_GeneralVarData]): self._sol = None if self._last_results_object is not None: self._last_results_object.solution_loader.invalidate() - indices_to_remove = list() + indices_to_remove = [] for v in variables: v_id = id(v) v_ndx = self._pyomo_var_to_solver_var_map.pop(v_id) @@ -484,7 +484,7 @@ def _remove_variables(self, variables: List[_GeneralVarData]): len(indices_to_remove), np.array(indices_to_remove) ) v_ndx = 0 - new_var_map = dict() + new_var_map = {} for v_id in self._pyomo_var_to_solver_var_map.keys(): new_var_map[v_id] = v_ndx v_ndx += 1 @@ -497,10 +497,10 @@ def _update_variables(self, variables: List[_GeneralVarData]): self._sol = None if self._last_results_object is not None: self._last_results_object.solution_loader.invalidate() - indices = list() - lbs = list() - ubs = list() - vtypes = list() + indices = [] + lbs = [] + ubs = [] + vtypes = [] for v in variables: v_id = id(v) @@ -541,7 +541,7 @@ def _set_objective(self, obj): n = len(self._pyomo_var_to_solver_var_map) indices = np.arange(n) costs = np.zeros(n, dtype=np.double) - self._objective_helpers = list() + self._objective_helpers = [] if obj is None: sense = highspy.ObjSense.kMinimize self._solver_model.changeObjectiveOffset(0) @@ -692,7 +692,7 @@ def get_primals(self, vars_to_load=None, solution_number=0): res = ComponentMap() if vars_to_load is None: - var_ids_to_load = list() + var_ids_to_load = [] for v, ref_info in self._referenced_variables.items(): using_cons, using_sos, using_obj = ref_info if using_cons or using_sos or (using_obj is not None): @@ -737,7 +737,7 @@ def get_duals(self, cons_to_load=None): 'check the termination condition.' ) - res = dict() + res = {} if cons_to_load is None: cons_to_load = list(self._pyomo_con_to_solver_con_map.keys()) @@ -756,7 +756,7 @@ def get_slacks(self, cons_to_load=None): 'check the termination condition.' ) - res = dict() + res = {} if cons_to_load is None: cons_to_load = list(self._pyomo_con_to_solver_con_map.keys()) diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index 8c0716c6e1e..c03f6e145f3 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -45,7 +45,7 @@ def __init__( implicit_domain=None, visibility=0, ): - super(IpoptConfig, self).__init__( + super().__init__( description=description, doc=doc, implicit=implicit, @@ -129,10 +129,10 @@ def __init__( class Ipopt(PersistentSolver): def __init__(self, only_child_vars=False): self._config = IpoptConfig() - self._solver_options = dict() + self._solver_options = {} self._writer = NLWriter(only_child_vars=only_child_vars) self._filename = None - self._dual_sol = dict() + self._dual_sol = {} self._primal_sol = ComponentMap() self._reduced_costs = ComponentMap() self._last_results_object: Optional[Results] = None @@ -347,7 +347,7 @@ def _parse_sol(self): + n_rc_lower ] - self._dual_sol = dict() + self._dual_sol = {} self._primal_sol = ComponentMap() self._reduced_costs = ComponentMap() diff --git a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py index de82b211092..2727cf2313b 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py @@ -1,5 +1,5 @@ from pyomo.common.errors import PyomoException -import pyomo.common.unittest as unittest +from pyomo.common import unittest import pyomo.environ as pe from pyomo.contrib.appsi.solvers.gurobi import Gurobi from pyomo.contrib.appsi.base import TerminationCondition diff --git a/pyomo/contrib/appsi/solvers/tests/test_ipopt_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_ipopt_persistent.py index 6b86deaa535..ce73b94ab74 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_ipopt_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_ipopt_persistent.py @@ -1,5 +1,5 @@ import pyomo.environ as pe -import pyomo.common.unittest as unittest +from pyomo.common import unittest from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.common.gsl import find_GSL diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index ec9f397bdc4..82db5f6286f 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -1,6 +1,6 @@ import pyomo.environ as pe from pyomo.common.dependencies import attempt_import -import pyomo.common.unittest as unittest +from pyomo.common import unittest parameterized, param_available = attempt_import('parameterized') parameterized = parameterized.parameterized @@ -68,7 +68,7 @@ def _load_tests(solver_list, only_child_vars_list): - res = list() + res = [] for solver_name, solver in solver_list: for child_var_option in only_child_vars_list: test_name = f"{solver_name}_only_child_vars_{child_var_option}" @@ -979,8 +979,8 @@ def test_time_limit( m.x = pe.Var(m.jobs, m.tasks, bounds=(0, 1)) random.seed(0) - coefs = list() - lin_vars = list() + coefs = [] + lin_vars = [] for j in m.jobs: for t in m.tasks: coefs.append(random.uniform(0, 10)) diff --git a/pyomo/contrib/appsi/tests/test_base.py b/pyomo/contrib/appsi/tests/test_base.py index 0d67ca4d01a..82a04b29e56 100644 --- a/pyomo/contrib/appsi/tests/test_base.py +++ b/pyomo/contrib/appsi/tests/test_base.py @@ -37,16 +37,16 @@ def test_results(self): m.c1 = pe.Constraint(expr=m.x == 1) m.c2 = pe.Constraint(expr=m.y == 2) - primals = dict() + primals = {} primals[id(m.x)] = (m.x, 1) primals[id(m.y)] = (m.y, 2) - duals = dict() + duals = {} duals[m.c1] = 3 duals[m.c2] = 4 - rc = dict() + rc = {} rc[id(m.x)] = (m.x, 5) rc[id(m.y)] = (m.y, 6) - slacks = dict() + slacks = {} slacks[m.c1] = 7 slacks[m.c2] = 8 diff --git a/pyomo/contrib/appsi/tests/test_interval.py b/pyomo/contrib/appsi/tests/test_interval.py index 7963cc31665..0924e3bbeed 100644 --- a/pyomo/contrib/appsi/tests/test_interval.py +++ b/pyomo/contrib/appsi/tests/test_interval.py @@ -1,5 +1,5 @@ from pyomo.contrib.appsi.cmodel import cmodel, cmodel_available -import pyomo.common.unittest as unittest +from pyomo.common import unittest import math from pyomo.contrib.fbbt.tests.test_interval import IntervalTestBase @@ -7,7 +7,7 @@ @unittest.skipUnless(cmodel_available, 'appsi extensions are not available') class TestInterval(IntervalTestBase, unittest.TestCase): def setUp(self): - super(TestInterval, self).setUp() + super().setUp() self.add = cmodel.py_interval_add self.sub = cmodel.py_interval_sub self.mul = cmodel.py_interval_mul diff --git a/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py b/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py index 9027080f08c..bfbbf5aecdf 100644 --- a/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py +++ b/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py @@ -4,10 +4,10 @@ class _VarAndNamedExprCollector(ExpressionValueVisitor): def __init__(self): - self.named_expressions = dict() - self.variables = dict() - self.fixed_vars = dict() - self._external_functions = dict() + self.named_expressions = {} + self.variables = {} + self.fixed_vars = {} + self._external_functions = {} def visit(self, node, values): pass diff --git a/pyomo/contrib/appsi/writers/config.py b/pyomo/contrib/appsi/writers/config.py index 7a7faadaabe..4376b9284fa 100644 --- a/pyomo/contrib/appsi/writers/config.py +++ b/pyomo/contrib/appsi/writers/config.py @@ -1,3 +1,3 @@ -class WriterConfig(object): +class WriterConfig(): def __init__(self): self.symbolic_solver_labels = False diff --git a/pyomo/contrib/appsi/writers/lp_writer.py b/pyomo/contrib/appsi/writers/lp_writer.py index 8a76fa5f9eb..6a4a4ab2ff7 100644 --- a/pyomo/contrib/appsi/writers/lp_writer.py +++ b/pyomo/contrib/appsi/writers/lp_writer.py @@ -17,7 +17,7 @@ class LPWriter(PersistentBase): def __init__(self, only_child_vars=False): - super(LPWriter, self).__init__(only_child_vars=only_child_vars) + super().__init__(only_child_vars=only_child_vars) self._config = WriterConfig() self._writer = None self._symbol_map = SymbolMap() @@ -25,11 +25,11 @@ def __init__(self, only_child_vars=False): self._con_labeler = None self._param_labeler = None self._obj_labeler = None - self._pyomo_var_to_solver_var_map = dict() - self._pyomo_con_to_solver_con_map = dict() - self._solver_var_to_pyomo_var_map = dict() - self._solver_con_to_pyomo_con_map = dict() - self._pyomo_param_to_solver_param_map = dict() + self._pyomo_var_to_solver_var_map = {} + self._pyomo_con_to_solver_con_map = {} + self._solver_var_to_pyomo_var_map = {} + self._solver_con_to_pyomo_con_map = {} + self._pyomo_param_to_solver_param_map = {} self._expr_types = None @property @@ -89,7 +89,7 @@ def _add_params(self, params: List[_ParamData]): self._pyomo_param_to_solver_param_map[id(p)] = cp def _add_constraints(self, cons: List[_GeneralConstraintData]): - cmodel.process_lp_constraints(cons, self) + cmodel.process_lp_constraints() def _add_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) != 0: diff --git a/pyomo/contrib/appsi/writers/nl_writer.py b/pyomo/contrib/appsi/writers/nl_writer.py index 9c739fd6ebb..d0bb443508d 100644 --- a/pyomo/contrib/appsi/writers/nl_writer.py +++ b/pyomo/contrib/appsi/writers/nl_writer.py @@ -20,18 +20,18 @@ class NLWriter(PersistentBase): def __init__(self, only_child_vars=False): - super(NLWriter, self).__init__(only_child_vars=only_child_vars) + super().__init__(only_child_vars=only_child_vars) self._config = WriterConfig() self._writer = None self._symbol_map = SymbolMap() self._var_labeler = None self._con_labeler = None self._param_labeler = None - self._pyomo_var_to_solver_var_map = dict() - self._pyomo_con_to_solver_con_map = dict() - self._solver_var_to_pyomo_var_map = dict() - self._solver_con_to_pyomo_con_map = dict() - self._pyomo_param_to_solver_param_map = dict() + self._pyomo_var_to_solver_var_map = {} + self._pyomo_con_to_solver_con_map = {} + self._solver_var_to_pyomo_var_map = {} + self._solver_con_to_pyomo_con_map = {} + self._pyomo_param_to_solver_param_map = {} self._expr_types = None @property @@ -172,8 +172,8 @@ def update_params(self): def _set_objective(self, obj: _GeneralObjectiveData): if obj is None: const = cmodel.Constant(0) - lin_vars = list() - lin_coef = list() + lin_vars = [] + lin_coef = [] nonlin = cmodel.Constant(0) sense = 0 else: @@ -240,7 +240,7 @@ def write(self, model: _BlockData, filename: str, timer: HierarchicalTimer = Non timer.stop('write file') def update(self, timer: HierarchicalTimer = None): - super(NLWriter, self).update(timer=timer) + super().update(timer=timer) self._set_pyomo_amplfunc_env() def get_ordered_vars(self): diff --git a/pyomo/contrib/appsi/writers/tests/test_nl_writer.py b/pyomo/contrib/appsi/writers/tests/test_nl_writer.py index 3b61a5901c3..297bc3d7617 100644 --- a/pyomo/contrib/appsi/writers/tests/test_nl_writer.py +++ b/pyomo/contrib/appsi/writers/tests/test_nl_writer.py @@ -1,4 +1,4 @@ -import pyomo.common.unittest as unittest +from pyomo.common import unittest from pyomo.common.tempfiles import TempfileManager import pyomo.environ as pe from pyomo.contrib import appsi From 561e5bc461e76ab973d05e5240b4e4c9194919cb Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 15 Aug 2023 13:29:23 -0600 Subject: [PATCH 0021/1204] Revert accidental list/dict changes --- pyomo/contrib/appsi/base.py | 10 +++++----- pyomo/contrib/appsi/build.py | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index 805e96dacf1..86268b04dbd 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -409,7 +409,7 @@ def get_duals( 'for the given problem type.' ) if cons_to_load is None: - duals = {self._duals} + duals = dict(self._duals) else: duals = {} for c in cons_to_load: @@ -426,7 +426,7 @@ def get_slacks( 'for the given problem type.' ) if cons_to_load is None: - slacks = {self._slacks} + slacks = dict(self._slacks) else: slacks = {} for c in cons_to_load: @@ -1116,7 +1116,7 @@ def _check_for_new_vars(self, variables: List[_GeneralVarData]): v_id = id(v) if v_id not in self._referenced_variables: new_vars[v_id] = v - self.add_variables([new_vars.values()]) + self.add_variables(list(new_vars.values())) def _check_to_remove_vars(self, variables: List[_GeneralVarData]): vars_to_remove = {} @@ -1125,7 +1125,7 @@ def _check_to_remove_vars(self, variables: List[_GeneralVarData]): ref_cons, ref_sos, ref_obj = self._referenced_variables[v_id] if len(ref_cons) == 0 and len(ref_sos) == 0 and ref_obj is None: vars_to_remove[v_id] = v - self.remove_variables([vars_to_remove.values()]) + self.remove_variables(list(vars_to_remove.values())) def add_constraints(self, cons: List[_GeneralConstraintData]): all_fixed_vars = {} @@ -1224,7 +1224,7 @@ def add_block(self, block): if p.mutable: for _p in p.values(): param_dict[id(_p)] = _p - self.add_params([param_dict.values()]) + self.add_params(list(param_dict.values())) if self._only_child_vars: self.add_variables( list( diff --git a/pyomo/contrib/appsi/build.py b/pyomo/contrib/appsi/build.py index 6146272978c..37826cf85fb 100644 --- a/pyomo/contrib/appsi/build.py +++ b/pyomo/contrib/appsi/build.py @@ -63,8 +63,7 @@ def get_appsi_extension(in_setup=False, appsi_root=None): def build_appsi(args=[]): print('\n\n**** Building APPSI ****') - import setuptools - from distutils.dist import Distribution + from setuptools.dist import Distribution from pybind11.setup_helpers import build_ext import pybind11.setup_helpers from pyomo.common.envvar import PYOMO_CONFIG_DIR From 154518ea537944ab6aa976d00fc0544b0a929764 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 15 Aug 2023 14:01:43 -0600 Subject: [PATCH 0022/1204] Fix references to TerminationCondition.ok --- pyomo/contrib/appsi/base.py | 2 +- .../contrib/appsi/examples/getting_started.py | 2 +- pyomo/contrib/appsi/solvers/cbc.py | 10 ++-- pyomo/contrib/appsi/solvers/cplex.py | 4 +- pyomo/contrib/appsi/solvers/gurobi.py | 4 +- pyomo/contrib/appsi/solvers/highs.py | 6 +- pyomo/contrib/appsi/solvers/ipopt.py | 10 ++-- .../solvers/tests/test_gurobi_persistent.py | 2 +- .../solvers/tests/test_persistent_solvers.py | 55 +++++++++---------- 9 files changed, 47 insertions(+), 48 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index 86268b04dbd..d85e1ef9dfe 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -478,7 +478,7 @@ class Results(): >>> opt = appsi.solvers.Ipopt() >>> opt.config.load_solution = False >>> results = opt.solve(m) #doctest:+SKIP - >>> if results.termination_condition == appsi.base.TerminationCondition.ok: #doctest:+SKIP + >>> if results.termination_condition == appsi.base.TerminationCondition.convergenceCriteriaSatisfied: #doctest:+SKIP ... print('optimal solution found: ', results.best_feasible_objective) #doctest:+SKIP ... results.solution_loader.load_vars() #doctest:+SKIP ... print('the optimal value of x is ', m.x.value) #doctest:+SKIP diff --git a/pyomo/contrib/appsi/examples/getting_started.py b/pyomo/contrib/appsi/examples/getting_started.py index 6d2cce76925..04092601c91 100644 --- a/pyomo/contrib/appsi/examples/getting_started.py +++ b/pyomo/contrib/appsi/examples/getting_started.py @@ -31,7 +31,7 @@ def main(plot=True, n_points=200): for p_val in p_values: m.p.value = p_val res = opt.solve(m, timer=timer) - assert res.termination_condition == appsi.base.TerminationCondition.ok + assert res.termination_condition == appsi.base.TerminationCondition.convergenceCriteriaSatisfied obj_values.append(res.best_feasible_objective) opt.load_vars([m.x]) x_values.append(m.x.value) diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index 84a38ec3cdb..74b9aa8ba8e 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -232,7 +232,7 @@ def _parse_soln(self): termination_line = all_lines[0].lower() obj_val = None if termination_line.startswith('optimal'): - results.termination_condition = TerminationCondition.ok + results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied obj_val = float(termination_line.split()[-1]) elif 'infeasible' in termination_line: results.termination_condition = TerminationCondition.infeasible @@ -307,7 +307,7 @@ def _parse_soln(self): self._reduced_costs[v_id] = (v, -rc_val) if ( - results.termination_condition == TerminationCondition.ok + results.termination_condition == TerminationCondition.convergenceCriteriaSatisfied and self.config.load_solution ): for v_id, (v, val) in self._primal_sol.items(): @@ -316,7 +316,7 @@ def _parse_soln(self): results.best_feasible_objective = None else: results.best_feasible_objective = obj_val - elif results.termination_condition == TerminationCondition.ok: + elif results.termination_condition == TerminationCondition.convergenceCriteriaSatisfied: if self._writer.get_active_objective() is None: results.best_feasible_objective = None else: @@ -451,7 +451,7 @@ def get_duals(self, cons_to_load=None): if ( self._last_results_object is None or self._last_results_object.termination_condition - != TerminationCondition.ok + != TerminationCondition.convergenceCriteriaSatisfied ): raise RuntimeError( 'Solver does not currently have valid duals. Please ' @@ -469,7 +469,7 @@ def get_reduced_costs( if ( self._last_results_object is None or self._last_results_object.termination_condition - != TerminationCondition.ok + != TerminationCondition.convergenceCriteriaSatisfied ): raise RuntimeError( 'Solver does not currently have valid reduced costs. Please ' diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 47042586d0b..c459effe325 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -284,7 +284,7 @@ def _postsolve(self, timer: HierarchicalTimer, solve_time): status = cpxprob.solution.get_status() if status in [1, 101, 102]: - results.termination_condition = TerminationCondition.ok + results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied elif status in [2, 40, 118, 133, 134]: results.termination_condition = TerminationCondition.unbounded elif status in [4, 119, 134]: @@ -336,7 +336,7 @@ def _postsolve(self, timer: HierarchicalTimer, solve_time): 'results.best_feasible_objective before loading a solution.' ) else: - if results.termination_condition != TerminationCondition.ok: + if results.termination_condition != TerminationCondition.convergenceCriteriaSatisfied: logger.warning( 'Loading a feasible but suboptimal solution. ' 'Please set load_solution=False and check ' diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index 23a87e06f1c..2f79a8515a3 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -874,7 +874,7 @@ def _postsolve(self, timer: HierarchicalTimer): if status == grb.LOADED: # problem is loaded, but no solution results.termination_condition = TerminationCondition.unknown elif status == grb.OPTIMAL: # optimal - results.termination_condition = TerminationCondition.ok + results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied elif status == grb.INFEASIBLE: results.termination_condition = TerminationCondition.infeasible elif status == grb.INF_OR_UNBD: @@ -925,7 +925,7 @@ def _postsolve(self, timer: HierarchicalTimer): timer.start('load solution') if config.load_solution: if gprob.SolCount > 0: - if results.termination_condition != TerminationCondition.ok: + if results.termination_condition != TerminationCondition.convergenceCriteriaSatisfied: logger.warning( 'Loading a feasible but suboptimal solution. ' 'Please set load_solution=False and check ' diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 528fb2f3087..cd17f5d90e8 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -610,7 +610,7 @@ def _postsolve(self, timer: HierarchicalTimer): elif status == highspy.HighsModelStatus.kModelEmpty: results.termination_condition = TerminationCondition.unknown elif status == highspy.HighsModelStatus.kOptimal: - results.termination_condition = TerminationCondition.ok + results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied elif status == highspy.HighsModelStatus.kInfeasible: results.termination_condition = TerminationCondition.infeasible elif status == highspy.HighsModelStatus.kUnboundedOrInfeasible: @@ -633,7 +633,7 @@ def _postsolve(self, timer: HierarchicalTimer): timer.start('load solution') self._sol = highs.getSolution() has_feasible_solution = False - if results.termination_condition == TerminationCondition.ok: + if results.termination_condition == TerminationCondition.convergenceCriteriaSatisfied: has_feasible_solution = True elif results.termination_condition in { TerminationCondition.objectiveLimit, @@ -645,7 +645,7 @@ def _postsolve(self, timer: HierarchicalTimer): if config.load_solution: if has_feasible_solution: - if results.termination_condition != TerminationCondition.ok: + if results.termination_condition != TerminationCondition.convergenceCriteriaSatisfied: logger.warning( 'Loading a feasible but suboptimal solution. ' 'Please set load_solution=False and check ' diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index c03f6e145f3..e19a68f6d85 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -303,7 +303,7 @@ def _parse_sol(self): termination_line = all_lines[1] if 'Optimal Solution Found' in termination_line: - results.termination_condition = TerminationCondition.ok + results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied elif 'Problem may be infeasible' in termination_line: results.termination_condition = TerminationCondition.infeasible elif 'problem might be unbounded' in termination_line: @@ -384,7 +384,7 @@ def _parse_sol(self): self._reduced_costs[var] = 0 if ( - results.termination_condition == TerminationCondition.ok + results.termination_condition == TerminationCondition.convergenceCriteriaSatisfied and self.config.load_solution ): for v, val in self._primal_sol.items(): @@ -395,7 +395,7 @@ def _parse_sol(self): results.best_feasible_objective = value( self._writer.get_active_objective().expr ) - elif results.termination_condition == TerminationCondition.ok: + elif results.termination_condition == TerminationCondition.convergenceCriteriaSatisfied: if self._writer.get_active_objective() is None: results.best_feasible_objective = None else: @@ -526,7 +526,7 @@ def get_duals( if ( self._last_results_object is None or self._last_results_object.termination_condition - != TerminationCondition.ok + != TerminationCondition.convergenceCriteriaSatisfied ): raise RuntimeError( 'Solver does not currently have valid duals. Please ' @@ -544,7 +544,7 @@ def get_reduced_costs( if ( self._last_results_object is None or self._last_results_object.termination_condition - != TerminationCondition.ok + != TerminationCondition.convergenceCriteriaSatisfied ): raise RuntimeError( 'Solver does not currently have valid reduced costs. Please ' diff --git a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py index 2727cf2313b..fcff8916b5b 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py @@ -160,7 +160,7 @@ def test_lp(self): res = opt.solve(self.m) self.assertAlmostEqual(x + y, res.best_feasible_objective) self.assertAlmostEqual(x + y, res.best_objective_bound) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertTrue(res.best_feasible_objective is not None) self.assertAlmostEqual(x, self.m.x.value) self.assertAlmostEqual(y, self.m.y.value) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 82db5f6286f..9e7abf04e08 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -9,7 +9,6 @@ from pyomo.contrib.appsi.solvers import Gurobi, Ipopt, Cplex, Cbc, Highs from typing import Type from pyomo.core.expr.numeric_expr import LinearExpression -import os numpy, numpy_available = attempt_import('numpy') import random @@ -91,7 +90,7 @@ def test_remove_variable_and_objective( m.x = pe.Var(bounds=(2, None)) m.obj = pe.Objective(expr=m.x) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(m.x.value, 2) del m.x @@ -99,7 +98,7 @@ def test_remove_variable_and_objective( m.x = pe.Var(bounds=(2, None)) m.obj = pe.Objective(expr=m.x) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(m.x.value, 2) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) @@ -159,13 +158,13 @@ def test_range_constraint( m.obj = pe.Objective(expr=m.x) m.c = pe.Constraint(expr=(-1, m.x, 1)) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(m.x.value, -1) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c], 1) m.obj.sense = pe.maximize res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(m.x.value, 1) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c], 1) @@ -182,7 +181,7 @@ def test_reduced_costs( m.y = pe.Var(bounds=(-2, 2)) m.obj = pe.Objective(expr=3 * m.x + 4 * m.y) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(m.x.value, -1) self.assertAlmostEqual(m.y.value, -2) rc = opt.get_reduced_costs() @@ -200,13 +199,13 @@ def test_reduced_costs2( m.x = pe.Var(bounds=(-1, 1)) m.obj = pe.Objective(expr=m.x) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(m.x.value, -1) rc = opt.get_reduced_costs() self.assertAlmostEqual(rc[m.x], 1) m.obj.sense = pe.maximize res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(m.x.value, 1) rc = opt.get_reduced_costs() self.assertAlmostEqual(rc[m.x], 1) @@ -236,7 +235,7 @@ def test_param_changes( m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) @@ -274,7 +273,7 @@ def test_immutable_param( m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) @@ -308,7 +307,7 @@ def test_equality( m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) @@ -348,7 +347,7 @@ def test_linear_expression( m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) self.assertTrue(res.best_objective_bound <= m.y.value) @@ -378,7 +377,7 @@ def test_no_objective( m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertEqual(res.best_feasible_objective, None) @@ -407,7 +406,7 @@ def test_add_remove_cons( m.c1 = pe.Constraint(expr=m.y >= a1 * m.x + b1) m.c2 = pe.Constraint(expr=m.y >= a2 * m.x + b2) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) @@ -418,7 +417,7 @@ def test_add_remove_cons( m.c3 = pe.Constraint(expr=m.y >= a3 * m.x + b3) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(m.x.value, (b3 - b1) / (a1 - a3)) self.assertAlmostEqual(m.y.value, a1 * (b3 - b1) / (a1 - a3) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) @@ -430,7 +429,7 @@ def test_add_remove_cons( del m.c3 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) @@ -456,7 +455,7 @@ def test_results_infeasible( res = opt.solve(m) opt.config.load_solution = False res = opt.solve(m) - self.assertNotEqual(res.termination_condition, TerminationCondition.ok) + self.assertNotEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) if opt_class is Ipopt: acceptable_termination_conditions = { TerminationCondition.infeasible, @@ -748,7 +747,7 @@ def test_mutable_param_with_range( m.c2.value = float(c2) m.obj.sense = sense res: Results = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) if sense is pe.minimize: self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2), 6) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1, 6) @@ -785,7 +784,7 @@ def test_add_and_remove_vars( opt.update_config.check_for_new_or_removed_vars = False opt.config.load_solution = False res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) opt.load_vars() self.assertAlmostEqual(m.y.value, -1) m.x = pe.Var() @@ -799,7 +798,7 @@ def test_add_and_remove_vars( opt.add_variables([m.x]) opt.add_constraints([m.c1, m.c2]) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) opt.load_vars() self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) @@ -808,7 +807,7 @@ def test_add_and_remove_vars( opt.remove_variables([m.x]) m.x.value = None res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) opt.load_vars() self.assertEqual(m.x.value, None) self.assertAlmostEqual(m.y.value, -1) @@ -869,7 +868,7 @@ def test_with_numpy( ) ) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) @@ -1211,14 +1210,14 @@ def test_variables_elsewhere(self, name: str, opt_class: Type[PersistentSolver]) m.b.c2 = pe.Constraint(expr=m.y >= -m.x) res = opt.solve(m.b) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(res.best_feasible_objective, 1) self.assertAlmostEqual(m.x.value, -1) self.assertAlmostEqual(m.y.value, 1) m.x.setlb(0) res = opt.solve(m.b) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(res.best_feasible_objective, 2) self.assertAlmostEqual(m.x.value, 0) self.assertAlmostEqual(m.y.value, 2) @@ -1241,7 +1240,7 @@ def test_variables_elsewhere2(self, name: str, opt_class: Type[PersistentSolver] m.c4 = pe.Constraint(expr=m.y >= -m.z + 1) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(res.best_feasible_objective, 1) sol = res.solution_loader.get_primals() self.assertIn(m.x, sol) @@ -1251,7 +1250,7 @@ def test_variables_elsewhere2(self, name: str, opt_class: Type[PersistentSolver] del m.c3 del m.c4 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(res.best_feasible_objective, 0) sol = res.solution_loader.get_primals() self.assertIn(m.x, sol) @@ -1273,12 +1272,12 @@ def test_bug_1(self, name: str, opt_class: Type[PersistentSolver], only_child_va m.c = pe.Constraint(expr=m.y >= m.p * m.x) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(res.best_feasible_objective, 0) m.p.value = 1 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.ok) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) self.assertAlmostEqual(res.best_feasible_objective, 3) From 7f0f73f5b7bd49a31a8bbcf101e4177bfeee03b4 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 16 Aug 2023 09:41:54 -0600 Subject: [PATCH 0023/1204] SAVING STATE --- doc/OnlineDocs/conf.py | 7 ------- .../developer_reference/solvers.rst | 20 +++++++++++++++++++ pyomo/common/config.py | 2 +- pyomo/contrib/appsi/base.py | 2 +- pyomo/contrib/appsi/solvers/cbc.py | 2 +- pyomo/contrib/appsi/solvers/cplex.py | 2 +- pyomo/contrib/appsi/solvers/gurobi.py | 4 ++-- pyomo/contrib/appsi/solvers/highs.py | 6 +++--- pyomo/contrib/appsi/solvers/ipopt.py | 6 +++--- .../solvers/tests/test_persistent_solvers.py | 2 +- 10 files changed, 33 insertions(+), 20 deletions(-) create mode 100644 doc/OnlineDocs/developer_reference/solvers.rst diff --git a/doc/OnlineDocs/conf.py b/doc/OnlineDocs/conf.py index 43df1263f82..d8939cf61dd 100644 --- a/doc/OnlineDocs/conf.py +++ b/doc/OnlineDocs/conf.py @@ -146,13 +146,6 @@ html_theme = 'sphinx_rtd_theme' -# Force HTML4: If we don't explicitly force HTML4, then the background -# of the Parameters/Returns/Return type headers is shaded the same as the -# method prototype (tested 15 April 21 with Sphinx=3.5.4 and -# sphinx-rtd-theme=0.5.2). -html4_writer = True -# html5_writer = True - if not on_rtd: # only import and set the theme if we're building docs locally import sphinx_rtd_theme diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst new file mode 100644 index 00000000000..374ba4fbee8 --- /dev/null +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -0,0 +1,20 @@ +Solver Interfaces +================= + +Pyomo offers interfaces into multiple solvers, both commercial and open source. + + +Termination Conditions +---------------------- + +Pyomo offers a standard set of termination conditions to map to solver +returns. + +.. currentmodule:: pyomo.contrib.appsi.base + +.. autosummary:: + + TerminationCondition + + + diff --git a/pyomo/common/config.py b/pyomo/common/config.py index 1b44d555b91..7bbcd693a72 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -2019,7 +2019,7 @@ def generate_documentation( ) if item_body is not None: deprecation_warning( - f"Overriding 'item_body' by passing strings to " + f"Overriding '{item_body}' by passing strings to " "generate_documentation is deprecated. Create an instance of a " "StringConfigFormatter and pass it as the 'format' argument.", version='6.6.0', diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index d85e1ef9dfe..5116b59322a 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -486,7 +486,7 @@ class Results(): ... print('sub-optimal but feasible solution found: ', results.best_feasible_objective) #doctest:+SKIP ... results.solution_loader.load_vars(vars_to_load=[m.x]) #doctest:+SKIP ... print('The value of x in the feasible solution is ', m.x.value) #doctest:+SKIP - ... elif results.termination_condition in {appsi.base.TerminationCondition.maxIterations, appsi.base.TerminationCondition.maxTimeLimit}: #doctest:+SKIP + ... elif results.termination_condition in {appsi.base.TerminationCondition.iterationLimit, appsi.base.TerminationCondition.maxTimeLimit}: #doctest:+SKIP ... print('No feasible solution was found. The best lower bound found was ', results.best_objective_bound) #doctest:+SKIP ... else: #doctest:+SKIP ... print('The following termination condition was encountered: ', results.termination_condition) #doctest:+SKIP diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index 74b9aa8ba8e..12e7555535e 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -242,7 +242,7 @@ def _parse_soln(self): results.termination_condition = TerminationCondition.maxTimeLimit obj_val = float(termination_line.split()[-1]) elif termination_line.startswith('stopped on iterations'): - results.termination_condition = TerminationCondition.maxIterations + results.termination_condition = TerminationCondition.iterationLimit obj_val = float(termination_line.split()[-1]) else: results.termination_condition = TerminationCondition.unknown diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index c459effe325..08d6b11fc76 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -292,7 +292,7 @@ def _postsolve(self, timer: HierarchicalTimer, solve_time): elif status in [3, 103]: results.termination_condition = TerminationCondition.infeasible elif status in [10]: - results.termination_condition = TerminationCondition.maxIterations + results.termination_condition = TerminationCondition.iterationLimit elif status in [11, 25, 107, 131]: results.termination_condition = TerminationCondition.maxTimeLimit else: diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index 2f79a8515a3..dfe3b441cd8 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -884,9 +884,9 @@ def _postsolve(self, timer: HierarchicalTimer): elif status == grb.CUTOFF: results.termination_condition = TerminationCondition.objectiveLimit elif status == grb.ITERATION_LIMIT: - results.termination_condition = TerminationCondition.maxIterations + results.termination_condition = TerminationCondition.iterationLimit elif status == grb.NODE_LIMIT: - results.termination_condition = TerminationCondition.maxIterations + results.termination_condition = TerminationCondition.iterationLimit elif status == grb.TIME_LIMIT: results.termination_condition = TerminationCondition.maxTimeLimit elif status == grb.SOLUTION_LIMIT: diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index cd17f5d90e8..4ec4ebeffb1 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -612,7 +612,7 @@ def _postsolve(self, timer: HierarchicalTimer): elif status == highspy.HighsModelStatus.kOptimal: results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied elif status == highspy.HighsModelStatus.kInfeasible: - results.termination_condition = TerminationCondition.infeasible + results.termination_condition = TerminationCondition.provenInfeasible elif status == highspy.HighsModelStatus.kUnboundedOrInfeasible: results.termination_condition = TerminationCondition.infeasibleOrUnbounded elif status == highspy.HighsModelStatus.kUnbounded: @@ -624,7 +624,7 @@ def _postsolve(self, timer: HierarchicalTimer): elif status == highspy.HighsModelStatus.kTimeLimit: results.termination_condition = TerminationCondition.maxTimeLimit elif status == highspy.HighsModelStatus.kIterationLimit: - results.termination_condition = TerminationCondition.maxIterations + results.termination_condition = TerminationCondition.iterationLimit elif status == highspy.HighsModelStatus.kUnknown: results.termination_condition = TerminationCondition.unknown else: @@ -637,7 +637,7 @@ def _postsolve(self, timer: HierarchicalTimer): has_feasible_solution = True elif results.termination_condition in { TerminationCondition.objectiveLimit, - TerminationCondition.maxIterations, + TerminationCondition.iterationLimit, TerminationCondition.maxTimeLimit, }: if self._sol.value_valid: diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index e19a68f6d85..6580c9a004a 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -16,7 +16,7 @@ from pyomo.common.collections import ComponentMap from pyomo.core.expr.numvalue import value from pyomo.core.expr.visitor import replace_expressions -from typing import Optional, Sequence, NoReturn, List, Mapping +from typing import Optional, Sequence, List, Mapping from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.block import _BlockData @@ -305,11 +305,11 @@ def _parse_sol(self): if 'Optimal Solution Found' in termination_line: results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied elif 'Problem may be infeasible' in termination_line: - results.termination_condition = TerminationCondition.infeasible + results.termination_condition = TerminationCondition.locallyInfeasible elif 'problem might be unbounded' in termination_line: results.termination_condition = TerminationCondition.unbounded elif 'Maximum Number of Iterations Exceeded' in termination_line: - results.termination_condition = TerminationCondition.maxIterations + results.termination_condition = TerminationCondition.iterationLimit elif 'Maximum CPU Time Exceeded' in termination_line: results.termination_condition = TerminationCondition.maxTimeLimit else: diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 9e7abf04e08..23236827a11 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -1014,7 +1014,7 @@ def test_time_limit( if type(opt) is Cbc: # I can't figure out why CBC is reporting max iter... self.assertIn( res.termination_condition, - {TerminationCondition.maxIterations, TerminationCondition.maxTimeLimit}, + {TerminationCondition.iterationLimit, TerminationCondition.maxTimeLimit}, ) else: self.assertEqual( From cda74ae09f83345a78ac513e0db42dd55e7f97a5 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 17 Aug 2023 08:53:57 -0600 Subject: [PATCH 0024/1204] Begin documentation for sovlers --- doc/OnlineDocs/developer_reference/index.rst | 1 + doc/OnlineDocs/developer_reference/solvers.rst | 7 +++---- pyomo/common/config.py | 2 +- pyomo/contrib/appsi/solvers/ipopt.py | 5 ++--- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/doc/OnlineDocs/developer_reference/index.rst b/doc/OnlineDocs/developer_reference/index.rst index 8c29150015c..0f0f636abee 100644 --- a/doc/OnlineDocs/developer_reference/index.rst +++ b/doc/OnlineDocs/developer_reference/index.rst @@ -12,3 +12,4 @@ scripts using Pyomo. config.rst deprecation.rst expressions/index.rst + solvers.rst diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index 374ba4fbee8..d48e270cc7c 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -10,11 +10,10 @@ Termination Conditions Pyomo offers a standard set of termination conditions to map to solver returns. -.. currentmodule:: pyomo.contrib.appsi.base +.. currentmodule:: pyomo.contrib.appsi -.. autosummary:: - - TerminationCondition +.. autoclass:: pyomo.contrib.appsi.base.TerminationCondition + :noindex: diff --git a/pyomo/common/config.py b/pyomo/common/config.py index 7bbcd693a72..61e4f682a2a 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -2019,7 +2019,7 @@ def generate_documentation( ) if item_body is not None: deprecation_warning( - f"Overriding '{item_body}' by passing strings to " + "Overriding 'item_body' by passing strings to " "generate_documentation is deprecated. Create an instance of a " "StringConfigFormatter and pass it as the 'format' argument.", version='6.6.0', diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index 6580c9a004a..68dcdae2492 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -297,9 +297,8 @@ def _parse_sol(self): solve_cons = self._writer.get_ordered_cons() results = Results() - f = open(self._filename + '.sol', 'r') - all_lines = list(f.readlines()) - f.close() + with open(self._filename + '.sol', 'r') as f: + all_lines = list(f.readlines()) termination_line = all_lines[1] if 'Optimal Solution Found' in termination_line: From 26375cb1e5363daddd4dc1d896ba6c7f54854912 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 18 Aug 2023 08:33:56 -0600 Subject: [PATCH 0025/1204] update workflows --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 69028e55a17..1c865462f60 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -280,7 +280,7 @@ jobs: || echo "WARNING: Gurobi is not available" python -m pip install --cache-dir cache/pip xpress \ || echo "WARNING: Xpress Community Edition is not available" - python -m pip install git+https://github.com/michaelbynum/wntr.git@working \ + python -m pip install git+https://github.com/usepa/wntr.git@main \ || echo "WARNING: WNTR is not available" fi python -c 'import sys; print("PYTHON_EXE=%s" \ diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 357a2fe866e..547e7972aa6 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -298,7 +298,7 @@ jobs: || echo "WARNING: Gurobi is not available" python -m pip install --cache-dir cache/pip xpress \ || echo "WARNING: Xpress Community Edition is not available" - python -m pip install git+https://github.com/michaelbynum/wntr.git@working \ + python -m pip install git+https://github.com/usepa/wntr.git@main \ || echo "WARNING: WNTR is not available" fi python -c 'import sys; print("PYTHON_EXE=%s" \ From bb03a18464b6f5f1f62dc696db2bed3686433d74 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 21 Aug 2023 16:54:28 -0400 Subject: [PATCH 0026/1204] fix load_state_into_pyomo bug --- pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py index 9a5dc50ef7b..fe3e62eb497 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py @@ -426,7 +426,7 @@ def load_state_into_pyomo(self, bound_multipliers=None): # not we are minimizing or maximizing - this is done in the ASL interface # for ipopt, but does not appear to be done in cyipopt. obj_sign = 1.0 - objs = list(m.component_data_objects(ctype=pyo.Objective, descend_into=True)) + objs = list(m.component_data_objects(ctype=pyo.Objective, active=True, descend_into=True)) assert len(objs) == 1 if objs[0].sense == pyo.maximize: obj_sign = -1.0 From 23cc0d69b6bce53f7f19984f56a3da03bd0cc4bd Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Tue, 29 Aug 2023 08:30:15 -0600 Subject: [PATCH 0027/1204] new latex branch --- .DS_Store | Bin 0 -> 6148 bytes pyomo/.DS_Store | Bin 0 -> 6148 bytes pyomo/contrib/.DS_Store | Bin 0 -> 6148 bytes pyomo/contrib/edi/.DS_Store | Bin 0 -> 6148 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 .DS_Store create mode 100644 pyomo/.DS_Store create mode 100644 pyomo/contrib/.DS_Store create mode 100644 pyomo/contrib/edi/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a6006a4ad1ba49dc60d16a61c045eaa5a228aacf GIT binary patch literal 6148 zcmeHK%}T>S5Z>*NZ7D(y3VK`cTCk;uw0H@zzJL)usMLfM4W`-Bq!uZK-1UWg5ueAI z-3_!DJc`&E*!^bbXE*af_J=XX-9^}A%wmi+p&@cqN(9ZNu8Ijp zbu?Lyt%Gx!WPX%P<|-iyClGRX6D6Tsx^j_(smk@VqXjayj#sPR&>nQepug^jRj)s= zJHoby>vgMncyfAnIew0(iG0&ca-dwvzQF?CLD?wj#hWLwOdi2nWE7Ev!~iis3=ji5 z$$&Wyn(dveo{A<0h=HFN!2Ll$Lv#%08r9YT9bTW&UqeIz9p4g&!k}X?*9aaEu2TVZ zDmPCIuG7ITOq^pd*QnDOS1ZFjX654X!qw_v7b=`_MP|R7$64z83VjF@6T?pgllC!MGe15YV?S0WiRQ01p5v*a!tFYlO^eT?H3RD9juwXi(97Jc^Pv z6a7UKeR~;(pkM|ueENPJq310cCGmLDXuOL;v9z^aMyZwW!bd$1C;iEE-07z`G`iF} ziE_OkUB$zB&)YrIYSNF@Ff|GBV2B~N*RdMtc}GvxU~F!_miyo1HUZ#R$Y(r>hu zb-D2U)=6EqoBncHt?V5honG{wl4qq~ESUm%H?rd}hgVd-)HMrJm1y;Vo;)j$W@HAK z0cL<1*dzwrDNw0xQqf#1Gr$b|hymIkBsRjpVP?^69oW(JnfxU}64dD}K`0#t4l|4B zK@m0;(WVOb#1J+e?b5{s4l|239fVmK=W#3Nj~8K9N4qrPAOefrGXu=PDg#A3^yvIQ z$6sdcBY!o8N6Y{-@Xr_!rEb{mU{UUD{Z<~GwG!JsHWG@s#|-=e10NI;O*jAm literal 0 HcmV?d00001 diff --git a/pyomo/contrib/.DS_Store b/pyomo/contrib/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..851319072b84f232eac3559cc3f19a730f4f9680 GIT binary patch literal 6148 zcmeHKJ5Iwu5S=9{EYYN-+$*G`Tq1LVTmS_lK#Juw^i~=Uz&*G{lxuJl-hB8(D_l}U zZ={*$ozJfQiXD%Lq}5Be6j_K!167n)HMMA5wUUeQ%z;VwSg!Afepls9Ika{r57No= z_OYsuNI$ggW;<+<+q@m$_aE1Xo1eOV=q94Or)t-!_hF0-kO4A42FSpi3QFBCHH9}Ii~ D`!y$X literal 0 HcmV?d00001 diff --git a/pyomo/contrib/edi/.DS_Store b/pyomo/contrib/edi/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..7c829d887e4d0c83c53ced83c678d4a5de433a25 GIT binary patch literal 6148 zcmeHKK}y6>3{A!nDsBecxcpbBE0>+2o}d@daRyzqw4eimn~F#B3?9G(xbP;v{DiT! z9dISXkU;W&^OOGH_e;|d5id5YlxRjo2~==$0y82qFFKHkd1R8~JsK)$O%LT=S`4Dy zv5ySs;jZb4Zm6Qp`Q6r4)7fx>bM3`cb)GNFdWo3i^W);>>+*dr<6+$DPjStCTKrn` zm>%VAf{ky~?%D2M-p-z1Z7-ets{Yx2vEVyuvLto4w%>i0H<(A!B~0;$q9y;VXKH42x}@(Q`uS!)^zxT#bt)AqNWpD z^TD<Z^cgtP%bC>wtKI#7KgqA00cYT#7~pAM Date: Tue, 29 Aug 2023 10:17:50 -0600 Subject: [PATCH 0028/1204] Fix termination conditions --- .../contrib/appsi/solvers/tests/test_persistent_solvers.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 23236827a11..df2eccd5eef 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -458,12 +458,14 @@ def test_results_infeasible( self.assertNotEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) if opt_class is Ipopt: acceptable_termination_conditions = { - TerminationCondition.infeasible, + TerminationCondition.provenInfeasible, + TerminationCondition.locallyInfeasible, TerminationCondition.unbounded, } else: acceptable_termination_conditions = { - TerminationCondition.infeasible, + TerminationCondition.provenInfeasible, + TerminationCondition.locallyInfeasible, TerminationCondition.infeasibleOrUnbounded, } self.assertIn(res.termination_condition, acceptable_termination_conditions) From 2b7d62f18c091aa264b0ee8b2e48301a85c86a18 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 29 Aug 2023 10:22:46 -0600 Subject: [PATCH 0029/1204] Modify termination conditions for solvers --- pyomo/contrib/appsi/solvers/cbc.py | 2 +- pyomo/contrib/appsi/solvers/cplex.py | 2 +- pyomo/contrib/appsi/solvers/gurobi.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index 12e7555535e..9a4f098d08b 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -235,7 +235,7 @@ def _parse_soln(self): results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied obj_val = float(termination_line.split()[-1]) elif 'infeasible' in termination_line: - results.termination_condition = TerminationCondition.infeasible + results.termination_condition = TerminationCondition.provenInfeasible elif 'unbounded' in termination_line: results.termination_condition = TerminationCondition.unbounded elif termination_line.startswith('stopped on time'): diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 08d6b11fc76..9c7683b81cf 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -290,7 +290,7 @@ def _postsolve(self, timer: HierarchicalTimer, solve_time): elif status in [4, 119, 134]: results.termination_condition = TerminationCondition.infeasibleOrUnbounded elif status in [3, 103]: - results.termination_condition = TerminationCondition.infeasible + results.termination_condition = TerminationCondition.provenInfeasible elif status in [10]: results.termination_condition = TerminationCondition.iterationLimit elif status in [11, 25, 107, 131]: diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index dfe3b441cd8..339c001369e 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -876,7 +876,7 @@ def _postsolve(self, timer: HierarchicalTimer): elif status == grb.OPTIMAL: # optimal results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied elif status == grb.INFEASIBLE: - results.termination_condition = TerminationCondition.infeasible + results.termination_condition = TerminationCondition.provenInfeasible elif status == grb.INF_OR_UNBD: results.termination_condition = TerminationCondition.infeasibleOrUnbounded elif status == grb.UNBOUNDED: From 3b94b79df27f20fbd5d7276e2e4e60d538ef3344 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 29 Aug 2023 10:53:07 -0600 Subject: [PATCH 0030/1204] Update params for Solver base class --- pyomo/contrib/appsi/base.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index 5116b59322a..f50a1e6135f 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -20,7 +20,7 @@ from .utils.get_objective import get_objective from .utils.collect_vars_and_named_exprs import collect_vars_and_named_exprs from pyomo.common.timing import HierarchicalTimer -from pyomo.common.config import ConfigDict, ConfigValue, NonNegativeFloat +from pyomo.common.config import ConfigDict, ConfigValue, NonNegativeFloat, NonNegativeInt from pyomo.common.errors import ApplicationError from pyomo.opt.base import SolverFactory as LegacySolverFactory from pyomo.common.factory import Factory @@ -177,6 +177,8 @@ class InterfaceConfig(ConfigDict): report_timing: bool - wrapper If True, then some timing information will be printed at the end of the solve. + threads: integer - sent to solver + Number of threads to be used by a solver. """ def __init__( @@ -199,6 +201,7 @@ def __init__( self.declare('load_solution', ConfigValue(domain=bool)) self.declare('symbolic_solver_labels', ConfigValue(domain=bool)) self.declare('report_timing', ConfigValue(domain=bool)) + self.declare('threads', ConfigValue(domain=NonNegativeInt, default=None)) self.time_limit: Optional[float] = self.declare( 'time_limit', ConfigValue(domain=NonNegativeFloat) @@ -744,7 +747,7 @@ def __str__(self): @abc.abstractmethod def solve( - self, model: _BlockData, tee=False, timer: HierarchicalTimer = None, **kwargs + self, model: _BlockData, tee: bool = False, timer: HierarchicalTimer = None, **kwargs ) -> Results: """ Solve a Pyomo model. From 49f226d247b30bf8071c091171075dce0a7153c5 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 29 Aug 2023 11:13:07 -0600 Subject: [PATCH 0031/1204] Change stream_solver back to tee --- pyomo/contrib/appsi/base.py | 16 +++++++--------- pyomo/contrib/appsi/solvers/cbc.py | 2 +- pyomo/contrib/appsi/solvers/cplex.py | 2 +- pyomo/contrib/appsi/solvers/gurobi.py | 4 ++-- pyomo/contrib/appsi/solvers/highs.py | 2 +- pyomo/contrib/appsi/solvers/ipopt.py | 2 +- .../solvers/tests/test_persistent_solvers.py | 2 +- 7 files changed, 14 insertions(+), 16 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index f50a1e6135f..62c87c5a0c9 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -165,7 +165,7 @@ class InterfaceConfig(ConfigDict): ---------- time_limit: float - sent to solver Time limit for the solver - stream_solver: bool - wrapper + tee: bool - wrapper If True, then the solver log goes to stdout load_solution: bool - wrapper If False, then the values of the primal variables will not be @@ -197,7 +197,7 @@ def __init__( visibility=visibility, ) - self.declare('stream_solver', ConfigValue(domain=bool)) + self.declare('tee', ConfigValue(domain=bool)) self.declare('load_solution', ConfigValue(domain=bool)) self.declare('symbolic_solver_labels', ConfigValue(domain=bool)) self.declare('report_timing', ConfigValue(domain=bool)) @@ -206,7 +206,7 @@ def __init__( self.time_limit: Optional[float] = self.declare( 'time_limit', ConfigValue(domain=NonNegativeFloat) ) - self.stream_solver: bool = False + self.tee: bool = False self.load_solution: bool = True self.symbolic_solver_labels: bool = False self.report_timing: bool = False @@ -454,7 +454,7 @@ def get_reduced_costs( return rc -class Results(): +class Results: """ Attributes ---------- @@ -747,7 +747,7 @@ def __str__(self): @abc.abstractmethod def solve( - self, model: _BlockData, tee: bool = False, timer: HierarchicalTimer = None, **kwargs + self, model: _BlockData, timer: HierarchicalTimer = None, **kwargs ) -> Results: """ Solve a Pyomo model. @@ -756,8 +756,6 @@ def solve( ---------- model: _BlockData The Pyomo model to be solved - tee: bool - Show solver output in the terminal timer: HierarchicalTimer An option timer for reporting timing **kwargs @@ -1635,7 +1633,7 @@ def update(self, timer: HierarchicalTimer = None): } -class LegacySolverInterface(): +class LegacySolverInterface: def solve( self, model: _BlockData, @@ -1653,7 +1651,7 @@ def solve( ): original_config = self.config self.config = self.config() - self.config.stream_solver = tee + self.config.tee = tee self.config.load_solution = load_solutions self.config.symbolic_solver_labels = symbolic_solver_labels self.config.time_limit = timelimit diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index 9a4f098d08b..35071ab17ea 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -383,7 +383,7 @@ def _check_and_escape_options(): level=self.config.log_level, logger=self.config.solver_output_logger ) ] - if self.config.stream_solver: + if self.config.tee: ostreams.append(sys.stdout) with TeeStream(*ostreams) as t: diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 9c7683b81cf..7f9844fc21d 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -245,7 +245,7 @@ def _apply_solver(self, timer: HierarchicalTimer): log_stream = LogStream( level=self.config.log_level, logger=self.config.solver_output_logger ) - if config.stream_solver: + if config.tee: def _process_stream(arg): sys.stdout.write(arg) diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index 339c001369e..3f8eab638b0 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -353,7 +353,7 @@ def _solve(self, timer: HierarchicalTimer): level=self.config.log_level, logger=self.config.solver_output_logger ) ] - if self.config.stream_solver: + if self.config.tee: ostreams.append(sys.stdout) with TeeStream(*ostreams) as t: @@ -1384,7 +1384,7 @@ def set_callback(self, func=None): >>> _c = _add_cut(4) # this is an arbitrary choice >>> >>> opt = appsi.solvers.Gurobi() - >>> opt.config.stream_solver = True + >>> opt.config.tee = True >>> opt.set_instance(m) # doctest:+SKIP >>> opt.gurobi_options['PreCrush'] = 1 >>> opt.gurobi_options['LazyConstraints'] = 1 diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 4ec4ebeffb1..e5c43d27c8d 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -211,7 +211,7 @@ def _solve(self, timer: HierarchicalTimer): level=self.config.log_level, logger=self.config.solver_output_logger ) ] - if self.config.stream_solver: + if self.config.tee: ostreams.append(sys.stdout) with TeeStream(*ostreams) as t: diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index 68dcdae2492..da42fc0be41 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -430,7 +430,7 @@ def _apply_solver(self, timer: HierarchicalTimer): level=self.config.log_level, logger=self.config.solver_output_logger ) ] - if self.config.stream_solver: + if self.config.tee: ostreams.append(sys.stdout) cmd = [ diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index df2eccd5eef..2d579611761 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -368,7 +368,7 @@ def test_no_objective( m.b2 = pe.Param(mutable=True) m.c1 = pe.Constraint(expr=m.y == m.a1 * m.x + m.b1) m.c2 = pe.Constraint(expr=m.y == m.a2 * m.x + m.b2) - opt.config.stream_solver = True + opt.config.tee = True params_to_test = [(1, -1, 2, 1), (1, -2, 2, 1), (1, -1, 3, 1)] for a1, a2, b1, b2 in params_to_test: From 50f64526a7187061c696433bd09c6765bfbbd822 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 29 Aug 2023 11:26:06 -0600 Subject: [PATCH 0032/1204] Isolate tests to just APPSI for speed --- .github/workflows/test_branches.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 99d5f7fc1a8..d1d6f73870d 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -598,8 +598,7 @@ jobs: run: | $PYTHON_EXE -m pytest -v \ -W ignore::Warning ${{matrix.category}} \ - pyomo `pwd`/pyomo-model-libraries \ - `pwd`/examples/pyomobook --junitxml="TEST-pyomo.xml" + pyomo/contrib/appsi --junitxml="TEST-pyomo.xml" - name: Run Pyomo MPI tests if: matrix.mpi != 0 From 2e3ad3ad20389c8d656855f3bf27074276174b99 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 29 Aug 2023 12:09:02 -0600 Subject: [PATCH 0033/1204] Remove kwargs for now --- pyomo/contrib/appsi/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index 62c87c5a0c9..5ce5421ee86 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -747,7 +747,7 @@ def __str__(self): @abc.abstractmethod def solve( - self, model: _BlockData, timer: HierarchicalTimer = None, **kwargs + self, model: _BlockData, timer: HierarchicalTimer = None, ) -> Results: """ Solve a Pyomo model. From 0a0a67da17eb25c76761e59d45b5ed5c9b432ec2 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 29 Aug 2023 12:21:28 -0600 Subject: [PATCH 0034/1204] Per Michael Bynum, remove cbc and cplex tests as C++ lp_writer won't be sustained --- .../solvers/tests/test_persistent_solvers.py | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 2d579611761..135f36d3695 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -6,7 +6,7 @@ parameterized = parameterized.parameterized from pyomo.contrib.appsi.base import TerminationCondition, Results, PersistentSolver from pyomo.contrib.appsi.cmodel import cmodel_available -from pyomo.contrib.appsi.solvers import Gurobi, Ipopt, Cplex, Cbc, Highs +from pyomo.contrib.appsi.solvers import Gurobi, Ipopt, Highs from typing import Type from pyomo.core.expr.numeric_expr import LinearExpression @@ -21,14 +21,12 @@ all_solvers = [ ('gurobi', Gurobi), ('ipopt', Ipopt), - ('cplex', Cplex), - ('cbc', Cbc), ('highs', Highs), ] -mip_solvers = [('gurobi', Gurobi), ('cplex', Cplex), ('cbc', Cbc), ('highs', Highs)] +mip_solvers = [('gurobi', Gurobi), ('highs', Highs)] nlp_solvers = [('ipopt', Ipopt)] -qcp_solvers = [('gurobi', Gurobi), ('ipopt', Ipopt), ('cplex', Cplex)] -miqcqp_solvers = [('gurobi', Gurobi), ('cplex', Cplex)] +qcp_solvers = [('gurobi', Gurobi), ('ipopt', Ipopt)] +miqcqp_solvers = [('gurobi', Gurobi)] only_child_vars_options = [True, False] @@ -1013,15 +1011,9 @@ def test_time_limit( opt.config.time_limit = 0 opt.config.load_solution = False res = opt.solve(m) - if type(opt) is Cbc: # I can't figure out why CBC is reporting max iter... - self.assertIn( - res.termination_condition, - {TerminationCondition.iterationLimit, TerminationCondition.maxTimeLimit}, - ) - else: - self.assertEqual( - res.termination_condition, TerminationCondition.maxTimeLimit - ) + self.assertEqual( + res.termination_condition, TerminationCondition.maxTimeLimit + ) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_objective_changes( From af5ee141afa06d3c7fafdffd8b01d362c604c826 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 29 Aug 2023 12:27:00 -0600 Subject: [PATCH 0035/1204] Turn off test_examples; uses cplex --- pyomo/contrib/appsi/examples/tests/test_examples.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/examples/tests/test_examples.py b/pyomo/contrib/appsi/examples/tests/test_examples.py index ffcecaf0c5f..db6e2910b77 100644 --- a/pyomo/contrib/appsi/examples/tests/test_examples.py +++ b/pyomo/contrib/appsi/examples/tests/test_examples.py @@ -5,7 +5,7 @@ from pyomo.contrib import appsi -@unittest.skipUnless(cmodel_available, 'appsi extensions are not available') +@unittest.skip('Currently turning off cplex support') class TestExamples(unittest.TestCase): def test_getting_started(self): try: From 3028138884b3b9125ffe6bebfc38058a8759c70c Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 29 Aug 2023 12:39:33 -0600 Subject: [PATCH 0036/1204] Allow macOS IPOPT download; update ubuntu download; try using ipopt instead of cplex for getting_started --- pyomo/contrib/appsi/examples/getting_started.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/examples/getting_started.py b/pyomo/contrib/appsi/examples/getting_started.py index 04092601c91..d907283f663 100644 --- a/pyomo/contrib/appsi/examples/getting_started.py +++ b/pyomo/contrib/appsi/examples/getting_started.py @@ -16,7 +16,7 @@ def main(plot=True, n_points=200): m.c1 = pe.Constraint(expr=m.y >= (m.x + 1) ** 2) m.c2 = pe.Constraint(expr=m.y >= (m.x - m.p) ** 2) - opt = appsi.solvers.Cplex() # create an APPSI solver interface + opt = appsi.solvers.Ipopt() # create an APPSI solver interface opt.config.load_solution = False # modify the config options # change how automatic updates are handled opt.update_config.check_for_new_or_removed_vars = False From ad7d9e028f9fdd68f7057fb4f45aea78dc0ebf75 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 29 Aug 2023 12:43:44 -0600 Subject: [PATCH 0037/1204] Allow macOS IPOPT download; update ubuntu download; try using ipopt instead of cplex for getting_started --- .github/workflows/test_branches.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index d1d6f73870d..9640171a7c1 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -390,10 +390,10 @@ jobs: IPOPT_TAR=${DOWNLOAD_DIR}/ipopt.tar.gz if test ! -e $IPOPT_TAR; then echo "...downloading Ipopt" - if test "${{matrix.TARGET}}" == osx; then - echo "IDAES Ipopt not available on OSX" - exit 0 - fi + # if test "${{matrix.TARGET}}" == osx; then + # echo "IDAES Ipopt not available on OSX" + # exit 0 + # fi URL=https://github.com/IDAES/idaes-ext RELEASE=$(curl --max-time 150 --retry 8 \ -L -s -H 'Accept: application/json' ${URL}/releases/latest) @@ -401,7 +401,11 @@ jobs: URL=${URL}/releases/download/$VER if test "${{matrix.TARGET}}" == linux; then curl --max-time 150 --retry 8 \ - -L $URL/idaes-solvers-ubuntu2004-x86_64.tar.gz \ + -L $URL/idaes-solvers-ubuntu2204-x86_64.tar.gz \ + > $IPOPT_TAR + elseif test "${{matrix.TARGET}}" == osx; then + curl --max-time 150 --retry 8 \ + -L $URL/idaes-solvers-darwin-x86_64.tar.gz \ > $IPOPT_TAR else curl --max-time 150 --retry 8 \ From cbe8b9902070df5c7ac0809bf4ae7965bb400299 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 29 Aug 2023 12:56:46 -0600 Subject: [PATCH 0038/1204] Fix broken bash --- .github/workflows/test_branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 9640171a7c1..ed0f9350206 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -403,7 +403,7 @@ jobs: curl --max-time 150 --retry 8 \ -L $URL/idaes-solvers-ubuntu2204-x86_64.tar.gz \ > $IPOPT_TAR - elseif test "${{matrix.TARGET}}" == osx; then + elif test "${{matrix.TARGET}}" == osx; then curl --max-time 150 --retry 8 \ -L $URL/idaes-solvers-darwin-x86_64.tar.gz \ > $IPOPT_TAR From 2d55e658b6b1a9effe5983dae21af75bcce319ed Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 29 Aug 2023 13:06:22 -0600 Subject: [PATCH 0039/1204] Change untar command --- .github/workflows/test_branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index ed0f9350206..76bdcfd0587 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -414,7 +414,7 @@ jobs: fi fi cd $IPOPT_DIR - tar -xzi < $IPOPT_TAR + tar -xzf < $IPOPT_TAR echo "" echo "$IPOPT_DIR" ls -l $IPOPT_DIR From 630662942f30c2fd70bca7e1392c532057e2ab8c Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 29 Aug 2023 13:11:14 -0600 Subject: [PATCH 0040/1204] Trying a different untar command --- .github/workflows/test_branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 76bdcfd0587..0ac37747a65 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -414,7 +414,7 @@ jobs: fi fi cd $IPOPT_DIR - tar -xzf < $IPOPT_TAR + tar -xz < $IPOPT_TAR echo "" echo "$IPOPT_DIR" ls -l $IPOPT_DIR From 0e1c8c750981f76828d5cb3283b1468c9f439541 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 29 Aug 2023 13:16:41 -0600 Subject: [PATCH 0041/1204] Turning on examples test --- pyomo/contrib/appsi/examples/tests/test_examples.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/appsi/examples/tests/test_examples.py b/pyomo/contrib/appsi/examples/tests/test_examples.py index db6e2910b77..2ea089a8cc6 100644 --- a/pyomo/contrib/appsi/examples/tests/test_examples.py +++ b/pyomo/contrib/appsi/examples/tests/test_examples.py @@ -5,14 +5,14 @@ from pyomo.contrib import appsi -@unittest.skip('Currently turning off cplex support') +@unittest.skipUnless(cmodel_available, 'appsi extensions are not available') class TestExamples(unittest.TestCase): def test_getting_started(self): try: import numpy as np except: raise unittest.SkipTest('numpy is not available') - opt = appsi.solvers.Cplex() + opt = appsi.solvers.Ipopt() if not opt.available(): - raise unittest.SkipTest('cplex is not available') + raise unittest.SkipTest('ipopt is not available') getting_started.main(plot=False, n_points=10) From d2b91264275b5611d7b577dc91d2b6c38e5b7db1 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 29 Aug 2023 14:37:13 -0600 Subject: [PATCH 0042/1204] Change test_examples skipping --- pyomo/contrib/appsi/examples/tests/test_examples.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/appsi/examples/tests/test_examples.py b/pyomo/contrib/appsi/examples/tests/test_examples.py index 2ea089a8cc6..7c577366c41 100644 --- a/pyomo/contrib/appsi/examples/tests/test_examples.py +++ b/pyomo/contrib/appsi/examples/tests/test_examples.py @@ -1,17 +1,16 @@ from pyomo.contrib.appsi.examples import getting_started from pyomo.common import unittest -import pyomo.environ as pe +from pyomo.common.dependencies import attempt_import from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.contrib import appsi +numpy, numpy_available = attempt_import('numpy') + @unittest.skipUnless(cmodel_available, 'appsi extensions are not available') +@unittest.skipUnless(numpy_available, 'numpy is not available') class TestExamples(unittest.TestCase): def test_getting_started(self): - try: - import numpy as np - except: - raise unittest.SkipTest('numpy is not available') opt = appsi.solvers.Ipopt() if not opt.available(): raise unittest.SkipTest('ipopt is not available') From d0cb12542307dbe0dae2320853c1054a552e9dd1 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 29 Aug 2023 16:38:28 -0600 Subject: [PATCH 0043/1204] SAVE POINT: starting to move items out of appsi --- pyomo/contrib/appsi/base.py | 31 +- pyomo/solver/__init__.py | 14 + pyomo/solver/base.py | 1240 +++++++++++++++++++++++++++ pyomo/solver/config.py | 270 ++++++ pyomo/solver/solution.py | 256 ++++++ pyomo/solver/tests/test_base.py | 0 pyomo/solver/tests/test_config.py | 0 pyomo/solver/tests/test_solution.py | 0 pyomo/solver/tests/test_util.py | 0 pyomo/solver/util.py | 23 + 10 files changed, 1830 insertions(+), 4 deletions(-) create mode 100644 pyomo/solver/__init__.py create mode 100644 pyomo/solver/base.py create mode 100644 pyomo/solver/config.py create mode 100644 pyomo/solver/solution.py create mode 100644 pyomo/solver/tests/test_base.py create mode 100644 pyomo/solver/tests/test_config.py create mode 100644 pyomo/solver/tests/test_solution.py create mode 100644 pyomo/solver/tests/test_util.py create mode 100644 pyomo/solver/util.py diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index 5ce5421ee86..aa17489c4d5 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -165,7 +165,7 @@ class InterfaceConfig(ConfigDict): ---------- time_limit: float - sent to solver Time limit for the solver - tee: bool - wrapper + tee: bool If True, then the solver log goes to stdout load_solution: bool - wrapper If False, then the values of the primal variables will not be @@ -720,7 +720,7 @@ def __init__( # End game: we are not supporting a `has_Xcapability` interface (CHECK BOOK). -class Solver(abc.ABC): +class SolverBase(abc.ABC): class Availability(enum.IntEnum): NotFound = 0 BadVersion = -1 @@ -747,7 +747,7 @@ def __str__(self): @abc.abstractmethod def solve( - self, model: _BlockData, timer: HierarchicalTimer = None, + self, model: _BlockData, timer: HierarchicalTimer = None, **kwargs ) -> Results: """ Solve a Pyomo model. @@ -827,8 +827,31 @@ def is_persistent(self): """ return False +# In a non-persistent interface, when the solver dies, it'll return +# everthing it is going to return. And when you parse, you'll parse everything, +# whether or not you needed it. -class PersistentSolver(Solver): +# In a persistent interface, if all I really care about is to keep going +# until the objective gets better. I may not need to parse the dual or state +# vars. If I only need the objective, why waste time bringing that extra +# cruft back? Why not just return what you ask for when you ask for it? + +# All the `gets_` is to be able to retrieve from the solver. Because the +# persistent interface is still holding onto the solver's definition, +# it saves time. Also helps avoid assuming that you are loading a model. + +# There is an argument whether or not the get methods could be called load. + +# For non-persistent, there are also questions about how we load everything. +# We tend to just load everything because it might disappear otherwise. +# In the file interface, we tend to parse everything, and the option is to turn +# it all off. We still parse everything... + +# IDEAL SITUATION -- +# load_solutions = True -> straight into model; otherwise, into results object + + +class PersistentSolver(SolverBase): def is_persistent(self): return True diff --git a/pyomo/solver/__init__.py b/pyomo/solver/__init__.py new file mode 100644 index 00000000000..64c6452d06d --- /dev/null +++ b/pyomo/solver/__init__.py @@ -0,0 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 . import util +from . import base +from . import solution diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py new file mode 100644 index 00000000000..b6d9e1592cb --- /dev/null +++ b/pyomo/solver/base.py @@ -0,0 +1,1240 @@ +# ___________________________________________________________________________ +# +# 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 abc +import enum +from typing import ( + Sequence, + Dict, + Optional, + Mapping, + NoReturn, + List, + Tuple, +) +from pyomo.core.base.constraint import _GeneralConstraintData, Constraint +from pyomo.core.base.sos import _SOSConstraintData, SOSConstraint +from pyomo.core.base.var import _GeneralVarData, Var +from pyomo.core.base.param import _ParamData, Param +from pyomo.core.base.block import _BlockData +from pyomo.core.base.objective import _GeneralObjectiveData +from pyomo.common.collections import ComponentMap +from .utils.get_objective import get_objective +from .utils.collect_vars_and_named_exprs import collect_vars_and_named_exprs +from pyomo.common.timing import HierarchicalTimer +from pyomo.common.errors import ApplicationError +from pyomo.opt.base import SolverFactory as LegacySolverFactory +from pyomo.common.factory import Factory +import os +from pyomo.opt.results.results_ import SolverResults as LegacySolverResults +from pyomo.opt.results.solution import ( + Solution as LegacySolution, + SolutionStatus as LegacySolutionStatus, +) +from pyomo.opt.results.solver import ( + TerminationCondition as LegacyTerminationCondition, + SolverStatus as LegacySolverStatus, +) +from pyomo.core.kernel.objective import minimize +from pyomo.core.base import SymbolMap +from .cmodel import cmodel, cmodel_available +from pyomo.core.staleflag import StaleFlagManager +from pyomo.core.expr.numvalue import NumericConstant +from pyomo.solver import ( + SolutionLoader, + SolutionLoaderBase, + UpdateConfig +) + + +class TerminationCondition(enum.Enum): + """ + An enumeration for checking the termination condition of solvers + """ + + """unknown serves as both a default value, and it is used when no other enum member makes sense""" + unknown = 42 + + """The solver exited because the convergence criteria were satisfied""" + convergenceCriteriaSatisfied = 0 + + """The solver exited due to a time limit""" + maxTimeLimit = 1 + + """The solver exited due to an iteration limit""" + iterationLimit = 2 + + """The solver exited due to an objective limit""" + objectiveLimit = 3 + + """The solver exited due to a minimum step length""" + minStepLength = 4 + + """The solver exited because the problem is unbounded""" + unbounded = 5 + + """The solver exited because the problem is proven infeasible""" + provenInfeasible = 6 + + """The solver exited because the problem was found to be locally infeasible""" + locallyInfeasible = 7 + + """The solver exited because the problem is either infeasible or unbounded""" + infeasibleOrUnbounded = 8 + + """The solver exited due to an error""" + error = 9 + + """The solver exited because it was interrupted""" + interrupted = 10 + + """The solver exited due to licensing problems""" + licensingProblems = 11 + + +class SolutionStatus(enum.IntEnum): + """ + An enumeration for interpreting the result of a termination. This describes the designated + status by the solver to be loaded back into the model. + + For now, we are choosing to use IntEnum such that return values are numerically + assigned in increasing order. + """ + + """No (single) solution found; possible that a population of solutions was returned""" + noSolution = 0 + + """Solution point does not satisfy some domains and/or constraints""" + infeasible = 10 + + """Feasible solution identified""" + feasible = 20 + + """Optimal solution identified""" + optimal = 30 + + +class Results: + """ + Attributes + ---------- + termination_condition: TerminationCondition + The reason the solver exited. This is a member of the + TerminationCondition enum. + best_feasible_objective: float + If a feasible solution was found, this is the objective value of + the best solution found. If no feasible solution was found, this is + None. + best_objective_bound: float + The best objective bound found. For minimization problems, this is + the lower bound. For maximization problems, this is the upper bound. + For solvers that do not provide an objective bound, this should be -inf + (minimization) or inf (maximization) + + Here is an example workflow: + + >>> import pyomo.environ as pe + >>> from pyomo.contrib import appsi + >>> m = pe.ConcreteModel() + >>> m.x = pe.Var() + >>> m.obj = pe.Objective(expr=m.x**2) + >>> opt = appsi.solvers.Ipopt() + >>> opt.config.load_solution = False + >>> results = opt.solve(m) #doctest:+SKIP + >>> if results.termination_condition == appsi.base.TerminationCondition.convergenceCriteriaSatisfied: #doctest:+SKIP + ... print('optimal solution found: ', results.best_feasible_objective) #doctest:+SKIP + ... results.solution_loader.load_vars() #doctest:+SKIP + ... print('the optimal value of x is ', m.x.value) #doctest:+SKIP + ... elif results.best_feasible_objective is not None: #doctest:+SKIP + ... print('sub-optimal but feasible solution found: ', results.best_feasible_objective) #doctest:+SKIP + ... results.solution_loader.load_vars(vars_to_load=[m.x]) #doctest:+SKIP + ... print('The value of x in the feasible solution is ', m.x.value) #doctest:+SKIP + ... elif results.termination_condition in {appsi.base.TerminationCondition.iterationLimit, appsi.base.TerminationCondition.maxTimeLimit}: #doctest:+SKIP + ... print('No feasible solution was found. The best lower bound found was ', results.best_objective_bound) #doctest:+SKIP + ... else: #doctest:+SKIP + ... print('The following termination condition was encountered: ', results.termination_condition) #doctest:+SKIP + """ + + def __init__(self): + self.solution_loader: SolutionLoaderBase = SolutionLoader( + None, None, None, None + ) + self.termination_condition: TerminationCondition = TerminationCondition.unknown + self.best_feasible_objective: Optional[float] = None + self.best_objective_bound: Optional[float] = None + + def __str__(self): + s = '' + s += 'termination_condition: ' + str(self.termination_condition) + '\n' + s += 'best_feasible_objective: ' + str(self.best_feasible_objective) + '\n' + s += 'best_objective_bound: ' + str(self.best_objective_bound) + return s + + +class SolverBase(abc.ABC): + class Availability(enum.IntEnum): + NotFound = 0 + BadVersion = -1 + BadLicense = -2 + FullLicense = 1 + LimitedLicense = 2 + NeedsCompiledExtension = -3 + + def __bool__(self): + return self._value_ > 0 + + def __format__(self, format_spec): + # We want general formatting of this Enum to return the + # formatted string value and not the int (which is the + # default implementation from IntEnum) + return format(self.name, format_spec) + + def __str__(self): + # Note: Python 3.11 changed the core enums so that the + # "mixin" type for standard enums overrides the behavior + # specified in __format__. We will override str() here to + # preserve the previous behavior + return self.name + + @abc.abstractmethod + def solve( + self, model: _BlockData, timer: HierarchicalTimer = None, **kwargs + ) -> Results: + """ + Solve a Pyomo model. + + Parameters + ---------- + model: _BlockData + The Pyomo model to be solved + timer: HierarchicalTimer + An option timer for reporting timing + **kwargs + Additional keyword arguments (including solver_options - passthrough options; delivered directly to the solver (with no validation)) + + Returns + ------- + results: Results + A results object + """ + pass + + @abc.abstractmethod + def available(self): + """Test if the solver is available on this system. + + Nominally, this will return True if the solver interface is + valid and can be used to solve problems and False if it cannot. + + Note that for licensed solvers there are a number of "levels" of + available: depending on the license, the solver may be available + with limitations on problem size or runtime (e.g., 'demo' + vs. 'community' vs. 'full'). In these cases, the solver may + return a subclass of enum.IntEnum, with members that resolve to + True if the solver is available (possibly with limitations). + The Enum may also have multiple members that all resolve to + False indicating the reason why the interface is not available + (not found, bad license, unsupported version, etc). + + Returns + ------- + available: Solver.Availability + An enum that indicates "how available" the solver is. + Note that the enum can be cast to bool, which will + be True if the solver is runable at all and False + otherwise. + """ + pass + + @abc.abstractmethod + def version(self) -> Tuple: + """ + Returns + ------- + version: tuple + A tuple representing the version + """ + + @property + @abc.abstractmethod + def config(self): + """ + An object for configuring solve options. + + Returns + ------- + InterfaceConfig + An object for configuring pyomo solve options such as the time limit. + These options are mostly independent of the solver. + """ + pass + + def is_persistent(self): + """ + Returns + ------- + is_persistent: bool + True if the solver is a persistent solver. + """ + return False + + +class PersistentSolver(SolverBase): + def is_persistent(self): + return True + + def load_vars( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> NoReturn: + """ + Load the solution of the primal variables into the value attribute of the variables. + + Parameters + ---------- + vars_to_load: list + A list of the variables whose solution should be loaded. If vars_to_load is None, then the solution + to all primal variables will be loaded. + """ + for v, val in self.get_primals(vars_to_load=vars_to_load).items(): + v.set_value(val, skip_validation=True) + StaleFlagManager.mark_all_as_stale(delayed=True) + + @abc.abstractmethod + def get_primals( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> Mapping[_GeneralVarData, float]: + pass + + def get_duals( + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None + ) -> Dict[_GeneralConstraintData, float]: + """ + Declare sign convention in docstring here. + + Parameters + ---------- + cons_to_load: list + A list of the constraints whose duals should be loaded. If cons_to_load is None, then the duals for all + constraints will be loaded. + + Returns + ------- + duals: dict + Maps constraints to dual values + """ + raise NotImplementedError( + '{0} does not support the get_duals method'.format(type(self)) + ) + + def get_slacks( + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None + ) -> Dict[_GeneralConstraintData, float]: + """ + Parameters + ---------- + cons_to_load: list + A list of the constraints whose slacks should be loaded. If cons_to_load is None, then the slacks for all + constraints will be loaded. + + Returns + ------- + slacks: dict + Maps constraints to slack values + """ + raise NotImplementedError( + '{0} does not support the get_slacks method'.format(type(self)) + ) + + def get_reduced_costs( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> Mapping[_GeneralVarData, float]: + """ + Parameters + ---------- + vars_to_load: list + A list of the variables whose reduced cost should be loaded. If vars_to_load is None, then all reduced costs + will be loaded. + + Returns + ------- + reduced_costs: ComponentMap + Maps variable to reduced cost + """ + raise NotImplementedError( + '{0} does not support the get_reduced_costs method'.format(type(self)) + ) + + @property + @abc.abstractmethod + def update_config(self) -> UpdateConfig: + pass + + @abc.abstractmethod + def set_instance(self, model): + pass + + @abc.abstractmethod + def add_variables(self, variables: List[_GeneralVarData]): + pass + + @abc.abstractmethod + def add_params(self, params: List[_ParamData]): + pass + + @abc.abstractmethod + def add_constraints(self, cons: List[_GeneralConstraintData]): + pass + + @abc.abstractmethod + def add_block(self, block: _BlockData): + pass + + @abc.abstractmethod + def remove_variables(self, variables: List[_GeneralVarData]): + pass + + @abc.abstractmethod + def remove_params(self, params: List[_ParamData]): + pass + + @abc.abstractmethod + def remove_constraints(self, cons: List[_GeneralConstraintData]): + pass + + @abc.abstractmethod + def remove_block(self, block: _BlockData): + pass + + @abc.abstractmethod + def set_objective(self, obj: _GeneralObjectiveData): + pass + + @abc.abstractmethod + def update_variables(self, variables: List[_GeneralVarData]): + pass + + @abc.abstractmethod + def update_params(self): + pass + + + +""" +What can change in a pyomo model? +- variables added or removed +- constraints added or removed +- objective changed +- objective expr changed +- params added or removed +- variable modified + - lb + - ub + - fixed or unfixed + - domain + - value +- constraint modified + - lower + - upper + - body + - active or not +- named expressions modified + - expr +- param modified + - value + +Ideas: +- Consider explicitly handling deactivated constraints; favor deactivation over removal + and activation over addition + +Notes: +- variable bounds cannot be updated with mutable params; you must call update_variables +""" + + +class PersistentBase(abc.ABC): + def __init__(self, only_child_vars=False): + self._model = None + self._active_constraints = {} # maps constraint to (lower, body, upper) + self._vars = {} # maps var id to (var, lb, ub, fixed, domain, value) + self._params = {} # maps param id to param + self._objective = None + self._objective_expr = None + self._objective_sense = None + self._named_expressions = ( + {} + ) # maps constraint to list of tuples (named_expr, named_expr.expr) + self._external_functions = ComponentMap() + self._obj_named_expressions = [] + self._update_config = UpdateConfig() + self._referenced_variables = ( + {} + ) # var_id: [dict[constraints, None], dict[sos constraints, None], None or objective] + self._vars_referenced_by_con = {} + self._vars_referenced_by_obj = [] + self._expr_types = None + self.use_extensions = False + self._only_child_vars = only_child_vars + + @property + def update_config(self): + return self._update_config + + @update_config.setter + def update_config(self, val: UpdateConfig): + self._update_config = val + + def set_instance(self, model): + saved_update_config = self.update_config + self.__init__() + self.update_config = saved_update_config + self._model = model + if self.use_extensions and cmodel_available: + self._expr_types = cmodel.PyomoExprTypes() + self.add_block(model) + if self._objective is None: + self.set_objective(None) + + @abc.abstractmethod + def _add_variables(self, variables: List[_GeneralVarData]): + pass + + def add_variables(self, variables: List[_GeneralVarData]): + for v in variables: + if id(v) in self._referenced_variables: + raise ValueError( + 'variable {name} has already been added'.format(name=v.name) + ) + self._referenced_variables[id(v)] = [{}, {}, None] + self._vars[id(v)] = ( + v, + v._lb, + v._ub, + v.fixed, + v.domain.get_interval(), + v.value, + ) + self._add_variables(variables) + + @abc.abstractmethod + def _add_params(self, params: List[_ParamData]): + pass + + def add_params(self, params: List[_ParamData]): + for p in params: + self._params[id(p)] = p + self._add_params(params) + + @abc.abstractmethod + def _add_constraints(self, cons: List[_GeneralConstraintData]): + pass + + def _check_for_new_vars(self, variables: List[_GeneralVarData]): + new_vars = {} + for v in variables: + v_id = id(v) + if v_id not in self._referenced_variables: + new_vars[v_id] = v + self.add_variables(list(new_vars.values())) + + def _check_to_remove_vars(self, variables: List[_GeneralVarData]): + vars_to_remove = {} + for v in variables: + v_id = id(v) + ref_cons, ref_sos, ref_obj = self._referenced_variables[v_id] + if len(ref_cons) == 0 and len(ref_sos) == 0 and ref_obj is None: + vars_to_remove[v_id] = v + self.remove_variables(list(vars_to_remove.values())) + + def add_constraints(self, cons: List[_GeneralConstraintData]): + all_fixed_vars = {} + for con in cons: + if con in self._named_expressions: + raise ValueError( + 'constraint {name} has already been added'.format(name=con.name) + ) + self._active_constraints[con] = (con.lower, con.body, con.upper) + if self.use_extensions and cmodel_available: + tmp = cmodel.prep_for_repn(con.body, self._expr_types) + else: + tmp = collect_vars_and_named_exprs(con.body) + named_exprs, variables, fixed_vars, external_functions = tmp + if not self._only_child_vars: + self._check_for_new_vars(variables) + self._named_expressions[con] = [(e, e.expr) for e in named_exprs] + if len(external_functions) > 0: + self._external_functions[con] = external_functions + self._vars_referenced_by_con[con] = variables + for v in variables: + self._referenced_variables[id(v)][0][con] = None + if not self.update_config.treat_fixed_vars_as_params: + for v in fixed_vars: + v.unfix() + all_fixed_vars[id(v)] = v + self._add_constraints(cons) + for v in all_fixed_vars.values(): + v.fix() + + @abc.abstractmethod + def _add_sos_constraints(self, cons: List[_SOSConstraintData]): + pass + + def add_sos_constraints(self, cons: List[_SOSConstraintData]): + for con in cons: + if con in self._vars_referenced_by_con: + raise ValueError( + 'constraint {name} has already been added'.format(name=con.name) + ) + self._active_constraints[con] = tuple() + variables = con.get_variables() + if not self._only_child_vars: + self._check_for_new_vars(variables) + self._named_expressions[con] = [] + self._vars_referenced_by_con[con] = variables + for v in variables: + self._referenced_variables[id(v)][1][con] = None + self._add_sos_constraints(cons) + + @abc.abstractmethod + def _set_objective(self, obj: _GeneralObjectiveData): + pass + + def set_objective(self, obj: _GeneralObjectiveData): + if self._objective is not None: + for v in self._vars_referenced_by_obj: + self._referenced_variables[id(v)][2] = None + if not self._only_child_vars: + self._check_to_remove_vars(self._vars_referenced_by_obj) + self._external_functions.pop(self._objective, None) + if obj is not None: + self._objective = obj + self._objective_expr = obj.expr + self._objective_sense = obj.sense + if self.use_extensions and cmodel_available: + tmp = cmodel.prep_for_repn(obj.expr, self._expr_types) + else: + tmp = collect_vars_and_named_exprs(obj.expr) + named_exprs, variables, fixed_vars, external_functions = tmp + if not self._only_child_vars: + self._check_for_new_vars(variables) + self._obj_named_expressions = [(i, i.expr) for i in named_exprs] + if len(external_functions) > 0: + self._external_functions[obj] = external_functions + self._vars_referenced_by_obj = variables + for v in variables: + self._referenced_variables[id(v)][2] = obj + if not self.update_config.treat_fixed_vars_as_params: + for v in fixed_vars: + v.unfix() + self._set_objective(obj) + for v in fixed_vars: + v.fix() + else: + self._vars_referenced_by_obj = [] + self._objective = None + self._objective_expr = None + self._objective_sense = None + self._obj_named_expressions = [] + self._set_objective(obj) + + def add_block(self, block): + param_dict = {} + for p in block.component_objects(Param, descend_into=True): + if p.mutable: + for _p in p.values(): + param_dict[id(_p)] = _p + self.add_params(list(param_dict.values())) + if self._only_child_vars: + self.add_variables( + list( + dict( + (id(var), var) + for var in block.component_data_objects(Var, descend_into=True) + ).values() + ) + ) + self.add_constraints( + list(block.component_data_objects(Constraint, descend_into=True, active=True)) + ) + self.add_sos_constraints( + list(block.component_data_objects(SOSConstraint, descend_into=True, active=True)) + ) + obj = get_objective(block) + if obj is not None: + self.set_objective(obj) + + @abc.abstractmethod + def _remove_constraints(self, cons: List[_GeneralConstraintData]): + pass + + def remove_constraints(self, cons: List[_GeneralConstraintData]): + self._remove_constraints(cons) + for con in cons: + if con not in self._named_expressions: + raise ValueError( + 'cannot remove constraint {name} - it was not added'.format( + name=con.name + ) + ) + for v in self._vars_referenced_by_con[con]: + self._referenced_variables[id(v)][0].pop(con) + if not self._only_child_vars: + self._check_to_remove_vars(self._vars_referenced_by_con[con]) + del self._active_constraints[con] + del self._named_expressions[con] + self._external_functions.pop(con, None) + del self._vars_referenced_by_con[con] + + @abc.abstractmethod + def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): + pass + + def remove_sos_constraints(self, cons: List[_SOSConstraintData]): + self._remove_sos_constraints(cons) + for con in cons: + if con not in self._vars_referenced_by_con: + raise ValueError( + 'cannot remove constraint {name} - it was not added'.format( + name=con.name + ) + ) + for v in self._vars_referenced_by_con[con]: + self._referenced_variables[id(v)][1].pop(con) + self._check_to_remove_vars(self._vars_referenced_by_con[con]) + del self._active_constraints[con] + del self._named_expressions[con] + del self._vars_referenced_by_con[con] + + @abc.abstractmethod + def _remove_variables(self, variables: List[_GeneralVarData]): + pass + + def remove_variables(self, variables: List[_GeneralVarData]): + self._remove_variables(variables) + for v in variables: + v_id = id(v) + if v_id not in self._referenced_variables: + raise ValueError( + 'cannot remove variable {name} - it has not been added'.format( + name=v.name + ) + ) + cons_using, sos_using, obj_using = self._referenced_variables[v_id] + if cons_using or sos_using or (obj_using is not None): + raise ValueError( + 'cannot remove variable {name} - it is still being used by constraints or the objective'.format( + name=v.name + ) + ) + del self._referenced_variables[v_id] + del self._vars[v_id] + + @abc.abstractmethod + def _remove_params(self, params: List[_ParamData]): + pass + + def remove_params(self, params: List[_ParamData]): + self._remove_params(params) + for p in params: + del self._params[id(p)] + + def remove_block(self, block): + self.remove_constraints( + list(block.component_data_objects(ctype=Constraint, descend_into=True, active=True)) + ) + self.remove_sos_constraints( + list(block.component_data_objects(ctype=SOSConstraint, descend_into=True, active=True)) + ) + if self._only_child_vars: + self.remove_variables( + list( + dict( + (id(var), var) + for var in block.component_data_objects( + ctype=Var, descend_into=True + ) + ).values() + ) + ) + self.remove_params( + list( + dict( + (id(p), p) + for p in block.component_data_objects( + ctype=Param, descend_into=True + ) + ).values() + ) + ) + + @abc.abstractmethod + def _update_variables(self, variables: List[_GeneralVarData]): + pass + + def update_variables(self, variables: List[_GeneralVarData]): + for v in variables: + self._vars[id(v)] = ( + v, + v._lb, + v._ub, + v.fixed, + v.domain.get_interval(), + v.value, + ) + self._update_variables(variables) + + @abc.abstractmethod + def update_params(self): + pass + + def update(self, timer: HierarchicalTimer = None): + if timer is None: + timer = HierarchicalTimer() + config = self.update_config + new_vars = [] + old_vars = [] + new_params = [] + old_params = [] + new_cons = [] + old_cons = [] + old_sos = [] + new_sos = [] + current_vars_dict = {} + current_cons_dict = {} + current_sos_dict = {} + timer.start('vars') + if self._only_child_vars and ( + config.check_for_new_or_removed_vars or config.update_vars + ): + current_vars_dict = { + id(v): v + for v in self._model.component_data_objects(Var, descend_into=True) + } + for v_id, v in current_vars_dict.items(): + if v_id not in self._vars: + new_vars.append(v) + for v_id, v_tuple in self._vars.items(): + if v_id not in current_vars_dict: + old_vars.append(v_tuple[0]) + elif config.update_vars: + start_vars = {v_id: v_tuple[0] for v_id, v_tuple in self._vars.items()} + timer.stop('vars') + timer.start('params') + if config.check_for_new_or_removed_params: + current_params_dict = {} + for p in self._model.component_objects(Param, descend_into=True): + if p.mutable: + for _p in p.values(): + current_params_dict[id(_p)] = _p + for p_id, p in current_params_dict.items(): + if p_id not in self._params: + new_params.append(p) + for p_id, p in self._params.items(): + if p_id not in current_params_dict: + old_params.append(p) + timer.stop('params') + timer.start('cons') + if config.check_for_new_or_removed_constraints or config.update_constraints: + current_cons_dict = { + c: None + for c in self._model.component_data_objects( + Constraint, descend_into=True, active=True + ) + } + current_sos_dict = { + c: None + for c in self._model.component_data_objects( + SOSConstraint, descend_into=True, active=True + ) + } + for c in current_cons_dict.keys(): + if c not in self._vars_referenced_by_con: + new_cons.append(c) + for c in current_sos_dict.keys(): + if c not in self._vars_referenced_by_con: + new_sos.append(c) + for c in self._vars_referenced_by_con.keys(): + if c not in current_cons_dict and c not in current_sos_dict: + if (c.ctype is Constraint) or ( + c.ctype is None and isinstance(c, _GeneralConstraintData) + ): + old_cons.append(c) + else: + assert (c.ctype is SOSConstraint) or ( + c.ctype is None and isinstance(c, _SOSConstraintData) + ) + old_sos.append(c) + self.remove_constraints(old_cons) + self.remove_sos_constraints(old_sos) + timer.stop('cons') + timer.start('params') + self.remove_params(old_params) + + # sticking this between removal and addition + # is important so that we don't do unnecessary work + if config.update_params: + self.update_params() + + self.add_params(new_params) + timer.stop('params') + timer.start('vars') + self.add_variables(new_vars) + timer.stop('vars') + timer.start('cons') + self.add_constraints(new_cons) + self.add_sos_constraints(new_sos) + new_cons_set = set(new_cons) + new_sos_set = set(new_sos) + new_vars_set = set(id(v) for v in new_vars) + cons_to_remove_and_add = {} + need_to_set_objective = False + if config.update_constraints: + cons_to_update = [] + sos_to_update = [] + for c in current_cons_dict.keys(): + if c not in new_cons_set: + cons_to_update.append(c) + for c in current_sos_dict.keys(): + if c not in new_sos_set: + sos_to_update.append(c) + for c in cons_to_update: + lower, body, upper = self._active_constraints[c] + new_lower, new_body, new_upper = c.lower, c.body, c.upper + if new_body is not body: + cons_to_remove_and_add[c] = None + continue + if new_lower is not lower: + if ( + type(new_lower) is NumericConstant + and type(lower) is NumericConstant + and new_lower.value == lower.value + ): + pass + else: + cons_to_remove_and_add[c] = None + continue + if new_upper is not upper: + if ( + type(new_upper) is NumericConstant + and type(upper) is NumericConstant + and new_upper.value == upper.value + ): + pass + else: + cons_to_remove_and_add[c] = None + continue + self.remove_sos_constraints(sos_to_update) + self.add_sos_constraints(sos_to_update) + timer.stop('cons') + timer.start('vars') + if self._only_child_vars and config.update_vars: + vars_to_check = [] + for v_id, v in current_vars_dict.items(): + if v_id not in new_vars_set: + vars_to_check.append(v) + elif config.update_vars: + end_vars = {v_id: v_tuple[0] for v_id, v_tuple in self._vars.items()} + vars_to_check = [v for v_id, v in end_vars.items() if v_id in start_vars] + if config.update_vars: + vars_to_update = [] + for v in vars_to_check: + _v, lb, ub, fixed, domain_interval, value = self._vars[id(v)] + if lb is not v._lb: + vars_to_update.append(v) + elif ub is not v._ub: + vars_to_update.append(v) + elif (fixed is not v.fixed) or (fixed and (value != v.value)): + vars_to_update.append(v) + if self.update_config.treat_fixed_vars_as_params: + for c in self._referenced_variables[id(v)][0]: + cons_to_remove_and_add[c] = None + if self._referenced_variables[id(v)][2] is not None: + need_to_set_objective = True + elif domain_interval != v.domain.get_interval(): + vars_to_update.append(v) + self.update_variables(vars_to_update) + timer.stop('vars') + timer.start('cons') + cons_to_remove_and_add = list(cons_to_remove_and_add.keys()) + self.remove_constraints(cons_to_remove_and_add) + self.add_constraints(cons_to_remove_and_add) + timer.stop('cons') + timer.start('named expressions') + if config.update_named_expressions: + cons_to_update = [] + for c, expr_list in self._named_expressions.items(): + if c in new_cons_set: + continue + for named_expr, old_expr in expr_list: + if named_expr.expr is not old_expr: + cons_to_update.append(c) + break + self.remove_constraints(cons_to_update) + self.add_constraints(cons_to_update) + for named_expr, old_expr in self._obj_named_expressions: + if named_expr.expr is not old_expr: + need_to_set_objective = True + break + timer.stop('named expressions') + timer.start('objective') + if self.update_config.check_for_new_objective: + pyomo_obj = get_objective(self._model) + if pyomo_obj is not self._objective: + need_to_set_objective = True + else: + pyomo_obj = self._objective + if self.update_config.update_objective: + if pyomo_obj is not None and pyomo_obj.expr is not self._objective_expr: + need_to_set_objective = True + elif pyomo_obj is not None and pyomo_obj.sense is not self._objective_sense: + # we can definitely do something faster here than resetting the whole objective + need_to_set_objective = True + if need_to_set_objective: + self.set_objective(pyomo_obj) + timer.stop('objective') + + # this has to be done after the objective and constraints in case the + # old objective/constraints use old variables + timer.start('vars') + self.remove_variables(old_vars) + timer.stop('vars') + + +# Everything below here preserves backwards compatibility + +legacy_termination_condition_map = { + TerminationCondition.unknown: LegacyTerminationCondition.unknown, + TerminationCondition.maxTimeLimit: LegacyTerminationCondition.maxTimeLimit, + TerminationCondition.iterationLimit: LegacyTerminationCondition.maxIterations, + TerminationCondition.objectiveLimit: LegacyTerminationCondition.minFunctionValue, + TerminationCondition.minStepLength: LegacyTerminationCondition.minStepLength, + TerminationCondition.convergenceCriteriaSatisfied: LegacyTerminationCondition.optimal, + TerminationCondition.unbounded: LegacyTerminationCondition.unbounded, + TerminationCondition.provenInfeasible: LegacyTerminationCondition.infeasible, + TerminationCondition.locallyInfeasible: LegacyTerminationCondition.infeasible, + TerminationCondition.infeasibleOrUnbounded: LegacyTerminationCondition.infeasibleOrUnbounded, + TerminationCondition.error: LegacyTerminationCondition.error, + TerminationCondition.interrupted: LegacyTerminationCondition.resourceInterrupt, + TerminationCondition.licensingProblems: LegacyTerminationCondition.licensingProblems, +} + + +legacy_solver_status_map = { + TerminationCondition.unknown: LegacySolverStatus.unknown, + TerminationCondition.maxTimeLimit: LegacySolverStatus.aborted, + TerminationCondition.iterationLimit: LegacySolverStatus.aborted, + TerminationCondition.objectiveLimit: LegacySolverStatus.aborted, + TerminationCondition.minStepLength: LegacySolverStatus.error, + TerminationCondition.convergenceCriteriaSatisfied: LegacySolverStatus.ok, + TerminationCondition.unbounded: LegacySolverStatus.error, + TerminationCondition.provenInfeasible: LegacySolverStatus.error, + TerminationCondition.locallyInfeasible: LegacySolverStatus.error, + TerminationCondition.infeasibleOrUnbounded: LegacySolverStatus.error, + TerminationCondition.error: LegacySolverStatus.error, + TerminationCondition.interrupted: LegacySolverStatus.aborted, + TerminationCondition.licensingProblems: LegacySolverStatus.error, +} + + +legacy_solution_status_map = { + TerminationCondition.unknown: LegacySolutionStatus.unknown, + TerminationCondition.maxTimeLimit: LegacySolutionStatus.stoppedByLimit, + TerminationCondition.iterationLimit: LegacySolutionStatus.stoppedByLimit, + TerminationCondition.objectiveLimit: LegacySolutionStatus.stoppedByLimit, + TerminationCondition.minStepLength: LegacySolutionStatus.error, + TerminationCondition.convergenceCriteriaSatisfied: LegacySolutionStatus.optimal, + TerminationCondition.unbounded: LegacySolutionStatus.unbounded, + TerminationCondition.provenInfeasible: LegacySolutionStatus.infeasible, + TerminationCondition.locallyInfeasible: LegacySolutionStatus.infeasible, + TerminationCondition.infeasibleOrUnbounded: LegacySolutionStatus.unsure, + TerminationCondition.error: LegacySolutionStatus.error, + TerminationCondition.interrupted: LegacySolutionStatus.error, + TerminationCondition.licensingProblems: LegacySolutionStatus.error, +} + + +class LegacySolverInterface: + def solve( + self, + model: _BlockData, + tee: bool = False, + load_solutions: bool = True, + logfile: Optional[str] = None, + solnfile: Optional[str] = None, + timelimit: Optional[float] = None, + report_timing: bool = False, + solver_io: Optional[str] = None, + suffixes: Optional[Sequence] = None, + options: Optional[Dict] = None, + keepfiles: bool = False, + symbolic_solver_labels: bool = False, + ): + original_config = self.config + self.config = self.config() + self.config.tee = tee + self.config.load_solution = load_solutions + self.config.symbolic_solver_labels = symbolic_solver_labels + self.config.time_limit = timelimit + self.config.report_timing = report_timing + if solver_io is not None: + raise NotImplementedError('Still working on this') + if suffixes is not None: + raise NotImplementedError('Still working on this') + if logfile is not None: + raise NotImplementedError('Still working on this') + if 'keepfiles' in self.config: + self.config.keepfiles = keepfiles + if solnfile is not None: + if 'filename' in self.config: + filename = os.path.splitext(solnfile)[0] + self.config.filename = filename + original_options = self.options + if options is not None: + self.options = options + + results: Results = super().solve(model) + + legacy_results = LegacySolverResults() + legacy_soln = LegacySolution() + legacy_results.solver.status = legacy_solver_status_map[ + results.termination_condition + ] + legacy_results.solver.termination_condition = legacy_termination_condition_map[ + results.termination_condition + ] + legacy_soln.status = legacy_solution_status_map[results.termination_condition] + legacy_results.solver.termination_message = str(results.termination_condition) + + obj = get_objective(model) + legacy_results.problem.sense = obj.sense + + if obj.sense == minimize: + legacy_results.problem.lower_bound = results.best_objective_bound + legacy_results.problem.upper_bound = results.best_feasible_objective + else: + legacy_results.problem.upper_bound = results.best_objective_bound + legacy_results.problem.lower_bound = results.best_feasible_objective + if ( + results.best_feasible_objective is not None + and results.best_objective_bound is not None + ): + legacy_soln.gap = abs( + results.best_feasible_objective - results.best_objective_bound + ) + else: + legacy_soln.gap = None + + symbol_map = SymbolMap() + symbol_map.byObject = dict(self.symbol_map.byObject) + symbol_map.bySymbol = dict(self.symbol_map.bySymbol) + symbol_map.aliases = dict(self.symbol_map.aliases) + symbol_map.default_labeler = self.symbol_map.default_labeler + model.solutions.add_symbol_map(symbol_map) + legacy_results._smap_id = id(symbol_map) + + delete_legacy_soln = True + if load_solutions: + if hasattr(model, 'dual') and model.dual.import_enabled(): + for c, val in results.solution_loader.get_duals().items(): + model.dual[c] = val + if hasattr(model, 'slack') and model.slack.import_enabled(): + for c, val in results.solution_loader.get_slacks().items(): + model.slack[c] = val + if hasattr(model, 'rc') and model.rc.import_enabled(): + for v, val in results.solution_loader.get_reduced_costs().items(): + model.rc[v] = val + elif results.best_feasible_objective is not None: + delete_legacy_soln = False + for v, val in results.solution_loader.get_primals().items(): + legacy_soln.variable[symbol_map.getSymbol(v)] = {'Value': val} + if hasattr(model, 'dual') and model.dual.import_enabled(): + for c, val in results.solution_loader.get_duals().items(): + legacy_soln.constraint[symbol_map.getSymbol(c)] = {'Dual': val} + if hasattr(model, 'slack') and model.slack.import_enabled(): + for c, val in results.solution_loader.get_slacks().items(): + symbol = symbol_map.getSymbol(c) + if symbol in legacy_soln.constraint: + legacy_soln.constraint[symbol]['Slack'] = val + if hasattr(model, 'rc') and model.rc.import_enabled(): + for v, val in results.solution_loader.get_reduced_costs().items(): + legacy_soln.variable['Rc'] = val + + legacy_results.solution.insert(legacy_soln) + if delete_legacy_soln: + legacy_results.solution.delete(0) + + self.config = original_config + self.options = original_options + + return legacy_results + + def available(self, exception_flag=True): + ans = super().available() + if exception_flag and not ans: + raise ApplicationError(f'Solver {self.__class__} is not available ({ans}).') + return bool(ans) + + def license_is_valid(self) -> bool: + """Test if the solver license is valid on this system. + + Note that this method is included for compatibility with the + legacy SolverFactory interface. Unlicensed or open source + solvers will return True by definition. Licensed solvers will + return True if a valid license is found. + + Returns + ------- + available: bool + True if the solver license is valid. Otherwise, False. + + """ + return bool(self.available()) + + @property + def options(self): + for solver_name in ['gurobi', 'ipopt', 'cplex', 'cbc', 'highs']: + if hasattr(self, solver_name + '_options'): + return getattr(self, solver_name + '_options') + raise NotImplementedError('Could not find the correct options') + + @options.setter + def options(self, val): + found = False + for solver_name in ['gurobi', 'ipopt', 'cplex', 'cbc', 'highs']: + if hasattr(self, solver_name + '_options'): + setattr(self, solver_name + '_options', val) + found = True + if not found: + raise NotImplementedError('Could not find the correct options') + + def __enter__(self): + return self + + def __exit__(self, t, v, traceback): + pass + + +class SolverFactoryClass(Factory): + def register(self, name, doc=None): + def decorator(cls): + self._cls[name] = cls + self._doc[name] = doc + + class LegacySolver(LegacySolverInterface, cls): + pass + + LegacySolverFactory.register(name, doc)(LegacySolver) + + return cls + + return decorator + + +SolverFactory = SolverFactoryClass() diff --git a/pyomo/solver/config.py b/pyomo/solver/config.py new file mode 100644 index 00000000000..ab9c30a0549 --- /dev/null +++ b/pyomo/solver/config.py @@ -0,0 +1,270 @@ +# ___________________________________________________________________________ +# +# 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 typing import Optional +from pyomo.common.config import ConfigDict, ConfigValue, NonNegativeFloat, NonNegativeInt + + +class InterfaceConfig(ConfigDict): + """ + Attributes + ---------- + time_limit: float - sent to solver + Time limit for the solver + tee: bool + If True, then the solver log goes to stdout + load_solution: bool - wrapper + If False, then the values of the primal variables will not be + loaded into the model + symbolic_solver_labels: bool - sent to solver + If True, the names given to the solver will reflect the names + of the pyomo components. Cannot be changed after set_instance + is called. + report_timing: bool - wrapper + If True, then some timing information will be printed at the + end of the solve. + threads: integer - sent to solver + Number of threads to be used by a solver. + """ + + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + super().__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, + ) + + self.declare('tee', ConfigValue(domain=bool)) + self.declare('load_solution', ConfigValue(domain=bool)) + self.declare('symbolic_solver_labels', ConfigValue(domain=bool)) + self.declare('report_timing', ConfigValue(domain=bool)) + self.declare('threads', ConfigValue(domain=NonNegativeInt, default=None)) + + self.time_limit: Optional[float] = self.declare( + 'time_limit', ConfigValue(domain=NonNegativeFloat) + ) + self.tee: bool = False + self.load_solution: bool = True + self.symbolic_solver_labels: bool = False + self.report_timing: bool = False + + +class MIPInterfaceConfig(InterfaceConfig): + """ + Attributes + ---------- + mip_gap: float + Solver will terminate if the mip gap is less than mip_gap + relax_integrality: bool + If True, all integer variables will be relaxed to continuous + variables before solving + """ + + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + super().__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, + ) + + self.declare('mip_gap', ConfigValue(domain=NonNegativeFloat)) + self.declare('relax_integrality', ConfigValue(domain=bool)) + + self.mip_gap: Optional[float] = None + self.relax_integrality: bool = False + + +class UpdateConfig(ConfigDict): + """ + This is necessary for persistent solvers. + + Attributes + ---------- + check_for_new_or_removed_constraints: bool + check_for_new_or_removed_vars: bool + check_for_new_or_removed_params: bool + update_constraints: bool + update_vars: bool + update_params: bool + update_named_expressions: bool + """ + + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + if doc is None: + doc = 'Configuration options to detect changes in model between solves' + super().__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, + ) + + self.declare( + 'check_for_new_or_removed_constraints', + ConfigValue( + domain=bool, + default=True, + doc=""" + If False, new/old constraints will not be automatically detected on subsequent + solves. Use False only when manually updating the solver with opt.add_constraints() + and opt.remove_constraints() or when you are certain constraints are not being + added to/removed from the model.""", + ), + ) + self.declare( + 'check_for_new_or_removed_vars', + ConfigValue( + domain=bool, + default=True, + doc=""" + If False, new/old variables will not be automatically detected on subsequent + solves. Use False only when manually updating the solver with opt.add_variables() and + opt.remove_variables() or when you are certain variables are not being added to / + removed from the model.""", + ), + ) + self.declare( + 'check_for_new_or_removed_params', + ConfigValue( + domain=bool, + default=True, + doc=""" + If False, new/old parameters will not be automatically detected on subsequent + solves. Use False only when manually updating the solver with opt.add_params() and + opt.remove_params() or when you are certain parameters are not being added to / + removed from the model.""", + ), + ) + self.declare( + 'check_for_new_objective', + ConfigValue( + domain=bool, + default=True, + doc=""" + If False, new/old objectives will not be automatically detected on subsequent + solves. Use False only when manually updating the solver with opt.set_objective() or + when you are certain objectives are not being added to / removed from the model.""", + ), + ) + self.declare( + 'update_constraints', + ConfigValue( + domain=bool, + default=True, + doc=""" + If False, changes to existing constraints will not be automatically detected on + subsequent solves. This includes changes to the lower, body, and upper attributes of + constraints. Use False only when manually updating the solver with + opt.remove_constraints() and opt.add_constraints() or when you are certain constraints + are not being modified.""", + ), + ) + self.declare( + 'update_vars', + ConfigValue( + domain=bool, + default=True, + doc=""" + If False, changes to existing variables will not be automatically detected on + subsequent solves. This includes changes to the lb, ub, domain, and fixed + attributes of variables. Use False only when manually updating the solver with + opt.update_variables() or when you are certain variables are not being modified.""", + ), + ) + self.declare( + 'update_params', + ConfigValue( + domain=bool, + default=True, + doc=""" + If False, changes to parameter values will not be automatically detected on + subsequent solves. Use False only when manually updating the solver with + opt.update_params() or when you are certain parameters are not being modified.""", + ), + ) + self.declare( + 'update_named_expressions', + ConfigValue( + domain=bool, + default=True, + doc=""" + If False, changes to Expressions will not be automatically detected on + subsequent solves. Use False only when manually updating the solver with + opt.remove_constraints() and opt.add_constraints() or when you are certain + Expressions are not being modified.""", + ), + ) + self.declare( + 'update_objective', + ConfigValue( + domain=bool, + default=True, + doc=""" + If False, changes to objectives will not be automatically detected on + subsequent solves. This includes the expr and sense attributes of objectives. Use + False only when manually updating the solver with opt.set_objective() or when you are + certain objectives are not being modified.""", + ), + ) + self.declare( + 'treat_fixed_vars_as_params', + ConfigValue( + domain=bool, + default=True, + doc=""" + This is an advanced option that should only be used in special circumstances. + With the default setting of True, fixed variables will be treated like parameters. + This means that z == x*y will be linear if x or y is fixed and the constraint + can be written to an LP file. If the value of the fixed variable gets changed, we have + to completely reprocess all constraints using that variable. If + treat_fixed_vars_as_params is False, then constraints will be processed as if fixed + variables are not fixed, and the solver will be told the variable is fixed. This means + z == x*y could not be written to an LP file even if x and/or y is fixed. However, + updating the values of fixed variables is much faster this way.""", + ), + ) + + self.check_for_new_or_removed_constraints: bool = True + self.check_for_new_or_removed_vars: bool = True + self.check_for_new_or_removed_params: bool = True + self.check_for_new_objective: bool = True + self.update_constraints: bool = True + self.update_vars: bool = True + self.update_params: bool = True + self.update_named_expressions: bool = True + self.update_objective: bool = True + self.treat_fixed_vars_as_params: bool = True diff --git a/pyomo/solver/solution.py b/pyomo/solver/solution.py new file mode 100644 index 00000000000..2d422736f2c --- /dev/null +++ b/pyomo/solver/solution.py @@ -0,0 +1,256 @@ +# ___________________________________________________________________________ +# +# 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 abc +from typing import ( + Sequence, + Dict, + Optional, + Mapping, + MutableMapping, + NoReturn, +) + +from pyomo.core.base.constraint import _GeneralConstraintData +from pyomo.core.base.var import _GeneralVarData +from pyomo.common.collections import ComponentMap +from pyomo.core.staleflag import StaleFlagManager + + +class SolutionLoaderBase(abc.ABC): + def load_vars( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> NoReturn: + """ + Load the solution of the primal variables into the value attribute of the variables. + + Parameters + ---------- + vars_to_load: list + A list of the variables whose solution should be loaded. If vars_to_load is None, then the solution + to all primal variables will be loaded. + """ + for v, val in self.get_primals(vars_to_load=vars_to_load).items(): + v.set_value(val, skip_validation=True) + StaleFlagManager.mark_all_as_stale(delayed=True) + + @abc.abstractmethod + def get_primals( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> Mapping[_GeneralVarData, float]: + """ + Returns a ComponentMap mapping variable to var value. + + Parameters + ---------- + vars_to_load: list + A list of the variables whose solution value should be retrieved. If vars_to_load is None, + then the values for all variables will be retrieved. + + Returns + ------- + primals: ComponentMap + Maps variables to solution values + """ + pass + + def get_duals( + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None + ) -> Dict[_GeneralConstraintData, float]: + """ + Returns a dictionary mapping constraint to dual value. + + Parameters + ---------- + cons_to_load: list + A list of the constraints whose duals should be retrieved. If cons_to_load is None, then the duals for all + constraints will be retrieved. + + Returns + ------- + duals: dict + Maps constraints to dual values + """ + raise NotImplementedError(f'{type(self)} does not support the get_duals method') + + def get_slacks( + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None + ) -> Dict[_GeneralConstraintData, float]: + """ + Returns a dictionary mapping constraint to slack. + + Parameters + ---------- + cons_to_load: list + A list of the constraints whose duals should be loaded. If cons_to_load is None, then the duals for all + constraints will be loaded. + + Returns + ------- + slacks: dict + Maps constraints to slacks + """ + raise NotImplementedError( + f'{type(self)} does not support the get_slacks method' + ) + + def get_reduced_costs( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> Mapping[_GeneralVarData, float]: + """ + Returns a ComponentMap mapping variable to reduced cost. + + Parameters + ---------- + vars_to_load: list + A list of the variables whose reduced cost should be retrieved. If vars_to_load is None, then the + reduced costs for all variables will be loaded. + + Returns + ------- + reduced_costs: ComponentMap + Maps variables to reduced costs + """ + raise NotImplementedError( + f'{type(self)} does not support the get_reduced_costs method' + ) + + +class SolutionLoader(SolutionLoaderBase): + def __init__( + self, + primals: Optional[MutableMapping], + duals: Optional[MutableMapping], + slacks: Optional[MutableMapping], + reduced_costs: Optional[MutableMapping], + ): + """ + Parameters + ---------- + primals: dict + maps id(Var) to (var, value) + duals: dict + maps Constraint to dual value + slacks: dict + maps Constraint to slack value + reduced_costs: dict + maps id(Var) to (var, reduced_cost) + """ + self._primals = primals + self._duals = duals + self._slacks = slacks + self._reduced_costs = reduced_costs + + def get_primals( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> Mapping[_GeneralVarData, float]: + if self._primals is None: + raise RuntimeError( + 'Solution loader does not currently have a valid solution. Please ' + 'check the termination condition.' + ) + if vars_to_load is None: + return ComponentMap(self._primals.values()) + else: + primals = ComponentMap() + for v in vars_to_load: + primals[v] = self._primals[id(v)][1] + return primals + + def get_duals( + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None + ) -> Dict[_GeneralConstraintData, float]: + if self._duals is None: + raise RuntimeError( + 'Solution loader does not currently have valid duals. Please ' + 'check the termination condition and ensure the solver returns duals ' + 'for the given problem type.' + ) + if cons_to_load is None: + duals = dict(self._duals) + else: + duals = {} + for c in cons_to_load: + duals[c] = self._duals[c] + return duals + + def get_slacks( + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None + ) -> Dict[_GeneralConstraintData, float]: + if self._slacks is None: + raise RuntimeError( + 'Solution loader does not currently have valid slacks. Please ' + 'check the termination condition and ensure the solver returns slacks ' + 'for the given problem type.' + ) + if cons_to_load is None: + slacks = dict(self._slacks) + else: + slacks = {} + for c in cons_to_load: + slacks[c] = self._slacks[c] + return slacks + + def get_reduced_costs( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> Mapping[_GeneralVarData, float]: + if self._reduced_costs is None: + raise RuntimeError( + 'Solution loader does not currently have valid reduced costs. Please ' + 'check the termination condition and ensure the solver returns reduced ' + 'costs for the given problem type.' + ) + if vars_to_load is None: + rc = ComponentMap(self._reduced_costs.values()) + else: + rc = ComponentMap() + for v in vars_to_load: + rc[v] = self._reduced_costs[id(v)][1] + return rc + + +class PersistentSolutionLoader(SolutionLoaderBase): + def __init__(self, solver): + self._solver = solver + self._valid = True + + def _assert_solution_still_valid(self): + if not self._valid: + raise RuntimeError('The results in the solver are no longer valid.') + + def get_primals(self, vars_to_load=None): + self._assert_solution_still_valid() + return self._solver.get_primals(vars_to_load=vars_to_load) + + def get_duals( + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None + ) -> Dict[_GeneralConstraintData, float]: + self._assert_solution_still_valid() + return self._solver.get_duals(cons_to_load=cons_to_load) + + def get_slacks( + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None + ) -> Dict[_GeneralConstraintData, float]: + self._assert_solution_still_valid() + return self._solver.get_slacks(cons_to_load=cons_to_load) + + def get_reduced_costs( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> Mapping[_GeneralVarData, float]: + self._assert_solution_still_valid() + return self._solver.get_reduced_costs(vars_to_load=vars_to_load) + + def invalidate(self): + self._valid = False + + + + diff --git a/pyomo/solver/tests/test_base.py b/pyomo/solver/tests/test_base.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pyomo/solver/tests/test_config.py b/pyomo/solver/tests/test_config.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pyomo/solver/tests/test_solution.py b/pyomo/solver/tests/test_solution.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pyomo/solver/tests/test_util.py b/pyomo/solver/tests/test_util.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pyomo/solver/util.py b/pyomo/solver/util.py new file mode 100644 index 00000000000..8c768061678 --- /dev/null +++ b/pyomo/solver/util.py @@ -0,0 +1,23 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + +class SolverUtils: + pass + +class SubprocessSolverUtils: + pass + +class DirectSolverUtils: + pass + +class PersistentSolverUtils: + pass + From 434ff9d984ee5446c150fbcf8af82df6716b4f30 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 08:59:25 -0600 Subject: [PATCH 0044/1204] Separate base class from APPSI --- pyomo/contrib/appsi/__init__.py | 1 - pyomo/contrib/appsi/base.py | 1836 ----------------- .../contrib/appsi/examples/getting_started.py | 3 +- pyomo/contrib/appsi/fbbt.py | 5 +- pyomo/contrib/appsi/solvers/cbc.py | 23 +- pyomo/contrib/appsi/solvers/cplex.py | 24 +- pyomo/contrib/appsi/solvers/gurobi.py | 25 +- pyomo/contrib/appsi/solvers/highs.py | 14 +- pyomo/contrib/appsi/solvers/ipopt.py | 24 +- .../solvers/tests/test_gurobi_persistent.py | 5 +- .../solvers/tests/test_persistent_solvers.py | 2 +- pyomo/contrib/appsi/tests/test_base.py | 91 - pyomo/contrib/appsi/writers/lp_writer.py | 6 +- pyomo/contrib/appsi/writers/nl_writer.py | 12 +- pyomo/environ/__init__.py | 1 + pyomo/solver/__init__.py | 4 +- pyomo/solver/base.py | 70 +- pyomo/solver/tests/test_base.py | 91 + 18 files changed, 159 insertions(+), 2078 deletions(-) delete mode 100644 pyomo/contrib/appsi/base.py delete mode 100644 pyomo/contrib/appsi/tests/test_base.py diff --git a/pyomo/contrib/appsi/__init__.py b/pyomo/contrib/appsi/__init__.py index df3ba212448..0134a96f363 100644 --- a/pyomo/contrib/appsi/__init__.py +++ b/pyomo/contrib/appsi/__init__.py @@ -1,4 +1,3 @@ -from . import base from . import solvers from . import writers from . import fbbt diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py deleted file mode 100644 index aa17489c4d5..00000000000 --- a/pyomo/contrib/appsi/base.py +++ /dev/null @@ -1,1836 +0,0 @@ -import abc -import enum -from typing import ( - Sequence, - Dict, - Optional, - Mapping, - NoReturn, - List, - Tuple, - MutableMapping, -) -from pyomo.core.base.constraint import _GeneralConstraintData, Constraint -from pyomo.core.base.sos import _SOSConstraintData, SOSConstraint -from pyomo.core.base.var import _GeneralVarData, Var -from pyomo.core.base.param import _ParamData, Param -from pyomo.core.base.block import _BlockData -from pyomo.core.base.objective import _GeneralObjectiveData -from pyomo.common.collections import ComponentMap -from .utils.get_objective import get_objective -from .utils.collect_vars_and_named_exprs import collect_vars_and_named_exprs -from pyomo.common.timing import HierarchicalTimer -from pyomo.common.config import ConfigDict, ConfigValue, NonNegativeFloat, NonNegativeInt -from pyomo.common.errors import ApplicationError -from pyomo.opt.base import SolverFactory as LegacySolverFactory -from pyomo.common.factory import Factory -import os -from pyomo.opt.results.results_ import SolverResults as LegacySolverResults -from pyomo.opt.results.solution import ( - Solution as LegacySolution, - SolutionStatus as LegacySolutionStatus, -) -from pyomo.opt.results.solver import ( - TerminationCondition as LegacyTerminationCondition, - SolverStatus as LegacySolverStatus, -) -from pyomo.core.kernel.objective import minimize -from pyomo.core.base import SymbolMap -from .cmodel import cmodel, cmodel_available -from pyomo.core.staleflag import StaleFlagManager -from pyomo.core.expr.numvalue import NumericConstant - - -# # TerminationCondition - -# We currently have: Termination condition, solver status, and solution status. -# LL: Michael was trying to go for simplicity. All three conditions can be confusing. -# It is likely okay to have termination condition and solver status. - -# ## Open Questions (User Perspective) -# - Did I (the user) get a reasonable answer back from the solver? -# - If the answer is not reasonable, can I figure out why? - -# ## Our Goal -# Solvers normally tell you what they did and hope the users understand that. -# *We* want to try to return that information but also _help_ the user. - -# ## Proposals -# PROPOSAL 1: PyomoCondition and SolverCondition -# - SolverCondition: what the solver said -# - PyomoCondition: what we interpret that the solver said - -# PROPOSAL 2: TerminationCondition contains... -# - Some finite list of conditions -# - Two flags: why did it exit (TerminationCondition)? how do we interpret the result (SolutionStatus)? -# - Replace `optimal` with `normal` or `ok` for the termination flag; `optimal` can be used differently for the solver flag -# - You can use something else like `local`, `global`, `feasible` for solution status - - -class TerminationCondition(enum.Enum): - """ - An enumeration for checking the termination condition of solvers - """ - - """unknown serves as both a default value, and it is used when no other enum member makes sense""" - unknown = 42 - - """The solver exited because the convergence criteria were satisfied""" - convergenceCriteriaSatisfied = 0 - - """The solver exited due to a time limit""" - maxTimeLimit = 1 - - """The solver exited due to an iteration limit""" - iterationLimit = 2 - - """The solver exited due to an objective limit""" - objectiveLimit = 3 - - """The solver exited due to a minimum step length""" - minStepLength = 4 - - """The solver exited because the problem is unbounded""" - unbounded = 5 - - """The solver exited because the problem is proven infeasible""" - provenInfeasible = 6 - - """The solver exited because the problem was found to be locally infeasible""" - locallyInfeasible = 7 - - """The solver exited because the problem is either infeasible or unbounded""" - infeasibleOrUnbounded = 8 - - """The solver exited due to an error""" - error = 9 - - """The solver exited because it was interrupted""" - interrupted = 10 - - """The solver exited due to licensing problems""" - licensingProblems = 11 - - -class SolutionStatus(enum.IntEnum): - """ - An enumeration for interpreting the result of a termination. This describes the designated - status by the solver to be loaded back into the model. - - For now, we are choosing to use IntEnum such that return values are numerically - assigned in increasing order. - """ - - """No (single) solution found; possible that a population of solutions was returned""" - noSolution = 0 - - """Solution point does not satisfy some domains and/or constraints""" - infeasible = 10 - - """Feasible solution identified""" - feasible = 20 - - """Optimal solution identified""" - optimal = 30 - - -# # InterfaceConfig - -# The idea here (currently / in theory) is that a call to solve will have a keyword argument `solver_config`: -# ``` -# solve(model, solver_config=...) -# config = self.config(solver_config) -# ``` - -# We have several flavors of options: -# - Solver options -# - Standardized options -# - Wrapper options -# - Interface options -# - potentially... more? - -# ## The Options - -# There are three basic structures: flat, doubly-nested, separate dicts. -# We need to pick between these three structures (and stick with it). - -# **Flat: Clear interface; ambiguous about what goes where; better solve interface.** <- WINNER -# Doubly: More obscure interface; less ambiguity; better programmatic interface. -# SepDicts: Clear delineation; **kwargs becomes confusing (what maps to what?) (NOT HAPPENING) - - -class InterfaceConfig(ConfigDict): - """ - Attributes - ---------- - time_limit: float - sent to solver - Time limit for the solver - tee: bool - If True, then the solver log goes to stdout - load_solution: bool - wrapper - If False, then the values of the primal variables will not be - loaded into the model - symbolic_solver_labels: bool - sent to solver - If True, the names given to the solver will reflect the names - of the pyomo components. Cannot be changed after set_instance - is called. - report_timing: bool - wrapper - If True, then some timing information will be printed at the - end of the solve. - threads: integer - sent to solver - Number of threads to be used by a solver. - """ - - def __init__( - self, - description=None, - doc=None, - implicit=False, - implicit_domain=None, - visibility=0, - ): - super().__init__( - description=description, - doc=doc, - implicit=implicit, - implicit_domain=implicit_domain, - visibility=visibility, - ) - - self.declare('tee', ConfigValue(domain=bool)) - self.declare('load_solution', ConfigValue(domain=bool)) - self.declare('symbolic_solver_labels', ConfigValue(domain=bool)) - self.declare('report_timing', ConfigValue(domain=bool)) - self.declare('threads', ConfigValue(domain=NonNegativeInt, default=None)) - - self.time_limit: Optional[float] = self.declare( - 'time_limit', ConfigValue(domain=NonNegativeFloat) - ) - self.tee: bool = False - self.load_solution: bool = True - self.symbolic_solver_labels: bool = False - self.report_timing: bool = False - - -class MIPInterfaceConfig(InterfaceConfig): - """ - Attributes - ---------- - mip_gap: float - Solver will terminate if the mip gap is less than mip_gap - relax_integrality: bool - If True, all integer variables will be relaxed to continuous - variables before solving - """ - - def __init__( - self, - description=None, - doc=None, - implicit=False, - implicit_domain=None, - visibility=0, - ): - super().__init__( - description=description, - doc=doc, - implicit=implicit, - implicit_domain=implicit_domain, - visibility=visibility, - ) - - self.declare('mip_gap', ConfigValue(domain=NonNegativeFloat)) - self.declare('relax_integrality', ConfigValue(domain=bool)) - - self.mip_gap: Optional[float] = None - self.relax_integrality: bool = False - - -# # SolutionLoaderBase - -# This is an attempt to answer the issue of persistent/non-persistent solution -# loading. This is an attribute of the results object (not the solver). - -# You wouldn't ask the solver to load a solution into a model. You would -# ask the result to load the solution - into the model you solved. -# The results object points to relevant elements; elements do NOT point to -# the results object. - -# Per Michael: This may be a bit clunky; but it works. -# Per Siirola: We may want to rethink `load_vars` and `get_primals`. In particular, -# this is for efficiency - don't create a dictionary you don't need to. And what is -# the client use-case for `get_primals`? - - -class SolutionLoaderBase(abc.ABC): - def load_vars( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> NoReturn: - """ - Load the solution of the primal variables into the value attribute of the variables. - - Parameters - ---------- - vars_to_load: list - A list of the variables whose solution should be loaded. If vars_to_load is None, then the solution - to all primal variables will be loaded. - """ - for v, val in self.get_primals(vars_to_load=vars_to_load).items(): - v.set_value(val, skip_validation=True) - StaleFlagManager.mark_all_as_stale(delayed=True) - - @abc.abstractmethod - def get_primals( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: - """ - Returns a ComponentMap mapping variable to var value. - - Parameters - ---------- - vars_to_load: list - A list of the variables whose solution value should be retrieved. If vars_to_load is None, - then the values for all variables will be retrieved. - - Returns - ------- - primals: ComponentMap - Maps variables to solution values - """ - pass - - def get_duals( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: - """ - Returns a dictionary mapping constraint to dual value. - - Parameters - ---------- - cons_to_load: list - A list of the constraints whose duals should be retrieved. If cons_to_load is None, then the duals for all - constraints will be retrieved. - - Returns - ------- - duals: dict - Maps constraints to dual values - """ - raise NotImplementedError(f'{type(self)} does not support the get_duals method') - - def get_slacks( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: - """ - Returns a dictionary mapping constraint to slack. - - Parameters - ---------- - cons_to_load: list - A list of the constraints whose duals should be loaded. If cons_to_load is None, then the duals for all - constraints will be loaded. - - Returns - ------- - slacks: dict - Maps constraints to slacks - """ - raise NotImplementedError( - f'{type(self)} does not support the get_slacks method' - ) - - def get_reduced_costs( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: - """ - Returns a ComponentMap mapping variable to reduced cost. - - Parameters - ---------- - vars_to_load: list - A list of the variables whose reduced cost should be retrieved. If vars_to_load is None, then the - reduced costs for all variables will be loaded. - - Returns - ------- - reduced_costs: ComponentMap - Maps variables to reduced costs - """ - raise NotImplementedError( - f'{type(self)} does not support the get_reduced_costs method' - ) - - -class SolutionLoader(SolutionLoaderBase): - def __init__( - self, - primals: Optional[MutableMapping], - duals: Optional[MutableMapping], - slacks: Optional[MutableMapping], - reduced_costs: Optional[MutableMapping], - ): - """ - Parameters - ---------- - primals: dict - maps id(Var) to (var, value) - duals: dict - maps Constraint to dual value - slacks: dict - maps Constraint to slack value - reduced_costs: dict - maps id(Var) to (var, reduced_cost) - """ - self._primals = primals - self._duals = duals - self._slacks = slacks - self._reduced_costs = reduced_costs - - def get_primals( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: - if self._primals is None: - raise RuntimeError( - 'Solution loader does not currently have a valid solution. Please ' - 'check the termination condition.' - ) - if vars_to_load is None: - return ComponentMap(self._primals.values()) - else: - primals = ComponentMap() - for v in vars_to_load: - primals[v] = self._primals[id(v)][1] - return primals - - def get_duals( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: - if self._duals is None: - raise RuntimeError( - 'Solution loader does not currently have valid duals. Please ' - 'check the termination condition and ensure the solver returns duals ' - 'for the given problem type.' - ) - if cons_to_load is None: - duals = dict(self._duals) - else: - duals = {} - for c in cons_to_load: - duals[c] = self._duals[c] - return duals - - def get_slacks( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: - if self._slacks is None: - raise RuntimeError( - 'Solution loader does not currently have valid slacks. Please ' - 'check the termination condition and ensure the solver returns slacks ' - 'for the given problem type.' - ) - if cons_to_load is None: - slacks = dict(self._slacks) - else: - slacks = {} - for c in cons_to_load: - slacks[c] = self._slacks[c] - return slacks - - def get_reduced_costs( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: - if self._reduced_costs is None: - raise RuntimeError( - 'Solution loader does not currently have valid reduced costs. Please ' - 'check the termination condition and ensure the solver returns reduced ' - 'costs for the given problem type.' - ) - if vars_to_load is None: - rc = ComponentMap(self._reduced_costs.values()) - else: - rc = ComponentMap() - for v in vars_to_load: - rc[v] = self._reduced_costs[id(v)][1] - return rc - - -class Results: - """ - Attributes - ---------- - termination_condition: TerminationCondition - The reason the solver exited. This is a member of the - TerminationCondition enum. - best_feasible_objective: float - If a feasible solution was found, this is the objective value of - the best solution found. If no feasible solution was found, this is - None. - best_objective_bound: float - The best objective bound found. For minimization problems, this is - the lower bound. For maximization problems, this is the upper bound. - For solvers that do not provide an objective bound, this should be -inf - (minimization) or inf (maximization) - - Here is an example workflow: - - >>> import pyomo.environ as pe - >>> from pyomo.contrib import appsi - >>> m = pe.ConcreteModel() - >>> m.x = pe.Var() - >>> m.obj = pe.Objective(expr=m.x**2) - >>> opt = appsi.solvers.Ipopt() - >>> opt.config.load_solution = False - >>> results = opt.solve(m) #doctest:+SKIP - >>> if results.termination_condition == appsi.base.TerminationCondition.convergenceCriteriaSatisfied: #doctest:+SKIP - ... print('optimal solution found: ', results.best_feasible_objective) #doctest:+SKIP - ... results.solution_loader.load_vars() #doctest:+SKIP - ... print('the optimal value of x is ', m.x.value) #doctest:+SKIP - ... elif results.best_feasible_objective is not None: #doctest:+SKIP - ... print('sub-optimal but feasible solution found: ', results.best_feasible_objective) #doctest:+SKIP - ... results.solution_loader.load_vars(vars_to_load=[m.x]) #doctest:+SKIP - ... print('The value of x in the feasible solution is ', m.x.value) #doctest:+SKIP - ... elif results.termination_condition in {appsi.base.TerminationCondition.iterationLimit, appsi.base.TerminationCondition.maxTimeLimit}: #doctest:+SKIP - ... print('No feasible solution was found. The best lower bound found was ', results.best_objective_bound) #doctest:+SKIP - ... else: #doctest:+SKIP - ... print('The following termination condition was encountered: ', results.termination_condition) #doctest:+SKIP - """ - - def __init__(self): - self.solution_loader: SolutionLoaderBase = SolutionLoader( - None, None, None, None - ) - self.termination_condition: TerminationCondition = TerminationCondition.unknown - self.best_feasible_objective: Optional[float] = None - self.best_objective_bound: Optional[float] = None - - def __str__(self): - s = '' - s += 'termination_condition: ' + str(self.termination_condition) + '\n' - s += 'best_feasible_objective: ' + str(self.best_feasible_objective) + '\n' - s += 'best_objective_bound: ' + str(self.best_objective_bound) - return s - - -class UpdateConfig(ConfigDict): - """ - Attributes - ---------- - check_for_new_or_removed_constraints: bool - check_for_new_or_removed_vars: bool - check_for_new_or_removed_params: bool - update_constraints: bool - update_vars: bool - update_params: bool - update_named_expressions: bool - """ - - def __init__( - self, - description=None, - doc=None, - implicit=False, - implicit_domain=None, - visibility=0, - ): - if doc is None: - doc = 'Configuration options to detect changes in model between solves' - super().__init__( - description=description, - doc=doc, - implicit=implicit, - implicit_domain=implicit_domain, - visibility=visibility, - ) - - self.declare( - 'check_for_new_or_removed_constraints', - ConfigValue( - domain=bool, - default=True, - doc=""" - If False, new/old constraints will not be automatically detected on subsequent - solves. Use False only when manually updating the solver with opt.add_constraints() - and opt.remove_constraints() or when you are certain constraints are not being - added to/removed from the model.""", - ), - ) - self.declare( - 'check_for_new_or_removed_vars', - ConfigValue( - domain=bool, - default=True, - doc=""" - If False, new/old variables will not be automatically detected on subsequent - solves. Use False only when manually updating the solver with opt.add_variables() and - opt.remove_variables() or when you are certain variables are not being added to / - removed from the model.""", - ), - ) - self.declare( - 'check_for_new_or_removed_params', - ConfigValue( - domain=bool, - default=True, - doc=""" - If False, new/old parameters will not be automatically detected on subsequent - solves. Use False only when manually updating the solver with opt.add_params() and - opt.remove_params() or when you are certain parameters are not being added to / - removed from the model.""", - ), - ) - self.declare( - 'check_for_new_objective', - ConfigValue( - domain=bool, - default=True, - doc=""" - If False, new/old objectives will not be automatically detected on subsequent - solves. Use False only when manually updating the solver with opt.set_objective() or - when you are certain objectives are not being added to / removed from the model.""", - ), - ) - self.declare( - 'update_constraints', - ConfigValue( - domain=bool, - default=True, - doc=""" - If False, changes to existing constraints will not be automatically detected on - subsequent solves. This includes changes to the lower, body, and upper attributes of - constraints. Use False only when manually updating the solver with - opt.remove_constraints() and opt.add_constraints() or when you are certain constraints - are not being modified.""", - ), - ) - self.declare( - 'update_vars', - ConfigValue( - domain=bool, - default=True, - doc=""" - If False, changes to existing variables will not be automatically detected on - subsequent solves. This includes changes to the lb, ub, domain, and fixed - attributes of variables. Use False only when manually updating the solver with - opt.update_variables() or when you are certain variables are not being modified.""", - ), - ) - self.declare( - 'update_params', - ConfigValue( - domain=bool, - default=True, - doc=""" - If False, changes to parameter values will not be automatically detected on - subsequent solves. Use False only when manually updating the solver with - opt.update_params() or when you are certain parameters are not being modified.""", - ), - ) - self.declare( - 'update_named_expressions', - ConfigValue( - domain=bool, - default=True, - doc=""" - If False, changes to Expressions will not be automatically detected on - subsequent solves. Use False only when manually updating the solver with - opt.remove_constraints() and opt.add_constraints() or when you are certain - Expressions are not being modified.""", - ), - ) - self.declare( - 'update_objective', - ConfigValue( - domain=bool, - default=True, - doc=""" - If False, changes to objectives will not be automatically detected on - subsequent solves. This includes the expr and sense attributes of objectives. Use - False only when manually updating the solver with opt.set_objective() or when you are - certain objectives are not being modified.""", - ), - ) - self.declare( - 'treat_fixed_vars_as_params', - ConfigValue( - domain=bool, - default=True, - doc=""" - This is an advanced option that should only be used in special circumstances. - With the default setting of True, fixed variables will be treated like parameters. - This means that z == x*y will be linear if x or y is fixed and the constraint - can be written to an LP file. If the value of the fixed variable gets changed, we have - to completely reprocess all constraints using that variable. If - treat_fixed_vars_as_params is False, then constraints will be processed as if fixed - variables are not fixed, and the solver will be told the variable is fixed. This means - z == x*y could not be written to an LP file even if x and/or y is fixed. However, - updating the values of fixed variables is much faster this way.""", - ), - ) - - self.check_for_new_or_removed_constraints: bool = True - self.check_for_new_or_removed_vars: bool = True - self.check_for_new_or_removed_params: bool = True - self.check_for_new_objective: bool = True - self.update_constraints: bool = True - self.update_vars: bool = True - self.update_params: bool = True - self.update_named_expressions: bool = True - self.update_objective: bool = True - self.treat_fixed_vars_as_params: bool = True - - -# # Solver - -# ## Open Question: What does 'solve' look like? - -# We may want to use the 80/20 rule here - we support 80% of the cases; anything -# fancier than that is going to require "writing code." The 80% would be offerings -# that are supported as part of the `pyomo` script. - -# ## Configs - -# We will likely have two configs for `solve`: standardized config (processes `**kwargs`) -# and implicit ConfigDict with some specialized options. - -# These have to be separated because there is a set that need to be passed -# directly to the solver. The other is Pyomo options / our standardized options -# (a few of which might be passed directly to solver, e.g., time_limit). - -# ## Contained Methods - -# We do not like `symbol_map`; it's keyed towards file-based interfaces. That -# is the `lp` writer; the `nl` writer doesn't need that (and in fact, it's -# obnoxious). The new `nl` writer returns back more meaningful things to the `nl` -# interface. - -# If the writer needs a symbol map, it will return it. But it is _not_ a -# solver thing. So it does not need to continue to exist in the solver interface. - -# All other options are reasonable. - -# ## Other (maybe should be contained) Methods - -# There are other methods in other solvers such as `warmstart`, `sos`; do we -# want to continue to support and/or offer those features? - -# The solver interface is not responsible for telling the client what -# it can do, e.g., `supports_sos2`. This is actually a contract between -# the solver and its writer. - -# End game: we are not supporting a `has_Xcapability` interface (CHECK BOOK). - - -class SolverBase(abc.ABC): - class Availability(enum.IntEnum): - NotFound = 0 - BadVersion = -1 - BadLicense = -2 - FullLicense = 1 - LimitedLicense = 2 - NeedsCompiledExtension = -3 - - def __bool__(self): - return self._value_ > 0 - - def __format__(self, format_spec): - # We want general formatting of this Enum to return the - # formatted string value and not the int (which is the - # default implementation from IntEnum) - return format(self.name, format_spec) - - def __str__(self): - # Note: Python 3.11 changed the core enums so that the - # "mixin" type for standard enums overrides the behavior - # specified in __format__. We will override str() here to - # preserve the previous behavior - return self.name - - @abc.abstractmethod - def solve( - self, model: _BlockData, timer: HierarchicalTimer = None, **kwargs - ) -> Results: - """ - Solve a Pyomo model. - - Parameters - ---------- - model: _BlockData - The Pyomo model to be solved - timer: HierarchicalTimer - An option timer for reporting timing - **kwargs - Additional keyword arguments (including solver_options - passthrough options; delivered directly to the solver (with no validation)) - - Returns - ------- - results: Results - A results object - """ - pass - - @abc.abstractmethod - def available(self): - """Test if the solver is available on this system. - - Nominally, this will return True if the solver interface is - valid and can be used to solve problems and False if it cannot. - - Note that for licensed solvers there are a number of "levels" of - available: depending on the license, the solver may be available - with limitations on problem size or runtime (e.g., 'demo' - vs. 'community' vs. 'full'). In these cases, the solver may - return a subclass of enum.IntEnum, with members that resolve to - True if the solver is available (possibly with limitations). - The Enum may also have multiple members that all resolve to - False indicating the reason why the interface is not available - (not found, bad license, unsupported version, etc). - - Returns - ------- - available: Solver.Availability - An enum that indicates "how available" the solver is. - Note that the enum can be cast to bool, which will - be True if the solver is runable at all and False - otherwise. - """ - pass - - @abc.abstractmethod - def version(self) -> Tuple: - """ - Returns - ------- - version: tuple - A tuple representing the version - """ - - @property - @abc.abstractmethod - def config(self): - """ - An object for configuring solve options. - - Returns - ------- - InterfaceConfig - An object for configuring pyomo solve options such as the time limit. - These options are mostly independent of the solver. - """ - pass - - def is_persistent(self): - """ - Returns - ------- - is_persistent: bool - True if the solver is a persistent solver. - """ - return False - -# In a non-persistent interface, when the solver dies, it'll return -# everthing it is going to return. And when you parse, you'll parse everything, -# whether or not you needed it. - -# In a persistent interface, if all I really care about is to keep going -# until the objective gets better. I may not need to parse the dual or state -# vars. If I only need the objective, why waste time bringing that extra -# cruft back? Why not just return what you ask for when you ask for it? - -# All the `gets_` is to be able to retrieve from the solver. Because the -# persistent interface is still holding onto the solver's definition, -# it saves time. Also helps avoid assuming that you are loading a model. - -# There is an argument whether or not the get methods could be called load. - -# For non-persistent, there are also questions about how we load everything. -# We tend to just load everything because it might disappear otherwise. -# In the file interface, we tend to parse everything, and the option is to turn -# it all off. We still parse everything... - -# IDEAL SITUATION -- -# load_solutions = True -> straight into model; otherwise, into results object - - -class PersistentSolver(SolverBase): - def is_persistent(self): - return True - - def load_vars( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> NoReturn: - """ - Load the solution of the primal variables into the value attribute of the variables. - - Parameters - ---------- - vars_to_load: list - A list of the variables whose solution should be loaded. If vars_to_load is None, then the solution - to all primal variables will be loaded. - """ - for v, val in self.get_primals(vars_to_load=vars_to_load).items(): - v.set_value(val, skip_validation=True) - StaleFlagManager.mark_all_as_stale(delayed=True) - - @abc.abstractmethod - def get_primals( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: - pass - - def get_duals( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: - """ - Declare sign convention in docstring here. - - Parameters - ---------- - cons_to_load: list - A list of the constraints whose duals should be loaded. If cons_to_load is None, then the duals for all - constraints will be loaded. - - Returns - ------- - duals: dict - Maps constraints to dual values - """ - raise NotImplementedError( - '{0} does not support the get_duals method'.format(type(self)) - ) - - def get_slacks( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: - """ - Parameters - ---------- - cons_to_load: list - A list of the constraints whose slacks should be loaded. If cons_to_load is None, then the slacks for all - constraints will be loaded. - - Returns - ------- - slacks: dict - Maps constraints to slack values - """ - raise NotImplementedError( - '{0} does not support the get_slacks method'.format(type(self)) - ) - - def get_reduced_costs( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: - """ - Parameters - ---------- - vars_to_load: list - A list of the variables whose reduced cost should be loaded. If vars_to_load is None, then all reduced costs - will be loaded. - - Returns - ------- - reduced_costs: ComponentMap - Maps variable to reduced cost - """ - raise NotImplementedError( - '{0} does not support the get_reduced_costs method'.format(type(self)) - ) - - @property - @abc.abstractmethod - def update_config(self) -> UpdateConfig: - pass - - @abc.abstractmethod - def set_instance(self, model): - pass - - @abc.abstractmethod - def add_variables(self, variables: List[_GeneralVarData]): - pass - - @abc.abstractmethod - def add_params(self, params: List[_ParamData]): - pass - - @abc.abstractmethod - def add_constraints(self, cons: List[_GeneralConstraintData]): - pass - - @abc.abstractmethod - def add_block(self, block: _BlockData): - pass - - @abc.abstractmethod - def remove_variables(self, variables: List[_GeneralVarData]): - pass - - @abc.abstractmethod - def remove_params(self, params: List[_ParamData]): - pass - - @abc.abstractmethod - def remove_constraints(self, cons: List[_GeneralConstraintData]): - pass - - @abc.abstractmethod - def remove_block(self, block: _BlockData): - pass - - @abc.abstractmethod - def set_objective(self, obj: _GeneralObjectiveData): - pass - - @abc.abstractmethod - def update_variables(self, variables: List[_GeneralVarData]): - pass - - @abc.abstractmethod - def update_params(self): - pass - - -class PersistentSolutionLoader(SolutionLoaderBase): - def __init__(self, solver: PersistentSolver): - self._solver = solver - self._valid = True - - def _assert_solution_still_valid(self): - if not self._valid: - raise RuntimeError('The results in the solver are no longer valid.') - - def get_primals(self, vars_to_load=None): - self._assert_solution_still_valid() - return self._solver.get_primals(vars_to_load=vars_to_load) - - def get_duals( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: - self._assert_solution_still_valid() - return self._solver.get_duals(cons_to_load=cons_to_load) - - def get_slacks( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: - self._assert_solution_still_valid() - return self._solver.get_slacks(cons_to_load=cons_to_load) - - def get_reduced_costs( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: - self._assert_solution_still_valid() - return self._solver.get_reduced_costs(vars_to_load=vars_to_load) - - def invalidate(self): - self._valid = False - - -""" -What can change in a pyomo model? -- variables added or removed -- constraints added or removed -- objective changed -- objective expr changed -- params added or removed -- variable modified - - lb - - ub - - fixed or unfixed - - domain - - value -- constraint modified - - lower - - upper - - body - - active or not -- named expressions modified - - expr -- param modified - - value - -Ideas: -- Consider explicitly handling deactivated constraints; favor deactivation over removal - and activation over addition - -Notes: -- variable bounds cannot be updated with mutable params; you must call update_variables -""" - - -class PersistentBase(abc.ABC): - def __init__(self, only_child_vars=False): - self._model = None - self._active_constraints = {} # maps constraint to (lower, body, upper) - self._vars = {} # maps var id to (var, lb, ub, fixed, domain, value) - self._params = {} # maps param id to param - self._objective = None - self._objective_expr = None - self._objective_sense = None - self._named_expressions = ( - {} - ) # maps constraint to list of tuples (named_expr, named_expr.expr) - self._external_functions = ComponentMap() - self._obj_named_expressions = [] - self._update_config = UpdateConfig() - self._referenced_variables = ( - {} - ) # var_id: [dict[constraints, None], dict[sos constraints, None], None or objective] - self._vars_referenced_by_con = {} - self._vars_referenced_by_obj = [] - self._expr_types = None - self.use_extensions = False - self._only_child_vars = only_child_vars - - @property - def update_config(self): - return self._update_config - - @update_config.setter - def update_config(self, val: UpdateConfig): - self._update_config = val - - def set_instance(self, model): - saved_update_config = self.update_config - self.__init__() - self.update_config = saved_update_config - self._model = model - if self.use_extensions and cmodel_available: - self._expr_types = cmodel.PyomoExprTypes() - self.add_block(model) - if self._objective is None: - self.set_objective(None) - - @abc.abstractmethod - def _add_variables(self, variables: List[_GeneralVarData]): - pass - - def add_variables(self, variables: List[_GeneralVarData]): - for v in variables: - if id(v) in self._referenced_variables: - raise ValueError( - 'variable {name} has already been added'.format(name=v.name) - ) - self._referenced_variables[id(v)] = [{}, {}, None] - self._vars[id(v)] = ( - v, - v._lb, - v._ub, - v.fixed, - v.domain.get_interval(), - v.value, - ) - self._add_variables(variables) - - @abc.abstractmethod - def _add_params(self, params: List[_ParamData]): - pass - - def add_params(self, params: List[_ParamData]): - for p in params: - self._params[id(p)] = p - self._add_params(params) - - @abc.abstractmethod - def _add_constraints(self, cons: List[_GeneralConstraintData]): - pass - - def _check_for_new_vars(self, variables: List[_GeneralVarData]): - new_vars = {} - for v in variables: - v_id = id(v) - if v_id not in self._referenced_variables: - new_vars[v_id] = v - self.add_variables(list(new_vars.values())) - - def _check_to_remove_vars(self, variables: List[_GeneralVarData]): - vars_to_remove = {} - for v in variables: - v_id = id(v) - ref_cons, ref_sos, ref_obj = self._referenced_variables[v_id] - if len(ref_cons) == 0 and len(ref_sos) == 0 and ref_obj is None: - vars_to_remove[v_id] = v - self.remove_variables(list(vars_to_remove.values())) - - def add_constraints(self, cons: List[_GeneralConstraintData]): - all_fixed_vars = {} - for con in cons: - if con in self._named_expressions: - raise ValueError( - 'constraint {name} has already been added'.format(name=con.name) - ) - self._active_constraints[con] = (con.lower, con.body, con.upper) - if self.use_extensions and cmodel_available: - tmp = cmodel.prep_for_repn(con.body, self._expr_types) - else: - tmp = collect_vars_and_named_exprs(con.body) - named_exprs, variables, fixed_vars, external_functions = tmp - if not self._only_child_vars: - self._check_for_new_vars(variables) - self._named_expressions[con] = [(e, e.expr) for e in named_exprs] - if len(external_functions) > 0: - self._external_functions[con] = external_functions - self._vars_referenced_by_con[con] = variables - for v in variables: - self._referenced_variables[id(v)][0][con] = None - if not self.update_config.treat_fixed_vars_as_params: - for v in fixed_vars: - v.unfix() - all_fixed_vars[id(v)] = v - self._add_constraints(cons) - for v in all_fixed_vars.values(): - v.fix() - - @abc.abstractmethod - def _add_sos_constraints(self, cons: List[_SOSConstraintData]): - pass - - def add_sos_constraints(self, cons: List[_SOSConstraintData]): - for con in cons: - if con in self._vars_referenced_by_con: - raise ValueError( - 'constraint {name} has already been added'.format(name=con.name) - ) - self._active_constraints[con] = tuple() - variables = con.get_variables() - if not self._only_child_vars: - self._check_for_new_vars(variables) - self._named_expressions[con] = [] - self._vars_referenced_by_con[con] = variables - for v in variables: - self._referenced_variables[id(v)][1][con] = None - self._add_sos_constraints(cons) - - @abc.abstractmethod - def _set_objective(self, obj: _GeneralObjectiveData): - pass - - def set_objective(self, obj: _GeneralObjectiveData): - if self._objective is not None: - for v in self._vars_referenced_by_obj: - self._referenced_variables[id(v)][2] = None - if not self._only_child_vars: - self._check_to_remove_vars(self._vars_referenced_by_obj) - self._external_functions.pop(self._objective, None) - if obj is not None: - self._objective = obj - self._objective_expr = obj.expr - self._objective_sense = obj.sense - if self.use_extensions and cmodel_available: - tmp = cmodel.prep_for_repn(obj.expr, self._expr_types) - else: - tmp = collect_vars_and_named_exprs(obj.expr) - named_exprs, variables, fixed_vars, external_functions = tmp - if not self._only_child_vars: - self._check_for_new_vars(variables) - self._obj_named_expressions = [(i, i.expr) for i in named_exprs] - if len(external_functions) > 0: - self._external_functions[obj] = external_functions - self._vars_referenced_by_obj = variables - for v in variables: - self._referenced_variables[id(v)][2] = obj - if not self.update_config.treat_fixed_vars_as_params: - for v in fixed_vars: - v.unfix() - self._set_objective(obj) - for v in fixed_vars: - v.fix() - else: - self._vars_referenced_by_obj = [] - self._objective = None - self._objective_expr = None - self._objective_sense = None - self._obj_named_expressions = [] - self._set_objective(obj) - - def add_block(self, block): - param_dict = {} - for p in block.component_objects(Param, descend_into=True): - if p.mutable: - for _p in p.values(): - param_dict[id(_p)] = _p - self.add_params(list(param_dict.values())) - if self._only_child_vars: - self.add_variables( - list( - dict( - (id(var), var) - for var in block.component_data_objects(Var, descend_into=True) - ).values() - ) - ) - self.add_constraints( - list(block.component_data_objects(Constraint, descend_into=True, active=True)) - ) - self.add_sos_constraints( - list(block.component_data_objects(SOSConstraint, descend_into=True, active=True)) - ) - obj = get_objective(block) - if obj is not None: - self.set_objective(obj) - - @abc.abstractmethod - def _remove_constraints(self, cons: List[_GeneralConstraintData]): - pass - - def remove_constraints(self, cons: List[_GeneralConstraintData]): - self._remove_constraints(cons) - for con in cons: - if con not in self._named_expressions: - raise ValueError( - 'cannot remove constraint {name} - it was not added'.format( - name=con.name - ) - ) - for v in self._vars_referenced_by_con[con]: - self._referenced_variables[id(v)][0].pop(con) - if not self._only_child_vars: - self._check_to_remove_vars(self._vars_referenced_by_con[con]) - del self._active_constraints[con] - del self._named_expressions[con] - self._external_functions.pop(con, None) - del self._vars_referenced_by_con[con] - - @abc.abstractmethod - def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): - pass - - def remove_sos_constraints(self, cons: List[_SOSConstraintData]): - self._remove_sos_constraints(cons) - for con in cons: - if con not in self._vars_referenced_by_con: - raise ValueError( - 'cannot remove constraint {name} - it was not added'.format( - name=con.name - ) - ) - for v in self._vars_referenced_by_con[con]: - self._referenced_variables[id(v)][1].pop(con) - self._check_to_remove_vars(self._vars_referenced_by_con[con]) - del self._active_constraints[con] - del self._named_expressions[con] - del self._vars_referenced_by_con[con] - - @abc.abstractmethod - def _remove_variables(self, variables: List[_GeneralVarData]): - pass - - def remove_variables(self, variables: List[_GeneralVarData]): - self._remove_variables(variables) - for v in variables: - v_id = id(v) - if v_id not in self._referenced_variables: - raise ValueError( - 'cannot remove variable {name} - it has not been added'.format( - name=v.name - ) - ) - cons_using, sos_using, obj_using = self._referenced_variables[v_id] - if cons_using or sos_using or (obj_using is not None): - raise ValueError( - 'cannot remove variable {name} - it is still being used by constraints or the objective'.format( - name=v.name - ) - ) - del self._referenced_variables[v_id] - del self._vars[v_id] - - @abc.abstractmethod - def _remove_params(self, params: List[_ParamData]): - pass - - def remove_params(self, params: List[_ParamData]): - self._remove_params(params) - for p in params: - del self._params[id(p)] - - def remove_block(self, block): - self.remove_constraints( - list(block.component_data_objects(ctype=Constraint, descend_into=True, active=True)) - ) - self.remove_sos_constraints( - list(block.component_data_objects(ctype=SOSConstraint, descend_into=True, active=True)) - ) - if self._only_child_vars: - self.remove_variables( - list( - dict( - (id(var), var) - for var in block.component_data_objects( - ctype=Var, descend_into=True - ) - ).values() - ) - ) - self.remove_params( - list( - dict( - (id(p), p) - for p in block.component_data_objects( - ctype=Param, descend_into=True - ) - ).values() - ) - ) - - @abc.abstractmethod - def _update_variables(self, variables: List[_GeneralVarData]): - pass - - def update_variables(self, variables: List[_GeneralVarData]): - for v in variables: - self._vars[id(v)] = ( - v, - v._lb, - v._ub, - v.fixed, - v.domain.get_interval(), - v.value, - ) - self._update_variables(variables) - - @abc.abstractmethod - def update_params(self): - pass - - def update(self, timer: HierarchicalTimer = None): - if timer is None: - timer = HierarchicalTimer() - config = self.update_config - new_vars = [] - old_vars = [] - new_params = [] - old_params = [] - new_cons = [] - old_cons = [] - old_sos = [] - new_sos = [] - current_vars_dict = {} - current_cons_dict = {} - current_sos_dict = {} - timer.start('vars') - if self._only_child_vars and ( - config.check_for_new_or_removed_vars or config.update_vars - ): - current_vars_dict = { - id(v): v - for v in self._model.component_data_objects(Var, descend_into=True) - } - for v_id, v in current_vars_dict.items(): - if v_id not in self._vars: - new_vars.append(v) - for v_id, v_tuple in self._vars.items(): - if v_id not in current_vars_dict: - old_vars.append(v_tuple[0]) - elif config.update_vars: - start_vars = {v_id: v_tuple[0] for v_id, v_tuple in self._vars.items()} - timer.stop('vars') - timer.start('params') - if config.check_for_new_or_removed_params: - current_params_dict = {} - for p in self._model.component_objects(Param, descend_into=True): - if p.mutable: - for _p in p.values(): - current_params_dict[id(_p)] = _p - for p_id, p in current_params_dict.items(): - if p_id not in self._params: - new_params.append(p) - for p_id, p in self._params.items(): - if p_id not in current_params_dict: - old_params.append(p) - timer.stop('params') - timer.start('cons') - if config.check_for_new_or_removed_constraints or config.update_constraints: - current_cons_dict = { - c: None - for c in self._model.component_data_objects( - Constraint, descend_into=True, active=True - ) - } - current_sos_dict = { - c: None - for c in self._model.component_data_objects( - SOSConstraint, descend_into=True, active=True - ) - } - for c in current_cons_dict.keys(): - if c not in self._vars_referenced_by_con: - new_cons.append(c) - for c in current_sos_dict.keys(): - if c not in self._vars_referenced_by_con: - new_sos.append(c) - for c in self._vars_referenced_by_con.keys(): - if c not in current_cons_dict and c not in current_sos_dict: - if (c.ctype is Constraint) or ( - c.ctype is None and isinstance(c, _GeneralConstraintData) - ): - old_cons.append(c) - else: - assert (c.ctype is SOSConstraint) or ( - c.ctype is None and isinstance(c, _SOSConstraintData) - ) - old_sos.append(c) - self.remove_constraints(old_cons) - self.remove_sos_constraints(old_sos) - timer.stop('cons') - timer.start('params') - self.remove_params(old_params) - - # sticking this between removal and addition - # is important so that we don't do unnecessary work - if config.update_params: - self.update_params() - - self.add_params(new_params) - timer.stop('params') - timer.start('vars') - self.add_variables(new_vars) - timer.stop('vars') - timer.start('cons') - self.add_constraints(new_cons) - self.add_sos_constraints(new_sos) - new_cons_set = set(new_cons) - new_sos_set = set(new_sos) - new_vars_set = set(id(v) for v in new_vars) - cons_to_remove_and_add = {} - need_to_set_objective = False - if config.update_constraints: - cons_to_update = [] - sos_to_update = [] - for c in current_cons_dict.keys(): - if c not in new_cons_set: - cons_to_update.append(c) - for c in current_sos_dict.keys(): - if c not in new_sos_set: - sos_to_update.append(c) - for c in cons_to_update: - lower, body, upper = self._active_constraints[c] - new_lower, new_body, new_upper = c.lower, c.body, c.upper - if new_body is not body: - cons_to_remove_and_add[c] = None - continue - if new_lower is not lower: - if ( - type(new_lower) is NumericConstant - and type(lower) is NumericConstant - and new_lower.value == lower.value - ): - pass - else: - cons_to_remove_and_add[c] = None - continue - if new_upper is not upper: - if ( - type(new_upper) is NumericConstant - and type(upper) is NumericConstant - and new_upper.value == upper.value - ): - pass - else: - cons_to_remove_and_add[c] = None - continue - self.remove_sos_constraints(sos_to_update) - self.add_sos_constraints(sos_to_update) - timer.stop('cons') - timer.start('vars') - if self._only_child_vars and config.update_vars: - vars_to_check = [] - for v_id, v in current_vars_dict.items(): - if v_id not in new_vars_set: - vars_to_check.append(v) - elif config.update_vars: - end_vars = {v_id: v_tuple[0] for v_id, v_tuple in self._vars.items()} - vars_to_check = [v for v_id, v in end_vars.items() if v_id in start_vars] - if config.update_vars: - vars_to_update = [] - for v in vars_to_check: - _v, lb, ub, fixed, domain_interval, value = self._vars[id(v)] - if lb is not v._lb: - vars_to_update.append(v) - elif ub is not v._ub: - vars_to_update.append(v) - elif (fixed is not v.fixed) or (fixed and (value != v.value)): - vars_to_update.append(v) - if self.update_config.treat_fixed_vars_as_params: - for c in self._referenced_variables[id(v)][0]: - cons_to_remove_and_add[c] = None - if self._referenced_variables[id(v)][2] is not None: - need_to_set_objective = True - elif domain_interval != v.domain.get_interval(): - vars_to_update.append(v) - self.update_variables(vars_to_update) - timer.stop('vars') - timer.start('cons') - cons_to_remove_and_add = list(cons_to_remove_and_add.keys()) - self.remove_constraints(cons_to_remove_and_add) - self.add_constraints(cons_to_remove_and_add) - timer.stop('cons') - timer.start('named expressions') - if config.update_named_expressions: - cons_to_update = [] - for c, expr_list in self._named_expressions.items(): - if c in new_cons_set: - continue - for named_expr, old_expr in expr_list: - if named_expr.expr is not old_expr: - cons_to_update.append(c) - break - self.remove_constraints(cons_to_update) - self.add_constraints(cons_to_update) - for named_expr, old_expr in self._obj_named_expressions: - if named_expr.expr is not old_expr: - need_to_set_objective = True - break - timer.stop('named expressions') - timer.start('objective') - if self.update_config.check_for_new_objective: - pyomo_obj = get_objective(self._model) - if pyomo_obj is not self._objective: - need_to_set_objective = True - else: - pyomo_obj = self._objective - if self.update_config.update_objective: - if pyomo_obj is not None and pyomo_obj.expr is not self._objective_expr: - need_to_set_objective = True - elif pyomo_obj is not None and pyomo_obj.sense is not self._objective_sense: - # we can definitely do something faster here than resetting the whole objective - need_to_set_objective = True - if need_to_set_objective: - self.set_objective(pyomo_obj) - timer.stop('objective') - - # this has to be done after the objective and constraints in case the - # old objective/constraints use old variables - timer.start('vars') - self.remove_variables(old_vars) - timer.stop('vars') - - -legacy_termination_condition_map = { - TerminationCondition.unknown: LegacyTerminationCondition.unknown, - TerminationCondition.maxTimeLimit: LegacyTerminationCondition.maxTimeLimit, - TerminationCondition.iterationLimit: LegacyTerminationCondition.maxIterations, - TerminationCondition.objectiveLimit: LegacyTerminationCondition.minFunctionValue, - TerminationCondition.minStepLength: LegacyTerminationCondition.minStepLength, - TerminationCondition.convergenceCriteriaSatisfied: LegacyTerminationCondition.optimal, - TerminationCondition.unbounded: LegacyTerminationCondition.unbounded, - TerminationCondition.provenInfeasible: LegacyTerminationCondition.infeasible, - TerminationCondition.locallyInfeasible: LegacyTerminationCondition.infeasible, - TerminationCondition.infeasibleOrUnbounded: LegacyTerminationCondition.infeasibleOrUnbounded, - TerminationCondition.error: LegacyTerminationCondition.error, - TerminationCondition.interrupted: LegacyTerminationCondition.resourceInterrupt, - TerminationCondition.licensingProblems: LegacyTerminationCondition.licensingProblems, -} - - -legacy_solver_status_map = { - TerminationCondition.unknown: LegacySolverStatus.unknown, - TerminationCondition.maxTimeLimit: LegacySolverStatus.aborted, - TerminationCondition.iterationLimit: LegacySolverStatus.aborted, - TerminationCondition.objectiveLimit: LegacySolverStatus.aborted, - TerminationCondition.minStepLength: LegacySolverStatus.error, - TerminationCondition.convergenceCriteriaSatisfied: LegacySolverStatus.ok, - TerminationCondition.unbounded: LegacySolverStatus.error, - TerminationCondition.provenInfeasible: LegacySolverStatus.error, - TerminationCondition.locallyInfeasible: LegacySolverStatus.error, - TerminationCondition.infeasibleOrUnbounded: LegacySolverStatus.error, - TerminationCondition.error: LegacySolverStatus.error, - TerminationCondition.interrupted: LegacySolverStatus.aborted, - TerminationCondition.licensingProblems: LegacySolverStatus.error, -} - - -legacy_solution_status_map = { - TerminationCondition.unknown: LegacySolutionStatus.unknown, - TerminationCondition.maxTimeLimit: LegacySolutionStatus.stoppedByLimit, - TerminationCondition.iterationLimit: LegacySolutionStatus.stoppedByLimit, - TerminationCondition.objectiveLimit: LegacySolutionStatus.stoppedByLimit, - TerminationCondition.minStepLength: LegacySolutionStatus.error, - TerminationCondition.convergenceCriteriaSatisfied: LegacySolutionStatus.optimal, - TerminationCondition.unbounded: LegacySolutionStatus.unbounded, - TerminationCondition.provenInfeasible: LegacySolutionStatus.infeasible, - TerminationCondition.locallyInfeasible: LegacySolutionStatus.infeasible, - TerminationCondition.infeasibleOrUnbounded: LegacySolutionStatus.unsure, - TerminationCondition.error: LegacySolutionStatus.error, - TerminationCondition.interrupted: LegacySolutionStatus.error, - TerminationCondition.licensingProblems: LegacySolutionStatus.error, -} - - -class LegacySolverInterface: - def solve( - self, - model: _BlockData, - tee: bool = False, - load_solutions: bool = True, - logfile: Optional[str] = None, - solnfile: Optional[str] = None, - timelimit: Optional[float] = None, - report_timing: bool = False, - solver_io: Optional[str] = None, - suffixes: Optional[Sequence] = None, - options: Optional[Dict] = None, - keepfiles: bool = False, - symbolic_solver_labels: bool = False, - ): - original_config = self.config - self.config = self.config() - self.config.tee = tee - self.config.load_solution = load_solutions - self.config.symbolic_solver_labels = symbolic_solver_labels - self.config.time_limit = timelimit - self.config.report_timing = report_timing - if solver_io is not None: - raise NotImplementedError('Still working on this') - if suffixes is not None: - raise NotImplementedError('Still working on this') - if logfile is not None: - raise NotImplementedError('Still working on this') - if 'keepfiles' in self.config: - self.config.keepfiles = keepfiles - if solnfile is not None: - if 'filename' in self.config: - filename = os.path.splitext(solnfile)[0] - self.config.filename = filename - original_options = self.options - if options is not None: - self.options = options - - results: Results = super().solve(model) - - legacy_results = LegacySolverResults() - legacy_soln = LegacySolution() - legacy_results.solver.status = legacy_solver_status_map[ - results.termination_condition - ] - legacy_results.solver.termination_condition = legacy_termination_condition_map[ - results.termination_condition - ] - legacy_soln.status = legacy_solution_status_map[results.termination_condition] - legacy_results.solver.termination_message = str(results.termination_condition) - - obj = get_objective(model) - legacy_results.problem.sense = obj.sense - - if obj.sense == minimize: - legacy_results.problem.lower_bound = results.best_objective_bound - legacy_results.problem.upper_bound = results.best_feasible_objective - else: - legacy_results.problem.upper_bound = results.best_objective_bound - legacy_results.problem.lower_bound = results.best_feasible_objective - if ( - results.best_feasible_objective is not None - and results.best_objective_bound is not None - ): - legacy_soln.gap = abs( - results.best_feasible_objective - results.best_objective_bound - ) - else: - legacy_soln.gap = None - - symbol_map = SymbolMap() - symbol_map.byObject = dict(self.symbol_map.byObject) - symbol_map.bySymbol = dict(self.symbol_map.bySymbol) - symbol_map.aliases = dict(self.symbol_map.aliases) - symbol_map.default_labeler = self.symbol_map.default_labeler - model.solutions.add_symbol_map(symbol_map) - legacy_results._smap_id = id(symbol_map) - - delete_legacy_soln = True - if load_solutions: - if hasattr(model, 'dual') and model.dual.import_enabled(): - for c, val in results.solution_loader.get_duals().items(): - model.dual[c] = val - if hasattr(model, 'slack') and model.slack.import_enabled(): - for c, val in results.solution_loader.get_slacks().items(): - model.slack[c] = val - if hasattr(model, 'rc') and model.rc.import_enabled(): - for v, val in results.solution_loader.get_reduced_costs().items(): - model.rc[v] = val - elif results.best_feasible_objective is not None: - delete_legacy_soln = False - for v, val in results.solution_loader.get_primals().items(): - legacy_soln.variable[symbol_map.getSymbol(v)] = {'Value': val} - if hasattr(model, 'dual') and model.dual.import_enabled(): - for c, val in results.solution_loader.get_duals().items(): - legacy_soln.constraint[symbol_map.getSymbol(c)] = {'Dual': val} - if hasattr(model, 'slack') and model.slack.import_enabled(): - for c, val in results.solution_loader.get_slacks().items(): - symbol = symbol_map.getSymbol(c) - if symbol in legacy_soln.constraint: - legacy_soln.constraint[symbol]['Slack'] = val - if hasattr(model, 'rc') and model.rc.import_enabled(): - for v, val in results.solution_loader.get_reduced_costs().items(): - legacy_soln.variable['Rc'] = val - - legacy_results.solution.insert(legacy_soln) - if delete_legacy_soln: - legacy_results.solution.delete(0) - - self.config = original_config - self.options = original_options - - return legacy_results - - def available(self, exception_flag=True): - ans = super().available() - if exception_flag and not ans: - raise ApplicationError(f'Solver {self.__class__} is not available ({ans}).') - return bool(ans) - - def license_is_valid(self) -> bool: - """Test if the solver license is valid on this system. - - Note that this method is included for compatibility with the - legacy SolverFactory interface. Unlicensed or open source - solvers will return True by definition. Licensed solvers will - return True if a valid license is found. - - Returns - ------- - available: bool - True if the solver license is valid. Otherwise, False. - - """ - return bool(self.available()) - - @property - def options(self): - for solver_name in ['gurobi', 'ipopt', 'cplex', 'cbc', 'highs']: - if hasattr(self, solver_name + '_options'): - return getattr(self, solver_name + '_options') - raise NotImplementedError('Could not find the correct options') - - @options.setter - def options(self, val): - found = False - for solver_name in ['gurobi', 'ipopt', 'cplex', 'cbc', 'highs']: - if hasattr(self, solver_name + '_options'): - setattr(self, solver_name + '_options', val) - found = True - if not found: - raise NotImplementedError('Could not find the correct options') - - def __enter__(self): - return self - - def __exit__(self, t, v, traceback): - pass - - -class SolverFactoryClass(Factory): - def register(self, name, doc=None): - def decorator(cls): - self._cls[name] = cls - self._doc[name] = doc - - class LegacySolver(LegacySolverInterface, cls): - pass - - LegacySolverFactory.register(name, doc)(LegacySolver) - - return cls - - return decorator - - -SolverFactory = SolverFactoryClass() diff --git a/pyomo/contrib/appsi/examples/getting_started.py b/pyomo/contrib/appsi/examples/getting_started.py index d907283f663..d65430e3c23 100644 --- a/pyomo/contrib/appsi/examples/getting_started.py +++ b/pyomo/contrib/appsi/examples/getting_started.py @@ -1,6 +1,7 @@ import pyomo.environ as pe from pyomo.contrib import appsi from pyomo.common.timing import HierarchicalTimer +from pyomo.solver import base as solver_base def main(plot=True, n_points=200): @@ -31,7 +32,7 @@ def main(plot=True, n_points=200): for p_val in p_values: m.p.value = p_val res = opt.solve(m, timer=timer) - assert res.termination_condition == appsi.base.TerminationCondition.convergenceCriteriaSatisfied + assert res.termination_condition == solver_base.TerminationCondition.convergenceCriteriaSatisfied obj_values.append(res.best_feasible_objective) opt.load_vars([m.x]) x_values.append(m.x.value) diff --git a/pyomo/contrib/appsi/fbbt.py b/pyomo/contrib/appsi/fbbt.py index 22badd83d12..78137e790b6 100644 --- a/pyomo/contrib/appsi/fbbt.py +++ b/pyomo/contrib/appsi/fbbt.py @@ -1,4 +1,4 @@ -from pyomo.contrib.appsi.base import PersistentBase +from pyomo.solver.base import PersistentBase from pyomo.common.config import ( ConfigDict, ConfigValue, @@ -11,10 +11,9 @@ from pyomo.core.base.param import _ParamData from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.sos import _SOSConstraintData -from pyomo.core.base.objective import _GeneralObjectiveData, minimize, maximize +from pyomo.core.base.objective import _GeneralObjectiveData, minimize from pyomo.core.base.block import _BlockData from pyomo.core.base import SymbolMap, TextLabeler -from pyomo.common.errors import InfeasibleConstraintException class IntervalConfig(ConfigDict): diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index 35071ab17ea..dd00089e84a 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -1,20 +1,16 @@ +import logging +import math +import subprocess +import sys +from typing import Optional, Sequence, Dict, List, Mapping + + from pyomo.common.tempfiles import TempfileManager from pyomo.common.fileutils import Executable -from pyomo.contrib.appsi.base import ( - PersistentSolver, - Results, - TerminationCondition, - InterfaceConfig, - PersistentSolutionLoader, -) from pyomo.contrib.appsi.writers import LPWriter from pyomo.common.log import LogStream -import logging -import subprocess from pyomo.core.kernel.objective import minimize, maximize -import math from pyomo.common.collections import ComponentMap -from typing import Optional, Sequence, NoReturn, List, Mapping from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.block import _BlockData @@ -22,12 +18,13 @@ from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.common.timing import HierarchicalTimer from pyomo.common.tee import TeeStream -import sys -from typing import Dict from pyomo.common.config import ConfigValue, NonNegativeInt from pyomo.common.errors import PyomoException from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager +from pyomo.solver.base import TerminationCondition, Results, PersistentSolver +from pyomo.solver.config import InterfaceConfig +from pyomo.solver.solution import PersistentSolutionLoader logger = logging.getLogger(__name__) diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 7f9844fc21d..9f39528b0b0 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -1,29 +1,27 @@ -from pyomo.common.tempfiles import TempfileManager -from pyomo.contrib.appsi.base import ( - PersistentSolver, - Results, - TerminationCondition, - MIPInterfaceConfig, - PersistentSolutionLoader, -) -from pyomo.contrib.appsi.writers import LPWriter import logging import math +import sys +import time +from typing import Optional, Sequence, Dict, List, Mapping + + +from pyomo.common.tempfiles import TempfileManager +from pyomo.contrib.appsi.writers import LPWriter +from pyomo.common.log import LogStream from pyomo.common.collections import ComponentMap -from typing import Optional, Sequence, NoReturn, List, Mapping, Dict from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.block import _BlockData from pyomo.core.base.param import _ParamData from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.common.timing import HierarchicalTimer -import sys -import time -from pyomo.common.log import LogStream from pyomo.common.config import ConfigValue, NonNegativeInt from pyomo.common.errors import PyomoException from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager +from pyomo.solver.base import TerminationCondition, Results, PersistentSolver +from pyomo.solver.config import MIPInterfaceConfig +from pyomo.solver.solution import PersistentSolutionLoader logger = logging.getLogger(__name__) diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index 3f8eab638b0..8aaae4e31d4 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -1,7 +1,9 @@ from collections.abc import Iterable import logging import math +import sys from typing import List, Dict, Optional + from pyomo.common.collections import ComponentSet, ComponentMap, OrderedSet from pyomo.common.log import LogStream from pyomo.common.dependencies import attempt_import @@ -12,24 +14,19 @@ from pyomo.common.config import ConfigValue, NonNegativeInt from pyomo.core.kernel.objective import minimize, maximize from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler -from pyomo.core.base.var import Var, _GeneralVarData +from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.sos import _SOSConstraintData from pyomo.core.base.param import _ParamData from pyomo.core.expr.numvalue import value, is_constant, is_fixed, native_numeric_types from pyomo.repn import generate_standard_repn from pyomo.core.expr.numeric_expr import NPV_MaxExpression, NPV_MinExpression -from pyomo.contrib.appsi.base import ( - PersistentSolver, - Results, - TerminationCondition, - MIPInterfaceConfig, - PersistentBase, - PersistentSolutionLoader, -) from pyomo.contrib.appsi.cmodel import cmodel, cmodel_available from pyomo.core.staleflag import StaleFlagManager -import sys +from pyomo.solver.base import TerminationCondition, Results, PersistentSolver, PersistentBase +from pyomo.solver.config import MIPInterfaceConfig +from pyomo.solver.solution import PersistentSolutionLoader + logger = logging.getLogger(__name__) @@ -1196,8 +1193,8 @@ def set_linear_constraint_attr(self, con, attr, val): if attr in {'Sense', 'RHS', 'ConstrName'}: raise ValueError( 'Linear constraint attr {0} cannot be set with' - + ' the set_linear_constraint_attr method. Please use' - + ' the remove_constraint and add_constraint methods.'.format(attr) + ' the set_linear_constraint_attr method. Please use' + ' the remove_constraint and add_constraint methods.'.format(attr) ) self._pyomo_con_to_solver_con_map[con].setAttr(attr, val) self._needs_updated = True @@ -1225,8 +1222,8 @@ def set_var_attr(self, var, attr, val): if attr in {'LB', 'UB', 'VType', 'VarName'}: raise ValueError( 'Var attr {0} cannot be set with' - + ' the set_var_attr method. Please use' - + ' the update_var method.'.format(attr) + ' the set_var_attr method. Please use' + ' the update_var method.'.format(attr) ) if attr == 'Obj': raise ValueError( diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index e5c43d27c8d..c93d69527d8 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -1,5 +1,7 @@ import logging +import sys from typing import List, Dict, Optional + from pyomo.common.collections import ComponentMap from pyomo.common.dependencies import attempt_import from pyomo.common.errors import PyomoException @@ -16,18 +18,12 @@ from pyomo.core.expr.numvalue import value, is_constant from pyomo.repn import generate_standard_repn from pyomo.core.expr.numeric_expr import NPV_MaxExpression, NPV_MinExpression -from pyomo.contrib.appsi.base import ( - PersistentSolver, - Results, - TerminationCondition, - MIPInterfaceConfig, - PersistentBase, - PersistentSolutionLoader, -) from pyomo.contrib.appsi.cmodel import cmodel, cmodel_available from pyomo.common.dependencies import numpy as np from pyomo.core.staleflag import StaleFlagManager -import sys +from pyomo.solver.base import TerminationCondition, Results, PersistentSolver, PersistentBase +from pyomo.solver.config import MIPInterfaceConfig +from pyomo.solver.solution import PersistentSolutionLoader logger = logging.getLogger(__name__) diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index da42fc0be41..f754b5e85c0 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -1,18 +1,16 @@ +import math +import os +import sys +from typing import Dict +import logging +import subprocess + + from pyomo.common.tempfiles import TempfileManager from pyomo.common.fileutils import Executable -from pyomo.contrib.appsi.base import ( - PersistentSolver, - Results, - TerminationCondition, - InterfaceConfig, - PersistentSolutionLoader, -) from pyomo.contrib.appsi.writers import NLWriter from pyomo.common.log import LogStream -import logging -import subprocess from pyomo.core.kernel.objective import minimize -import math from pyomo.common.collections import ComponentMap from pyomo.core.expr.numvalue import value from pyomo.core.expr.visitor import replace_expressions @@ -24,13 +22,13 @@ from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.common.timing import HierarchicalTimer from pyomo.common.tee import TeeStream -import sys -from typing import Dict from pyomo.common.config import ConfigValue, NonNegativeInt from pyomo.common.errors import PyomoException -import os from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager +from pyomo.solver.base import TerminationCondition, Results, PersistentSolver +from pyomo.solver.config import InterfaceConfig +from pyomo.solver.solution import PersistentSolutionLoader logger = logging.getLogger(__name__) diff --git a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py index fcff8916b5b..877d0971f2b 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py @@ -1,11 +1,8 @@ -from pyomo.common.errors import PyomoException from pyomo.common import unittest import pyomo.environ as pe from pyomo.contrib.appsi.solvers.gurobi import Gurobi -from pyomo.contrib.appsi.base import TerminationCondition -from pyomo.core.expr.numeric_expr import LinearExpression +from pyomo.solver.base import TerminationCondition from pyomo.core.expr.taylor_series import taylor_series_expansion -from pyomo.contrib.appsi.cmodel import cmodel_available opt = Gurobi() diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 135f36d3695..fc97ba43fd0 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -4,7 +4,7 @@ parameterized, param_available = attempt_import('parameterized') parameterized = parameterized.parameterized -from pyomo.contrib.appsi.base import TerminationCondition, Results, PersistentSolver +from pyomo.solver.base import TerminationCondition, Results, PersistentSolver from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.contrib.appsi.solvers import Gurobi, Ipopt, Highs from typing import Type diff --git a/pyomo/contrib/appsi/tests/test_base.py b/pyomo/contrib/appsi/tests/test_base.py deleted file mode 100644 index 82a04b29e56..00000000000 --- a/pyomo/contrib/appsi/tests/test_base.py +++ /dev/null @@ -1,91 +0,0 @@ -from pyomo.common import unittest -from pyomo.contrib import appsi -import pyomo.environ as pe -from pyomo.core.base.var import ScalarVar - - -class TestResults(unittest.TestCase): - def test_uninitialized(self): - res = appsi.base.Results() - self.assertIsNone(res.best_feasible_objective) - self.assertIsNone(res.best_objective_bound) - self.assertEqual( - res.termination_condition, appsi.base.TerminationCondition.unknown - ) - - with self.assertRaisesRegex( - RuntimeError, '.*does not currently have a valid solution.*' - ): - res.solution_loader.load_vars() - with self.assertRaisesRegex( - RuntimeError, '.*does not currently have valid duals.*' - ): - res.solution_loader.get_duals() - with self.assertRaisesRegex( - RuntimeError, '.*does not currently have valid reduced costs.*' - ): - res.solution_loader.get_reduced_costs() - with self.assertRaisesRegex( - RuntimeError, '.*does not currently have valid slacks.*' - ): - res.solution_loader.get_slacks() - - def test_results(self): - m = pe.ConcreteModel() - m.x = ScalarVar() - m.y = ScalarVar() - m.c1 = pe.Constraint(expr=m.x == 1) - m.c2 = pe.Constraint(expr=m.y == 2) - - primals = {} - primals[id(m.x)] = (m.x, 1) - primals[id(m.y)] = (m.y, 2) - duals = {} - duals[m.c1] = 3 - duals[m.c2] = 4 - rc = {} - rc[id(m.x)] = (m.x, 5) - rc[id(m.y)] = (m.y, 6) - slacks = {} - slacks[m.c1] = 7 - slacks[m.c2] = 8 - - res = appsi.base.Results() - res.solution_loader = appsi.base.SolutionLoader( - primals=primals, duals=duals, slacks=slacks, reduced_costs=rc - ) - - res.solution_loader.load_vars() - self.assertAlmostEqual(m.x.value, 1) - self.assertAlmostEqual(m.y.value, 2) - - m.x.value = None - m.y.value = None - - res.solution_loader.load_vars([m.y]) - self.assertIsNone(m.x.value) - self.assertAlmostEqual(m.y.value, 2) - - duals2 = res.solution_loader.get_duals() - self.assertAlmostEqual(duals[m.c1], duals2[m.c1]) - self.assertAlmostEqual(duals[m.c2], duals2[m.c2]) - - duals2 = res.solution_loader.get_duals([m.c2]) - self.assertNotIn(m.c1, duals2) - self.assertAlmostEqual(duals[m.c2], duals2[m.c2]) - - rc2 = res.solution_loader.get_reduced_costs() - self.assertAlmostEqual(rc[id(m.x)][1], rc2[m.x]) - self.assertAlmostEqual(rc[id(m.y)][1], rc2[m.y]) - - rc2 = res.solution_loader.get_reduced_costs([m.y]) - self.assertNotIn(m.x, rc2) - self.assertAlmostEqual(rc[id(m.y)][1], rc2[m.y]) - - slacks2 = res.solution_loader.get_slacks() - self.assertAlmostEqual(slacks[m.c1], slacks2[m.c1]) - self.assertAlmostEqual(slacks[m.c2], slacks2[m.c2]) - - slacks2 = res.solution_loader.get_slacks([m.c2]) - self.assertNotIn(m.c1, slacks2) - self.assertAlmostEqual(slacks[m.c2], slacks2[m.c2]) diff --git a/pyomo/contrib/appsi/writers/lp_writer.py b/pyomo/contrib/appsi/writers/lp_writer.py index 6a4a4ab2ff7..6ebc26b7b31 100644 --- a/pyomo/contrib/appsi/writers/lp_writer.py +++ b/pyomo/contrib/appsi/writers/lp_writer.py @@ -5,12 +5,10 @@ from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.core.base.sos import _SOSConstraintData from pyomo.core.base.block import _BlockData -from pyomo.repn.standard_repn import generate_standard_repn -from pyomo.core.expr.numvalue import value -from pyomo.contrib.appsi.base import PersistentBase from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler from pyomo.common.timing import HierarchicalTimer -from pyomo.core.kernel.objective import minimize, maximize +from pyomo.core.kernel.objective import minimize +from pyomo.solver.base import PersistentBase from .config import WriterConfig from ..cmodel import cmodel, cmodel_available diff --git a/pyomo/contrib/appsi/writers/nl_writer.py b/pyomo/contrib/appsi/writers/nl_writer.py index d0bb443508d..39aed3732aa 100644 --- a/pyomo/contrib/appsi/writers/nl_writer.py +++ b/pyomo/contrib/appsi/writers/nl_writer.py @@ -1,4 +1,6 @@ +import os from typing import List + from pyomo.core.base.param import _ParamData from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.constraint import _GeneralConstraintData @@ -6,17 +8,15 @@ from pyomo.core.base.sos import _SOSConstraintData from pyomo.core.base.block import _BlockData from pyomo.repn.standard_repn import generate_standard_repn -from pyomo.core.expr.numvalue import value -from pyomo.contrib.appsi.base import PersistentBase -from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler +from pyomo.core.base import SymbolMap, TextLabeler from pyomo.common.timing import HierarchicalTimer from pyomo.core.kernel.objective import minimize -from .config import WriterConfig from pyomo.common.collections import OrderedSet -import os -from ..cmodel import cmodel, cmodel_available from pyomo.repn.plugins.ampl.ampl_ import set_pyomo_amplfunc_env +from pyomo.solver.base import PersistentBase +from .config import WriterConfig +from ..cmodel import cmodel, cmodel_available class NLWriter(PersistentBase): def __init__(self, only_child_vars=False): diff --git a/pyomo/environ/__init__.py b/pyomo/environ/__init__.py index 51c68449247..2cd562edb2b 100644 --- a/pyomo/environ/__init__.py +++ b/pyomo/environ/__init__.py @@ -30,6 +30,7 @@ def _do_import(pkg_name): 'pyomo.repn', 'pyomo.neos', 'pyomo.solvers', + 'pyomo.solver', 'pyomo.gdp', 'pyomo.mpec', 'pyomo.dae', diff --git a/pyomo/solver/__init__.py b/pyomo/solver/__init__.py index 64c6452d06d..13b8b463662 100644 --- a/pyomo/solver/__init__.py +++ b/pyomo/solver/__init__.py @@ -9,6 +9,8 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from . import util from . import base +from . import config from . import solution +from . import util + diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index b6d9e1592cb..8b39f387c92 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -45,7 +45,6 @@ ) from pyomo.core.kernel.objective import minimize from pyomo.core.base import SymbolMap -from .cmodel import cmodel, cmodel_available from pyomo.core.staleflag import StaleFlagManager from pyomo.core.expr.numvalue import NumericConstant from pyomo.solver import ( @@ -138,29 +137,6 @@ class Results: the lower bound. For maximization problems, this is the upper bound. For solvers that do not provide an objective bound, this should be -inf (minimization) or inf (maximization) - - Here is an example workflow: - - >>> import pyomo.environ as pe - >>> from pyomo.contrib import appsi - >>> m = pe.ConcreteModel() - >>> m.x = pe.Var() - >>> m.obj = pe.Objective(expr=m.x**2) - >>> opt = appsi.solvers.Ipopt() - >>> opt.config.load_solution = False - >>> results = opt.solve(m) #doctest:+SKIP - >>> if results.termination_condition == appsi.base.TerminationCondition.convergenceCriteriaSatisfied: #doctest:+SKIP - ... print('optimal solution found: ', results.best_feasible_objective) #doctest:+SKIP - ... results.solution_loader.load_vars() #doctest:+SKIP - ... print('the optimal value of x is ', m.x.value) #doctest:+SKIP - ... elif results.best_feasible_objective is not None: #doctest:+SKIP - ... print('sub-optimal but feasible solution found: ', results.best_feasible_objective) #doctest:+SKIP - ... results.solution_loader.load_vars(vars_to_load=[m.x]) #doctest:+SKIP - ... print('The value of x in the feasible solution is ', m.x.value) #doctest:+SKIP - ... elif results.termination_condition in {appsi.base.TerminationCondition.iterationLimit, appsi.base.TerminationCondition.maxTimeLimit}: #doctest:+SKIP - ... print('No feasible solution was found. The best lower bound found was ', results.best_objective_bound) #doctest:+SKIP - ... else: #doctest:+SKIP - ... print('The following termination condition was encountered: ', results.termination_condition) #doctest:+SKIP """ def __init__(self): @@ -426,39 +402,6 @@ def update_params(self): pass - -""" -What can change in a pyomo model? -- variables added or removed -- constraints added or removed -- objective changed -- objective expr changed -- params added or removed -- variable modified - - lb - - ub - - fixed or unfixed - - domain - - value -- constraint modified - - lower - - upper - - body - - active or not -- named expressions modified - - expr -- param modified - - value - -Ideas: -- Consider explicitly handling deactivated constraints; favor deactivation over removal - and activation over addition - -Notes: -- variable bounds cannot be updated with mutable params; you must call update_variables -""" - - class PersistentBase(abc.ABC): def __init__(self, only_child_vars=False): self._model = None @@ -480,7 +423,6 @@ def __init__(self, only_child_vars=False): self._vars_referenced_by_con = {} self._vars_referenced_by_obj = [] self._expr_types = None - self.use_extensions = False self._only_child_vars = only_child_vars @property @@ -496,8 +438,6 @@ def set_instance(self, model): self.__init__() self.update_config = saved_update_config self._model = model - if self.use_extensions and cmodel_available: - self._expr_types = cmodel.PyomoExprTypes() self.add_block(model) if self._objective is None: self.set_objective(None) @@ -561,10 +501,7 @@ def add_constraints(self, cons: List[_GeneralConstraintData]): 'constraint {name} has already been added'.format(name=con.name) ) self._active_constraints[con] = (con.lower, con.body, con.upper) - if self.use_extensions and cmodel_available: - tmp = cmodel.prep_for_repn(con.body, self._expr_types) - else: - tmp = collect_vars_and_named_exprs(con.body) + tmp = collect_vars_and_named_exprs(con.body) named_exprs, variables, fixed_vars, external_functions = tmp if not self._only_child_vars: self._check_for_new_vars(variables) @@ -617,10 +554,7 @@ def set_objective(self, obj: _GeneralObjectiveData): self._objective = obj self._objective_expr = obj.expr self._objective_sense = obj.sense - if self.use_extensions and cmodel_available: - tmp = cmodel.prep_for_repn(obj.expr, self._expr_types) - else: - tmp = collect_vars_and_named_exprs(obj.expr) + tmp = collect_vars_and_named_exprs(obj.expr) named_exprs, variables, fixed_vars, external_functions = tmp if not self._only_child_vars: self._check_for_new_vars(variables) diff --git a/pyomo/solver/tests/test_base.py b/pyomo/solver/tests/test_base.py index e69de29bb2d..b5fcc4c4242 100644 --- a/pyomo/solver/tests/test_base.py +++ b/pyomo/solver/tests/test_base.py @@ -0,0 +1,91 @@ +from pyomo.common import unittest +from pyomo.solver import base +import pyomo.environ as pe +from pyomo.core.base.var import ScalarVar + + +class TestResults(unittest.TestCase): + def test_uninitialized(self): + res = base.Results() + self.assertIsNone(res.best_feasible_objective) + self.assertIsNone(res.best_objective_bound) + self.assertEqual( + res.termination_condition, base.TerminationCondition.unknown + ) + + with self.assertRaisesRegex( + RuntimeError, '.*does not currently have a valid solution.*' + ): + res.solution_loader.load_vars() + with self.assertRaisesRegex( + RuntimeError, '.*does not currently have valid duals.*' + ): + res.solution_loader.get_duals() + with self.assertRaisesRegex( + RuntimeError, '.*does not currently have valid reduced costs.*' + ): + res.solution_loader.get_reduced_costs() + with self.assertRaisesRegex( + RuntimeError, '.*does not currently have valid slacks.*' + ): + res.solution_loader.get_slacks() + + def test_results(self): + m = pe.ConcreteModel() + m.x = ScalarVar() + m.y = ScalarVar() + m.c1 = pe.Constraint(expr=m.x == 1) + m.c2 = pe.Constraint(expr=m.y == 2) + + primals = {} + primals[id(m.x)] = (m.x, 1) + primals[id(m.y)] = (m.y, 2) + duals = {} + duals[m.c1] = 3 + duals[m.c2] = 4 + rc = {} + rc[id(m.x)] = (m.x, 5) + rc[id(m.y)] = (m.y, 6) + slacks = {} + slacks[m.c1] = 7 + slacks[m.c2] = 8 + + res = base.Results() + res.solution_loader = base.SolutionLoader( + primals=primals, duals=duals, slacks=slacks, reduced_costs=rc + ) + + res.solution_loader.load_vars() + self.assertAlmostEqual(m.x.value, 1) + self.assertAlmostEqual(m.y.value, 2) + + m.x.value = None + m.y.value = None + + res.solution_loader.load_vars([m.y]) + self.assertIsNone(m.x.value) + self.assertAlmostEqual(m.y.value, 2) + + duals2 = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.c1], duals2[m.c1]) + self.assertAlmostEqual(duals[m.c2], duals2[m.c2]) + + duals2 = res.solution_loader.get_duals([m.c2]) + self.assertNotIn(m.c1, duals2) + self.assertAlmostEqual(duals[m.c2], duals2[m.c2]) + + rc2 = res.solution_loader.get_reduced_costs() + self.assertAlmostEqual(rc[id(m.x)][1], rc2[m.x]) + self.assertAlmostEqual(rc[id(m.y)][1], rc2[m.y]) + + rc2 = res.solution_loader.get_reduced_costs([m.y]) + self.assertNotIn(m.x, rc2) + self.assertAlmostEqual(rc[id(m.y)][1], rc2[m.y]) + + slacks2 = res.solution_loader.get_slacks() + self.assertAlmostEqual(slacks[m.c1], slacks2[m.c1]) + self.assertAlmostEqual(slacks[m.c2], slacks2[m.c2]) + + slacks2 = res.solution_loader.get_slacks([m.c2]) + self.assertNotIn(m.c1, slacks2) + self.assertAlmostEqual(slacks[m.c2], slacks2[m.c2]) From 04d71cff3c3834c144b1462846c96b2fe6586953 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 09:03:25 -0600 Subject: [PATCH 0045/1204] Fix broken imports --- pyomo/solver/base.py | 13 +++++---- pyomo/solver/util.py | 64 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 8b39f387c92..7a451e59c11 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -27,8 +27,7 @@ from pyomo.core.base.block import _BlockData from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.common.collections import ComponentMap -from .utils.get_objective import get_objective -from .utils.collect_vars_and_named_exprs import collect_vars_and_named_exprs + from pyomo.common.timing import HierarchicalTimer from pyomo.common.errors import ApplicationError from pyomo.opt.base import SolverFactory as LegacySolverFactory @@ -47,11 +46,11 @@ from pyomo.core.base import SymbolMap from pyomo.core.staleflag import StaleFlagManager from pyomo.core.expr.numvalue import NumericConstant -from pyomo.solver import ( - SolutionLoader, - SolutionLoaderBase, - UpdateConfig -) + +from pyomo.solver.config import UpdateConfig +from pyomo.solver.solution import SolutionLoader, SolutionLoaderBase +from pyomo.solver.util import get_objective, collect_vars_and_named_exprs + class TerminationCondition(enum.Enum): diff --git a/pyomo/solver/util.py b/pyomo/solver/util.py index 8c768061678..4b8acf0de2e 100644 --- a/pyomo/solver/util.py +++ b/pyomo/solver/util.py @@ -9,6 +9,70 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +from pyomo.core.base.objective import Objective +from pyomo.core.expr.visitor import ExpressionValueVisitor, nonpyomo_leaf_types +import pyomo.core.expr as EXPR + + +def get_objective(block): + obj = None + for o in block.component_data_objects( + Objective, descend_into=True, active=True, sort=True + ): + if obj is not None: + raise ValueError('Multiple active objectives found') + obj = o + return obj + + +class _VarAndNamedExprCollector(ExpressionValueVisitor): + def __init__(self): + self.named_expressions = {} + self.variables = {} + self.fixed_vars = {} + self._external_functions = {} + + def visit(self, node, values): + pass + + def visiting_potential_leaf(self, node): + if type(node) in nonpyomo_leaf_types: + return True, None + + if node.is_variable_type(): + self.variables[id(node)] = node + if node.is_fixed(): + self.fixed_vars[id(node)] = node + return True, None + + if node.is_named_expression_type(): + self.named_expressions[id(node)] = node + return False, None + + if type(node) is EXPR.ExternalFunctionExpression: + self._external_functions[id(node)] = node + return False, None + + if node.is_expression_type(): + return False, None + + return True, None + + +_visitor = _VarAndNamedExprCollector() + + +def collect_vars_and_named_exprs(expr): + _visitor.__init__() + _visitor.dfs_postorder_stack(expr) + return ( + list(_visitor.named_expressions.values()), + list(_visitor.variables.values()), + list(_visitor.fixed_vars.values()), + list(_visitor._external_functions.values()), + ) + + class SolverUtils: pass From fe9cea4b8d2ec14a8b26ca0b566a5665f66640fb Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 09:05:22 -0600 Subject: [PATCH 0046/1204] Trying to fix broken imports again --- pyomo/environ/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/environ/__init__.py b/pyomo/environ/__init__.py index 2cd562edb2b..51c68449247 100644 --- a/pyomo/environ/__init__.py +++ b/pyomo/environ/__init__.py @@ -30,7 +30,6 @@ def _do_import(pkg_name): 'pyomo.repn', 'pyomo.neos', 'pyomo.solvers', - 'pyomo.solver', 'pyomo.gdp', 'pyomo.mpec', 'pyomo.dae', From b41cf0089700e33741cd862f269fec668e6af490 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 09:07:59 -0600 Subject: [PATCH 0047/1204] Update plugins --- pyomo/contrib/appsi/plugins.py | 1 - pyomo/environ/__init__.py | 1 + pyomo/solver/plugins.py | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 pyomo/solver/plugins.py diff --git a/pyomo/contrib/appsi/plugins.py b/pyomo/contrib/appsi/plugins.py index 5333158239e..75161e3548c 100644 --- a/pyomo/contrib/appsi/plugins.py +++ b/pyomo/contrib/appsi/plugins.py @@ -1,5 +1,4 @@ from pyomo.common.extensions import ExtensionBuilderFactory -from .base import SolverFactory from .solvers import Gurobi, Ipopt, Cbc, Cplex, Highs from .build import AppsiBuilder diff --git a/pyomo/environ/__init__.py b/pyomo/environ/__init__.py index 51c68449247..2cd562edb2b 100644 --- a/pyomo/environ/__init__.py +++ b/pyomo/environ/__init__.py @@ -30,6 +30,7 @@ def _do_import(pkg_name): 'pyomo.repn', 'pyomo.neos', 'pyomo.solvers', + 'pyomo.solver', 'pyomo.gdp', 'pyomo.mpec', 'pyomo.dae', diff --git a/pyomo/solver/plugins.py b/pyomo/solver/plugins.py new file mode 100644 index 00000000000..926ac346f32 --- /dev/null +++ b/pyomo/solver/plugins.py @@ -0,0 +1 @@ +from .base import SolverFactory From 6a84fcf77aff7ef00206035a408130dc8a200ac5 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 09:10:20 -0600 Subject: [PATCH 0048/1204] Trying again with plugins --- pyomo/solver/plugins.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyomo/solver/plugins.py b/pyomo/solver/plugins.py index 926ac346f32..e15d1a585b1 100644 --- a/pyomo/solver/plugins.py +++ b/pyomo/solver/plugins.py @@ -1 +1,5 @@ from .base import SolverFactory + +def load(): + pass + From 2e1529828b3cfd6533f09936feccb5a27e92d1ad Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 09:12:39 -0600 Subject: [PATCH 0049/1204] PPlugins are my bane --- pyomo/contrib/appsi/plugins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/appsi/plugins.py b/pyomo/contrib/appsi/plugins.py index 75161e3548c..86dcd298a93 100644 --- a/pyomo/contrib/appsi/plugins.py +++ b/pyomo/contrib/appsi/plugins.py @@ -1,4 +1,5 @@ from pyomo.common.extensions import ExtensionBuilderFactory +from pyomo.solver.base import SolverFactory from .solvers import Gurobi, Ipopt, Cbc, Cplex, Highs from .build import AppsiBuilder From 94be7450f7f35ace9ece2d78c4ffcf4d167ac891 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 09:27:01 -0600 Subject: [PATCH 0050/1204] Remove use_extensions attribute --- pyomo/contrib/appsi/solvers/gurobi.py | 2 -- pyomo/contrib/appsi/solvers/highs.py | 2 -- .../contrib/appsi/solvers/tests/test_persistent_solvers.py | 7 ------- 3 files changed, 11 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index 8aaae4e31d4..a02b8c55170 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -498,8 +498,6 @@ def set_instance(self, model): ) self._reinit() self._model = model - if self.use_extensions and cmodel_available: - self._expr_types = cmodel.PyomoExprTypes() if self.config.symbolic_solver_labels: self._labeler = TextLabeler() diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index c93d69527d8..a1477125ca9 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -343,8 +343,6 @@ def set_instance(self, model): ) self._reinit() self._model = model - if self.use_extensions and cmodel_available: - self._expr_types = cmodel.PyomoExprTypes() self._solver_model = highspy.Highs() self.add_block(model) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index fc97ba43fd0..3629aeceb1e 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -1182,13 +1182,6 @@ def test_with_gdp( self.assertAlmostEqual(m.x.value, 0) self.assertAlmostEqual(m.y.value, 1) - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) - opt.use_extensions = True - res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1) - self.assertAlmostEqual(m.x.value, 0) - self.assertAlmostEqual(m.y.value, 1) - @parameterized.expand(input=all_solvers) def test_variables_elsewhere(self, name: str, opt_class: Type[PersistentSolver]): opt: PersistentSolver = opt_class(only_child_vars=False) From 4017abcc127da5fb05e1dbfd9377cf544205e2d4 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 09:40:00 -0600 Subject: [PATCH 0051/1204] Turn on pyomo.solver tests --- .github/workflows/test_branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 0ac37747a65..ff8b5901189 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -602,7 +602,7 @@ jobs: run: | $PYTHON_EXE -m pytest -v \ -W ignore::Warning ${{matrix.category}} \ - pyomo/contrib/appsi --junitxml="TEST-pyomo.xml" + pyomo/contrib/appsi pyomo/solver --junitxml="TEST-pyomo.xml" - name: Run Pyomo MPI tests if: matrix.mpi != 0 From ee064d2f09fdf372f5dc76ac1b7395031b2dd842 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 11:17:41 -0600 Subject: [PATCH 0052/1204] SAVE POINT: about to mess with persistent base --- pyomo/solver/base.py | 279 ++++++++++++++++---------------- pyomo/solver/tests/test_base.py | 66 ++++++++ 2 files changed, 206 insertions(+), 139 deletions(-) diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 7a451e59c11..510b61f7479 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -262,145 +262,6 @@ def is_persistent(self): return False -class PersistentSolver(SolverBase): - def is_persistent(self): - return True - - def load_vars( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> NoReturn: - """ - Load the solution of the primal variables into the value attribute of the variables. - - Parameters - ---------- - vars_to_load: list - A list of the variables whose solution should be loaded. If vars_to_load is None, then the solution - to all primal variables will be loaded. - """ - for v, val in self.get_primals(vars_to_load=vars_to_load).items(): - v.set_value(val, skip_validation=True) - StaleFlagManager.mark_all_as_stale(delayed=True) - - @abc.abstractmethod - def get_primals( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: - pass - - def get_duals( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: - """ - Declare sign convention in docstring here. - - Parameters - ---------- - cons_to_load: list - A list of the constraints whose duals should be loaded. If cons_to_load is None, then the duals for all - constraints will be loaded. - - Returns - ------- - duals: dict - Maps constraints to dual values - """ - raise NotImplementedError( - '{0} does not support the get_duals method'.format(type(self)) - ) - - def get_slacks( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: - """ - Parameters - ---------- - cons_to_load: list - A list of the constraints whose slacks should be loaded. If cons_to_load is None, then the slacks for all - constraints will be loaded. - - Returns - ------- - slacks: dict - Maps constraints to slack values - """ - raise NotImplementedError( - '{0} does not support the get_slacks method'.format(type(self)) - ) - - def get_reduced_costs( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: - """ - Parameters - ---------- - vars_to_load: list - A list of the variables whose reduced cost should be loaded. If vars_to_load is None, then all reduced costs - will be loaded. - - Returns - ------- - reduced_costs: ComponentMap - Maps variable to reduced cost - """ - raise NotImplementedError( - '{0} does not support the get_reduced_costs method'.format(type(self)) - ) - - @property - @abc.abstractmethod - def update_config(self) -> UpdateConfig: - pass - - @abc.abstractmethod - def set_instance(self, model): - pass - - @abc.abstractmethod - def add_variables(self, variables: List[_GeneralVarData]): - pass - - @abc.abstractmethod - def add_params(self, params: List[_ParamData]): - pass - - @abc.abstractmethod - def add_constraints(self, cons: List[_GeneralConstraintData]): - pass - - @abc.abstractmethod - def add_block(self, block: _BlockData): - pass - - @abc.abstractmethod - def remove_variables(self, variables: List[_GeneralVarData]): - pass - - @abc.abstractmethod - def remove_params(self, params: List[_ParamData]): - pass - - @abc.abstractmethod - def remove_constraints(self, cons: List[_GeneralConstraintData]): - pass - - @abc.abstractmethod - def remove_block(self, block: _BlockData): - pass - - @abc.abstractmethod - def set_objective(self, obj: _GeneralObjectiveData): - pass - - @abc.abstractmethod - def update_variables(self, variables: List[_GeneralVarData]): - pass - - @abc.abstractmethod - def update_params(self): - pass - - class PersistentBase(abc.ABC): def __init__(self, only_child_vars=False): self._model = None @@ -940,6 +801,146 @@ def update(self, timer: HierarchicalTimer = None): timer.stop('vars') +class PersistentSolver(SolverBase): + def is_persistent(self): + return True + + def load_vars( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> NoReturn: + """ + Load the solution of the primal variables into the value attribute of the variables. + + Parameters + ---------- + vars_to_load: list + A list of the variables whose solution should be loaded. If vars_to_load is None, then the solution + to all primal variables will be loaded. + """ + for v, val in self.get_primals(vars_to_load=vars_to_load).items(): + v.set_value(val, skip_validation=True) + StaleFlagManager.mark_all_as_stale(delayed=True) + + @abc.abstractmethod + def get_primals( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> Mapping[_GeneralVarData, float]: + pass + + def get_duals( + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None + ) -> Dict[_GeneralConstraintData, float]: + """ + Declare sign convention in docstring here. + + Parameters + ---------- + cons_to_load: list + A list of the constraints whose duals should be loaded. If cons_to_load is None, then the duals for all + constraints will be loaded. + + Returns + ------- + duals: dict + Maps constraints to dual values + """ + raise NotImplementedError( + '{0} does not support the get_duals method'.format(type(self)) + ) + + def get_slacks( + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None + ) -> Dict[_GeneralConstraintData, float]: + """ + Parameters + ---------- + cons_to_load: list + A list of the constraints whose slacks should be loaded. If cons_to_load is None, then the slacks for all + constraints will be loaded. + + Returns + ------- + slacks: dict + Maps constraints to slack values + """ + raise NotImplementedError( + '{0} does not support the get_slacks method'.format(type(self)) + ) + + def get_reduced_costs( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> Mapping[_GeneralVarData, float]: + """ + Parameters + ---------- + vars_to_load: list + A list of the variables whose reduced cost should be loaded. If vars_to_load is None, then all reduced costs + will be loaded. + + Returns + ------- + reduced_costs: ComponentMap + Maps variable to reduced cost + """ + raise NotImplementedError( + '{0} does not support the get_reduced_costs method'.format(type(self)) + ) + + @property + @abc.abstractmethod + def update_config(self) -> UpdateConfig: + pass + + @abc.abstractmethod + def set_instance(self, model): + pass + + @abc.abstractmethod + def add_variables(self, variables: List[_GeneralVarData]): + pass + + @abc.abstractmethod + def add_params(self, params: List[_ParamData]): + pass + + @abc.abstractmethod + def add_constraints(self, cons: List[_GeneralConstraintData]): + pass + + @abc.abstractmethod + def add_block(self, block: _BlockData): + pass + + @abc.abstractmethod + def remove_variables(self, variables: List[_GeneralVarData]): + pass + + @abc.abstractmethod + def remove_params(self, params: List[_ParamData]): + pass + + @abc.abstractmethod + def remove_constraints(self, cons: List[_GeneralConstraintData]): + pass + + @abc.abstractmethod + def remove_block(self, block: _BlockData): + pass + + @abc.abstractmethod + def set_objective(self, obj: _GeneralObjectiveData): + pass + + @abc.abstractmethod + def update_variables(self, variables: List[_GeneralVarData]): + pass + + @abc.abstractmethod + def update_params(self): + pass + + + # Everything below here preserves backwards compatibility legacy_termination_condition_map = { diff --git a/pyomo/solver/tests/test_base.py b/pyomo/solver/tests/test_base.py index b5fcc4c4242..3c389175d08 100644 --- a/pyomo/solver/tests/test_base.py +++ b/pyomo/solver/tests/test_base.py @@ -4,6 +4,72 @@ from pyomo.core.base.var import ScalarVar +class TestTerminationCondition(unittest.TestCase): + def test_member_list(self): + member_list = base.TerminationCondition._member_names_ + expected_list = ['unknown', + 'convergenceCriteriaSatisfied', + 'maxTimeLimit', + 'iterationLimit', + 'objectiveLimit', + 'minStepLength', + 'unbounded', + 'provenInfeasible', + 'locallyInfeasible', + 'infeasibleOrUnbounded', + 'error', + 'interrupted', + 'licensingProblems'] + self.assertEqual(member_list, expected_list) + + def test_codes(self): + self.assertEqual(base.TerminationCondition.unknown.value, 42) + self.assertEqual(base.TerminationCondition.convergenceCriteriaSatisfied.value, 0) + self.assertEqual(base.TerminationCondition.maxTimeLimit.value, 1) + self.assertEqual(base.TerminationCondition.iterationLimit.value, 2) + self.assertEqual(base.TerminationCondition.objectiveLimit.value, 3) + self.assertEqual(base.TerminationCondition.minStepLength.value, 4) + self.assertEqual(base.TerminationCondition.unbounded.value, 5) + self.assertEqual(base.TerminationCondition.provenInfeasible.value, 6) + self.assertEqual(base.TerminationCondition.locallyInfeasible.value, 7) + self.assertEqual(base.TerminationCondition.infeasibleOrUnbounded.value, 8) + self.assertEqual(base.TerminationCondition.error.value, 9) + self.assertEqual(base.TerminationCondition.interrupted.value, 10) + self.assertEqual(base.TerminationCondition.licensingProblems.value, 11) + + +class TestSolutionStatus(unittest.TestCase): + def test_member_list(self): + member_list = base.SolutionStatus._member_names_ + expected_list = ['noSolution', 'infeasible', 'feasible', 'optimal'] + self.assertEqual(member_list, expected_list) + + def test_codes(self): + self.assertEqual(base.SolutionStatus.noSolution.value, 0) + self.assertEqual(base.SolutionStatus.infeasible.value, 10) + self.assertEqual(base.SolutionStatus.feasible.value, 20) + self.assertEqual(base.SolutionStatus.optimal.value, 30) + + +class TestSolverBase(unittest.TestCase): + @unittest.mock.patch.multiple(base.SolverBase, __abstractmethods__=set()) + def test_solver_base(self): + self.instance = base.SolverBase() + self.assertFalse(self.instance.is_persistent()) + self.assertEqual(self.instance.version(), None) + self.assertEqual(self.instance.config, None) + self.assertEqual(self.instance.solve(None), None) + self.assertEqual(self.instance.available(), None) + + @unittest.mock.patch.multiple(base.SolverBase, __abstractmethods__=set()) + def test_solver_availability(self): + self.instance = base.SolverBase() + self.instance.Availability._value_ = 1 + self.assertTrue(self.instance.Availability.__bool__(self.instance.Availability)) + self.instance.Availability._value_ = -1 + self.assertFalse(self.instance.Availability.__bool__(self.instance.Availability)) + + class TestResults(unittest.TestCase): def test_uninitialized(self): res = base.Results() From 9bcec5d863e9bb529b09e00096fea87f2b1fd784 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 12:06:10 -0600 Subject: [PATCH 0053/1204] Rename PersistentSolver to PersistentSolverBase; PersistentBase to PersistentSolverUtils --- pyomo/contrib/appsi/fbbt.py | 4 +- pyomo/contrib/appsi/solvers/cbc.py | 4 +- pyomo/contrib/appsi/solvers/cplex.py | 4 +- pyomo/contrib/appsi/solvers/gurobi.py | 6 +- pyomo/contrib/appsi/solvers/highs.py | 6 +- pyomo/contrib/appsi/solvers/ipopt.py | 4 +- .../solvers/tests/test_persistent_solvers.py | 142 ++--- pyomo/contrib/appsi/writers/lp_writer.py | 4 +- pyomo/contrib/appsi/writers/nl_writer.py | 4 +- pyomo/solver/base.py | 554 +---------------- pyomo/solver/util.py | 555 +++++++++++++++++- 11 files changed, 646 insertions(+), 641 deletions(-) diff --git a/pyomo/contrib/appsi/fbbt.py b/pyomo/contrib/appsi/fbbt.py index 78137e790b6..cff1085de0d 100644 --- a/pyomo/contrib/appsi/fbbt.py +++ b/pyomo/contrib/appsi/fbbt.py @@ -1,4 +1,4 @@ -from pyomo.solver.base import PersistentBase +from pyomo.solver.util import PersistentSolverUtils from pyomo.common.config import ( ConfigDict, ConfigValue, @@ -59,7 +59,7 @@ def __init__( ) -class IntervalTightener(PersistentBase): +class IntervalTightener(PersistentSolverUtils): def __init__(self): super().__init__() self._config = IntervalConfig() diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index dd00089e84a..30250f66a86 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -22,7 +22,7 @@ from pyomo.common.errors import PyomoException from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager -from pyomo.solver.base import TerminationCondition, Results, PersistentSolver +from pyomo.solver.base import TerminationCondition, Results, PersistentSolverBase from pyomo.solver.config import InterfaceConfig from pyomo.solver.solution import PersistentSolutionLoader @@ -60,7 +60,7 @@ def __init__( self.log_level = logging.INFO -class Cbc(PersistentSolver): +class Cbc(PersistentSolverBase): def __init__(self, only_child_vars=False): self._config = CbcConfig() self._solver_options = {} diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 9f39528b0b0..0b1bd552370 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -19,7 +19,7 @@ from pyomo.common.errors import PyomoException from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager -from pyomo.solver.base import TerminationCondition, Results, PersistentSolver +from pyomo.solver.base import TerminationCondition, Results, PersistentSolverBase from pyomo.solver.config import MIPInterfaceConfig from pyomo.solver.solution import PersistentSolutionLoader @@ -62,7 +62,7 @@ def __init__(self, solver): self.solution_loader = PersistentSolutionLoader(solver=solver) -class Cplex(PersistentSolver): +class Cplex(PersistentSolverBase): _available = None def __init__(self, only_child_vars=False): diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index a02b8c55170..ba89c3e5d57 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -21,11 +21,11 @@ from pyomo.core.expr.numvalue import value, is_constant, is_fixed, native_numeric_types from pyomo.repn import generate_standard_repn from pyomo.core.expr.numeric_expr import NPV_MaxExpression, NPV_MinExpression -from pyomo.contrib.appsi.cmodel import cmodel, cmodel_available from pyomo.core.staleflag import StaleFlagManager -from pyomo.solver.base import TerminationCondition, Results, PersistentSolver, PersistentBase +from pyomo.solver.base import TerminationCondition, Results, PersistentSolverBase from pyomo.solver.config import MIPInterfaceConfig from pyomo.solver.solution import PersistentSolutionLoader +from pyomo.solver.util import PersistentSolverUtils logger = logging.getLogger(__name__) @@ -221,7 +221,7 @@ def __init__(self): self.var2 = None -class Gurobi(PersistentBase, PersistentSolver): +class Gurobi(PersistentSolverUtils, PersistentSolverBase): """ Interface to Gurobi """ diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index a1477125ca9..4a23d7c309a 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -18,12 +18,12 @@ from pyomo.core.expr.numvalue import value, is_constant from pyomo.repn import generate_standard_repn from pyomo.core.expr.numeric_expr import NPV_MaxExpression, NPV_MinExpression -from pyomo.contrib.appsi.cmodel import cmodel, cmodel_available from pyomo.common.dependencies import numpy as np from pyomo.core.staleflag import StaleFlagManager -from pyomo.solver.base import TerminationCondition, Results, PersistentSolver, PersistentBase +from pyomo.solver.base import TerminationCondition, Results, PersistentSolverBase from pyomo.solver.config import MIPInterfaceConfig from pyomo.solver.solution import PersistentSolutionLoader +from pyomo.solver.util import PersistentSolverUtils logger = logging.getLogger(__name__) @@ -133,7 +133,7 @@ def update(self): self.highs.changeRowBounds(row_ndx, lb, ub) -class Highs(PersistentBase, PersistentSolver): +class Highs(PersistentSolverUtils, PersistentSolverBase): """ Interface to HiGHS """ diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index f754b5e85c0..467040a0967 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -26,7 +26,7 @@ from pyomo.common.errors import PyomoException from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager -from pyomo.solver.base import TerminationCondition, Results, PersistentSolver +from pyomo.solver.base import TerminationCondition, Results, PersistentSolverBase from pyomo.solver.config import InterfaceConfig from pyomo.solver.solution import PersistentSolutionLoader @@ -124,7 +124,7 @@ def __init__( } -class Ipopt(PersistentSolver): +class Ipopt(PersistentSolverBase): def __init__(self, only_child_vars=False): self._config = IpoptConfig() self._solver_options = {} diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 3629aeceb1e..1f357acf209 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -4,7 +4,7 @@ parameterized, param_available = attempt_import('parameterized') parameterized = parameterized.parameterized -from pyomo.solver.base import TerminationCondition, Results, PersistentSolver +from pyomo.solver.base import TerminationCondition, Results, PersistentSolverBase from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.contrib.appsi.solvers import Gurobi, Ipopt, Highs from typing import Type @@ -78,10 +78,10 @@ def _load_tests(solver_list, only_child_vars_list): class TestSolvers(unittest.TestCase): @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_remove_variable_and_objective( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): # this test is for issue #2888 - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -101,9 +101,9 @@ def test_remove_variable_and_objective( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_stale_vars( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -146,9 +146,9 @@ def test_stale_vars( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_range_constraint( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -169,9 +169,9 @@ def test_range_constraint( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_reduced_costs( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -188,9 +188,9 @@ def test_reduced_costs( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_reduced_costs2( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -210,9 +210,9 @@ def test_reduced_costs2( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_param_changes( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -244,13 +244,13 @@ def test_param_changes( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_immutable_param( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): """ This test is important because component_data_objects returns immutable params as floats. We want to make sure we process these correctly. """ - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -282,9 +282,9 @@ def test_immutable_param( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_equality( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -316,9 +316,9 @@ def test_equality( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_linear_expression( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -352,9 +352,9 @@ def test_linear_expression( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_no_objective( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -386,9 +386,9 @@ def test_no_objective( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_add_remove_cons( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -438,9 +438,9 @@ def test_add_remove_cons( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_results_infeasible( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -485,8 +485,8 @@ def test_results_infeasible( res.solution_loader.get_reduced_costs() @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) - def test_duals(self, name: str, opt_class: Type[PersistentSolver], only_child_vars): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + def test_duals(self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars): + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -509,9 +509,9 @@ def test_duals(self, name: str, opt_class: Type[PersistentSolver], only_child_va @parameterized.expand(input=_load_tests(qcp_solvers, only_child_vars_options)) def test_mutable_quadratic_coefficient( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -533,9 +533,9 @@ def test_mutable_quadratic_coefficient( @parameterized.expand(input=_load_tests(qcp_solvers, only_child_vars_options)) def test_mutable_quadratic_objective( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -560,9 +560,9 @@ def test_mutable_quadratic_objective( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_fixed_vars( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) for treat_fixed_vars_as_params in [True, False]: opt.update_config.treat_fixed_vars_as_params = treat_fixed_vars_as_params if not opt.available(): @@ -600,9 +600,9 @@ def test_fixed_vars( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_fixed_vars_2( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) opt.update_config.treat_fixed_vars_as_params = True if not opt.available(): raise unittest.SkipTest @@ -639,9 +639,9 @@ def test_fixed_vars_2( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_fixed_vars_3( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) opt.update_config.treat_fixed_vars_as_params = True if not opt.available(): raise unittest.SkipTest @@ -656,9 +656,9 @@ def test_fixed_vars_3( @parameterized.expand(input=_load_tests(nlp_solvers, only_child_vars_options)) def test_fixed_vars_4( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) opt.update_config.treat_fixed_vars_as_params = True if not opt.available(): raise unittest.SkipTest @@ -677,9 +677,9 @@ def test_fixed_vars_4( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_mutable_param_with_range( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest try: @@ -767,7 +767,7 @@ def test_mutable_param_with_range( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_add_and_remove_vars( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): opt = opt_class(only_child_vars=only_child_vars) if not opt.available(): @@ -815,7 +815,7 @@ def test_add_and_remove_vars( opt.load_vars([m.x]) @parameterized.expand(input=_load_tests(nlp_solvers, only_child_vars_options)) - def test_exp(self, name: str, opt_class: Type[PersistentSolver], only_child_vars): + def test_exp(self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars): opt = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest @@ -829,7 +829,7 @@ def test_exp(self, name: str, opt_class: Type[PersistentSolver], only_child_vars self.assertAlmostEqual(m.y.value, 0.6529186341994245) @parameterized.expand(input=_load_tests(nlp_solvers, only_child_vars_options)) - def test_log(self, name: str, opt_class: Type[PersistentSolver], only_child_vars): + def test_log(self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars): opt = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest @@ -844,9 +844,9 @@ def test_log(self, name: str, opt_class: Type[PersistentSolver], only_child_vars @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_with_numpy( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -874,9 +874,9 @@ def test_with_numpy( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_bounds_with_params( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -908,9 +908,9 @@ def test_bounds_with_params( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_solution_loader( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -961,9 +961,9 @@ def test_solution_loader( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_time_limit( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest from sys import platform @@ -1017,9 +1017,9 @@ def test_time_limit( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_objective_changes( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -1078,9 +1078,9 @@ def test_objective_changes( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_domain( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -1104,9 +1104,9 @@ def test_domain( @parameterized.expand(input=_load_tests(mip_solvers, only_child_vars_options)) def test_domain_with_integers( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -1130,9 +1130,9 @@ def test_domain_with_integers( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_fixed_binaries( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -1147,7 +1147,7 @@ def test_fixed_binaries( res = opt.solve(m) self.assertAlmostEqual(res.best_feasible_objective, 1) - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) opt.update_config.treat_fixed_vars_as_params = False m.x.fix(0) res = opt.solve(m) @@ -1158,9 +1158,9 @@ def test_fixed_binaries( @parameterized.expand(input=_load_tests(mip_solvers, only_child_vars_options)) def test_with_gdp( - self, name: str, opt_class: Type[PersistentSolver], only_child_vars + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest @@ -1183,8 +1183,8 @@ def test_with_gdp( self.assertAlmostEqual(m.y.value, 1) @parameterized.expand(input=all_solvers) - def test_variables_elsewhere(self, name: str, opt_class: Type[PersistentSolver]): - opt: PersistentSolver = opt_class(only_child_vars=False) + def test_variables_elsewhere(self, name: str, opt_class: Type[PersistentSolverBase]): + opt: PersistentSolverBase = opt_class(only_child_vars=False) if not opt.available(): raise unittest.SkipTest @@ -1210,8 +1210,8 @@ def test_variables_elsewhere(self, name: str, opt_class: Type[PersistentSolver]) self.assertAlmostEqual(m.y.value, 2) @parameterized.expand(input=all_solvers) - def test_variables_elsewhere2(self, name: str, opt_class: Type[PersistentSolver]): - opt: PersistentSolver = opt_class(only_child_vars=False) + def test_variables_elsewhere2(self, name: str, opt_class: Type[PersistentSolverBase]): + opt: PersistentSolverBase = opt_class(only_child_vars=False) if not opt.available(): raise unittest.SkipTest @@ -1245,8 +1245,8 @@ def test_variables_elsewhere2(self, name: str, opt_class: Type[PersistentSolver] self.assertNotIn(m.z, sol) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) - def test_bug_1(self, name: str, opt_class: Type[PersistentSolver], only_child_vars): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + def test_bug_1(self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars): + opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest @@ -1271,7 +1271,7 @@ def test_bug_1(self, name: str, opt_class: Type[PersistentSolver], only_child_va @unittest.skipUnless(cmodel_available, 'appsi extensions are not available') class TestLegacySolverInterface(unittest.TestCase): @parameterized.expand(input=all_solvers) - def test_param_updates(self, name: str, opt_class: Type[PersistentSolver]): + def test_param_updates(self, name: str, opt_class: Type[PersistentSolverBase]): opt = pe.SolverFactory('appsi_' + name) if not opt.available(exception_flag=False): raise unittest.SkipTest @@ -1301,7 +1301,7 @@ def test_param_updates(self, name: str, opt_class: Type[PersistentSolver]): self.assertAlmostEqual(m.dual[m.c2], a1 / (a2 - a1)) @parameterized.expand(input=all_solvers) - def test_load_solutions(self, name: str, opt_class: Type[PersistentSolver]): + def test_load_solutions(self, name: str, opt_class: Type[PersistentSolverBase]): opt = pe.SolverFactory('appsi_' + name) if not opt.available(exception_flag=False): raise unittest.SkipTest diff --git a/pyomo/contrib/appsi/writers/lp_writer.py b/pyomo/contrib/appsi/writers/lp_writer.py index 6ebc26b7b31..8deb92640c1 100644 --- a/pyomo/contrib/appsi/writers/lp_writer.py +++ b/pyomo/contrib/appsi/writers/lp_writer.py @@ -8,12 +8,12 @@ from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler from pyomo.common.timing import HierarchicalTimer from pyomo.core.kernel.objective import minimize -from pyomo.solver.base import PersistentBase +from pyomo.solver.util import PersistentSolverUtils from .config import WriterConfig from ..cmodel import cmodel, cmodel_available -class LPWriter(PersistentBase): +class LPWriter(PersistentSolverUtils): def __init__(self, only_child_vars=False): super().__init__(only_child_vars=only_child_vars) self._config = WriterConfig() diff --git a/pyomo/contrib/appsi/writers/nl_writer.py b/pyomo/contrib/appsi/writers/nl_writer.py index 39aed3732aa..e853e22c96f 100644 --- a/pyomo/contrib/appsi/writers/nl_writer.py +++ b/pyomo/contrib/appsi/writers/nl_writer.py @@ -13,12 +13,12 @@ from pyomo.core.kernel.objective import minimize from pyomo.common.collections import OrderedSet from pyomo.repn.plugins.ampl.ampl_ import set_pyomo_amplfunc_env -from pyomo.solver.base import PersistentBase +from pyomo.solver.base import PersistentSolverUtils from .config import WriterConfig from ..cmodel import cmodel, cmodel_available -class NLWriter(PersistentBase): +class NLWriter(PersistentSolverUtils): def __init__(self, only_child_vars=False): super().__init__(only_child_vars=only_child_vars) self._config = WriterConfig() diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 510b61f7479..332f8cddff4 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -20,14 +20,11 @@ List, Tuple, ) -from pyomo.core.base.constraint import _GeneralConstraintData, Constraint -from pyomo.core.base.sos import _SOSConstraintData, SOSConstraint -from pyomo.core.base.var import _GeneralVarData, Var -from pyomo.core.base.param import _ParamData, Param +from pyomo.core.base.constraint import _GeneralConstraintData +from pyomo.core.base.var import _GeneralVarData +from pyomo.core.base.param import _ParamData from pyomo.core.base.block import _BlockData from pyomo.core.base.objective import _GeneralObjectiveData -from pyomo.common.collections import ComponentMap - from pyomo.common.timing import HierarchicalTimer from pyomo.common.errors import ApplicationError from pyomo.opt.base import SolverFactory as LegacySolverFactory @@ -45,11 +42,9 @@ from pyomo.core.kernel.objective import minimize from pyomo.core.base import SymbolMap from pyomo.core.staleflag import StaleFlagManager -from pyomo.core.expr.numvalue import NumericConstant - from pyomo.solver.config import UpdateConfig from pyomo.solver.solution import SolutionLoader, SolutionLoaderBase -from pyomo.solver.util import get_objective, collect_vars_and_named_exprs +from pyomo.solver.util import get_objective @@ -262,546 +257,7 @@ def is_persistent(self): return False -class PersistentBase(abc.ABC): - def __init__(self, only_child_vars=False): - self._model = None - self._active_constraints = {} # maps constraint to (lower, body, upper) - self._vars = {} # maps var id to (var, lb, ub, fixed, domain, value) - self._params = {} # maps param id to param - self._objective = None - self._objective_expr = None - self._objective_sense = None - self._named_expressions = ( - {} - ) # maps constraint to list of tuples (named_expr, named_expr.expr) - self._external_functions = ComponentMap() - self._obj_named_expressions = [] - self._update_config = UpdateConfig() - self._referenced_variables = ( - {} - ) # var_id: [dict[constraints, None], dict[sos constraints, None], None or objective] - self._vars_referenced_by_con = {} - self._vars_referenced_by_obj = [] - self._expr_types = None - self._only_child_vars = only_child_vars - - @property - def update_config(self): - return self._update_config - - @update_config.setter - def update_config(self, val: UpdateConfig): - self._update_config = val - - def set_instance(self, model): - saved_update_config = self.update_config - self.__init__() - self.update_config = saved_update_config - self._model = model - self.add_block(model) - if self._objective is None: - self.set_objective(None) - - @abc.abstractmethod - def _add_variables(self, variables: List[_GeneralVarData]): - pass - - def add_variables(self, variables: List[_GeneralVarData]): - for v in variables: - if id(v) in self._referenced_variables: - raise ValueError( - 'variable {name} has already been added'.format(name=v.name) - ) - self._referenced_variables[id(v)] = [{}, {}, None] - self._vars[id(v)] = ( - v, - v._lb, - v._ub, - v.fixed, - v.domain.get_interval(), - v.value, - ) - self._add_variables(variables) - - @abc.abstractmethod - def _add_params(self, params: List[_ParamData]): - pass - - def add_params(self, params: List[_ParamData]): - for p in params: - self._params[id(p)] = p - self._add_params(params) - - @abc.abstractmethod - def _add_constraints(self, cons: List[_GeneralConstraintData]): - pass - - def _check_for_new_vars(self, variables: List[_GeneralVarData]): - new_vars = {} - for v in variables: - v_id = id(v) - if v_id not in self._referenced_variables: - new_vars[v_id] = v - self.add_variables(list(new_vars.values())) - - def _check_to_remove_vars(self, variables: List[_GeneralVarData]): - vars_to_remove = {} - for v in variables: - v_id = id(v) - ref_cons, ref_sos, ref_obj = self._referenced_variables[v_id] - if len(ref_cons) == 0 and len(ref_sos) == 0 and ref_obj is None: - vars_to_remove[v_id] = v - self.remove_variables(list(vars_to_remove.values())) - - def add_constraints(self, cons: List[_GeneralConstraintData]): - all_fixed_vars = {} - for con in cons: - if con in self._named_expressions: - raise ValueError( - 'constraint {name} has already been added'.format(name=con.name) - ) - self._active_constraints[con] = (con.lower, con.body, con.upper) - tmp = collect_vars_and_named_exprs(con.body) - named_exprs, variables, fixed_vars, external_functions = tmp - if not self._only_child_vars: - self._check_for_new_vars(variables) - self._named_expressions[con] = [(e, e.expr) for e in named_exprs] - if len(external_functions) > 0: - self._external_functions[con] = external_functions - self._vars_referenced_by_con[con] = variables - for v in variables: - self._referenced_variables[id(v)][0][con] = None - if not self.update_config.treat_fixed_vars_as_params: - for v in fixed_vars: - v.unfix() - all_fixed_vars[id(v)] = v - self._add_constraints(cons) - for v in all_fixed_vars.values(): - v.fix() - - @abc.abstractmethod - def _add_sos_constraints(self, cons: List[_SOSConstraintData]): - pass - - def add_sos_constraints(self, cons: List[_SOSConstraintData]): - for con in cons: - if con in self._vars_referenced_by_con: - raise ValueError( - 'constraint {name} has already been added'.format(name=con.name) - ) - self._active_constraints[con] = tuple() - variables = con.get_variables() - if not self._only_child_vars: - self._check_for_new_vars(variables) - self._named_expressions[con] = [] - self._vars_referenced_by_con[con] = variables - for v in variables: - self._referenced_variables[id(v)][1][con] = None - self._add_sos_constraints(cons) - - @abc.abstractmethod - def _set_objective(self, obj: _GeneralObjectiveData): - pass - - def set_objective(self, obj: _GeneralObjectiveData): - if self._objective is not None: - for v in self._vars_referenced_by_obj: - self._referenced_variables[id(v)][2] = None - if not self._only_child_vars: - self._check_to_remove_vars(self._vars_referenced_by_obj) - self._external_functions.pop(self._objective, None) - if obj is not None: - self._objective = obj - self._objective_expr = obj.expr - self._objective_sense = obj.sense - tmp = collect_vars_and_named_exprs(obj.expr) - named_exprs, variables, fixed_vars, external_functions = tmp - if not self._only_child_vars: - self._check_for_new_vars(variables) - self._obj_named_expressions = [(i, i.expr) for i in named_exprs] - if len(external_functions) > 0: - self._external_functions[obj] = external_functions - self._vars_referenced_by_obj = variables - for v in variables: - self._referenced_variables[id(v)][2] = obj - if not self.update_config.treat_fixed_vars_as_params: - for v in fixed_vars: - v.unfix() - self._set_objective(obj) - for v in fixed_vars: - v.fix() - else: - self._vars_referenced_by_obj = [] - self._objective = None - self._objective_expr = None - self._objective_sense = None - self._obj_named_expressions = [] - self._set_objective(obj) - - def add_block(self, block): - param_dict = {} - for p in block.component_objects(Param, descend_into=True): - if p.mutable: - for _p in p.values(): - param_dict[id(_p)] = _p - self.add_params(list(param_dict.values())) - if self._only_child_vars: - self.add_variables( - list( - dict( - (id(var), var) - for var in block.component_data_objects(Var, descend_into=True) - ).values() - ) - ) - self.add_constraints( - list(block.component_data_objects(Constraint, descend_into=True, active=True)) - ) - self.add_sos_constraints( - list(block.component_data_objects(SOSConstraint, descend_into=True, active=True)) - ) - obj = get_objective(block) - if obj is not None: - self.set_objective(obj) - - @abc.abstractmethod - def _remove_constraints(self, cons: List[_GeneralConstraintData]): - pass - - def remove_constraints(self, cons: List[_GeneralConstraintData]): - self._remove_constraints(cons) - for con in cons: - if con not in self._named_expressions: - raise ValueError( - 'cannot remove constraint {name} - it was not added'.format( - name=con.name - ) - ) - for v in self._vars_referenced_by_con[con]: - self._referenced_variables[id(v)][0].pop(con) - if not self._only_child_vars: - self._check_to_remove_vars(self._vars_referenced_by_con[con]) - del self._active_constraints[con] - del self._named_expressions[con] - self._external_functions.pop(con, None) - del self._vars_referenced_by_con[con] - - @abc.abstractmethod - def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): - pass - - def remove_sos_constraints(self, cons: List[_SOSConstraintData]): - self._remove_sos_constraints(cons) - for con in cons: - if con not in self._vars_referenced_by_con: - raise ValueError( - 'cannot remove constraint {name} - it was not added'.format( - name=con.name - ) - ) - for v in self._vars_referenced_by_con[con]: - self._referenced_variables[id(v)][1].pop(con) - self._check_to_remove_vars(self._vars_referenced_by_con[con]) - del self._active_constraints[con] - del self._named_expressions[con] - del self._vars_referenced_by_con[con] - - @abc.abstractmethod - def _remove_variables(self, variables: List[_GeneralVarData]): - pass - - def remove_variables(self, variables: List[_GeneralVarData]): - self._remove_variables(variables) - for v in variables: - v_id = id(v) - if v_id not in self._referenced_variables: - raise ValueError( - 'cannot remove variable {name} - it has not been added'.format( - name=v.name - ) - ) - cons_using, sos_using, obj_using = self._referenced_variables[v_id] - if cons_using or sos_using or (obj_using is not None): - raise ValueError( - 'cannot remove variable {name} - it is still being used by constraints or the objective'.format( - name=v.name - ) - ) - del self._referenced_variables[v_id] - del self._vars[v_id] - - @abc.abstractmethod - def _remove_params(self, params: List[_ParamData]): - pass - - def remove_params(self, params: List[_ParamData]): - self._remove_params(params) - for p in params: - del self._params[id(p)] - - def remove_block(self, block): - self.remove_constraints( - list(block.component_data_objects(ctype=Constraint, descend_into=True, active=True)) - ) - self.remove_sos_constraints( - list(block.component_data_objects(ctype=SOSConstraint, descend_into=True, active=True)) - ) - if self._only_child_vars: - self.remove_variables( - list( - dict( - (id(var), var) - for var in block.component_data_objects( - ctype=Var, descend_into=True - ) - ).values() - ) - ) - self.remove_params( - list( - dict( - (id(p), p) - for p in block.component_data_objects( - ctype=Param, descend_into=True - ) - ).values() - ) - ) - - @abc.abstractmethod - def _update_variables(self, variables: List[_GeneralVarData]): - pass - - def update_variables(self, variables: List[_GeneralVarData]): - for v in variables: - self._vars[id(v)] = ( - v, - v._lb, - v._ub, - v.fixed, - v.domain.get_interval(), - v.value, - ) - self._update_variables(variables) - - @abc.abstractmethod - def update_params(self): - pass - - def update(self, timer: HierarchicalTimer = None): - if timer is None: - timer = HierarchicalTimer() - config = self.update_config - new_vars = [] - old_vars = [] - new_params = [] - old_params = [] - new_cons = [] - old_cons = [] - old_sos = [] - new_sos = [] - current_vars_dict = {} - current_cons_dict = {} - current_sos_dict = {} - timer.start('vars') - if self._only_child_vars and ( - config.check_for_new_or_removed_vars or config.update_vars - ): - current_vars_dict = { - id(v): v - for v in self._model.component_data_objects(Var, descend_into=True) - } - for v_id, v in current_vars_dict.items(): - if v_id not in self._vars: - new_vars.append(v) - for v_id, v_tuple in self._vars.items(): - if v_id not in current_vars_dict: - old_vars.append(v_tuple[0]) - elif config.update_vars: - start_vars = {v_id: v_tuple[0] for v_id, v_tuple in self._vars.items()} - timer.stop('vars') - timer.start('params') - if config.check_for_new_or_removed_params: - current_params_dict = {} - for p in self._model.component_objects(Param, descend_into=True): - if p.mutable: - for _p in p.values(): - current_params_dict[id(_p)] = _p - for p_id, p in current_params_dict.items(): - if p_id not in self._params: - new_params.append(p) - for p_id, p in self._params.items(): - if p_id not in current_params_dict: - old_params.append(p) - timer.stop('params') - timer.start('cons') - if config.check_for_new_or_removed_constraints or config.update_constraints: - current_cons_dict = { - c: None - for c in self._model.component_data_objects( - Constraint, descend_into=True, active=True - ) - } - current_sos_dict = { - c: None - for c in self._model.component_data_objects( - SOSConstraint, descend_into=True, active=True - ) - } - for c in current_cons_dict.keys(): - if c not in self._vars_referenced_by_con: - new_cons.append(c) - for c in current_sos_dict.keys(): - if c not in self._vars_referenced_by_con: - new_sos.append(c) - for c in self._vars_referenced_by_con.keys(): - if c not in current_cons_dict and c not in current_sos_dict: - if (c.ctype is Constraint) or ( - c.ctype is None and isinstance(c, _GeneralConstraintData) - ): - old_cons.append(c) - else: - assert (c.ctype is SOSConstraint) or ( - c.ctype is None and isinstance(c, _SOSConstraintData) - ) - old_sos.append(c) - self.remove_constraints(old_cons) - self.remove_sos_constraints(old_sos) - timer.stop('cons') - timer.start('params') - self.remove_params(old_params) - - # sticking this between removal and addition - # is important so that we don't do unnecessary work - if config.update_params: - self.update_params() - - self.add_params(new_params) - timer.stop('params') - timer.start('vars') - self.add_variables(new_vars) - timer.stop('vars') - timer.start('cons') - self.add_constraints(new_cons) - self.add_sos_constraints(new_sos) - new_cons_set = set(new_cons) - new_sos_set = set(new_sos) - new_vars_set = set(id(v) for v in new_vars) - cons_to_remove_and_add = {} - need_to_set_objective = False - if config.update_constraints: - cons_to_update = [] - sos_to_update = [] - for c in current_cons_dict.keys(): - if c not in new_cons_set: - cons_to_update.append(c) - for c in current_sos_dict.keys(): - if c not in new_sos_set: - sos_to_update.append(c) - for c in cons_to_update: - lower, body, upper = self._active_constraints[c] - new_lower, new_body, new_upper = c.lower, c.body, c.upper - if new_body is not body: - cons_to_remove_and_add[c] = None - continue - if new_lower is not lower: - if ( - type(new_lower) is NumericConstant - and type(lower) is NumericConstant - and new_lower.value == lower.value - ): - pass - else: - cons_to_remove_and_add[c] = None - continue - if new_upper is not upper: - if ( - type(new_upper) is NumericConstant - and type(upper) is NumericConstant - and new_upper.value == upper.value - ): - pass - else: - cons_to_remove_and_add[c] = None - continue - self.remove_sos_constraints(sos_to_update) - self.add_sos_constraints(sos_to_update) - timer.stop('cons') - timer.start('vars') - if self._only_child_vars and config.update_vars: - vars_to_check = [] - for v_id, v in current_vars_dict.items(): - if v_id not in new_vars_set: - vars_to_check.append(v) - elif config.update_vars: - end_vars = {v_id: v_tuple[0] for v_id, v_tuple in self._vars.items()} - vars_to_check = [v for v_id, v in end_vars.items() if v_id in start_vars] - if config.update_vars: - vars_to_update = [] - for v in vars_to_check: - _v, lb, ub, fixed, domain_interval, value = self._vars[id(v)] - if lb is not v._lb: - vars_to_update.append(v) - elif ub is not v._ub: - vars_to_update.append(v) - elif (fixed is not v.fixed) or (fixed and (value != v.value)): - vars_to_update.append(v) - if self.update_config.treat_fixed_vars_as_params: - for c in self._referenced_variables[id(v)][0]: - cons_to_remove_and_add[c] = None - if self._referenced_variables[id(v)][2] is not None: - need_to_set_objective = True - elif domain_interval != v.domain.get_interval(): - vars_to_update.append(v) - self.update_variables(vars_to_update) - timer.stop('vars') - timer.start('cons') - cons_to_remove_and_add = list(cons_to_remove_and_add.keys()) - self.remove_constraints(cons_to_remove_and_add) - self.add_constraints(cons_to_remove_and_add) - timer.stop('cons') - timer.start('named expressions') - if config.update_named_expressions: - cons_to_update = [] - for c, expr_list in self._named_expressions.items(): - if c in new_cons_set: - continue - for named_expr, old_expr in expr_list: - if named_expr.expr is not old_expr: - cons_to_update.append(c) - break - self.remove_constraints(cons_to_update) - self.add_constraints(cons_to_update) - for named_expr, old_expr in self._obj_named_expressions: - if named_expr.expr is not old_expr: - need_to_set_objective = True - break - timer.stop('named expressions') - timer.start('objective') - if self.update_config.check_for_new_objective: - pyomo_obj = get_objective(self._model) - if pyomo_obj is not self._objective: - need_to_set_objective = True - else: - pyomo_obj = self._objective - if self.update_config.update_objective: - if pyomo_obj is not None and pyomo_obj.expr is not self._objective_expr: - need_to_set_objective = True - elif pyomo_obj is not None and pyomo_obj.sense is not self._objective_sense: - # we can definitely do something faster here than resetting the whole objective - need_to_set_objective = True - if need_to_set_objective: - self.set_objective(pyomo_obj) - timer.stop('objective') - - # this has to be done after the objective and constraints in case the - # old objective/constraints use old variables - timer.start('vars') - self.remove_variables(old_vars) - timer.stop('vars') - - -class PersistentSolver(SolverBase): +class PersistentSolverBase(SolverBase): def is_persistent(self): return True diff --git a/pyomo/solver/util.py b/pyomo/solver/util.py index 4b8acf0de2e..fa2782f6bc4 100644 --- a/pyomo/solver/util.py +++ b/pyomo/solver/util.py @@ -9,9 +9,20 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from pyomo.core.base.objective import Objective +import abc +from typing import List + from pyomo.core.expr.visitor import ExpressionValueVisitor, nonpyomo_leaf_types import pyomo.core.expr as EXPR +from pyomo.core.base.constraint import _GeneralConstraintData, Constraint +from pyomo.core.base.sos import _SOSConstraintData, SOSConstraint +from pyomo.core.base.var import _GeneralVarData, Var +from pyomo.core.base.param import _ParamData, Param +from pyomo.core.base.objective import Objective, _GeneralObjectiveData +from pyomo.common.collections import ComponentMap +from pyomo.common.timing import HierarchicalTimer +from pyomo.core.expr.numvalue import NumericConstant +from pyomo.solver.config import UpdateConfig def get_objective(block): @@ -76,12 +87,550 @@ def collect_vars_and_named_exprs(expr): class SolverUtils: pass + class SubprocessSolverUtils: pass + class DirectSolverUtils: pass -class PersistentSolverUtils: - pass + +class PersistentSolverUtils(abc.ABC): + def __init__(self, only_child_vars=False): + self._model = None + self._active_constraints = {} # maps constraint to (lower, body, upper) + self._vars = {} # maps var id to (var, lb, ub, fixed, domain, value) + self._params = {} # maps param id to param + self._objective = None + self._objective_expr = None + self._objective_sense = None + self._named_expressions = ( + {} + ) # maps constraint to list of tuples (named_expr, named_expr.expr) + self._external_functions = ComponentMap() + self._obj_named_expressions = [] + self._update_config = UpdateConfig() + self._referenced_variables = ( + {} + ) # var_id: [dict[constraints, None], dict[sos constraints, None], None or objective] + self._vars_referenced_by_con = {} + self._vars_referenced_by_obj = [] + self._expr_types = None + self._only_child_vars = only_child_vars + + @property + def update_config(self): + return self._update_config + + @update_config.setter + def update_config(self, val: UpdateConfig): + self._update_config = val + + def set_instance(self, model): + saved_update_config = self.update_config + self.__init__() + self.update_config = saved_update_config + self._model = model + self.add_block(model) + if self._objective is None: + self.set_objective(None) + + @abc.abstractmethod + def _add_variables(self, variables: List[_GeneralVarData]): + pass + + def add_variables(self, variables: List[_GeneralVarData]): + for v in variables: + if id(v) in self._referenced_variables: + raise ValueError( + 'variable {name} has already been added'.format(name=v.name) + ) + self._referenced_variables[id(v)] = [{}, {}, None] + self._vars[id(v)] = ( + v, + v._lb, + v._ub, + v.fixed, + v.domain.get_interval(), + v.value, + ) + self._add_variables(variables) + + @abc.abstractmethod + def _add_params(self, params: List[_ParamData]): + pass + + def add_params(self, params: List[_ParamData]): + for p in params: + self._params[id(p)] = p + self._add_params(params) + + @abc.abstractmethod + def _add_constraints(self, cons: List[_GeneralConstraintData]): + pass + + def _check_for_new_vars(self, variables: List[_GeneralVarData]): + new_vars = {} + for v in variables: + v_id = id(v) + if v_id not in self._referenced_variables: + new_vars[v_id] = v + self.add_variables(list(new_vars.values())) + + def _check_to_remove_vars(self, variables: List[_GeneralVarData]): + vars_to_remove = {} + for v in variables: + v_id = id(v) + ref_cons, ref_sos, ref_obj = self._referenced_variables[v_id] + if len(ref_cons) == 0 and len(ref_sos) == 0 and ref_obj is None: + vars_to_remove[v_id] = v + self.remove_variables(list(vars_to_remove.values())) + + def add_constraints(self, cons: List[_GeneralConstraintData]): + all_fixed_vars = {} + for con in cons: + if con in self._named_expressions: + raise ValueError( + 'constraint {name} has already been added'.format(name=con.name) + ) + self._active_constraints[con] = (con.lower, con.body, con.upper) + tmp = collect_vars_and_named_exprs(con.body) + named_exprs, variables, fixed_vars, external_functions = tmp + if not self._only_child_vars: + self._check_for_new_vars(variables) + self._named_expressions[con] = [(e, e.expr) for e in named_exprs] + if len(external_functions) > 0: + self._external_functions[con] = external_functions + self._vars_referenced_by_con[con] = variables + for v in variables: + self._referenced_variables[id(v)][0][con] = None + if not self.update_config.treat_fixed_vars_as_params: + for v in fixed_vars: + v.unfix() + all_fixed_vars[id(v)] = v + self._add_constraints(cons) + for v in all_fixed_vars.values(): + v.fix() + + @abc.abstractmethod + def _add_sos_constraints(self, cons: List[_SOSConstraintData]): + pass + + def add_sos_constraints(self, cons: List[_SOSConstraintData]): + for con in cons: + if con in self._vars_referenced_by_con: + raise ValueError( + 'constraint {name} has already been added'.format(name=con.name) + ) + self._active_constraints[con] = tuple() + variables = con.get_variables() + if not self._only_child_vars: + self._check_for_new_vars(variables) + self._named_expressions[con] = [] + self._vars_referenced_by_con[con] = variables + for v in variables: + self._referenced_variables[id(v)][1][con] = None + self._add_sos_constraints(cons) + + @abc.abstractmethod + def _set_objective(self, obj: _GeneralObjectiveData): + pass + + def set_objective(self, obj: _GeneralObjectiveData): + if self._objective is not None: + for v in self._vars_referenced_by_obj: + self._referenced_variables[id(v)][2] = None + if not self._only_child_vars: + self._check_to_remove_vars(self._vars_referenced_by_obj) + self._external_functions.pop(self._objective, None) + if obj is not None: + self._objective = obj + self._objective_expr = obj.expr + self._objective_sense = obj.sense + tmp = collect_vars_and_named_exprs(obj.expr) + named_exprs, variables, fixed_vars, external_functions = tmp + if not self._only_child_vars: + self._check_for_new_vars(variables) + self._obj_named_expressions = [(i, i.expr) for i in named_exprs] + if len(external_functions) > 0: + self._external_functions[obj] = external_functions + self._vars_referenced_by_obj = variables + for v in variables: + self._referenced_variables[id(v)][2] = obj + if not self.update_config.treat_fixed_vars_as_params: + for v in fixed_vars: + v.unfix() + self._set_objective(obj) + for v in fixed_vars: + v.fix() + else: + self._vars_referenced_by_obj = [] + self._objective = None + self._objective_expr = None + self._objective_sense = None + self._obj_named_expressions = [] + self._set_objective(obj) + + def add_block(self, block): + param_dict = {} + for p in block.component_objects(Param, descend_into=True): + if p.mutable: + for _p in p.values(): + param_dict[id(_p)] = _p + self.add_params(list(param_dict.values())) + if self._only_child_vars: + self.add_variables( + list( + dict( + (id(var), var) + for var in block.component_data_objects(Var, descend_into=True) + ).values() + ) + ) + self.add_constraints( + list(block.component_data_objects(Constraint, descend_into=True, active=True)) + ) + self.add_sos_constraints( + list(block.component_data_objects(SOSConstraint, descend_into=True, active=True)) + ) + obj = get_objective(block) + if obj is not None: + self.set_objective(obj) + + @abc.abstractmethod + def _remove_constraints(self, cons: List[_GeneralConstraintData]): + pass + + def remove_constraints(self, cons: List[_GeneralConstraintData]): + self._remove_constraints(cons) + for con in cons: + if con not in self._named_expressions: + raise ValueError( + 'cannot remove constraint {name} - it was not added'.format( + name=con.name + ) + ) + for v in self._vars_referenced_by_con[con]: + self._referenced_variables[id(v)][0].pop(con) + if not self._only_child_vars: + self._check_to_remove_vars(self._vars_referenced_by_con[con]) + del self._active_constraints[con] + del self._named_expressions[con] + self._external_functions.pop(con, None) + del self._vars_referenced_by_con[con] + + @abc.abstractmethod + def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): + pass + + def remove_sos_constraints(self, cons: List[_SOSConstraintData]): + self._remove_sos_constraints(cons) + for con in cons: + if con not in self._vars_referenced_by_con: + raise ValueError( + 'cannot remove constraint {name} - it was not added'.format( + name=con.name + ) + ) + for v in self._vars_referenced_by_con[con]: + self._referenced_variables[id(v)][1].pop(con) + self._check_to_remove_vars(self._vars_referenced_by_con[con]) + del self._active_constraints[con] + del self._named_expressions[con] + del self._vars_referenced_by_con[con] + + @abc.abstractmethod + def _remove_variables(self, variables: List[_GeneralVarData]): + pass + + def remove_variables(self, variables: List[_GeneralVarData]): + self._remove_variables(variables) + for v in variables: + v_id = id(v) + if v_id not in self._referenced_variables: + raise ValueError( + 'cannot remove variable {name} - it has not been added'.format( + name=v.name + ) + ) + cons_using, sos_using, obj_using = self._referenced_variables[v_id] + if cons_using or sos_using or (obj_using is not None): + raise ValueError( + 'cannot remove variable {name} - it is still being used by constraints or the objective'.format( + name=v.name + ) + ) + del self._referenced_variables[v_id] + del self._vars[v_id] + + @abc.abstractmethod + def _remove_params(self, params: List[_ParamData]): + pass + + def remove_params(self, params: List[_ParamData]): + self._remove_params(params) + for p in params: + del self._params[id(p)] + + def remove_block(self, block): + self.remove_constraints( + list(block.component_data_objects(ctype=Constraint, descend_into=True, active=True)) + ) + self.remove_sos_constraints( + list(block.component_data_objects(ctype=SOSConstraint, descend_into=True, active=True)) + ) + if self._only_child_vars: + self.remove_variables( + list( + dict( + (id(var), var) + for var in block.component_data_objects( + ctype=Var, descend_into=True + ) + ).values() + ) + ) + self.remove_params( + list( + dict( + (id(p), p) + for p in block.component_data_objects( + ctype=Param, descend_into=True + ) + ).values() + ) + ) + + @abc.abstractmethod + def _update_variables(self, variables: List[_GeneralVarData]): + pass + + def update_variables(self, variables: List[_GeneralVarData]): + for v in variables: + self._vars[id(v)] = ( + v, + v._lb, + v._ub, + v.fixed, + v.domain.get_interval(), + v.value, + ) + self._update_variables(variables) + + @abc.abstractmethod + def update_params(self): + pass + + def update(self, timer: HierarchicalTimer = None): + if timer is None: + timer = HierarchicalTimer() + config = self.update_config + new_vars = [] + old_vars = [] + new_params = [] + old_params = [] + new_cons = [] + old_cons = [] + old_sos = [] + new_sos = [] + current_vars_dict = {} + current_cons_dict = {} + current_sos_dict = {} + timer.start('vars') + if self._only_child_vars and ( + config.check_for_new_or_removed_vars or config.update_vars + ): + current_vars_dict = { + id(v): v + for v in self._model.component_data_objects(Var, descend_into=True) + } + for v_id, v in current_vars_dict.items(): + if v_id not in self._vars: + new_vars.append(v) + for v_id, v_tuple in self._vars.items(): + if v_id not in current_vars_dict: + old_vars.append(v_tuple[0]) + elif config.update_vars: + start_vars = {v_id: v_tuple[0] for v_id, v_tuple in self._vars.items()} + timer.stop('vars') + timer.start('params') + if config.check_for_new_or_removed_params: + current_params_dict = {} + for p in self._model.component_objects(Param, descend_into=True): + if p.mutable: + for _p in p.values(): + current_params_dict[id(_p)] = _p + for p_id, p in current_params_dict.items(): + if p_id not in self._params: + new_params.append(p) + for p_id, p in self._params.items(): + if p_id not in current_params_dict: + old_params.append(p) + timer.stop('params') + timer.start('cons') + if config.check_for_new_or_removed_constraints or config.update_constraints: + current_cons_dict = { + c: None + for c in self._model.component_data_objects( + Constraint, descend_into=True, active=True + ) + } + current_sos_dict = { + c: None + for c in self._model.component_data_objects( + SOSConstraint, descend_into=True, active=True + ) + } + for c in current_cons_dict.keys(): + if c not in self._vars_referenced_by_con: + new_cons.append(c) + for c in current_sos_dict.keys(): + if c not in self._vars_referenced_by_con: + new_sos.append(c) + for c in self._vars_referenced_by_con.keys(): + if c not in current_cons_dict and c not in current_sos_dict: + if (c.ctype is Constraint) or ( + c.ctype is None and isinstance(c, _GeneralConstraintData) + ): + old_cons.append(c) + else: + assert (c.ctype is SOSConstraint) or ( + c.ctype is None and isinstance(c, _SOSConstraintData) + ) + old_sos.append(c) + self.remove_constraints(old_cons) + self.remove_sos_constraints(old_sos) + timer.stop('cons') + timer.start('params') + self.remove_params(old_params) + + # sticking this between removal and addition + # is important so that we don't do unnecessary work + if config.update_params: + self.update_params() + + self.add_params(new_params) + timer.stop('params') + timer.start('vars') + self.add_variables(new_vars) + timer.stop('vars') + timer.start('cons') + self.add_constraints(new_cons) + self.add_sos_constraints(new_sos) + new_cons_set = set(new_cons) + new_sos_set = set(new_sos) + new_vars_set = set(id(v) for v in new_vars) + cons_to_remove_and_add = {} + need_to_set_objective = False + if config.update_constraints: + cons_to_update = [] + sos_to_update = [] + for c in current_cons_dict.keys(): + if c not in new_cons_set: + cons_to_update.append(c) + for c in current_sos_dict.keys(): + if c not in new_sos_set: + sos_to_update.append(c) + for c in cons_to_update: + lower, body, upper = self._active_constraints[c] + new_lower, new_body, new_upper = c.lower, c.body, c.upper + if new_body is not body: + cons_to_remove_and_add[c] = None + continue + if new_lower is not lower: + if ( + type(new_lower) is NumericConstant + and type(lower) is NumericConstant + and new_lower.value == lower.value + ): + pass + else: + cons_to_remove_and_add[c] = None + continue + if new_upper is not upper: + if ( + type(new_upper) is NumericConstant + and type(upper) is NumericConstant + and new_upper.value == upper.value + ): + pass + else: + cons_to_remove_and_add[c] = None + continue + self.remove_sos_constraints(sos_to_update) + self.add_sos_constraints(sos_to_update) + timer.stop('cons') + timer.start('vars') + if self._only_child_vars and config.update_vars: + vars_to_check = [] + for v_id, v in current_vars_dict.items(): + if v_id not in new_vars_set: + vars_to_check.append(v) + elif config.update_vars: + end_vars = {v_id: v_tuple[0] for v_id, v_tuple in self._vars.items()} + vars_to_check = [v for v_id, v in end_vars.items() if v_id in start_vars] + if config.update_vars: + vars_to_update = [] + for v in vars_to_check: + _v, lb, ub, fixed, domain_interval, value = self._vars[id(v)] + if lb is not v._lb: + vars_to_update.append(v) + elif ub is not v._ub: + vars_to_update.append(v) + elif (fixed is not v.fixed) or (fixed and (value != v.value)): + vars_to_update.append(v) + if self.update_config.treat_fixed_vars_as_params: + for c in self._referenced_variables[id(v)][0]: + cons_to_remove_and_add[c] = None + if self._referenced_variables[id(v)][2] is not None: + need_to_set_objective = True + elif domain_interval != v.domain.get_interval(): + vars_to_update.append(v) + self.update_variables(vars_to_update) + timer.stop('vars') + timer.start('cons') + cons_to_remove_and_add = list(cons_to_remove_and_add.keys()) + self.remove_constraints(cons_to_remove_and_add) + self.add_constraints(cons_to_remove_and_add) + timer.stop('cons') + timer.start('named expressions') + if config.update_named_expressions: + cons_to_update = [] + for c, expr_list in self._named_expressions.items(): + if c in new_cons_set: + continue + for named_expr, old_expr in expr_list: + if named_expr.expr is not old_expr: + cons_to_update.append(c) + break + self.remove_constraints(cons_to_update) + self.add_constraints(cons_to_update) + for named_expr, old_expr in self._obj_named_expressions: + if named_expr.expr is not old_expr: + need_to_set_objective = True + break + timer.stop('named expressions') + timer.start('objective') + if self.update_config.check_for_new_objective: + pyomo_obj = get_objective(self._model) + if pyomo_obj is not self._objective: + need_to_set_objective = True + else: + pyomo_obj = self._objective + if self.update_config.update_objective: + if pyomo_obj is not None and pyomo_obj.expr is not self._objective_expr: + need_to_set_objective = True + elif pyomo_obj is not None and pyomo_obj.sense is not self._objective_sense: + # we can definitely do something faster here than resetting the whole objective + need_to_set_objective = True + if need_to_set_objective: + self.set_objective(pyomo_obj) + timer.stop('objective') + + # this has to be done after the objective and constraints in case the + # old objective/constraints use old variables + timer.start('vars') + self.remove_variables(old_vars) + timer.stop('vars') From 54fa01c9d1d280ae24e207e8407752322a0ed69a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 12:09:40 -0600 Subject: [PATCH 0054/1204] Correct broken import --- pyomo/contrib/appsi/writers/nl_writer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/writers/nl_writer.py b/pyomo/contrib/appsi/writers/nl_writer.py index e853e22c96f..f6edc076b04 100644 --- a/pyomo/contrib/appsi/writers/nl_writer.py +++ b/pyomo/contrib/appsi/writers/nl_writer.py @@ -13,7 +13,7 @@ from pyomo.core.kernel.objective import minimize from pyomo.common.collections import OrderedSet from pyomo.repn.plugins.ampl.ampl_ import set_pyomo_amplfunc_env -from pyomo.solver.base import PersistentSolverUtils +from pyomo.solver.util import PersistentSolverUtils from .config import WriterConfig from ..cmodel import cmodel, cmodel_available From 511a54cfa75a63bba431aebd7cd13b1b531f3767 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 12:44:36 -0600 Subject: [PATCH 0055/1204] Add in util tests; reformat with black --- pyomo/contrib/appsi/build.py | 2 +- .../contrib/appsi/examples/getting_started.py | 5 +- pyomo/contrib/appsi/solvers/cbc.py | 12 +- pyomo/contrib/appsi/solvers/cplex.py | 9 +- pyomo/contrib/appsi/solvers/gurobi.py | 25 +-- pyomo/contrib/appsi/solvers/highs.py | 24 ++- pyomo/contrib/appsi/solvers/ipopt.py | 12 +- .../solvers/tests/test_gurobi_persistent.py | 4 +- .../solvers/tests/test_persistent_solvers.py | 148 +++++++++++++----- pyomo/contrib/appsi/writers/config.py | 2 +- pyomo/contrib/appsi/writers/nl_writer.py | 1 + pyomo/solver/__init__.py | 1 - pyomo/solver/base.py | 14 +- pyomo/solver/config.py | 7 +- pyomo/solver/plugins.py | 2 +- pyomo/solver/solution.py | 13 +- pyomo/solver/tests/test_base.py | 51 +++--- pyomo/solver/tests/test_config.py | 10 ++ pyomo/solver/tests/test_solution.py | 10 ++ pyomo/solver/tests/test_util.py | 75 +++++++++ pyomo/solver/util.py | 23 ++- 21 files changed, 329 insertions(+), 121 deletions(-) diff --git a/pyomo/contrib/appsi/build.py b/pyomo/contrib/appsi/build.py index 37826cf85fb..3d37135665a 100644 --- a/pyomo/contrib/appsi/build.py +++ b/pyomo/contrib/appsi/build.py @@ -116,7 +116,7 @@ def run(self): pybind11.setup_helpers.MACOS = original_pybind11_setup_helpers_macos -class AppsiBuilder(): +class AppsiBuilder: def __call__(self, parallel): return build_appsi() diff --git a/pyomo/contrib/appsi/examples/getting_started.py b/pyomo/contrib/appsi/examples/getting_started.py index d65430e3c23..de5357776f4 100644 --- a/pyomo/contrib/appsi/examples/getting_started.py +++ b/pyomo/contrib/appsi/examples/getting_started.py @@ -32,7 +32,10 @@ def main(plot=True, n_points=200): for p_val in p_values: m.p.value = p_val res = opt.solve(m, timer=timer) - assert res.termination_condition == solver_base.TerminationCondition.convergenceCriteriaSatisfied + assert ( + res.termination_condition + == solver_base.TerminationCondition.convergenceCriteriaSatisfied + ) obj_values.append(res.best_feasible_objective) opt.load_vars([m.x]) x_values.append(m.x.value) diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index 30250f66a86..021ff76217d 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -229,7 +229,9 @@ def _parse_soln(self): termination_line = all_lines[0].lower() obj_val = None if termination_line.startswith('optimal'): - results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied + results.termination_condition = ( + TerminationCondition.convergenceCriteriaSatisfied + ) obj_val = float(termination_line.split()[-1]) elif 'infeasible' in termination_line: results.termination_condition = TerminationCondition.provenInfeasible @@ -304,7 +306,8 @@ def _parse_soln(self): self._reduced_costs[v_id] = (v, -rc_val) if ( - results.termination_condition == TerminationCondition.convergenceCriteriaSatisfied + results.termination_condition + == TerminationCondition.convergenceCriteriaSatisfied and self.config.load_solution ): for v_id, (v, val) in self._primal_sol.items(): @@ -313,7 +316,10 @@ def _parse_soln(self): results.best_feasible_objective = None else: results.best_feasible_objective = obj_val - elif results.termination_condition == TerminationCondition.convergenceCriteriaSatisfied: + elif ( + results.termination_condition + == TerminationCondition.convergenceCriteriaSatisfied + ): if self._writer.get_active_objective() is None: results.best_feasible_objective = None else: diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 0b1bd552370..759bd7ff9d5 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -282,7 +282,9 @@ def _postsolve(self, timer: HierarchicalTimer, solve_time): status = cpxprob.solution.get_status() if status in [1, 101, 102]: - results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied + results.termination_condition = ( + TerminationCondition.convergenceCriteriaSatisfied + ) elif status in [2, 40, 118, 133, 134]: results.termination_condition = TerminationCondition.unbounded elif status in [4, 119, 134]: @@ -334,7 +336,10 @@ def _postsolve(self, timer: HierarchicalTimer, solve_time): 'results.best_feasible_objective before loading a solution.' ) else: - if results.termination_condition != TerminationCondition.convergenceCriteriaSatisfied: + if ( + results.termination_condition + != TerminationCondition.convergenceCriteriaSatisfied + ): logger.warning( 'Loading a feasible but suboptimal solution. ' 'Please set load_solution=False and check ' diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index ba89c3e5d57..c2db835922d 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -97,7 +97,7 @@ def __init__(self, solver): self.solution_loader = GurobiSolutionLoader(solver=solver) -class _MutableLowerBound(): +class _MutableLowerBound: def __init__(self, expr): self.var = None self.expr = expr @@ -106,7 +106,7 @@ def update(self): self.var.setAttr('lb', value(self.expr)) -class _MutableUpperBound(): +class _MutableUpperBound: def __init__(self, expr): self.var = None self.expr = expr @@ -115,7 +115,7 @@ def update(self): self.var.setAttr('ub', value(self.expr)) -class _MutableLinearCoefficient(): +class _MutableLinearCoefficient: def __init__(self): self.expr = None self.var = None @@ -126,7 +126,7 @@ def update(self): self.gurobi_model.chgCoeff(self.con, self.var, value(self.expr)) -class _MutableRangeConstant(): +class _MutableRangeConstant: def __init__(self): self.lhs_expr = None self.rhs_expr = None @@ -142,7 +142,7 @@ def update(self): slack.ub = rhs_val - lhs_val -class _MutableConstant(): +class _MutableConstant: def __init__(self): self.expr = None self.con = None @@ -151,7 +151,7 @@ def update(self): self.con.rhs = value(self.expr) -class _MutableQuadraticConstraint(): +class _MutableQuadraticConstraint: def __init__( self, gurobi_model, gurobi_con, constant, linear_coefs, quadratic_coefs ): @@ -186,7 +186,7 @@ def get_updated_rhs(self): return value(self.constant.expr) -class _MutableObjective(): +class _MutableObjective: def __init__(self, gurobi_model, constant, linear_coefs, quadratic_coefs): self.gurobi_model = gurobi_model self.constant = constant @@ -214,7 +214,7 @@ def get_updated_expression(self): return gurobi_expr -class _MutableQuadraticCoefficient(): +class _MutableQuadraticCoefficient: def __init__(self): self.expr = None self.var1 = None @@ -869,7 +869,9 @@ def _postsolve(self, timer: HierarchicalTimer): if status == grb.LOADED: # problem is loaded, but no solution results.termination_condition = TerminationCondition.unknown elif status == grb.OPTIMAL: # optimal - results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied + results.termination_condition = ( + TerminationCondition.convergenceCriteriaSatisfied + ) elif status == grb.INFEASIBLE: results.termination_condition = TerminationCondition.provenInfeasible elif status == grb.INF_OR_UNBD: @@ -920,7 +922,10 @@ def _postsolve(self, timer: HierarchicalTimer): timer.start('load solution') if config.load_solution: if gprob.SolCount > 0: - if results.termination_condition != TerminationCondition.convergenceCriteriaSatisfied: + if ( + results.termination_condition + != TerminationCondition.convergenceCriteriaSatisfied + ): logger.warning( 'Loading a feasible but suboptimal solution. ' 'Please set load_solution=False and check ' diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 4a23d7c309a..3b7c92ed9e8 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -67,7 +67,7 @@ def __init__(self, solver): self.solution_loader = PersistentSolutionLoader(solver=solver) -class _MutableVarBounds(): +class _MutableVarBounds: def __init__(self, lower_expr, upper_expr, pyomo_var_id, var_map, highs): self.pyomo_var_id = pyomo_var_id self.lower_expr = lower_expr @@ -82,7 +82,7 @@ def update(self): self.highs.changeColBounds(col_ndx, lb, ub) -class _MutableLinearCoefficient(): +class _MutableLinearCoefficient: def __init__(self, pyomo_con, pyomo_var_id, con_map, var_map, expr, highs): self.expr = expr self.highs = highs @@ -97,7 +97,7 @@ def update(self): self.highs.changeCoeff(row_ndx, col_ndx, value(self.expr)) -class _MutableObjectiveCoefficient(): +class _MutableObjectiveCoefficient: def __init__(self, pyomo_var_id, var_map, expr, highs): self.expr = expr self.highs = highs @@ -109,7 +109,7 @@ def update(self): self.highs.changeColCost(col_ndx, value(self.expr)) -class _MutableObjectiveOffset(): +class _MutableObjectiveOffset: def __init__(self, expr, highs): self.expr = expr self.highs = highs @@ -118,7 +118,7 @@ def update(self): self.highs.changeObjectiveOffset(value(self.expr)) -class _MutableConstraintBounds(): +class _MutableConstraintBounds: def __init__(self, lower_expr, upper_expr, pyomo_con, con_map, highs): self.lower_expr = lower_expr self.upper_expr = upper_expr @@ -604,7 +604,9 @@ def _postsolve(self, timer: HierarchicalTimer): elif status == highspy.HighsModelStatus.kModelEmpty: results.termination_condition = TerminationCondition.unknown elif status == highspy.HighsModelStatus.kOptimal: - results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied + results.termination_condition = ( + TerminationCondition.convergenceCriteriaSatisfied + ) elif status == highspy.HighsModelStatus.kInfeasible: results.termination_condition = TerminationCondition.provenInfeasible elif status == highspy.HighsModelStatus.kUnboundedOrInfeasible: @@ -627,7 +629,10 @@ def _postsolve(self, timer: HierarchicalTimer): timer.start('load solution') self._sol = highs.getSolution() has_feasible_solution = False - if results.termination_condition == TerminationCondition.convergenceCriteriaSatisfied: + if ( + results.termination_condition + == TerminationCondition.convergenceCriteriaSatisfied + ): has_feasible_solution = True elif results.termination_condition in { TerminationCondition.objectiveLimit, @@ -639,7 +644,10 @@ def _postsolve(self, timer: HierarchicalTimer): if config.load_solution: if has_feasible_solution: - if results.termination_condition != TerminationCondition.convergenceCriteriaSatisfied: + if ( + results.termination_condition + != TerminationCondition.convergenceCriteriaSatisfied + ): logger.warning( 'Loading a feasible but suboptimal solution. ' 'Please set load_solution=False and check ' diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index 467040a0967..6c4b7601d2c 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -300,7 +300,9 @@ def _parse_sol(self): termination_line = all_lines[1] if 'Optimal Solution Found' in termination_line: - results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied + results.termination_condition = ( + TerminationCondition.convergenceCriteriaSatisfied + ) elif 'Problem may be infeasible' in termination_line: results.termination_condition = TerminationCondition.locallyInfeasible elif 'problem might be unbounded' in termination_line: @@ -381,7 +383,8 @@ def _parse_sol(self): self._reduced_costs[var] = 0 if ( - results.termination_condition == TerminationCondition.convergenceCriteriaSatisfied + results.termination_condition + == TerminationCondition.convergenceCriteriaSatisfied and self.config.load_solution ): for v, val in self._primal_sol.items(): @@ -392,7 +395,10 @@ def _parse_sol(self): results.best_feasible_objective = value( self._writer.get_active_objective().expr ) - elif results.termination_condition == TerminationCondition.convergenceCriteriaSatisfied: + elif ( + results.termination_condition + == TerminationCondition.convergenceCriteriaSatisfied + ): if self._writer.get_active_objective() is None: results.best_feasible_objective = None else: diff --git a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py index 877d0971f2b..9fdce87b8de 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py @@ -157,7 +157,9 @@ def test_lp(self): res = opt.solve(self.m) self.assertAlmostEqual(x + y, res.best_feasible_objective) self.assertAlmostEqual(x + y, res.best_objective_bound) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertTrue(res.best_feasible_objective is not None) self.assertAlmostEqual(x, self.m.x.value) self.assertAlmostEqual(y, self.m.y.value) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 1f357acf209..bf92244ec36 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -18,11 +18,7 @@ if not param_available: raise unittest.SkipTest('Parameterized is not available.') -all_solvers = [ - ('gurobi', Gurobi), - ('ipopt', Ipopt), - ('highs', Highs), -] +all_solvers = [('gurobi', Gurobi), ('ipopt', Ipopt), ('highs', Highs)] mip_solvers = [('gurobi', Gurobi), ('highs', Highs)] nlp_solvers = [('ipopt', Ipopt)] qcp_solvers = [('gurobi', Gurobi), ('ipopt', Ipopt)] @@ -88,7 +84,9 @@ def test_remove_variable_and_objective( m.x = pe.Var(bounds=(2, None)) m.obj = pe.Objective(expr=m.x) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertAlmostEqual(m.x.value, 2) del m.x @@ -96,7 +94,9 @@ def test_remove_variable_and_objective( m.x = pe.Var(bounds=(2, None)) m.obj = pe.Objective(expr=m.x) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertAlmostEqual(m.x.value, 2) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) @@ -156,13 +156,17 @@ def test_range_constraint( m.obj = pe.Objective(expr=m.x) m.c = pe.Constraint(expr=(-1, m.x, 1)) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertAlmostEqual(m.x.value, -1) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c], 1) m.obj.sense = pe.maximize res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertAlmostEqual(m.x.value, 1) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c], 1) @@ -179,7 +183,9 @@ def test_reduced_costs( m.y = pe.Var(bounds=(-2, 2)) m.obj = pe.Objective(expr=3 * m.x + 4 * m.y) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertAlmostEqual(m.x.value, -1) self.assertAlmostEqual(m.y.value, -2) rc = opt.get_reduced_costs() @@ -197,13 +203,17 @@ def test_reduced_costs2( m.x = pe.Var(bounds=(-1, 1)) m.obj = pe.Objective(expr=m.x) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertAlmostEqual(m.x.value, -1) rc = opt.get_reduced_costs() self.assertAlmostEqual(rc[m.x], 1) m.obj.sense = pe.maximize res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertAlmostEqual(m.x.value, 1) rc = opt.get_reduced_costs() self.assertAlmostEqual(rc[m.x], 1) @@ -233,7 +243,10 @@ def test_param_changes( m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, + TerminationCondition.convergenceCriteriaSatisfied, + ) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) @@ -271,7 +284,10 @@ def test_immutable_param( m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, + TerminationCondition.convergenceCriteriaSatisfied, + ) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) @@ -305,7 +321,10 @@ def test_equality( m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, + TerminationCondition.convergenceCriteriaSatisfied, + ) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) @@ -345,7 +364,10 @@ def test_linear_expression( m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, + TerminationCondition.convergenceCriteriaSatisfied, + ) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) self.assertTrue(res.best_objective_bound <= m.y.value) @@ -375,7 +397,10 @@ def test_no_objective( m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, + TerminationCondition.convergenceCriteriaSatisfied, + ) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertEqual(res.best_feasible_objective, None) @@ -404,7 +429,9 @@ def test_add_remove_cons( m.c1 = pe.Constraint(expr=m.y >= a1 * m.x + b1) m.c2 = pe.Constraint(expr=m.y >= a2 * m.x + b2) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) @@ -415,7 +442,9 @@ def test_add_remove_cons( m.c3 = pe.Constraint(expr=m.y >= a3 * m.x + b3) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertAlmostEqual(m.x.value, (b3 - b1) / (a1 - a3)) self.assertAlmostEqual(m.y.value, a1 * (b3 - b1) / (a1 - a3) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) @@ -427,7 +456,9 @@ def test_add_remove_cons( del m.c3 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertAlmostEqual(res.best_feasible_objective, m.y.value) @@ -453,7 +484,9 @@ def test_results_infeasible( res = opt.solve(m) opt.config.load_solution = False res = opt.solve(m) - self.assertNotEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertNotEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) if opt_class is Ipopt: acceptable_termination_conditions = { TerminationCondition.provenInfeasible, @@ -485,7 +518,9 @@ def test_results_infeasible( res.solution_loader.get_reduced_costs() @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) - def test_duals(self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars): + def test_duals( + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + ): opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest @@ -747,7 +782,10 @@ def test_mutable_param_with_range( m.c2.value = float(c2) m.obj.sense = sense res: Results = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, + TerminationCondition.convergenceCriteriaSatisfied, + ) if sense is pe.minimize: self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2), 6) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1, 6) @@ -784,7 +822,9 @@ def test_add_and_remove_vars( opt.update_config.check_for_new_or_removed_vars = False opt.config.load_solution = False res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) opt.load_vars() self.assertAlmostEqual(m.y.value, -1) m.x = pe.Var() @@ -798,7 +838,9 @@ def test_add_and_remove_vars( opt.add_variables([m.x]) opt.add_constraints([m.c1, m.c2]) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) opt.load_vars() self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) @@ -807,7 +849,9 @@ def test_add_and_remove_vars( opt.remove_variables([m.x]) m.x.value = None res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) opt.load_vars() self.assertEqual(m.x.value, None) self.assertAlmostEqual(m.y.value, -1) @@ -815,7 +859,9 @@ def test_add_and_remove_vars( opt.load_vars([m.x]) @parameterized.expand(input=_load_tests(nlp_solvers, only_child_vars_options)) - def test_exp(self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars): + def test_exp( + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + ): opt = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest @@ -829,7 +875,9 @@ def test_exp(self, name: str, opt_class: Type[PersistentSolverBase], only_child_ self.assertAlmostEqual(m.y.value, 0.6529186341994245) @parameterized.expand(input=_load_tests(nlp_solvers, only_child_vars_options)) - def test_log(self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars): + def test_log( + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + ): opt = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest @@ -868,7 +916,9 @@ def test_with_numpy( ) ) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) @@ -1011,9 +1061,7 @@ def test_time_limit( opt.config.time_limit = 0 opt.config.load_solution = False res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.maxTimeLimit - ) + self.assertEqual(res.termination_condition, TerminationCondition.maxTimeLimit) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_objective_changes( @@ -1183,7 +1231,9 @@ def test_with_gdp( self.assertAlmostEqual(m.y.value, 1) @parameterized.expand(input=all_solvers) - def test_variables_elsewhere(self, name: str, opt_class: Type[PersistentSolverBase]): + def test_variables_elsewhere( + self, name: str, opt_class: Type[PersistentSolverBase] + ): opt: PersistentSolverBase = opt_class(only_child_vars=False) if not opt.available(): raise unittest.SkipTest @@ -1197,20 +1247,26 @@ def test_variables_elsewhere(self, name: str, opt_class: Type[PersistentSolverBa m.b.c2 = pe.Constraint(expr=m.y >= -m.x) res = opt.solve(m.b) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertAlmostEqual(res.best_feasible_objective, 1) self.assertAlmostEqual(m.x.value, -1) self.assertAlmostEqual(m.y.value, 1) m.x.setlb(0) res = opt.solve(m.b) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertAlmostEqual(res.best_feasible_objective, 2) self.assertAlmostEqual(m.x.value, 0) self.assertAlmostEqual(m.y.value, 2) @parameterized.expand(input=all_solvers) - def test_variables_elsewhere2(self, name: str, opt_class: Type[PersistentSolverBase]): + def test_variables_elsewhere2( + self, name: str, opt_class: Type[PersistentSolverBase] + ): opt: PersistentSolverBase = opt_class(only_child_vars=False) if not opt.available(): raise unittest.SkipTest @@ -1227,7 +1283,9 @@ def test_variables_elsewhere2(self, name: str, opt_class: Type[PersistentSolverB m.c4 = pe.Constraint(expr=m.y >= -m.z + 1) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertAlmostEqual(res.best_feasible_objective, 1) sol = res.solution_loader.get_primals() self.assertIn(m.x, sol) @@ -1237,7 +1295,9 @@ def test_variables_elsewhere2(self, name: str, opt_class: Type[PersistentSolverB del m.c3 del m.c4 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertAlmostEqual(res.best_feasible_objective, 0) sol = res.solution_loader.get_primals() self.assertIn(m.x, sol) @@ -1245,7 +1305,9 @@ def test_variables_elsewhere2(self, name: str, opt_class: Type[PersistentSolverB self.assertNotIn(m.z, sol) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) - def test_bug_1(self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars): + def test_bug_1( + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + ): opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest @@ -1259,12 +1321,16 @@ def test_bug_1(self, name: str, opt_class: Type[PersistentSolverBase], only_chil m.c = pe.Constraint(expr=m.y >= m.p * m.x) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertAlmostEqual(res.best_feasible_objective, 0) m.p.value = 1 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertAlmostEqual(res.best_feasible_objective, 3) diff --git a/pyomo/contrib/appsi/writers/config.py b/pyomo/contrib/appsi/writers/config.py index 4376b9284fa..2a4e638f097 100644 --- a/pyomo/contrib/appsi/writers/config.py +++ b/pyomo/contrib/appsi/writers/config.py @@ -1,3 +1,3 @@ -class WriterConfig(): +class WriterConfig: def __init__(self): self.symbolic_solver_labels = False diff --git a/pyomo/contrib/appsi/writers/nl_writer.py b/pyomo/contrib/appsi/writers/nl_writer.py index f6edc076b04..1be657ba762 100644 --- a/pyomo/contrib/appsi/writers/nl_writer.py +++ b/pyomo/contrib/appsi/writers/nl_writer.py @@ -18,6 +18,7 @@ from .config import WriterConfig from ..cmodel import cmodel, cmodel_available + class NLWriter(PersistentSolverUtils): def __init__(self, only_child_vars=False): super().__init__(only_child_vars=only_child_vars) diff --git a/pyomo/solver/__init__.py b/pyomo/solver/__init__.py index 13b8b463662..a3c2e0e95e8 100644 --- a/pyomo/solver/__init__.py +++ b/pyomo/solver/__init__.py @@ -13,4 +13,3 @@ from . import config from . import solution from . import util - diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 332f8cddff4..f0a07d0aca3 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -11,15 +11,7 @@ import abc import enum -from typing import ( - Sequence, - Dict, - Optional, - Mapping, - NoReturn, - List, - Tuple, -) +from typing import Sequence, Dict, Optional, Mapping, NoReturn, List, Tuple from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.param import _ParamData @@ -47,7 +39,6 @@ from pyomo.solver.util import get_objective - class TerminationCondition(enum.Enum): """ An enumeration for checking the termination condition of solvers @@ -97,7 +88,7 @@ class SolutionStatus(enum.IntEnum): """ An enumeration for interpreting the result of a termination. This describes the designated status by the solver to be loaded back into the model. - + For now, we are choosing to use IntEnum such that return values are numerically assigned in increasing order. """ @@ -396,7 +387,6 @@ def update_params(self): pass - # Everything below here preserves backwards compatibility legacy_termination_condition_map = { diff --git a/pyomo/solver/config.py b/pyomo/solver/config.py index ab9c30a0549..f446dc714db 100644 --- a/pyomo/solver/config.py +++ b/pyomo/solver/config.py @@ -10,7 +10,12 @@ # ___________________________________________________________________________ from typing import Optional -from pyomo.common.config import ConfigDict, ConfigValue, NonNegativeFloat, NonNegativeInt +from pyomo.common.config import ( + ConfigDict, + ConfigValue, + NonNegativeFloat, + NonNegativeInt, +) class InterfaceConfig(ConfigDict): diff --git a/pyomo/solver/plugins.py b/pyomo/solver/plugins.py index e15d1a585b1..7e479474605 100644 --- a/pyomo/solver/plugins.py +++ b/pyomo/solver/plugins.py @@ -1,5 +1,5 @@ from .base import SolverFactory + def load(): pass - diff --git a/pyomo/solver/solution.py b/pyomo/solver/solution.py index 2d422736f2c..1ef79050701 100644 --- a/pyomo/solver/solution.py +++ b/pyomo/solver/solution.py @@ -10,14 +10,7 @@ # ___________________________________________________________________________ import abc -from typing import ( - Sequence, - Dict, - Optional, - Mapping, - MutableMapping, - NoReturn, -) +from typing import Sequence, Dict, Optional, Mapping, MutableMapping, NoReturn from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.var import _GeneralVarData @@ -250,7 +243,3 @@ def get_reduced_costs( def invalidate(self): self._valid = False - - - - diff --git a/pyomo/solver/tests/test_base.py b/pyomo/solver/tests/test_base.py index 3c389175d08..dcbe13e8230 100644 --- a/pyomo/solver/tests/test_base.py +++ b/pyomo/solver/tests/test_base.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common import unittest from pyomo.solver import base import pyomo.environ as pe @@ -7,24 +18,28 @@ class TestTerminationCondition(unittest.TestCase): def test_member_list(self): member_list = base.TerminationCondition._member_names_ - expected_list = ['unknown', - 'convergenceCriteriaSatisfied', - 'maxTimeLimit', - 'iterationLimit', - 'objectiveLimit', - 'minStepLength', - 'unbounded', - 'provenInfeasible', - 'locallyInfeasible', - 'infeasibleOrUnbounded', - 'error', - 'interrupted', - 'licensingProblems'] + expected_list = [ + 'unknown', + 'convergenceCriteriaSatisfied', + 'maxTimeLimit', + 'iterationLimit', + 'objectiveLimit', + 'minStepLength', + 'unbounded', + 'provenInfeasible', + 'locallyInfeasible', + 'infeasibleOrUnbounded', + 'error', + 'interrupted', + 'licensingProblems', + ] self.assertEqual(member_list, expected_list) def test_codes(self): self.assertEqual(base.TerminationCondition.unknown.value, 42) - self.assertEqual(base.TerminationCondition.convergenceCriteriaSatisfied.value, 0) + self.assertEqual( + base.TerminationCondition.convergenceCriteriaSatisfied.value, 0 + ) self.assertEqual(base.TerminationCondition.maxTimeLimit.value, 1) self.assertEqual(base.TerminationCondition.iterationLimit.value, 2) self.assertEqual(base.TerminationCondition.objectiveLimit.value, 3) @@ -67,7 +82,9 @@ def test_solver_availability(self): self.instance.Availability._value_ = 1 self.assertTrue(self.instance.Availability.__bool__(self.instance.Availability)) self.instance.Availability._value_ = -1 - self.assertFalse(self.instance.Availability.__bool__(self.instance.Availability)) + self.assertFalse( + self.instance.Availability.__bool__(self.instance.Availability) + ) class TestResults(unittest.TestCase): @@ -75,9 +92,7 @@ def test_uninitialized(self): res = base.Results() self.assertIsNone(res.best_feasible_objective) self.assertIsNone(res.best_objective_bound) - self.assertEqual( - res.termination_condition, base.TerminationCondition.unknown - ) + self.assertEqual(res.termination_condition, base.TerminationCondition.unknown) with self.assertRaisesRegex( RuntimeError, '.*does not currently have a valid solution.*' diff --git a/pyomo/solver/tests/test_config.py b/pyomo/solver/tests/test_config.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/solver/tests/test_config.py +++ b/pyomo/solver/tests/test_config.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/solver/tests/test_solution.py b/pyomo/solver/tests/test_solution.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/solver/tests/test_solution.py +++ b/pyomo/solver/tests/test_solution.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/solver/tests/test_util.py b/pyomo/solver/tests/test_util.py index e69de29bb2d..737a271d603 100644 --- a/pyomo/solver/tests/test_util.py +++ b/pyomo/solver/tests/test_util.py @@ -0,0 +1,75 @@ +# ___________________________________________________________________________ +# +# 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.common import unittest +import pyomo.environ as pyo +from pyomo.solver.util import collect_vars_and_named_exprs, get_objective +from typing import Callable +from pyomo.common.gsl import find_GSL + + +class TestGenericUtils(unittest.TestCase): + def basics_helper(self, collector: Callable, *args): + m = pyo.ConcreteModel() + m.x = pyo.Var() + m.y = pyo.Var() + m.z = pyo.Var() + m.E = pyo.Expression(expr=2 * m.z + 1) + m.y.fix(3) + e = m.x * m.y + m.x * m.E + named_exprs, var_list, fixed_vars, external_funcs = collector(e, *args) + self.assertEqual([m.E], named_exprs) + self.assertEqual([m.x, m.y, m.z], var_list) + self.assertEqual([m.y], fixed_vars) + self.assertEqual([], external_funcs) + + def test_collect_vars_basics(self): + self.basics_helper(collect_vars_and_named_exprs) + + def external_func_helper(self, collector: Callable, *args): + DLL = find_GSL() + if not DLL: + self.skipTest('Could not find amplgsl.dll library') + + m = pyo.ConcreteModel() + m.x = pyo.Var() + m.y = pyo.Var() + m.z = pyo.Var() + m.hypot = pyo.ExternalFunction(library=DLL, function='gsl_hypot') + func = m.hypot(m.x, m.x * m.y) + m.E = pyo.Expression(expr=2 * func) + m.y.fix(3) + e = m.z + m.x * m.E + named_exprs, var_list, fixed_vars, external_funcs = collector(e, *args) + self.assertEqual([m.E], named_exprs) + self.assertEqual([m.z, m.x, m.y], var_list) + self.assertEqual([m.y], fixed_vars) + self.assertEqual([func], external_funcs) + + def test_collect_vars_external(self): + self.external_func_helper(collect_vars_and_named_exprs) + + def simple_model(self): + model = pyo.ConcreteModel() + model.x = pyo.Var([1, 2], domain=pyo.NonNegativeReals) + model.OBJ = pyo.Objective(expr=2 * model.x[1] + 3 * model.x[2]) + model.Constraint1 = pyo.Constraint(expr=3 * model.x[1] + 4 * model.x[2] >= 1) + return model + + def test_get_objective_success(self): + model = self.simple_model() + self.assertEqual(model.OBJ, get_objective(model)) + + def test_get_objective_raise(self): + model = self.simple_model() + model.OBJ2 = pyo.Objective(expr=model.x[1] - 4 * model.x[2]) + with self.assertRaises(ValueError): + get_objective(model) diff --git a/pyomo/solver/util.py b/pyomo/solver/util.py index fa2782f6bc4..1fb1738470b 100644 --- a/pyomo/solver/util.py +++ b/pyomo/solver/util.py @@ -289,10 +289,16 @@ def add_block(self, block): ) ) self.add_constraints( - list(block.component_data_objects(Constraint, descend_into=True, active=True)) + list( + block.component_data_objects(Constraint, descend_into=True, active=True) + ) ) self.add_sos_constraints( - list(block.component_data_objects(SOSConstraint, descend_into=True, active=True)) + list( + block.component_data_objects( + SOSConstraint, descend_into=True, active=True + ) + ) ) obj = get_objective(block) if obj is not None: @@ -375,10 +381,18 @@ def remove_params(self, params: List[_ParamData]): def remove_block(self, block): self.remove_constraints( - list(block.component_data_objects(ctype=Constraint, descend_into=True, active=True)) + list( + block.component_data_objects( + ctype=Constraint, descend_into=True, active=True + ) + ) ) self.remove_sos_constraints( - list(block.component_data_objects(ctype=SOSConstraint, descend_into=True, active=True)) + list( + block.component_data_objects( + ctype=SOSConstraint, descend_into=True, active=True + ) + ) ) if self._only_child_vars: self.remove_variables( @@ -633,4 +647,3 @@ def update(self, timer: HierarchicalTimer = None): timer.start('vars') self.remove_variables(old_vars) timer.stop('vars') - From 6cf3f8221761e6f97ebe0b92695debf78312ebf6 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 13:41:44 -0600 Subject: [PATCH 0056/1204] Add more unit tests --- pyomo/solver/tests/test_base.py | 49 +++++++++++++++++++++++++++++++ pyomo/solver/tests/test_config.py | 47 +++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/pyomo/solver/tests/test_base.py b/pyomo/solver/tests/test_base.py index dcbe13e8230..355941a1eb1 100644 --- a/pyomo/solver/tests/test_base.py +++ b/pyomo/solver/tests/test_base.py @@ -87,6 +87,55 @@ def test_solver_availability(self): ) +class TestPersistentSolverBase(unittest.TestCase): + def test_abstract_member_list(self): + expected_list = ['remove_params', + 'version', + 'config', + 'update_variables', + 'remove_variables', + 'add_constraints', + 'get_primals', + 'set_instance', + 'set_objective', + 'update_params', + 'remove_block', + 'add_block', + 'available', + 'update_config', + 'add_params', + 'remove_constraints', + 'add_variables', + 'solve'] + member_list = list(base.PersistentSolverBase.__abstractmethods__) + self.assertEqual(sorted(expected_list), sorted(member_list)) + + @unittest.mock.patch.multiple(base.PersistentSolverBase, __abstractmethods__=set()) + def test_persistent_solver_base(self): + self.instance = base.PersistentSolverBase() + self.assertTrue(self.instance.is_persistent()) + self.assertEqual(self.instance.get_primals(), None) + self.assertEqual(self.instance.update_config, None) + self.assertEqual(self.instance.set_instance(None), None) + self.assertEqual(self.instance.add_variables(None), None) + self.assertEqual(self.instance.add_params(None), None) + self.assertEqual(self.instance.add_constraints(None), None) + self.assertEqual(self.instance.add_block(None), None) + self.assertEqual(self.instance.remove_variables(None), None) + self.assertEqual(self.instance.remove_params(None), None) + self.assertEqual(self.instance.remove_constraints(None), None) + self.assertEqual(self.instance.remove_block(None), None) + self.assertEqual(self.instance.set_objective(None), None) + self.assertEqual(self.instance.update_variables(None), None) + self.assertEqual(self.instance.update_params(), None) + with self.assertRaises(NotImplementedError): + self.instance.get_duals() + with self.assertRaises(NotImplementedError): + self.instance.get_slacks() + with self.assertRaises(NotImplementedError): + self.instance.get_reduced_costs() + + class TestResults(unittest.TestCase): def test_uninitialized(self): res = base.Results() diff --git a/pyomo/solver/tests/test_config.py b/pyomo/solver/tests/test_config.py index d93cfd77b3c..378facb58d2 100644 --- a/pyomo/solver/tests/test_config.py +++ b/pyomo/solver/tests/test_config.py @@ -8,3 +8,50 @@ # rights in this software. # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ + +from pyomo.common import unittest +from pyomo.solver.config import InterfaceConfig, MIPInterfaceConfig + +class TestInterfaceConfig(unittest.TestCase): + + def test_interface_default_instantiation(self): + config = InterfaceConfig() + self.assertEqual(config._description, None) + self.assertEqual(config._visibility, 0) + self.assertFalse(config.tee) + self.assertTrue(config.load_solution) + self.assertFalse(config.symbolic_solver_labels) + self.assertFalse(config.report_timing) + + def test_interface_custom_instantiation(self): + config = InterfaceConfig(description="A description") + config.tee = True + self.assertTrue(config.tee) + self.assertEqual(config._description, "A description") + self.assertFalse(config.time_limit) + config.time_limit = 1.0 + self.assertEqual(config.time_limit, 1.0) + + +class TestMIPInterfaceConfig(unittest.TestCase): + def test_interface_default_instantiation(self): + config = MIPInterfaceConfig() + self.assertEqual(config._description, None) + self.assertEqual(config._visibility, 0) + self.assertFalse(config.tee) + self.assertTrue(config.load_solution) + self.assertFalse(config.symbolic_solver_labels) + self.assertFalse(config.report_timing) + self.assertEqual(config.mip_gap, None) + self.assertFalse(config.relax_integrality) + + def test_interface_custom_instantiation(self): + config = MIPInterfaceConfig(description="A description") + config.tee = True + self.assertTrue(config.tee) + self.assertEqual(config._description, "A description") + self.assertFalse(config.time_limit) + config.time_limit = 1.0 + self.assertEqual(config.time_limit, 1.0) + config.mip_gap = 2.5 + self.assertEqual(config.mip_gap, 2.5) From 4d3191aa11d5f69695e4ea469655a896b09f35d1 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 13:49:07 -0600 Subject: [PATCH 0057/1204] Add more unit tests --- pyomo/solver/tests/test_solution.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pyomo/solver/tests/test_solution.py b/pyomo/solver/tests/test_solution.py index d93cfd77b3c..c4c2f790b55 100644 --- a/pyomo/solver/tests/test_solution.py +++ b/pyomo/solver/tests/test_solution.py @@ -8,3 +8,23 @@ # rights in this software. # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ + +from pyomo.common import unittest +from pyomo.solver import solution + +class TestPersistentSolverBase(unittest.TestCase): + def test_abstract_member_list(self): + expected_list = ['get_primals'] + member_list = list(solution.SolutionLoaderBase.__abstractmethods__) + self.assertEqual(sorted(expected_list), sorted(member_list)) + + @unittest.mock.patch.multiple(solution.SolutionLoaderBase, __abstractmethods__=set()) + def test_solution_loader_base(self): + self.instance = solution.SolutionLoaderBase() + self.assertEqual(self.instance.get_primals(), None) + with self.assertRaises(NotImplementedError): + self.instance.get_duals() + with self.assertRaises(NotImplementedError): + self.instance.get_slacks() + with self.assertRaises(NotImplementedError): + self.instance.get_reduced_costs() From 9dffd2605bd6e7316a3e7952cbca5793cb4af7db Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 13:50:48 -0600 Subject: [PATCH 0058/1204] Remove APPSI utils -> have been moved to pyomo.solver.util --- pyomo/contrib/appsi/utils/__init__.py | 2 - .../utils/collect_vars_and_named_exprs.py | 50 ----------------- pyomo/contrib/appsi/utils/get_objective.py | 12 ---- pyomo/contrib/appsi/utils/tests/__init__.py | 0 .../test_collect_vars_and_named_exprs.py | 56 ------------------- 5 files changed, 120 deletions(-) delete mode 100644 pyomo/contrib/appsi/utils/__init__.py delete mode 100644 pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py delete mode 100644 pyomo/contrib/appsi/utils/get_objective.py delete mode 100644 pyomo/contrib/appsi/utils/tests/__init__.py delete mode 100644 pyomo/contrib/appsi/utils/tests/test_collect_vars_and_named_exprs.py diff --git a/pyomo/contrib/appsi/utils/__init__.py b/pyomo/contrib/appsi/utils/__init__.py deleted file mode 100644 index f665736fd4a..00000000000 --- a/pyomo/contrib/appsi/utils/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .get_objective import get_objective -from .collect_vars_and_named_exprs import collect_vars_and_named_exprs diff --git a/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py b/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py deleted file mode 100644 index bfbbf5aecdf..00000000000 --- a/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py +++ /dev/null @@ -1,50 +0,0 @@ -from pyomo.core.expr.visitor import ExpressionValueVisitor, nonpyomo_leaf_types -import pyomo.core.expr as EXPR - - -class _VarAndNamedExprCollector(ExpressionValueVisitor): - def __init__(self): - self.named_expressions = {} - self.variables = {} - self.fixed_vars = {} - self._external_functions = {} - - def visit(self, node, values): - pass - - def visiting_potential_leaf(self, node): - if type(node) in nonpyomo_leaf_types: - return True, None - - if node.is_variable_type(): - self.variables[id(node)] = node - if node.is_fixed(): - self.fixed_vars[id(node)] = node - return True, None - - if node.is_named_expression_type(): - self.named_expressions[id(node)] = node - return False, None - - if type(node) is EXPR.ExternalFunctionExpression: - self._external_functions[id(node)] = node - return False, None - - if node.is_expression_type(): - return False, None - - return True, None - - -_visitor = _VarAndNamedExprCollector() - - -def collect_vars_and_named_exprs(expr): - _visitor.__init__() - _visitor.dfs_postorder_stack(expr) - return ( - list(_visitor.named_expressions.values()), - list(_visitor.variables.values()), - list(_visitor.fixed_vars.values()), - list(_visitor._external_functions.values()), - ) diff --git a/pyomo/contrib/appsi/utils/get_objective.py b/pyomo/contrib/appsi/utils/get_objective.py deleted file mode 100644 index 30dd911f9c8..00000000000 --- a/pyomo/contrib/appsi/utils/get_objective.py +++ /dev/null @@ -1,12 +0,0 @@ -from pyomo.core.base.objective import Objective - - -def get_objective(block): - obj = None - for o in block.component_data_objects( - Objective, descend_into=True, active=True, sort=True - ): - if obj is not None: - raise ValueError('Multiple active objectives found') - obj = o - return obj diff --git a/pyomo/contrib/appsi/utils/tests/__init__.py b/pyomo/contrib/appsi/utils/tests/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/pyomo/contrib/appsi/utils/tests/test_collect_vars_and_named_exprs.py b/pyomo/contrib/appsi/utils/tests/test_collect_vars_and_named_exprs.py deleted file mode 100644 index 4c2a167a017..00000000000 --- a/pyomo/contrib/appsi/utils/tests/test_collect_vars_and_named_exprs.py +++ /dev/null @@ -1,56 +0,0 @@ -from pyomo.common import unittest -import pyomo.environ as pe -from pyomo.contrib.appsi.utils import collect_vars_and_named_exprs -from pyomo.contrib.appsi.cmodel import cmodel, cmodel_available -from typing import Callable -from pyomo.common.gsl import find_GSL - - -class TestCollectVarsAndNamedExpressions(unittest.TestCase): - def basics_helper(self, collector: Callable, *args): - m = pe.ConcreteModel() - m.x = pe.Var() - m.y = pe.Var() - m.z = pe.Var() - m.E = pe.Expression(expr=2 * m.z + 1) - m.y.fix(3) - e = m.x * m.y + m.x * m.E - named_exprs, var_list, fixed_vars, external_funcs = collector(e, *args) - self.assertEqual([m.E], named_exprs) - self.assertEqual([m.x, m.y, m.z], var_list) - self.assertEqual([m.y], fixed_vars) - self.assertEqual([], external_funcs) - - def test_basics(self): - self.basics_helper(collect_vars_and_named_exprs) - - @unittest.skipUnless(cmodel_available, 'appsi extensions are not available') - def test_basics_cmodel(self): - self.basics_helper(cmodel.prep_for_repn, cmodel.PyomoExprTypes()) - - def external_func_helper(self, collector: Callable, *args): - DLL = find_GSL() - if not DLL: - self.skipTest('Could not find amplgsl.dll library') - - m = pe.ConcreteModel() - m.x = pe.Var() - m.y = pe.Var() - m.z = pe.Var() - m.hypot = pe.ExternalFunction(library=DLL, function='gsl_hypot') - func = m.hypot(m.x, m.x * m.y) - m.E = pe.Expression(expr=2 * func) - m.y.fix(3) - e = m.z + m.x * m.E - named_exprs, var_list, fixed_vars, external_funcs = collector(e, *args) - self.assertEqual([m.E], named_exprs) - self.assertEqual([m.z, m.x, m.y], var_list) - self.assertEqual([m.y], fixed_vars) - self.assertEqual([func], external_funcs) - - def test_external(self): - self.external_func_helper(collect_vars_and_named_exprs) - - @unittest.skipUnless(cmodel_available, 'appsi extensions are not available') - def test_external_cmodel(self): - self.basics_helper(cmodel.prep_for_repn, cmodel.PyomoExprTypes()) From 793fb38df3f98b04afde959f302e6e5185e33b6d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 13:57:34 -0600 Subject: [PATCH 0059/1204] Reverting test_branches file --- .github/workflows/test_branches.yml | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index ff8b5901189..99d5f7fc1a8 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -390,10 +390,10 @@ jobs: IPOPT_TAR=${DOWNLOAD_DIR}/ipopt.tar.gz if test ! -e $IPOPT_TAR; then echo "...downloading Ipopt" - # if test "${{matrix.TARGET}}" == osx; then - # echo "IDAES Ipopt not available on OSX" - # exit 0 - # fi + if test "${{matrix.TARGET}}" == osx; then + echo "IDAES Ipopt not available on OSX" + exit 0 + fi URL=https://github.com/IDAES/idaes-ext RELEASE=$(curl --max-time 150 --retry 8 \ -L -s -H 'Accept: application/json' ${URL}/releases/latest) @@ -401,11 +401,7 @@ jobs: URL=${URL}/releases/download/$VER if test "${{matrix.TARGET}}" == linux; then curl --max-time 150 --retry 8 \ - -L $URL/idaes-solvers-ubuntu2204-x86_64.tar.gz \ - > $IPOPT_TAR - elif test "${{matrix.TARGET}}" == osx; then - curl --max-time 150 --retry 8 \ - -L $URL/idaes-solvers-darwin-x86_64.tar.gz \ + -L $URL/idaes-solvers-ubuntu2004-x86_64.tar.gz \ > $IPOPT_TAR else curl --max-time 150 --retry 8 \ @@ -414,7 +410,7 @@ jobs: fi fi cd $IPOPT_DIR - tar -xz < $IPOPT_TAR + tar -xzi < $IPOPT_TAR echo "" echo "$IPOPT_DIR" ls -l $IPOPT_DIR @@ -602,7 +598,8 @@ jobs: run: | $PYTHON_EXE -m pytest -v \ -W ignore::Warning ${{matrix.category}} \ - pyomo/contrib/appsi pyomo/solver --junitxml="TEST-pyomo.xml" + pyomo `pwd`/pyomo-model-libraries \ + `pwd`/examples/pyomobook --junitxml="TEST-pyomo.xml" - name: Run Pyomo MPI tests if: matrix.mpi != 0 From 35e921ad611d0b8d9bbab1bffb488c6e17263e10 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 14:31:06 -0600 Subject: [PATCH 0060/1204] Add __init__ to test directory --- pyomo/solver/tests/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 pyomo/solver/tests/__init__.py diff --git a/pyomo/solver/tests/__init__.py b/pyomo/solver/tests/__init__.py new file mode 100644 index 00000000000..9a63db93d6a --- /dev/null +++ b/pyomo/solver/tests/__init__.py @@ -0,0 +1,12 @@ +# ___________________________________________________________________________ +# +# 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 3489dfcfa3a39d732596d17bcbdc58bb46233710 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 16:32:58 -0600 Subject: [PATCH 0061/1204] Update the results object --- .../contrib/appsi/examples/getting_started.py | 2 +- pyomo/contrib/appsi/solvers/cbc.py | 24 ++--- pyomo/contrib/appsi/solvers/cplex.py | 20 ++-- pyomo/contrib/appsi/solvers/gurobi.py | 22 ++-- pyomo/contrib/appsi/solvers/highs.py | 14 +-- pyomo/contrib/appsi/solvers/ipopt.py | 22 ++-- .../solvers/tests/test_gurobi_persistent.py | 22 ++-- .../solvers/tests/test_persistent_solvers.py | 102 +++++++++--------- pyomo/solver/base.py | 65 +++++++---- pyomo/solver/config.py | 12 +-- pyomo/solver/tests/test_base.py | 4 +- 11 files changed, 166 insertions(+), 143 deletions(-) diff --git a/pyomo/contrib/appsi/examples/getting_started.py b/pyomo/contrib/appsi/examples/getting_started.py index de5357776f4..79f1aa845b3 100644 --- a/pyomo/contrib/appsi/examples/getting_started.py +++ b/pyomo/contrib/appsi/examples/getting_started.py @@ -36,7 +36,7 @@ def main(plot=True, n_points=200): res.termination_condition == solver_base.TerminationCondition.convergenceCriteriaSatisfied ) - obj_values.append(res.best_feasible_objective) + obj_values.append(res.incumbent_objective) opt.load_vars([m.x]) x_values.append(m.x.value) timer.stop('p loop') diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index 021ff76217d..9ae1ecba1f2 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -313,23 +313,23 @@ def _parse_soln(self): for v_id, (v, val) in self._primal_sol.items(): v.set_value(val, skip_validation=True) if self._writer.get_active_objective() is None: - results.best_feasible_objective = None + results.incumbent_objective = None else: - results.best_feasible_objective = obj_val + results.incumbent_objective = obj_val elif ( results.termination_condition == TerminationCondition.convergenceCriteriaSatisfied ): if self._writer.get_active_objective() is None: - results.best_feasible_objective = None + results.incumbent_objective = None else: - results.best_feasible_objective = obj_val + results.incumbent_objective = obj_val elif self.config.load_solution: raise RuntimeError( 'A feasible solution was not found, so no solution can be loaded.' 'Please set opt.config.load_solution=False and check ' 'results.termination_condition and ' - 'results.best_feasible_objective before loading a solution.' + 'results.incumbent_objective before loading a solution.' ) return results @@ -406,24 +406,24 @@ def _check_and_escape_options(): 'A feasible solution was not found, so no solution can be loaded.' 'Please set opt.config.load_solution=False and check ' 'results.termination_condition and ' - 'results.best_feasible_objective before loading a solution.' + 'results.incumbent_objective before loading a solution.' ) results = Results() results.termination_condition = TerminationCondition.error - results.best_feasible_objective = None + results.incumbent_objective = None else: timer.start('parse solution') results = self._parse_soln() timer.stop('parse solution') if self._writer.get_active_objective() is None: - results.best_feasible_objective = None - results.best_objective_bound = None + results.incumbent_objective = None + results.objective_bound = None else: if self._writer.get_active_objective().sense == minimize: - results.best_objective_bound = -math.inf + results.objective_bound = -math.inf else: - results.best_objective_bound = math.inf + results.objective_bound = math.inf results.solution_loader = PersistentSolutionLoader(solver=self) @@ -434,7 +434,7 @@ def get_primals( ) -> Mapping[_GeneralVarData, float]: if ( self._last_results_object is None - or self._last_results_object.best_feasible_objective is None + or self._last_results_object.incumbent_objective is None ): raise RuntimeError( 'Solver does not currently have a valid solution. Please ' diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 759bd7ff9d5..bab6afd7375 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -299,33 +299,33 @@ def _postsolve(self, timer: HierarchicalTimer, solve_time): results.termination_condition = TerminationCondition.unknown if self._writer.get_active_objective() is None: - results.best_feasible_objective = None - results.best_objective_bound = None + results.incumbent_objective = None + results.objective_bound = None else: if cpxprob.solution.get_solution_type() != cpxprob.solution.type.none: if ( cpxprob.variables.get_num_binary() + cpxprob.variables.get_num_integer() ) == 0: - results.best_feasible_objective = ( + results.incumbent_objective = ( cpxprob.solution.get_objective_value() ) - results.best_objective_bound = ( + results.objective_bound = ( cpxprob.solution.get_objective_value() ) else: - results.best_feasible_objective = ( + results.incumbent_objective = ( cpxprob.solution.get_objective_value() ) - results.best_objective_bound = ( + results.objective_bound = ( cpxprob.solution.MIP.get_best_objective() ) else: - results.best_feasible_objective = None + results.incumbent_objective = None if cpxprob.objective.get_sense() == cpxprob.objective.sense.minimize: - results.best_objective_bound = -math.inf + results.objective_bound = -math.inf else: - results.best_objective_bound = math.inf + results.objective_bound = math.inf if config.load_solution: if cpxprob.solution.get_solution_type() == cpxprob.solution.type.none: @@ -333,7 +333,7 @@ def _postsolve(self, timer: HierarchicalTimer, solve_time): 'A feasible solution was not found, so no solution can be loades. ' 'Please set opt.config.load_solution=False and check ' 'results.termination_condition and ' - 'results.best_feasible_objective before loading a solution.' + 'results.incumbent_objective before loading a solution.' ) else: if ( diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index c2db835922d..8691151f475 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -899,25 +899,25 @@ def _postsolve(self, timer: HierarchicalTimer): else: results.termination_condition = TerminationCondition.unknown - results.best_feasible_objective = None - results.best_objective_bound = None + results.incumbent_objective = None + results.objective_bound = None if self._objective is not None: try: - results.best_feasible_objective = gprob.ObjVal + results.incumbent_objective = gprob.ObjVal except (gurobipy.GurobiError, AttributeError): - results.best_feasible_objective = None + results.incumbent_objective = None try: - results.best_objective_bound = gprob.ObjBound + results.objective_bound = gprob.ObjBound except (gurobipy.GurobiError, AttributeError): if self._objective.sense == minimize: - results.best_objective_bound = -math.inf + results.objective_bound = -math.inf else: - results.best_objective_bound = math.inf + results.objective_bound = math.inf - if results.best_feasible_objective is not None and not math.isfinite( - results.best_feasible_objective + if results.incumbent_objective is not None and not math.isfinite( + results.incumbent_objective ): - results.best_feasible_objective = None + results.incumbent_objective = None timer.start('load solution') if config.load_solution: @@ -938,7 +938,7 @@ def _postsolve(self, timer: HierarchicalTimer): 'A feasible solution was not found, so no solution can be loaded.' 'Please set opt.config.load_solution=False and check ' 'results.termination_condition and ' - 'results.best_feasible_objective before loading a solution.' + 'results.incumbent_objective before loading a solution.' ) timer.stop('load solution') diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 3b7c92ed9e8..7b973a297f6 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -660,23 +660,23 @@ def _postsolve(self, timer: HierarchicalTimer): 'A feasible solution was not found, so no solution can be loaded.' 'Please set opt.config.load_solution=False and check ' 'results.termination_condition and ' - 'results.best_feasible_objective before loading a solution.' + 'results.incumbent_objective before loading a solution.' ) timer.stop('load solution') info = highs.getInfo() - results.best_objective_bound = None - results.best_feasible_objective = None + results.objective_bound = None + results.incumbent_objective = None if self._objective is not None: if has_feasible_solution: - results.best_feasible_objective = info.objective_function_value + results.incumbent_objective = info.objective_function_value if info.mip_node_count == -1: if has_feasible_solution: - results.best_objective_bound = info.objective_function_value + results.objective_bound = info.objective_function_value else: - results.best_objective_bound = None + results.objective_bound = None else: - results.best_objective_bound = info.mip_dual_bound + results.objective_bound = info.mip_dual_bound return results diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index 6c4b7601d2c..0249d97258f 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -390,9 +390,9 @@ def _parse_sol(self): for v, val in self._primal_sol.items(): v.set_value(val, skip_validation=True) if self._writer.get_active_objective() is None: - results.best_feasible_objective = None + results.incumbent_objective = None else: - results.best_feasible_objective = value( + results.incumbent_objective = value( self._writer.get_active_objective().expr ) elif ( @@ -400,7 +400,7 @@ def _parse_sol(self): == TerminationCondition.convergenceCriteriaSatisfied ): if self._writer.get_active_objective() is None: - results.best_feasible_objective = None + results.incumbent_objective = None else: obj_expr_evaluated = replace_expressions( self._writer.get_active_objective().expr, @@ -410,13 +410,13 @@ def _parse_sol(self): descend_into_named_expressions=True, remove_named_expressions=True, ) - results.best_feasible_objective = value(obj_expr_evaluated) + results.incumbent_objective = value(obj_expr_evaluated) elif self.config.load_solution: raise RuntimeError( 'A feasible solution was not found, so no solution can be loaded.' 'Please set opt.config.load_solution=False and check ' 'results.termination_condition and ' - 'results.best_feasible_objective before loading a solution.' + 'results.incumbent_objective before loading a solution.' ) return results @@ -480,23 +480,23 @@ def _apply_solver(self, timer: HierarchicalTimer): 'A feasible solution was not found, so no solution can be loaded.' 'Please set opt.config.load_solution=False and check ' 'results.termination_condition and ' - 'results.best_feasible_objective before loading a solution.' + 'results.incumbent_objective before loading a solution.' ) results = Results() results.termination_condition = TerminationCondition.error - results.best_feasible_objective = None + results.incumbent_objective = None else: timer.start('parse solution') results = self._parse_sol() timer.stop('parse solution') if self._writer.get_active_objective() is None: - results.best_objective_bound = None + results.objective_bound = None else: if self._writer.get_active_objective().sense == minimize: - results.best_objective_bound = -math.inf + results.objective_bound = -math.inf else: - results.best_objective_bound = math.inf + results.objective_bound = math.inf results.solution_loader = PersistentSolutionLoader(solver=self) @@ -507,7 +507,7 @@ def get_primals( ) -> Mapping[_GeneralVarData, float]: if ( self._last_results_object is None - or self._last_results_object.best_feasible_objective is None + or self._last_results_object.incumbent_objective is None ): raise RuntimeError( 'Solver does not currently have a valid solution. Please ' diff --git a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py index 9fdce87b8de..7e1d3e37af6 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py @@ -155,12 +155,12 @@ def test_lp(self): x, y = self.get_solution() opt = Gurobi() res = opt.solve(self.m) - self.assertAlmostEqual(x + y, res.best_feasible_objective) - self.assertAlmostEqual(x + y, res.best_objective_bound) + self.assertAlmostEqual(x + y, res.incumbent_objective) + self.assertAlmostEqual(x + y, res.objective_bound) self.assertEqual( res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied ) - self.assertTrue(res.best_feasible_objective is not None) + self.assertTrue(res.incumbent_objective is not None) self.assertAlmostEqual(x, self.m.x.value) self.assertAlmostEqual(y, self.m.y.value) @@ -196,11 +196,11 @@ def test_nonconvex_qcp_objective_bound_1(self): opt.gurobi_options['BestBdStop'] = -8 opt.config.load_solution = False res = opt.solve(m) - self.assertEqual(res.best_feasible_objective, None) - self.assertAlmostEqual(res.best_objective_bound, -8) + self.assertEqual(res.incumbent_objective, None) + self.assertAlmostEqual(res.objective_bound, -8) def test_nonconvex_qcp_objective_bound_2(self): - # the goal of this test is to ensure we can best_objective_bound properly + # the goal of this test is to ensure we can objective_bound properly # for nonconvex but continuous problems when the solver terminates with a nonzero gap # # This is a fragile test because it could fail if Gurobi's algorithms change @@ -214,8 +214,8 @@ def test_nonconvex_qcp_objective_bound_2(self): opt.gurobi_options['nonconvex'] = 2 opt.gurobi_options['MIPGap'] = 0.5 res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, -4) - self.assertAlmostEqual(res.best_objective_bound, -6) + self.assertAlmostEqual(res.incumbent_objective, -4) + self.assertAlmostEqual(res.objective_bound, -6) def test_range_constraints(self): m = pe.ConcreteModel() @@ -282,7 +282,7 @@ def test_quadratic_objective(self): res = opt.solve(m) self.assertAlmostEqual(m.x.value, -m.b.value / (2 * m.a.value)) self.assertAlmostEqual( - res.best_feasible_objective, + res.incumbent_objective, m.a.value * m.x.value**2 + m.b.value * m.x.value + m.c.value, ) @@ -292,7 +292,7 @@ def test_quadratic_objective(self): res = opt.solve(m) self.assertAlmostEqual(m.x.value, -m.b.value / (2 * m.a.value)) self.assertAlmostEqual( - res.best_feasible_objective, + res.incumbent_objective, m.a.value * m.x.value**2 + m.b.value * m.x.value + m.c.value, ) @@ -467,7 +467,7 @@ def test_zero_time_limit(self): # what we are trying to test. Unfortunately, I'm # not sure of a good way to guarantee that if num_solutions == 0: - self.assertIsNone(res.best_feasible_objective) + self.assertIsNone(res.incumbent_objective) class TestManualModel(unittest.TestCase): diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index bf92244ec36..352f93b7ad1 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -249,8 +249,8 @@ def test_param_changes( ) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) - self.assertAlmostEqual(res.best_feasible_objective, m.y.value) - self.assertTrue(res.best_objective_bound <= m.y.value) + self.assertAlmostEqual(res.incumbent_objective, m.y.value) + self.assertTrue(res.objective_bound <= m.y.value) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], (1 + a1 / (a2 - a1))) self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) @@ -290,8 +290,8 @@ def test_immutable_param( ) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) - self.assertAlmostEqual(res.best_feasible_objective, m.y.value) - self.assertTrue(res.best_objective_bound <= m.y.value) + self.assertAlmostEqual(res.incumbent_objective, m.y.value) + self.assertTrue(res.objective_bound <= m.y.value) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], (1 + a1 / (a2 - a1))) self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) @@ -327,8 +327,8 @@ def test_equality( ) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) - self.assertAlmostEqual(res.best_feasible_objective, m.y.value) - self.assertTrue(res.best_objective_bound <= m.y.value) + self.assertAlmostEqual(res.incumbent_objective, m.y.value) + self.assertTrue(res.objective_bound <= m.y.value) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], (1 + a1 / (a2 - a1))) self.assertAlmostEqual(duals[m.c2], -a1 / (a2 - a1)) @@ -369,8 +369,8 @@ def test_linear_expression( TerminationCondition.convergenceCriteriaSatisfied, ) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) - self.assertAlmostEqual(res.best_feasible_objective, m.y.value) - self.assertTrue(res.best_objective_bound <= m.y.value) + self.assertAlmostEqual(res.incumbent_objective, m.y.value) + self.assertTrue(res.objective_bound <= m.y.value) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_no_objective( @@ -403,8 +403,8 @@ def test_no_objective( ) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) - self.assertEqual(res.best_feasible_objective, None) - self.assertEqual(res.best_objective_bound, None) + self.assertEqual(res.incumbent_objective, None) + self.assertEqual(res.objective_bound, None) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], 0) self.assertAlmostEqual(duals[m.c2], 0) @@ -434,8 +434,8 @@ def test_add_remove_cons( ) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) - self.assertAlmostEqual(res.best_feasible_objective, m.y.value) - self.assertTrue(res.best_objective_bound <= m.y.value) + self.assertAlmostEqual(res.incumbent_objective, m.y.value) + self.assertTrue(res.objective_bound <= m.y.value) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], -(1 + a1 / (a2 - a1))) self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) @@ -447,8 +447,8 @@ def test_add_remove_cons( ) self.assertAlmostEqual(m.x.value, (b3 - b1) / (a1 - a3)) self.assertAlmostEqual(m.y.value, a1 * (b3 - b1) / (a1 - a3) + b1) - self.assertAlmostEqual(res.best_feasible_objective, m.y.value) - self.assertTrue(res.best_objective_bound <= m.y.value) + self.assertAlmostEqual(res.incumbent_objective, m.y.value) + self.assertTrue(res.objective_bound <= m.y.value) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], -(1 + a1 / (a3 - a1))) self.assertAlmostEqual(duals[m.c2], 0) @@ -461,8 +461,8 @@ def test_add_remove_cons( ) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) - self.assertAlmostEqual(res.best_feasible_objective, m.y.value) - self.assertTrue(res.best_objective_bound <= m.y.value) + self.assertAlmostEqual(res.incumbent_objective, m.y.value) + self.assertTrue(res.objective_bound <= m.y.value) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], -(1 + a1 / (a2 - a1))) self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) @@ -502,7 +502,7 @@ def test_results_infeasible( self.assertIn(res.termination_condition, acceptable_termination_conditions) self.assertAlmostEqual(m.x.value, None) self.assertAlmostEqual(m.y.value, None) - self.assertTrue(res.best_feasible_objective is None) + self.assertTrue(res.incumbent_objective is None) with self.assertRaisesRegex( RuntimeError, '.*does not currently have a valid solution.*' @@ -789,16 +789,16 @@ def test_mutable_param_with_range( if sense is pe.minimize: self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2), 6) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1, 6) - self.assertAlmostEqual(res.best_feasible_objective, m.y.value, 6) - self.assertTrue(res.best_objective_bound <= m.y.value + 1e-12) + self.assertAlmostEqual(res.incumbent_objective, m.y.value, 6) + self.assertTrue(res.objective_bound <= m.y.value + 1e-12) duals = opt.get_duals() self.assertAlmostEqual(duals[m.con1], (1 + a1 / (a2 - a1)), 6) self.assertAlmostEqual(duals[m.con2], -a1 / (a2 - a1), 6) else: self.assertAlmostEqual(m.x.value, (c2 - c1) / (a1 - a2), 6) self.assertAlmostEqual(m.y.value, a1 * (c2 - c1) / (a1 - a2) + c1, 6) - self.assertAlmostEqual(res.best_feasible_objective, m.y.value, 6) - self.assertTrue(res.best_objective_bound >= m.y.value - 1e-12) + self.assertAlmostEqual(res.incumbent_objective, m.y.value, 6) + self.assertTrue(res.objective_bound >= m.y.value - 1e-12) duals = opt.get_duals() self.assertAlmostEqual(duals[m.con1], (1 + a1 / (a2 - a1)), 6) self.assertAlmostEqual(duals[m.con2], -a1 / (a2 - a1), 6) @@ -1077,13 +1077,13 @@ def test_objective_changes( m.c2 = pe.Constraint(expr=m.y >= -m.x + 1) m.obj = pe.Objective(expr=m.y) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1) + self.assertAlmostEqual(res.incumbent_objective, 1) m.obj = pe.Objective(expr=2 * m.y) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 2) + self.assertAlmostEqual(res.incumbent_objective, 2) m.obj.expr = 3 * m.y res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 3) + self.assertAlmostEqual(res.incumbent_objective, 3) m.obj.sense = pe.maximize opt.config.load_solution = False res = opt.solve(m) @@ -1099,30 +1099,30 @@ def test_objective_changes( m.obj = pe.Objective(expr=m.x * m.y) m.x.fix(2) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 6, 6) + self.assertAlmostEqual(res.incumbent_objective, 6, 6) m.x.fix(3) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 12, 6) + self.assertAlmostEqual(res.incumbent_objective, 12, 6) m.x.unfix() m.y.fix(2) m.x.setlb(-3) m.x.setub(5) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, -2, 6) + self.assertAlmostEqual(res.incumbent_objective, -2, 6) m.y.unfix() m.x.setlb(None) m.x.setub(None) m.e = pe.Expression(expr=2) m.obj = pe.Objective(expr=m.e * m.y) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 2) + self.assertAlmostEqual(res.incumbent_objective, 2) m.e.expr = 3 res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 3) + self.assertAlmostEqual(res.incumbent_objective, 3) opt.update_config.check_for_new_objective = False m.e.expr = 4 res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 4) + self.assertAlmostEqual(res.incumbent_objective, 4) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_domain( @@ -1135,20 +1135,20 @@ def test_domain( m.x = pe.Var(bounds=(1, None), domain=pe.NonNegativeReals) m.obj = pe.Objective(expr=m.x) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1) + self.assertAlmostEqual(res.incumbent_objective, 1) m.x.setlb(-1) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 0) + self.assertAlmostEqual(res.incumbent_objective, 0) m.x.setlb(1) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1) + self.assertAlmostEqual(res.incumbent_objective, 1) m.x.setlb(-1) m.x.domain = pe.Reals res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, -1) + self.assertAlmostEqual(res.incumbent_objective, -1) m.x.domain = pe.NonNegativeReals res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 0) + self.assertAlmostEqual(res.incumbent_objective, 0) @parameterized.expand(input=_load_tests(mip_solvers, only_child_vars_options)) def test_domain_with_integers( @@ -1161,20 +1161,20 @@ def test_domain_with_integers( m.x = pe.Var(bounds=(-1, None), domain=pe.NonNegativeIntegers) m.obj = pe.Objective(expr=m.x) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 0) + self.assertAlmostEqual(res.incumbent_objective, 0) m.x.setlb(0.5) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1) + self.assertAlmostEqual(res.incumbent_objective, 1) m.x.setlb(-5.5) m.x.domain = pe.Integers res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, -5) + self.assertAlmostEqual(res.incumbent_objective, -5) m.x.domain = pe.Binary res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 0) + self.assertAlmostEqual(res.incumbent_objective, 0) m.x.setlb(0.5) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1) + self.assertAlmostEqual(res.incumbent_objective, 1) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_fixed_binaries( @@ -1190,19 +1190,19 @@ def test_fixed_binaries( m.c = pe.Constraint(expr=m.y >= m.x) m.x.fix(0) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 0) + self.assertAlmostEqual(res.incumbent_objective, 0) m.x.fix(1) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1) + self.assertAlmostEqual(res.incumbent_objective, 1) opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) opt.update_config.treat_fixed_vars_as_params = False m.x.fix(0) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 0) + self.assertAlmostEqual(res.incumbent_objective, 0) m.x.fix(1) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1) + self.assertAlmostEqual(res.incumbent_objective, 1) @parameterized.expand(input=_load_tests(mip_solvers, only_child_vars_options)) def test_with_gdp( @@ -1226,7 +1226,7 @@ def test_with_gdp( pe.TransformationFactory("gdp.bigm").apply_to(m) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1) + self.assertAlmostEqual(res.incumbent_objective, 1) self.assertAlmostEqual(m.x.value, 0) self.assertAlmostEqual(m.y.value, 1) @@ -1250,7 +1250,7 @@ def test_variables_elsewhere( self.assertEqual( res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied ) - self.assertAlmostEqual(res.best_feasible_objective, 1) + self.assertAlmostEqual(res.incumbent_objective, 1) self.assertAlmostEqual(m.x.value, -1) self.assertAlmostEqual(m.y.value, 1) @@ -1259,7 +1259,7 @@ def test_variables_elsewhere( self.assertEqual( res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied ) - self.assertAlmostEqual(res.best_feasible_objective, 2) + self.assertAlmostEqual(res.incumbent_objective, 2) self.assertAlmostEqual(m.x.value, 0) self.assertAlmostEqual(m.y.value, 2) @@ -1286,7 +1286,7 @@ def test_variables_elsewhere2( self.assertEqual( res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied ) - self.assertAlmostEqual(res.best_feasible_objective, 1) + self.assertAlmostEqual(res.incumbent_objective, 1) sol = res.solution_loader.get_primals() self.assertIn(m.x, sol) self.assertIn(m.y, sol) @@ -1298,7 +1298,7 @@ def test_variables_elsewhere2( self.assertEqual( res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied ) - self.assertAlmostEqual(res.best_feasible_objective, 0) + self.assertAlmostEqual(res.incumbent_objective, 0) sol = res.solution_loader.get_primals() self.assertIn(m.x, sol) self.assertIn(m.y, sol) @@ -1324,14 +1324,14 @@ def test_bug_1( self.assertEqual( res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied ) - self.assertAlmostEqual(res.best_feasible_objective, 0) + self.assertAlmostEqual(res.incumbent_objective, 0) m.p.value = 1 res = opt.solve(m) self.assertEqual( res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied ) - self.assertAlmostEqual(res.best_feasible_objective, 3) + self.assertAlmostEqual(res.incumbent_objective, 3) @unittest.skipUnless(cmodel_available, 'appsi extensions are not available') diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index f0a07d0aca3..efce7b09f54 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -11,12 +11,14 @@ import abc import enum +from datetime import datetime from typing import Sequence, Dict, Optional, Mapping, NoReturn, List, Tuple from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.param import _ParamData from pyomo.core.base.block import _BlockData from pyomo.core.base.objective import _GeneralObjectiveData +from pyomo.common.config import ConfigDict, ConfigValue, NonNegativeInt, In, NonNegativeFloat from pyomo.common.timing import HierarchicalTimer from pyomo.common.errors import ApplicationError from pyomo.opt.base import SolverFactory as LegacySolverFactory @@ -106,37 +108,62 @@ class SolutionStatus(enum.IntEnum): optimal = 30 -class Results: +class Results(ConfigDict): """ Attributes ---------- termination_condition: TerminationCondition The reason the solver exited. This is a member of the TerminationCondition enum. - best_feasible_objective: float + incumbent_objective: float If a feasible solution was found, this is the objective value of the best solution found. If no feasible solution was found, this is None. - best_objective_bound: float + objective_bound: float The best objective bound found. For minimization problems, this is the lower bound. For maximization problems, this is the upper bound. For solvers that do not provide an objective bound, this should be -inf (minimization) or inf (maximization) """ - def __init__(self): - self.solution_loader: SolutionLoaderBase = SolutionLoader( - None, None, None, None + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + super().__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, ) - self.termination_condition: TerminationCondition = TerminationCondition.unknown - self.best_feasible_objective: Optional[float] = None - self.best_objective_bound: Optional[float] = None + + self.declare('solution_loader', ConfigValue(domain=In(SolutionLoaderBase), default=SolutionLoader( + None, None, None, None + ))) + self.declare('termination_condition', ConfigValue(domain=In(TerminationCondition), default=TerminationCondition.unknown)) + self.declare('solution_status', ConfigValue(domain=In(SolutionStatus), default=SolutionStatus.noSolution)) + self.incumbent_objective: Optional[float] = self.declare('incumbent_objective', ConfigValue(domain=float)) + self.objective_bound: Optional[float] = self.declare('objective_bound', ConfigValue(domain=float)) + self.declare('solver_name', ConfigValue(domain=str)) + self.declare('solver_version', ConfigValue(domain=tuple)) + self.declare('termination_message', ConfigValue(domain=str)) + self.declare('iteration_count', ConfigValue(domain=NonNegativeInt)) + self.declare('timing_info', ConfigDict()) + self.timing_info.declare('start', ConfigValue=In(datetime)) + self.timing_info.declare('wall_time', ConfigValue(domain=NonNegativeFloat)) + self.timing_info.declare('solver_wall_time', ConfigValue(domain=NonNegativeFloat)) + self.declare('extra_info', ConfigDict(implicit=True)) def __str__(self): s = '' s += 'termination_condition: ' + str(self.termination_condition) + '\n' - s += 'best_feasible_objective: ' + str(self.best_feasible_objective) + '\n' - s += 'best_objective_bound: ' + str(self.best_objective_bound) + s += 'incumbent_objective: ' + str(self.incumbent_objective) + '\n' + s += 'objective_bound: ' + str(self.objective_bound) return s @@ -496,17 +523,17 @@ def solve( legacy_results.problem.sense = obj.sense if obj.sense == minimize: - legacy_results.problem.lower_bound = results.best_objective_bound - legacy_results.problem.upper_bound = results.best_feasible_objective + legacy_results.problem.lower_bound = results.objective_bound + legacy_results.problem.upper_bound = results.incumbent_objective else: - legacy_results.problem.upper_bound = results.best_objective_bound - legacy_results.problem.lower_bound = results.best_feasible_objective + legacy_results.problem.upper_bound = results.objective_bound + legacy_results.problem.lower_bound = results.incumbent_objective if ( - results.best_feasible_objective is not None - and results.best_objective_bound is not None + results.incumbent_objective is not None + and results.objective_bound is not None ): legacy_soln.gap = abs( - results.best_feasible_objective - results.best_objective_bound + results.incumbent_objective - results.objective_bound ) else: legacy_soln.gap = None @@ -530,7 +557,7 @@ def solve( if hasattr(model, 'rc') and model.rc.import_enabled(): for v, val in results.solution_loader.get_reduced_costs().items(): model.rc[v] = val - elif results.best_feasible_objective is not None: + elif results.incumbent_objective is not None: delete_legacy_soln = False for v, val in results.solution_loader.get_primals().items(): legacy_soln.variable[symbol_map.getSymbol(v)] = {'Value': val} diff --git a/pyomo/solver/config.py b/pyomo/solver/config.py index f446dc714db..32f6e1d5da0 100644 --- a/pyomo/solver/config.py +++ b/pyomo/solver/config.py @@ -56,19 +56,15 @@ def __init__( visibility=visibility, ) - self.declare('tee', ConfigValue(domain=bool)) - self.declare('load_solution', ConfigValue(domain=bool)) - self.declare('symbolic_solver_labels', ConfigValue(domain=bool)) - self.declare('report_timing', ConfigValue(domain=bool)) + self.declare('tee', ConfigValue(domain=bool, default=False)) + self.declare('load_solution', ConfigValue(domain=bool, default=True)) + self.declare('symbolic_solver_labels', ConfigValue(domain=bool, default=False)) + self.declare('report_timing', ConfigValue(domain=bool, default=False)) self.declare('threads', ConfigValue(domain=NonNegativeInt, default=None)) self.time_limit: Optional[float] = self.declare( 'time_limit', ConfigValue(domain=NonNegativeFloat) ) - self.tee: bool = False - self.load_solution: bool = True - self.symbolic_solver_labels: bool = False - self.report_timing: bool = False class MIPInterfaceConfig(InterfaceConfig): diff --git a/pyomo/solver/tests/test_base.py b/pyomo/solver/tests/test_base.py index 355941a1eb1..41b768520c1 100644 --- a/pyomo/solver/tests/test_base.py +++ b/pyomo/solver/tests/test_base.py @@ -139,8 +139,8 @@ def test_persistent_solver_base(self): class TestResults(unittest.TestCase): def test_uninitialized(self): res = base.Results() - self.assertIsNone(res.best_feasible_objective) - self.assertIsNone(res.best_objective_bound) + self.assertIsNone(res.incumbent_objective) + self.assertIsNone(res.objective_bound) self.assertEqual(res.termination_condition, base.TerminationCondition.unknown) with self.assertRaisesRegex( From 146fa0a10e70995ca3b82b29897a5efcc2e26fad Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 16:39:34 -0600 Subject: [PATCH 0062/1204] Back to only running appsi/solver test --- .github/workflows/test_branches.yml | 3 +-- pyomo/solver/base.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 99d5f7fc1a8..a944bbdd645 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -598,8 +598,7 @@ jobs: run: | $PYTHON_EXE -m pytest -v \ -W ignore::Warning ${{matrix.category}} \ - pyomo `pwd`/pyomo-model-libraries \ - `pwd`/examples/pyomobook --junitxml="TEST-pyomo.xml" + pyomo/contrib/appsi pyomo/solver --junitxml="TEST-pyomo.xml" - name: Run Pyomo MPI tests if: matrix.mpi != 0 diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index efce7b09f54..2b5f81bef82 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -147,8 +147,8 @@ def __init__( ))) self.declare('termination_condition', ConfigValue(domain=In(TerminationCondition), default=TerminationCondition.unknown)) self.declare('solution_status', ConfigValue(domain=In(SolutionStatus), default=SolutionStatus.noSolution)) - self.incumbent_objective: Optional[float] = self.declare('incumbent_objective', ConfigValue(domain=float)) - self.objective_bound: Optional[float] = self.declare('objective_bound', ConfigValue(domain=float)) + self.incumbent_objective: Optional[float] = self.declare('incumbent_objective', ConfigValue(domain=NonNegativeFloat)) + self.objective_bound: Optional[float] = self.declare('objective_bound', ConfigValue(domain=NonNegativeFloat)) self.declare('solver_name', ConfigValue(domain=str)) self.declare('solver_version', ConfigValue(domain=tuple)) self.declare('termination_message', ConfigValue(domain=str)) From e2d0592ec5f9714082959870eca482946f998c5d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 16:50:55 -0600 Subject: [PATCH 0063/1204] Remove domain specification --- pyomo/solver/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 2b5f81bef82..e39e47264ad 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -142,7 +142,7 @@ def __init__( visibility=visibility, ) - self.declare('solution_loader', ConfigValue(domain=In(SolutionLoaderBase), default=SolutionLoader( + self.declare('solution_loader', ConfigValue(default=SolutionLoader( None, None, None, None ))) self.declare('termination_condition', ConfigValue(domain=In(TerminationCondition), default=TerminationCondition.unknown)) From b40ff29f023852963c50f27886068b5d07baf47d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 30 Aug 2023 16:57:40 -0600 Subject: [PATCH 0064/1204] Fix domain typo --- pyomo/solver/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index e39e47264ad..1872011bcd9 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -154,7 +154,7 @@ def __init__( self.declare('termination_message', ConfigValue(domain=str)) self.declare('iteration_count', ConfigValue(domain=NonNegativeInt)) self.declare('timing_info', ConfigDict()) - self.timing_info.declare('start', ConfigValue=In(datetime)) + self.timing_info.declare('start', ConfigValue(domain=In(datetime))) self.timing_info.declare('wall_time', ConfigValue(domain=NonNegativeFloat)) self.timing_info.declare('solver_wall_time', ConfigValue(domain=NonNegativeFloat)) self.declare('extra_info', ConfigDict(implicit=True)) From 050ceb661d48ab3d2ce825b63a2a600745e8b217 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 31 Aug 2023 08:15:10 -0600 Subject: [PATCH 0065/1204] Allow negative floats --- pyomo/solver/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 1872011bcd9..0c77839e358 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -147,8 +147,8 @@ def __init__( ))) self.declare('termination_condition', ConfigValue(domain=In(TerminationCondition), default=TerminationCondition.unknown)) self.declare('solution_status', ConfigValue(domain=In(SolutionStatus), default=SolutionStatus.noSolution)) - self.incumbent_objective: Optional[float] = self.declare('incumbent_objective', ConfigValue(domain=NonNegativeFloat)) - self.objective_bound: Optional[float] = self.declare('objective_bound', ConfigValue(domain=NonNegativeFloat)) + self.incumbent_objective: Optional[float] = self.declare('incumbent_objective', ConfigValue(domain=float)) + self.objective_bound: Optional[float] = self.declare('objective_bound', ConfigValue(domain=float)) self.declare('solver_name', ConfigValue(domain=str)) self.declare('solver_version', ConfigValue(domain=tuple)) self.declare('termination_message', ConfigValue(domain=str)) From ceb858a1bf17cc0a0e2935809b78f2fc5fb4e90c Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 31 Aug 2023 08:16:10 -0600 Subject: [PATCH 0066/1204] Remove type checking for start_time --- pyomo/solver/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 0c77839e358..846431ed918 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -154,7 +154,7 @@ def __init__( self.declare('termination_message', ConfigValue(domain=str)) self.declare('iteration_count', ConfigValue(domain=NonNegativeInt)) self.declare('timing_info', ConfigDict()) - self.timing_info.declare('start', ConfigValue(domain=In(datetime))) + self.timing_info.declare('start_time') self.timing_info.declare('wall_time', ConfigValue(domain=NonNegativeFloat)) self.timing_info.declare('solver_wall_time', ConfigValue(domain=NonNegativeFloat)) self.declare('extra_info', ConfigDict(implicit=True)) From cc0ad9b33dcfb1d3f7db2db3791301778bf5c125 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 31 Aug 2023 08:25:11 -0600 Subject: [PATCH 0067/1204] Add empty config value to start_time --- pyomo/contrib/appsi/solvers/cplex.py | 16 +++------- pyomo/solver/base.py | 45 ++++++++++++++++++++-------- pyomo/solver/tests/__init__.py | 2 -- pyomo/solver/tests/test_base.py | 38 ++++++++++++----------- pyomo/solver/tests/test_config.py | 2 +- pyomo/solver/tests/test_solution.py | 5 +++- 6 files changed, 61 insertions(+), 47 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index bab6afd7375..ac9eaab471f 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -307,19 +307,11 @@ def _postsolve(self, timer: HierarchicalTimer, solve_time): cpxprob.variables.get_num_binary() + cpxprob.variables.get_num_integer() ) == 0: - results.incumbent_objective = ( - cpxprob.solution.get_objective_value() - ) - results.objective_bound = ( - cpxprob.solution.get_objective_value() - ) + results.incumbent_objective = cpxprob.solution.get_objective_value() + results.objective_bound = cpxprob.solution.get_objective_value() else: - results.incumbent_objective = ( - cpxprob.solution.get_objective_value() - ) - results.objective_bound = ( - cpxprob.solution.MIP.get_best_objective() - ) + results.incumbent_objective = cpxprob.solution.get_objective_value() + results.objective_bound = cpxprob.solution.MIP.get_best_objective() else: results.incumbent_objective = None if cpxprob.objective.get_sense() == cpxprob.objective.sense.minimize: diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 846431ed918..2e34747884d 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -18,7 +18,13 @@ from pyomo.core.base.param import _ParamData from pyomo.core.base.block import _BlockData from pyomo.core.base.objective import _GeneralObjectiveData -from pyomo.common.config import ConfigDict, ConfigValue, NonNegativeInt, In, NonNegativeFloat +from pyomo.common.config import ( + ConfigDict, + ConfigValue, + NonNegativeInt, + In, + NonNegativeFloat, +) from pyomo.common.timing import HierarchicalTimer from pyomo.common.errors import ApplicationError from pyomo.opt.base import SolverFactory as LegacySolverFactory @@ -142,21 +148,36 @@ def __init__( visibility=visibility, ) - self.declare('solution_loader', ConfigValue(default=SolutionLoader( - None, None, None, None - ))) - self.declare('termination_condition', ConfigValue(domain=In(TerminationCondition), default=TerminationCondition.unknown)) - self.declare('solution_status', ConfigValue(domain=In(SolutionStatus), default=SolutionStatus.noSolution)) - self.incumbent_objective: Optional[float] = self.declare('incumbent_objective', ConfigValue(domain=float)) - self.objective_bound: Optional[float] = self.declare('objective_bound', ConfigValue(domain=float)) + self.declare( + 'solution_loader', + ConfigValue(default=SolutionLoader(None, None, None, None)), + ) + self.declare( + 'termination_condition', + ConfigValue( + domain=In(TerminationCondition), default=TerminationCondition.unknown + ), + ) + self.declare( + 'solution_status', + ConfigValue(domain=In(SolutionStatus), default=SolutionStatus.noSolution), + ) + self.incumbent_objective: Optional[float] = self.declare( + 'incumbent_objective', ConfigValue(domain=float) + ) + self.objective_bound: Optional[float] = self.declare( + 'objective_bound', ConfigValue(domain=float) + ) self.declare('solver_name', ConfigValue(domain=str)) self.declare('solver_version', ConfigValue(domain=tuple)) self.declare('termination_message', ConfigValue(domain=str)) self.declare('iteration_count', ConfigValue(domain=NonNegativeInt)) self.declare('timing_info', ConfigDict()) - self.timing_info.declare('start_time') + self.timing_info.declare('start_time', ConfigValue()) self.timing_info.declare('wall_time', ConfigValue(domain=NonNegativeFloat)) - self.timing_info.declare('solver_wall_time', ConfigValue(domain=NonNegativeFloat)) + self.timing_info.declare( + 'solver_wall_time', ConfigValue(domain=NonNegativeFloat) + ) self.declare('extra_info', ConfigDict(implicit=True)) def __str__(self): @@ -532,9 +553,7 @@ def solve( results.incumbent_objective is not None and results.objective_bound is not None ): - legacy_soln.gap = abs( - results.incumbent_objective - results.objective_bound - ) + legacy_soln.gap = abs(results.incumbent_objective - results.objective_bound) else: legacy_soln.gap = None diff --git a/pyomo/solver/tests/__init__.py b/pyomo/solver/tests/__init__.py index 9a63db93d6a..d93cfd77b3c 100644 --- a/pyomo/solver/tests/__init__.py +++ b/pyomo/solver/tests/__init__.py @@ -8,5 +8,3 @@ # rights in this software. # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ - - diff --git a/pyomo/solver/tests/test_base.py b/pyomo/solver/tests/test_base.py index 41b768520c1..34d5c47d11c 100644 --- a/pyomo/solver/tests/test_base.py +++ b/pyomo/solver/tests/test_base.py @@ -89,24 +89,26 @@ def test_solver_availability(self): class TestPersistentSolverBase(unittest.TestCase): def test_abstract_member_list(self): - expected_list = ['remove_params', - 'version', - 'config', - 'update_variables', - 'remove_variables', - 'add_constraints', - 'get_primals', - 'set_instance', - 'set_objective', - 'update_params', - 'remove_block', - 'add_block', - 'available', - 'update_config', - 'add_params', - 'remove_constraints', - 'add_variables', - 'solve'] + expected_list = [ + 'remove_params', + 'version', + 'config', + 'update_variables', + 'remove_variables', + 'add_constraints', + 'get_primals', + 'set_instance', + 'set_objective', + 'update_params', + 'remove_block', + 'add_block', + 'available', + 'update_config', + 'add_params', + 'remove_constraints', + 'add_variables', + 'solve', + ] member_list = list(base.PersistentSolverBase.__abstractmethods__) self.assertEqual(sorted(expected_list), sorted(member_list)) diff --git a/pyomo/solver/tests/test_config.py b/pyomo/solver/tests/test_config.py index 378facb58d2..49d26513e2e 100644 --- a/pyomo/solver/tests/test_config.py +++ b/pyomo/solver/tests/test_config.py @@ -12,8 +12,8 @@ from pyomo.common import unittest from pyomo.solver.config import InterfaceConfig, MIPInterfaceConfig -class TestInterfaceConfig(unittest.TestCase): +class TestInterfaceConfig(unittest.TestCase): def test_interface_default_instantiation(self): config = InterfaceConfig() self.assertEqual(config._description, None) diff --git a/pyomo/solver/tests/test_solution.py b/pyomo/solver/tests/test_solution.py index c4c2f790b55..f4c33a60c84 100644 --- a/pyomo/solver/tests/test_solution.py +++ b/pyomo/solver/tests/test_solution.py @@ -12,13 +12,16 @@ from pyomo.common import unittest from pyomo.solver import solution + class TestPersistentSolverBase(unittest.TestCase): def test_abstract_member_list(self): expected_list = ['get_primals'] member_list = list(solution.SolutionLoaderBase.__abstractmethods__) self.assertEqual(sorted(expected_list), sorted(member_list)) - @unittest.mock.patch.multiple(solution.SolutionLoaderBase, __abstractmethods__=set()) + @unittest.mock.patch.multiple( + solution.SolutionLoaderBase, __abstractmethods__=set() + ) def test_solution_loader_base(self): self.instance = solution.SolutionLoaderBase() self.assertEqual(self.instance.get_primals(), None) From 74a459e6ca0cbfd76099540710e1a3c19ef0772e Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 31 Aug 2023 08:34:15 -0600 Subject: [PATCH 0068/1204] Change result attribute in HiGHS to match new standard --- pyomo/contrib/appsi/solvers/highs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 7b973a297f6..f8003599387 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -63,7 +63,7 @@ def __init__( class HighsResults(Results): def __init__(self, solver): super().__init__() - self.wallclock_time = None + self.timing_info.wall_time = None self.solution_loader = PersistentSolutionLoader(solver=solver) From 154b43803a7e4eef4c88891cf0c0cfd7702852f3 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 31 Aug 2023 08:41:18 -0600 Subject: [PATCH 0069/1204] Replace all other instances of wallclock_time --- pyomo/contrib/appsi/solvers/cplex.py | 4 ++-- pyomo/contrib/appsi/solvers/gurobi.py | 4 ++-- pyomo/contrib/appsi/solvers/highs.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index ac9eaab471f..34ad88aeb7a 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -58,7 +58,7 @@ def __init__( class CplexResults(Results): def __init__(self, solver): super().__init__() - self.wallclock_time = None + self.timing_info.wall_time = None self.solution_loader = PersistentSolutionLoader(solver=solver) @@ -278,7 +278,7 @@ def _postsolve(self, timer: HierarchicalTimer, solve_time): cpxprob = self._cplex_model results = CplexResults(solver=self) - results.wallclock_time = solve_time + results.timing_info.wall_time = solve_time status = cpxprob.solution.get_status() if status in [1, 101, 102]: diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index 8691151f475..cd116bcbefa 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -93,7 +93,7 @@ def get_primals(self, vars_to_load=None, solution_number=0): class GurobiResults(Results): def __init__(self, solver): super().__init__() - self.wallclock_time = None + self.timing_info.wall_time = None self.solution_loader = GurobiSolutionLoader(solver=solver) @@ -864,7 +864,7 @@ def _postsolve(self, timer: HierarchicalTimer): status = gprob.Status results = GurobiResults(self) - results.wallclock_time = gprob.Runtime + results.timing_info.wall_time = gprob.Runtime if status == grb.LOADED: # problem is loaded, but no solution results.termination_condition = TerminationCondition.unknown diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index f8003599387..a29dc2a597f 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -587,7 +587,7 @@ def _postsolve(self, timer: HierarchicalTimer): status = highs.getModelStatus() results = HighsResults(self) - results.wallclock_time = highs.getRunTime() + results.timing_info.wall_time = highs.getRunTime() if status == highspy.HighsModelStatus.kNotset: results.termination_condition = TerminationCondition.unknown From b6f1e2a63aa2c8ad235afb57f73a9da9aa6a36a6 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 31 Aug 2023 08:58:39 -0600 Subject: [PATCH 0070/1204] Update unit tests for Results object --- pyomo/solver/tests/test_base.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pyomo/solver/tests/test_base.py b/pyomo/solver/tests/test_base.py index 34d5c47d11c..0e0780fd6fe 100644 --- a/pyomo/solver/tests/test_base.py +++ b/pyomo/solver/tests/test_base.py @@ -10,6 +10,7 @@ # ___________________________________________________________________________ from pyomo.common import unittest +from pyomo.common.config import ConfigDict from pyomo.solver import base import pyomo.environ as pe from pyomo.core.base.var import ScalarVar @@ -130,20 +131,51 @@ def test_persistent_solver_base(self): self.assertEqual(self.instance.set_objective(None), None) self.assertEqual(self.instance.update_variables(None), None) self.assertEqual(self.instance.update_params(), None) + with self.assertRaises(NotImplementedError): self.instance.get_duals() + with self.assertRaises(NotImplementedError): self.instance.get_slacks() + with self.assertRaises(NotImplementedError): self.instance.get_reduced_costs() class TestResults(unittest.TestCase): + def test_declared_items(self): + res = base.Results() + expected_declared = { + 'extra_info', + 'incumbent_objective', + 'iteration_count', + 'objective_bound', + 'solution_loader', + 'solution_status', + 'solver_name', + 'solver_version', + 'termination_condition', + 'termination_message', + 'timing_info', + } + actual_declared = res._declared + self.assertEqual(expected_declared, actual_declared) + def test_uninitialized(self): res = base.Results() self.assertIsNone(res.incumbent_objective) self.assertIsNone(res.objective_bound) self.assertEqual(res.termination_condition, base.TerminationCondition.unknown) + self.assertEqual(res.solution_status, base.SolutionStatus.noSolution) + self.assertIsNone(res.solver_name) + self.assertIsNone(res.solver_version) + self.assertIsNone(res.termination_message) + self.assertIsNone(res.iteration_count) + self.assertIsInstance(res.timing_info, ConfigDict) + self.assertIsInstance(res.extra_info, ConfigDict) + self.assertIsNone(res.timing_info.start_time) + self.assertIsNone(res.timing_info.wall_time) + self.assertIsNone(res.timing_info.solver_wall_time) with self.assertRaisesRegex( RuntimeError, '.*does not currently have a valid solution.*' From 072ac658c18d71aa7f68331d790db11e3c892a71 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 31 Aug 2023 09:20:37 -0600 Subject: [PATCH 0071/1204] Update solution status map --- pyomo/solver/base.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 2e34747884d..fa52883f3e2 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -43,7 +43,7 @@ from pyomo.core.base import SymbolMap from pyomo.core.staleflag import StaleFlagManager from pyomo.solver.config import UpdateConfig -from pyomo.solver.solution import SolutionLoader, SolutionLoaderBase +from pyomo.solver.solution import SolutionLoader from pyomo.solver.util import get_objective @@ -173,6 +173,7 @@ def __init__( self.declare('termination_message', ConfigValue(domain=str)) self.declare('iteration_count', ConfigValue(domain=NonNegativeInt)) self.declare('timing_info', ConfigDict()) + # TODO: Set up type checking for start_time self.timing_info.declare('start_time', ConfigValue()) self.timing_info.declare('wall_time', ConfigValue(domain=NonNegativeFloat)) self.timing_info.declare( @@ -183,6 +184,7 @@ def __init__( def __str__(self): s = '' s += 'termination_condition: ' + str(self.termination_condition) + '\n' + s += 'solution_status: ' + str(self.solution_status) + '\n' s += 'incumbent_objective: ' + str(self.incumbent_objective) + '\n' s += 'objective_bound: ' + str(self.objective_bound) return s @@ -472,19 +474,18 @@ def update_params(self): legacy_solution_status_map = { - TerminationCondition.unknown: LegacySolutionStatus.unknown, - TerminationCondition.maxTimeLimit: LegacySolutionStatus.stoppedByLimit, - TerminationCondition.iterationLimit: LegacySolutionStatus.stoppedByLimit, - TerminationCondition.objectiveLimit: LegacySolutionStatus.stoppedByLimit, - TerminationCondition.minStepLength: LegacySolutionStatus.error, - TerminationCondition.convergenceCriteriaSatisfied: LegacySolutionStatus.optimal, - TerminationCondition.unbounded: LegacySolutionStatus.unbounded, - TerminationCondition.provenInfeasible: LegacySolutionStatus.infeasible, - TerminationCondition.locallyInfeasible: LegacySolutionStatus.infeasible, - TerminationCondition.infeasibleOrUnbounded: LegacySolutionStatus.unsure, - TerminationCondition.error: LegacySolutionStatus.error, - TerminationCondition.interrupted: LegacySolutionStatus.error, - TerminationCondition.licensingProblems: LegacySolutionStatus.error, + SolutionStatus.noSolution: LegacySolutionStatus.unknown, + SolutionStatus.noSolution: LegacySolutionStatus.stoppedByLimit, + SolutionStatus.noSolution: LegacySolutionStatus.error, + SolutionStatus.noSolution: LegacySolutionStatus.other, + SolutionStatus.noSolution: LegacySolutionStatus.unsure, + SolutionStatus.noSolution: LegacySolutionStatus.unbounded, + SolutionStatus.optimal: LegacySolutionStatus.locallyOptimal, + SolutionStatus.optimal: LegacySolutionStatus.globallyOptimal, + SolutionStatus.optimal: LegacySolutionStatus.optimal, + SolutionStatus.infeasible: LegacySolutionStatus.infeasible, + SolutionStatus.feasible: LegacySolutionStatus.feasible, + SolutionStatus.feasible: LegacySolutionStatus.bestSoFar, } From eeceb8667a9d01c49c9c7f3076a847a63829e677 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 31 Aug 2023 09:37:32 -0600 Subject: [PATCH 0072/1204] Refactor Results to be in its own file --- pyomo/contrib/appsi/solvers/cbc.py | 3 +- pyomo/contrib/appsi/solvers/cplex.py | 3 +- pyomo/contrib/appsi/solvers/gurobi.py | 3 +- pyomo/contrib/appsi/solvers/highs.py | 3 +- pyomo/contrib/appsi/solvers/ipopt.py | 3 +- .../solvers/tests/test_persistent_solvers.py | 3 +- pyomo/solver/base.py | 213 +---------------- pyomo/solver/results.py | 225 ++++++++++++++++++ pyomo/solver/tests/test_base.py | 167 ------------- pyomo/solver/tests/test_results.py | 180 ++++++++++++++ 10 files changed, 420 insertions(+), 383 deletions(-) create mode 100644 pyomo/solver/results.py create mode 100644 pyomo/solver/tests/test_results.py diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index 9ae1ecba1f2..c2686475b15 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -22,8 +22,9 @@ from pyomo.common.errors import PyomoException from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager -from pyomo.solver.base import TerminationCondition, Results, PersistentSolverBase +from pyomo.solver.base import PersistentSolverBase from pyomo.solver.config import InterfaceConfig +from pyomo.solver.results import TerminationCondition, Results from pyomo.solver.solution import PersistentSolutionLoader diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 34ad88aeb7a..86d50f1b82a 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -19,8 +19,9 @@ from pyomo.common.errors import PyomoException from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager -from pyomo.solver.base import TerminationCondition, Results, PersistentSolverBase +from pyomo.solver.base import PersistentSolverBase from pyomo.solver.config import MIPInterfaceConfig +from pyomo.solver.results import TerminationCondition, Results from pyomo.solver.solution import PersistentSolutionLoader diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index cd116bcbefa..1f295dfcb49 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -22,8 +22,9 @@ from pyomo.repn import generate_standard_repn from pyomo.core.expr.numeric_expr import NPV_MaxExpression, NPV_MinExpression from pyomo.core.staleflag import StaleFlagManager -from pyomo.solver.base import TerminationCondition, Results, PersistentSolverBase +from pyomo.solver.base import PersistentSolverBase from pyomo.solver.config import MIPInterfaceConfig +from pyomo.solver.results import TerminationCondition, Results from pyomo.solver.solution import PersistentSolutionLoader from pyomo.solver.util import PersistentSolverUtils diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index a29dc2a597f..b5b2cc3b694 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -20,8 +20,9 @@ from pyomo.core.expr.numeric_expr import NPV_MaxExpression, NPV_MinExpression from pyomo.common.dependencies import numpy as np from pyomo.core.staleflag import StaleFlagManager -from pyomo.solver.base import TerminationCondition, Results, PersistentSolverBase +from pyomo.solver.base import PersistentSolverBase from pyomo.solver.config import MIPInterfaceConfig +from pyomo.solver.results import TerminationCondition, Results from pyomo.solver.solution import PersistentSolutionLoader from pyomo.solver.util import PersistentSolverUtils diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index 0249d97258f..b16ca4dc792 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -26,8 +26,9 @@ from pyomo.common.errors import PyomoException from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager -from pyomo.solver.base import TerminationCondition, Results, PersistentSolverBase +from pyomo.solver.base import PersistentSolverBase from pyomo.solver.config import InterfaceConfig +from pyomo.solver.results import TerminationCondition, Results from pyomo.solver.solution import PersistentSolutionLoader diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 352f93b7ad1..5ef6dd7ba50 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -4,7 +4,8 @@ parameterized, param_available = attempt_import('parameterized') parameterized = parameterized.parameterized -from pyomo.solver.base import TerminationCondition, Results, PersistentSolverBase +from pyomo.solver.base import PersistentSolverBase +from pyomo.solver.results import TerminationCondition, Results from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.contrib.appsi.solvers import Gurobi, Ipopt, Highs from typing import Type diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index fa52883f3e2..2d5bde41329 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -11,183 +11,25 @@ import abc import enum -from datetime import datetime from typing import Sequence, Dict, Optional, Mapping, NoReturn, List, Tuple from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.param import _ParamData from pyomo.core.base.block import _BlockData from pyomo.core.base.objective import _GeneralObjectiveData -from pyomo.common.config import ( - ConfigDict, - ConfigValue, - NonNegativeInt, - In, - NonNegativeFloat, -) from pyomo.common.timing import HierarchicalTimer from pyomo.common.errors import ApplicationError from pyomo.opt.base import SolverFactory as LegacySolverFactory from pyomo.common.factory import Factory import os from pyomo.opt.results.results_ import SolverResults as LegacySolverResults -from pyomo.opt.results.solution import ( - Solution as LegacySolution, - SolutionStatus as LegacySolutionStatus, -) -from pyomo.opt.results.solver import ( - TerminationCondition as LegacyTerminationCondition, - SolverStatus as LegacySolverStatus, -) +from pyomo.opt.results.solution import Solution as LegacySolution from pyomo.core.kernel.objective import minimize from pyomo.core.base import SymbolMap from pyomo.core.staleflag import StaleFlagManager from pyomo.solver.config import UpdateConfig -from pyomo.solver.solution import SolutionLoader from pyomo.solver.util import get_objective - - -class TerminationCondition(enum.Enum): - """ - An enumeration for checking the termination condition of solvers - """ - - """unknown serves as both a default value, and it is used when no other enum member makes sense""" - unknown = 42 - - """The solver exited because the convergence criteria were satisfied""" - convergenceCriteriaSatisfied = 0 - - """The solver exited due to a time limit""" - maxTimeLimit = 1 - - """The solver exited due to an iteration limit""" - iterationLimit = 2 - - """The solver exited due to an objective limit""" - objectiveLimit = 3 - - """The solver exited due to a minimum step length""" - minStepLength = 4 - - """The solver exited because the problem is unbounded""" - unbounded = 5 - - """The solver exited because the problem is proven infeasible""" - provenInfeasible = 6 - - """The solver exited because the problem was found to be locally infeasible""" - locallyInfeasible = 7 - - """The solver exited because the problem is either infeasible or unbounded""" - infeasibleOrUnbounded = 8 - - """The solver exited due to an error""" - error = 9 - - """The solver exited because it was interrupted""" - interrupted = 10 - - """The solver exited due to licensing problems""" - licensingProblems = 11 - - -class SolutionStatus(enum.IntEnum): - """ - An enumeration for interpreting the result of a termination. This describes the designated - status by the solver to be loaded back into the model. - - For now, we are choosing to use IntEnum such that return values are numerically - assigned in increasing order. - """ - - """No (single) solution found; possible that a population of solutions was returned""" - noSolution = 0 - - """Solution point does not satisfy some domains and/or constraints""" - infeasible = 10 - - """Feasible solution identified""" - feasible = 20 - - """Optimal solution identified""" - optimal = 30 - - -class Results(ConfigDict): - """ - Attributes - ---------- - termination_condition: TerminationCondition - The reason the solver exited. This is a member of the - TerminationCondition enum. - incumbent_objective: float - If a feasible solution was found, this is the objective value of - the best solution found. If no feasible solution was found, this is - None. - objective_bound: float - The best objective bound found. For minimization problems, this is - the lower bound. For maximization problems, this is the upper bound. - For solvers that do not provide an objective bound, this should be -inf - (minimization) or inf (maximization) - """ - - def __init__( - self, - description=None, - doc=None, - implicit=False, - implicit_domain=None, - visibility=0, - ): - super().__init__( - description=description, - doc=doc, - implicit=implicit, - implicit_domain=implicit_domain, - visibility=visibility, - ) - - self.declare( - 'solution_loader', - ConfigValue(default=SolutionLoader(None, None, None, None)), - ) - self.declare( - 'termination_condition', - ConfigValue( - domain=In(TerminationCondition), default=TerminationCondition.unknown - ), - ) - self.declare( - 'solution_status', - ConfigValue(domain=In(SolutionStatus), default=SolutionStatus.noSolution), - ) - self.incumbent_objective: Optional[float] = self.declare( - 'incumbent_objective', ConfigValue(domain=float) - ) - self.objective_bound: Optional[float] = self.declare( - 'objective_bound', ConfigValue(domain=float) - ) - self.declare('solver_name', ConfigValue(domain=str)) - self.declare('solver_version', ConfigValue(domain=tuple)) - self.declare('termination_message', ConfigValue(domain=str)) - self.declare('iteration_count', ConfigValue(domain=NonNegativeInt)) - self.declare('timing_info', ConfigDict()) - # TODO: Set up type checking for start_time - self.timing_info.declare('start_time', ConfigValue()) - self.timing_info.declare('wall_time', ConfigValue(domain=NonNegativeFloat)) - self.timing_info.declare( - 'solver_wall_time', ConfigValue(domain=NonNegativeFloat) - ) - self.declare('extra_info', ConfigDict(implicit=True)) - - def __str__(self): - s = '' - s += 'termination_condition: ' + str(self.termination_condition) + '\n' - s += 'solution_status: ' + str(self.solution_status) + '\n' - s += 'incumbent_objective: ' + str(self.incumbent_objective) + '\n' - s += 'objective_bound: ' + str(self.objective_bound) - return s +from pyomo.solver.results import Results, legacy_solver_status_map, legacy_termination_condition_map, legacy_solution_status_map class SolverBase(abc.ABC): @@ -437,56 +279,7 @@ def update_params(self): pass -# Everything below here preserves backwards compatibility - -legacy_termination_condition_map = { - TerminationCondition.unknown: LegacyTerminationCondition.unknown, - TerminationCondition.maxTimeLimit: LegacyTerminationCondition.maxTimeLimit, - TerminationCondition.iterationLimit: LegacyTerminationCondition.maxIterations, - TerminationCondition.objectiveLimit: LegacyTerminationCondition.minFunctionValue, - TerminationCondition.minStepLength: LegacyTerminationCondition.minStepLength, - TerminationCondition.convergenceCriteriaSatisfied: LegacyTerminationCondition.optimal, - TerminationCondition.unbounded: LegacyTerminationCondition.unbounded, - TerminationCondition.provenInfeasible: LegacyTerminationCondition.infeasible, - TerminationCondition.locallyInfeasible: LegacyTerminationCondition.infeasible, - TerminationCondition.infeasibleOrUnbounded: LegacyTerminationCondition.infeasibleOrUnbounded, - TerminationCondition.error: LegacyTerminationCondition.error, - TerminationCondition.interrupted: LegacyTerminationCondition.resourceInterrupt, - TerminationCondition.licensingProblems: LegacyTerminationCondition.licensingProblems, -} - - -legacy_solver_status_map = { - TerminationCondition.unknown: LegacySolverStatus.unknown, - TerminationCondition.maxTimeLimit: LegacySolverStatus.aborted, - TerminationCondition.iterationLimit: LegacySolverStatus.aborted, - TerminationCondition.objectiveLimit: LegacySolverStatus.aborted, - TerminationCondition.minStepLength: LegacySolverStatus.error, - TerminationCondition.convergenceCriteriaSatisfied: LegacySolverStatus.ok, - TerminationCondition.unbounded: LegacySolverStatus.error, - TerminationCondition.provenInfeasible: LegacySolverStatus.error, - TerminationCondition.locallyInfeasible: LegacySolverStatus.error, - TerminationCondition.infeasibleOrUnbounded: LegacySolverStatus.error, - TerminationCondition.error: LegacySolverStatus.error, - TerminationCondition.interrupted: LegacySolverStatus.aborted, - TerminationCondition.licensingProblems: LegacySolverStatus.error, -} - - -legacy_solution_status_map = { - SolutionStatus.noSolution: LegacySolutionStatus.unknown, - SolutionStatus.noSolution: LegacySolutionStatus.stoppedByLimit, - SolutionStatus.noSolution: LegacySolutionStatus.error, - SolutionStatus.noSolution: LegacySolutionStatus.other, - SolutionStatus.noSolution: LegacySolutionStatus.unsure, - SolutionStatus.noSolution: LegacySolutionStatus.unbounded, - SolutionStatus.optimal: LegacySolutionStatus.locallyOptimal, - SolutionStatus.optimal: LegacySolutionStatus.globallyOptimal, - SolutionStatus.optimal: LegacySolutionStatus.optimal, - SolutionStatus.infeasible: LegacySolutionStatus.infeasible, - SolutionStatus.feasible: LegacySolutionStatus.feasible, - SolutionStatus.feasible: LegacySolutionStatus.bestSoFar, -} + class LegacySolverInterface: diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py new file mode 100644 index 00000000000..0b6fdcafbc4 --- /dev/null +++ b/pyomo/solver/results.py @@ -0,0 +1,225 @@ +# ___________________________________________________________________________ +# +# 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 enum +from typing import Optional +from pyomo.common.config import ( + ConfigDict, + ConfigValue, + NonNegativeInt, + In, + NonNegativeFloat, +) +from pyomo.solver.solution import SolutionLoader +from pyomo.opt.results.solution import ( + SolutionStatus as LegacySolutionStatus, +) +from pyomo.opt.results.solver import ( + TerminationCondition as LegacyTerminationCondition, + SolverStatus as LegacySolverStatus, +) + + +class TerminationCondition(enum.Enum): + """ + An enumeration for checking the termination condition of solvers + """ + + """unknown serves as both a default value, and it is used when no other enum member makes sense""" + unknown = 42 + + """The solver exited because the convergence criteria were satisfied""" + convergenceCriteriaSatisfied = 0 + + """The solver exited due to a time limit""" + maxTimeLimit = 1 + + """The solver exited due to an iteration limit""" + iterationLimit = 2 + + """The solver exited due to an objective limit""" + objectiveLimit = 3 + + """The solver exited due to a minimum step length""" + minStepLength = 4 + + """The solver exited because the problem is unbounded""" + unbounded = 5 + + """The solver exited because the problem is proven infeasible""" + provenInfeasible = 6 + + """The solver exited because the problem was found to be locally infeasible""" + locallyInfeasible = 7 + + """The solver exited because the problem is either infeasible or unbounded""" + infeasibleOrUnbounded = 8 + + """The solver exited due to an error""" + error = 9 + + """The solver exited because it was interrupted""" + interrupted = 10 + + """The solver exited due to licensing problems""" + licensingProblems = 11 + + +class SolutionStatus(enum.IntEnum): + """ + An enumeration for interpreting the result of a termination. This describes the designated + status by the solver to be loaded back into the model. + + For now, we are choosing to use IntEnum such that return values are numerically + assigned in increasing order. + """ + + """No (single) solution found; possible that a population of solutions was returned""" + noSolution = 0 + + """Solution point does not satisfy some domains and/or constraints""" + infeasible = 10 + + """Feasible solution identified""" + feasible = 20 + + """Optimal solution identified""" + optimal = 30 + + +class Results(ConfigDict): + """ + Attributes + ---------- + termination_condition: TerminationCondition + The reason the solver exited. This is a member of the + TerminationCondition enum. + incumbent_objective: float + If a feasible solution was found, this is the objective value of + the best solution found. If no feasible solution was found, this is + None. + objective_bound: float + The best objective bound found. For minimization problems, this is + the lower bound. For maximization problems, this is the upper bound. + For solvers that do not provide an objective bound, this should be -inf + (minimization) or inf (maximization) + """ + + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + super().__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, + ) + + self.declare( + 'solution_loader', + ConfigValue(default=SolutionLoader(None, None, None, None)), + ) + self.declare( + 'termination_condition', + ConfigValue( + domain=In(TerminationCondition), default=TerminationCondition.unknown + ), + ) + self.declare( + 'solution_status', + ConfigValue(domain=In(SolutionStatus), default=SolutionStatus.noSolution), + ) + self.incumbent_objective: Optional[float] = self.declare( + 'incumbent_objective', ConfigValue(domain=float) + ) + self.objective_bound: Optional[float] = self.declare( + 'objective_bound', ConfigValue(domain=float) + ) + self.declare('solver_name', ConfigValue(domain=str)) + self.declare('solver_version', ConfigValue(domain=tuple)) + self.declare('termination_message', ConfigValue(domain=str)) + self.declare('iteration_count', ConfigValue(domain=NonNegativeInt)) + self.declare('timing_info', ConfigDict()) + # TODO: Set up type checking for start_time + self.timing_info.declare('start_time', ConfigValue()) + self.timing_info.declare('wall_time', ConfigValue(domain=NonNegativeFloat)) + self.timing_info.declare( + 'solver_wall_time', ConfigValue(domain=NonNegativeFloat) + ) + self.declare('extra_info', ConfigDict(implicit=True)) + + def __str__(self): + s = '' + s += 'termination_condition: ' + str(self.termination_condition) + '\n' + s += 'solution_status: ' + str(self.solution_status) + '\n' + s += 'incumbent_objective: ' + str(self.incumbent_objective) + '\n' + s += 'objective_bound: ' + str(self.objective_bound) + return s + + +# Everything below here preserves backwards compatibility + +legacy_termination_condition_map = { + TerminationCondition.unknown: LegacyTerminationCondition.unknown, + TerminationCondition.maxTimeLimit: LegacyTerminationCondition.maxTimeLimit, + TerminationCondition.iterationLimit: LegacyTerminationCondition.maxIterations, + TerminationCondition.objectiveLimit: LegacyTerminationCondition.minFunctionValue, + TerminationCondition.minStepLength: LegacyTerminationCondition.minStepLength, + TerminationCondition.convergenceCriteriaSatisfied: LegacyTerminationCondition.optimal, + TerminationCondition.unbounded: LegacyTerminationCondition.unbounded, + TerminationCondition.provenInfeasible: LegacyTerminationCondition.infeasible, + TerminationCondition.locallyInfeasible: LegacyTerminationCondition.infeasible, + TerminationCondition.infeasibleOrUnbounded: LegacyTerminationCondition.infeasibleOrUnbounded, + TerminationCondition.error: LegacyTerminationCondition.error, + TerminationCondition.interrupted: LegacyTerminationCondition.resourceInterrupt, + TerminationCondition.licensingProblems: LegacyTerminationCondition.licensingProblems, +} + + +legacy_solver_status_map = { + TerminationCondition.unknown: LegacySolverStatus.unknown, + TerminationCondition.maxTimeLimit: LegacySolverStatus.aborted, + TerminationCondition.iterationLimit: LegacySolverStatus.aborted, + TerminationCondition.objectiveLimit: LegacySolverStatus.aborted, + TerminationCondition.minStepLength: LegacySolverStatus.error, + TerminationCondition.convergenceCriteriaSatisfied: LegacySolverStatus.ok, + TerminationCondition.unbounded: LegacySolverStatus.error, + TerminationCondition.provenInfeasible: LegacySolverStatus.error, + TerminationCondition.locallyInfeasible: LegacySolverStatus.error, + TerminationCondition.infeasibleOrUnbounded: LegacySolverStatus.error, + TerminationCondition.error: LegacySolverStatus.error, + TerminationCondition.interrupted: LegacySolverStatus.aborted, + TerminationCondition.licensingProblems: LegacySolverStatus.error, +} + + +legacy_solution_status_map = { + SolutionStatus.noSolution: LegacySolutionStatus.unknown, + SolutionStatus.noSolution: LegacySolutionStatus.stoppedByLimit, + SolutionStatus.noSolution: LegacySolutionStatus.error, + SolutionStatus.noSolution: LegacySolutionStatus.other, + SolutionStatus.noSolution: LegacySolutionStatus.unsure, + SolutionStatus.noSolution: LegacySolutionStatus.unbounded, + SolutionStatus.optimal: LegacySolutionStatus.locallyOptimal, + SolutionStatus.optimal: LegacySolutionStatus.globallyOptimal, + SolutionStatus.optimal: LegacySolutionStatus.optimal, + SolutionStatus.infeasible: LegacySolutionStatus.infeasible, + SolutionStatus.feasible: LegacySolutionStatus.feasible, + SolutionStatus.feasible: LegacySolutionStatus.bestSoFar, +} + + diff --git a/pyomo/solver/tests/test_base.py b/pyomo/solver/tests/test_base.py index 0e0780fd6fe..d8084e9b5b7 100644 --- a/pyomo/solver/tests/test_base.py +++ b/pyomo/solver/tests/test_base.py @@ -10,61 +10,7 @@ # ___________________________________________________________________________ from pyomo.common import unittest -from pyomo.common.config import ConfigDict from pyomo.solver import base -import pyomo.environ as pe -from pyomo.core.base.var import ScalarVar - - -class TestTerminationCondition(unittest.TestCase): - def test_member_list(self): - member_list = base.TerminationCondition._member_names_ - expected_list = [ - 'unknown', - 'convergenceCriteriaSatisfied', - 'maxTimeLimit', - 'iterationLimit', - 'objectiveLimit', - 'minStepLength', - 'unbounded', - 'provenInfeasible', - 'locallyInfeasible', - 'infeasibleOrUnbounded', - 'error', - 'interrupted', - 'licensingProblems', - ] - self.assertEqual(member_list, expected_list) - - def test_codes(self): - self.assertEqual(base.TerminationCondition.unknown.value, 42) - self.assertEqual( - base.TerminationCondition.convergenceCriteriaSatisfied.value, 0 - ) - self.assertEqual(base.TerminationCondition.maxTimeLimit.value, 1) - self.assertEqual(base.TerminationCondition.iterationLimit.value, 2) - self.assertEqual(base.TerminationCondition.objectiveLimit.value, 3) - self.assertEqual(base.TerminationCondition.minStepLength.value, 4) - self.assertEqual(base.TerminationCondition.unbounded.value, 5) - self.assertEqual(base.TerminationCondition.provenInfeasible.value, 6) - self.assertEqual(base.TerminationCondition.locallyInfeasible.value, 7) - self.assertEqual(base.TerminationCondition.infeasibleOrUnbounded.value, 8) - self.assertEqual(base.TerminationCondition.error.value, 9) - self.assertEqual(base.TerminationCondition.interrupted.value, 10) - self.assertEqual(base.TerminationCondition.licensingProblems.value, 11) - - -class TestSolutionStatus(unittest.TestCase): - def test_member_list(self): - member_list = base.SolutionStatus._member_names_ - expected_list = ['noSolution', 'infeasible', 'feasible', 'optimal'] - self.assertEqual(member_list, expected_list) - - def test_codes(self): - self.assertEqual(base.SolutionStatus.noSolution.value, 0) - self.assertEqual(base.SolutionStatus.infeasible.value, 10) - self.assertEqual(base.SolutionStatus.feasible.value, 20) - self.assertEqual(base.SolutionStatus.optimal.value, 30) class TestSolverBase(unittest.TestCase): @@ -140,116 +86,3 @@ def test_persistent_solver_base(self): with self.assertRaises(NotImplementedError): self.instance.get_reduced_costs() - - -class TestResults(unittest.TestCase): - def test_declared_items(self): - res = base.Results() - expected_declared = { - 'extra_info', - 'incumbent_objective', - 'iteration_count', - 'objective_bound', - 'solution_loader', - 'solution_status', - 'solver_name', - 'solver_version', - 'termination_condition', - 'termination_message', - 'timing_info', - } - actual_declared = res._declared - self.assertEqual(expected_declared, actual_declared) - - def test_uninitialized(self): - res = base.Results() - self.assertIsNone(res.incumbent_objective) - self.assertIsNone(res.objective_bound) - self.assertEqual(res.termination_condition, base.TerminationCondition.unknown) - self.assertEqual(res.solution_status, base.SolutionStatus.noSolution) - self.assertIsNone(res.solver_name) - self.assertIsNone(res.solver_version) - self.assertIsNone(res.termination_message) - self.assertIsNone(res.iteration_count) - self.assertIsInstance(res.timing_info, ConfigDict) - self.assertIsInstance(res.extra_info, ConfigDict) - self.assertIsNone(res.timing_info.start_time) - self.assertIsNone(res.timing_info.wall_time) - self.assertIsNone(res.timing_info.solver_wall_time) - - with self.assertRaisesRegex( - RuntimeError, '.*does not currently have a valid solution.*' - ): - res.solution_loader.load_vars() - with self.assertRaisesRegex( - RuntimeError, '.*does not currently have valid duals.*' - ): - res.solution_loader.get_duals() - with self.assertRaisesRegex( - RuntimeError, '.*does not currently have valid reduced costs.*' - ): - res.solution_loader.get_reduced_costs() - with self.assertRaisesRegex( - RuntimeError, '.*does not currently have valid slacks.*' - ): - res.solution_loader.get_slacks() - - def test_results(self): - m = pe.ConcreteModel() - m.x = ScalarVar() - m.y = ScalarVar() - m.c1 = pe.Constraint(expr=m.x == 1) - m.c2 = pe.Constraint(expr=m.y == 2) - - primals = {} - primals[id(m.x)] = (m.x, 1) - primals[id(m.y)] = (m.y, 2) - duals = {} - duals[m.c1] = 3 - duals[m.c2] = 4 - rc = {} - rc[id(m.x)] = (m.x, 5) - rc[id(m.y)] = (m.y, 6) - slacks = {} - slacks[m.c1] = 7 - slacks[m.c2] = 8 - - res = base.Results() - res.solution_loader = base.SolutionLoader( - primals=primals, duals=duals, slacks=slacks, reduced_costs=rc - ) - - res.solution_loader.load_vars() - self.assertAlmostEqual(m.x.value, 1) - self.assertAlmostEqual(m.y.value, 2) - - m.x.value = None - m.y.value = None - - res.solution_loader.load_vars([m.y]) - self.assertIsNone(m.x.value) - self.assertAlmostEqual(m.y.value, 2) - - duals2 = res.solution_loader.get_duals() - self.assertAlmostEqual(duals[m.c1], duals2[m.c1]) - self.assertAlmostEqual(duals[m.c2], duals2[m.c2]) - - duals2 = res.solution_loader.get_duals([m.c2]) - self.assertNotIn(m.c1, duals2) - self.assertAlmostEqual(duals[m.c2], duals2[m.c2]) - - rc2 = res.solution_loader.get_reduced_costs() - self.assertAlmostEqual(rc[id(m.x)][1], rc2[m.x]) - self.assertAlmostEqual(rc[id(m.y)][1], rc2[m.y]) - - rc2 = res.solution_loader.get_reduced_costs([m.y]) - self.assertNotIn(m.x, rc2) - self.assertAlmostEqual(rc[id(m.y)][1], rc2[m.y]) - - slacks2 = res.solution_loader.get_slacks() - self.assertAlmostEqual(slacks[m.c1], slacks2[m.c1]) - self.assertAlmostEqual(slacks[m.c2], slacks2[m.c2]) - - slacks2 = res.solution_loader.get_slacks([m.c2]) - self.assertNotIn(m.c1, slacks2) - self.assertAlmostEqual(slacks[m.c2], slacks2[m.c2]) diff --git a/pyomo/solver/tests/test_results.py b/pyomo/solver/tests/test_results.py new file mode 100644 index 00000000000..74c0f9f2256 --- /dev/null +++ b/pyomo/solver/tests/test_results.py @@ -0,0 +1,180 @@ +# ___________________________________________________________________________ +# +# 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.common import unittest +from pyomo.common.config import ConfigDict +from pyomo.solver import results +import pyomo.environ as pyo +from pyomo.core.base.var import ScalarVar + + +class TestTerminationCondition(unittest.TestCase): + def test_member_list(self): + member_list = results.TerminationCondition._member_names_ + expected_list = [ + 'unknown', + 'convergenceCriteriaSatisfied', + 'maxTimeLimit', + 'iterationLimit', + 'objectiveLimit', + 'minStepLength', + 'unbounded', + 'provenInfeasible', + 'locallyInfeasible', + 'infeasibleOrUnbounded', + 'error', + 'interrupted', + 'licensingProblems', + ] + self.assertEqual(member_list, expected_list) + + def test_codes(self): + self.assertEqual(results.TerminationCondition.unknown.value, 42) + self.assertEqual( + results.TerminationCondition.convergenceCriteriaSatisfied.value, 0 + ) + self.assertEqual(results.TerminationCondition.maxTimeLimit.value, 1) + self.assertEqual(results.TerminationCondition.iterationLimit.value, 2) + self.assertEqual(results.TerminationCondition.objectiveLimit.value, 3) + self.assertEqual(results.TerminationCondition.minStepLength.value, 4) + self.assertEqual(results.TerminationCondition.unbounded.value, 5) + self.assertEqual(results.TerminationCondition.provenInfeasible.value, 6) + self.assertEqual(results.TerminationCondition.locallyInfeasible.value, 7) + self.assertEqual(results.TerminationCondition.infeasibleOrUnbounded.value, 8) + self.assertEqual(results.TerminationCondition.error.value, 9) + self.assertEqual(results.TerminationCondition.interrupted.value, 10) + self.assertEqual(results.TerminationCondition.licensingProblems.value, 11) + + +class TestSolutionStatus(unittest.TestCase): + def test_member_list(self): + member_list = results.SolutionStatus._member_names_ + expected_list = ['noSolution', 'infeasible', 'feasible', 'optimal'] + self.assertEqual(member_list, expected_list) + + def test_codes(self): + self.assertEqual(results.SolutionStatus.noSolution.value, 0) + self.assertEqual(results.SolutionStatus.infeasible.value, 10) + self.assertEqual(results.SolutionStatus.feasible.value, 20) + self.assertEqual(results.SolutionStatus.optimal.value, 30) + + +class TestResults(unittest.TestCase): + def test_declared_items(self): + res = results.Results() + expected_declared = { + 'extra_info', + 'incumbent_objective', + 'iteration_count', + 'objective_bound', + 'solution_loader', + 'solution_status', + 'solver_name', + 'solver_version', + 'termination_condition', + 'termination_message', + 'timing_info', + } + actual_declared = res._declared + self.assertEqual(expected_declared, actual_declared) + + def test_uninitialized(self): + res = results.Results() + self.assertIsNone(res.incumbent_objective) + self.assertIsNone(res.objective_bound) + self.assertEqual(res.termination_condition, results.TerminationCondition.unknown) + self.assertEqual(res.solution_status, results.SolutionStatus.noSolution) + self.assertIsNone(res.solver_name) + self.assertIsNone(res.solver_version) + self.assertIsNone(res.termination_message) + self.assertIsNone(res.iteration_count) + self.assertIsInstance(res.timing_info, ConfigDict) + self.assertIsInstance(res.extra_info, ConfigDict) + self.assertIsNone(res.timing_info.start_time) + self.assertIsNone(res.timing_info.wall_time) + self.assertIsNone(res.timing_info.solver_wall_time) + + with self.assertRaisesRegex( + RuntimeError, '.*does not currently have a valid solution.*' + ): + res.solution_loader.load_vars() + with self.assertRaisesRegex( + RuntimeError, '.*does not currently have valid duals.*' + ): + res.solution_loader.get_duals() + with self.assertRaisesRegex( + RuntimeError, '.*does not currently have valid reduced costs.*' + ): + res.solution_loader.get_reduced_costs() + with self.assertRaisesRegex( + RuntimeError, '.*does not currently have valid slacks.*' + ): + res.solution_loader.get_slacks() + + def test_results(self): + m = pyo.ConcreteModel() + m.x = ScalarVar() + m.y = ScalarVar() + m.c1 = pyo.Constraint(expr=m.x == 1) + m.c2 = pyo.Constraint(expr=m.y == 2) + + primals = {} + primals[id(m.x)] = (m.x, 1) + primals[id(m.y)] = (m.y, 2) + duals = {} + duals[m.c1] = 3 + duals[m.c2] = 4 + rc = {} + rc[id(m.x)] = (m.x, 5) + rc[id(m.y)] = (m.y, 6) + slacks = {} + slacks[m.c1] = 7 + slacks[m.c2] = 8 + + res = results.Results() + res.solution_loader = results.SolutionLoader( + primals=primals, duals=duals, slacks=slacks, reduced_costs=rc + ) + + res.solution_loader.load_vars() + self.assertAlmostEqual(m.x.value, 1) + self.assertAlmostEqual(m.y.value, 2) + + m.x.value = None + m.y.value = None + + res.solution_loader.load_vars([m.y]) + self.assertIsNone(m.x.value) + self.assertAlmostEqual(m.y.value, 2) + + duals2 = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.c1], duals2[m.c1]) + self.assertAlmostEqual(duals[m.c2], duals2[m.c2]) + + duals2 = res.solution_loader.get_duals([m.c2]) + self.assertNotIn(m.c1, duals2) + self.assertAlmostEqual(duals[m.c2], duals2[m.c2]) + + rc2 = res.solution_loader.get_reduced_costs() + self.assertAlmostEqual(rc[id(m.x)][1], rc2[m.x]) + self.assertAlmostEqual(rc[id(m.y)][1], rc2[m.y]) + + rc2 = res.solution_loader.get_reduced_costs([m.y]) + self.assertNotIn(m.x, rc2) + self.assertAlmostEqual(rc[id(m.y)][1], rc2[m.y]) + + slacks2 = res.solution_loader.get_slacks() + self.assertAlmostEqual(slacks[m.c1], slacks2[m.c1]) + self.assertAlmostEqual(slacks[m.c2], slacks2[m.c2]) + + slacks2 = res.solution_loader.get_slacks([m.c2]) + self.assertNotIn(m.c1, slacks2) + self.assertAlmostEqual(slacks[m.c2], slacks2[m.c2]) From f0a942cba9e33b1272e3f8a5296ab4a439ce2d0a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 31 Aug 2023 09:40:38 -0600 Subject: [PATCH 0073/1204] Update key value for solution status --- pyomo/solver/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 2d5bde41329..ea49f7e26e4 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -331,7 +331,7 @@ def solve( legacy_results.solver.termination_condition = legacy_termination_condition_map[ results.termination_condition ] - legacy_soln.status = legacy_solution_status_map[results.termination_condition] + legacy_soln.status = legacy_solution_status_map[results.solution_status] legacy_results.solver.termination_message = str(results.termination_condition) obj = get_objective(model) From 51b97f5bc837689f5eafc8db2ca74bfeabcca556 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 31 Aug 2023 09:45:54 -0600 Subject: [PATCH 0074/1204] Fix broken import statement --- pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py index 7e1d3e37af6..c1825879dbe 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py @@ -1,7 +1,7 @@ from pyomo.common import unittest import pyomo.environ as pe from pyomo.contrib.appsi.solvers.gurobi import Gurobi -from pyomo.solver.base import TerminationCondition +from pyomo.solver.results import TerminationCondition from pyomo.core.expr.taylor_series import taylor_series_expansion From 678df6fc48984ac81d399b8990320f8a00a40381 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 31 Aug 2023 09:51:24 -0600 Subject: [PATCH 0075/1204] Correct one more broken import statement; apply black --- pyomo/contrib/appsi/examples/getting_started.py | 4 ++-- pyomo/solver/base.py | 10 ++++++---- pyomo/solver/results.py | 6 +----- pyomo/solver/tests/test_results.py | 4 +++- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pyomo/contrib/appsi/examples/getting_started.py b/pyomo/contrib/appsi/examples/getting_started.py index 79f1aa845b3..52f4992b37b 100644 --- a/pyomo/contrib/appsi/examples/getting_started.py +++ b/pyomo/contrib/appsi/examples/getting_started.py @@ -1,7 +1,7 @@ import pyomo.environ as pe from pyomo.contrib import appsi from pyomo.common.timing import HierarchicalTimer -from pyomo.solver import base as solver_base +from pyomo.solver import results def main(plot=True, n_points=200): @@ -34,7 +34,7 @@ def main(plot=True, n_points=200): res = opt.solve(m, timer=timer) assert ( res.termination_condition - == solver_base.TerminationCondition.convergenceCriteriaSatisfied + == results.TerminationCondition.convergenceCriteriaSatisfied ) obj_values.append(res.incumbent_objective) opt.load_vars([m.x]) diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index ea49f7e26e4..9a7e19d7c85 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -29,7 +29,12 @@ from pyomo.core.staleflag import StaleFlagManager from pyomo.solver.config import UpdateConfig from pyomo.solver.util import get_objective -from pyomo.solver.results import Results, legacy_solver_status_map, legacy_termination_condition_map, legacy_solution_status_map +from pyomo.solver.results import ( + Results, + legacy_solver_status_map, + legacy_termination_condition_map, + legacy_solution_status_map, +) class SolverBase(abc.ABC): @@ -279,9 +284,6 @@ def update_params(self): pass - - - class LegacySolverInterface: def solve( self, diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index 0b6fdcafbc4..d51efa38168 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -19,9 +19,7 @@ NonNegativeFloat, ) from pyomo.solver.solution import SolutionLoader -from pyomo.opt.results.solution import ( - SolutionStatus as LegacySolutionStatus, -) +from pyomo.opt.results.solution import SolutionStatus as LegacySolutionStatus from pyomo.opt.results.solver import ( TerminationCondition as LegacyTerminationCondition, SolverStatus as LegacySolverStatus, @@ -221,5 +219,3 @@ def __str__(self): SolutionStatus.feasible: LegacySolutionStatus.feasible, SolutionStatus.feasible: LegacySolutionStatus.bestSoFar, } - - diff --git a/pyomo/solver/tests/test_results.py b/pyomo/solver/tests/test_results.py index 74c0f9f2256..7dea76c856f 100644 --- a/pyomo/solver/tests/test_results.py +++ b/pyomo/solver/tests/test_results.py @@ -90,7 +90,9 @@ def test_uninitialized(self): res = results.Results() self.assertIsNone(res.incumbent_objective) self.assertIsNone(res.objective_bound) - self.assertEqual(res.termination_condition, results.TerminationCondition.unknown) + self.assertEqual( + res.termination_condition, results.TerminationCondition.unknown + ) self.assertEqual(res.solution_status, results.SolutionStatus.noSolution) self.assertIsNone(res.solver_name) self.assertIsNone(res.solver_version) From f0d843cb62f9a5b1e5501c3b3db8a570505a4b8d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 31 Aug 2023 10:10:50 -0600 Subject: [PATCH 0076/1204] Reorder imports for prettiness --- pyomo/solver/base.py | 3 ++- pyomo/solver/config.py | 1 + pyomo/solver/results.py | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 9a7e19d7c85..2bd6245feda 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -12,6 +12,8 @@ import abc import enum from typing import Sequence, Dict, Optional, Mapping, NoReturn, List, Tuple +import os + from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.param import _ParamData @@ -21,7 +23,6 @@ from pyomo.common.errors import ApplicationError from pyomo.opt.base import SolverFactory as LegacySolverFactory from pyomo.common.factory import Factory -import os from pyomo.opt.results.results_ import SolverResults as LegacySolverResults from pyomo.opt.results.solution import Solution as LegacySolution from pyomo.core.kernel.objective import minimize diff --git a/pyomo/solver/config.py b/pyomo/solver/config.py index 32f6e1d5da0..d80e78eb2f2 100644 --- a/pyomo/solver/config.py +++ b/pyomo/solver/config.py @@ -10,6 +10,7 @@ # ___________________________________________________________________________ from typing import Optional + from pyomo.common.config import ( ConfigDict, ConfigValue, diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index d51efa38168..17e9416862e 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -11,6 +11,7 @@ import enum from typing import Optional + from pyomo.common.config import ( ConfigDict, ConfigValue, From 46d3c9095241f505540ec8e45069344021533e11 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 31 Aug 2023 10:15:04 -0600 Subject: [PATCH 0077/1204] Copyright added; init updated --- pyomo/solver/__init__.py | 1 + pyomo/solver/config.py | 2 +- pyomo/solver/plugins.py | 13 ++++++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/pyomo/solver/__init__.py b/pyomo/solver/__init__.py index a3c2e0e95e8..e3eafa991cc 100644 --- a/pyomo/solver/__init__.py +++ b/pyomo/solver/__init__.py @@ -11,5 +11,6 @@ from . import base from . import config +from . import results from . import solution from . import util diff --git a/pyomo/solver/config.py b/pyomo/solver/config.py index d80e78eb2f2..22399caa35e 100644 --- a/pyomo/solver/config.py +++ b/pyomo/solver/config.py @@ -61,7 +61,7 @@ def __init__( self.declare('load_solution', ConfigValue(domain=bool, default=True)) self.declare('symbolic_solver_labels', ConfigValue(domain=bool, default=False)) self.declare('report_timing', ConfigValue(domain=bool, default=False)) - self.declare('threads', ConfigValue(domain=NonNegativeInt, default=None)) + self.declare('threads', ConfigValue(domain=NonNegativeInt)) self.time_limit: Optional[float] = self.declare( 'time_limit', ConfigValue(domain=NonNegativeFloat) diff --git a/pyomo/solver/plugins.py b/pyomo/solver/plugins.py index 7e479474605..229488742cd 100644 --- a/pyomo/solver/plugins.py +++ b/pyomo/solver/plugins.py @@ -1,5 +1,16 @@ -from .base import SolverFactory +# ___________________________________________________________________________ +# +# 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 .base import SolverFactory def load(): pass From a619aca4ef54401d3e0658006fa569f35e6784e2 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 31 Aug 2023 11:07:12 -0600 Subject: [PATCH 0078/1204] Change several names; add type checking --- pyomo/contrib/appsi/solvers/cbc.py | 4 +-- pyomo/contrib/appsi/solvers/cplex.py | 8 +++--- pyomo/contrib/appsi/solvers/gurobi.py | 8 +++--- pyomo/contrib/appsi/solvers/highs.py | 8 +++--- pyomo/contrib/appsi/solvers/ipopt.py | 4 +-- pyomo/solver/base.py | 2 +- pyomo/solver/config.py | 40 ++++++++++++++++----------- pyomo/solver/plugins.py | 1 + pyomo/solver/results.py | 40 +++++++++++++++------------ pyomo/solver/solution.py | 2 ++ pyomo/solver/tests/test_config.py | 18 ++++++------ 11 files changed, 76 insertions(+), 59 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index c2686475b15..62404890d0b 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -23,7 +23,7 @@ from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager from pyomo.solver.base import PersistentSolverBase -from pyomo.solver.config import InterfaceConfig +from pyomo.solver.config import SolverConfig from pyomo.solver.results import TerminationCondition, Results from pyomo.solver.solution import PersistentSolutionLoader @@ -31,7 +31,7 @@ logger = logging.getLogger(__name__) -class CbcConfig(InterfaceConfig): +class CbcConfig(SolverConfig): def __init__( self, description=None, diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 86d50f1b82a..1837b5690a0 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -20,7 +20,7 @@ from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager from pyomo.solver.base import PersistentSolverBase -from pyomo.solver.config import MIPInterfaceConfig +from pyomo.solver.config import BranchAndBoundConfig from pyomo.solver.results import TerminationCondition, Results from pyomo.solver.solution import PersistentSolutionLoader @@ -28,7 +28,7 @@ logger = logging.getLogger(__name__) -class CplexConfig(MIPInterfaceConfig): +class CplexConfig(BranchAndBoundConfig): def __init__( self, description=None, @@ -263,8 +263,8 @@ def _process_stream(arg): if config.time_limit is not None: cplex_model.parameters.timelimit.set(config.time_limit) - if config.mip_gap is not None: - cplex_model.parameters.mip.tolerances.mipgap.set(config.mip_gap) + if config.rel_gap is not None: + cplex_model.parameters.mip.tolerances.mipgap.set(config.rel_gap) timer.start('cplex solve') t0 = time.time() diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index 1f295dfcb49..99fa19820a5 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -23,7 +23,7 @@ from pyomo.core.expr.numeric_expr import NPV_MaxExpression, NPV_MinExpression from pyomo.core.staleflag import StaleFlagManager from pyomo.solver.base import PersistentSolverBase -from pyomo.solver.config import MIPInterfaceConfig +from pyomo.solver.config import BranchAndBoundConfig from pyomo.solver.results import TerminationCondition, Results from pyomo.solver.solution import PersistentSolutionLoader from pyomo.solver.util import PersistentSolverUtils @@ -51,7 +51,7 @@ class DegreeError(PyomoException): pass -class GurobiConfig(MIPInterfaceConfig): +class GurobiConfig(BranchAndBoundConfig): def __init__( self, description=None, @@ -364,8 +364,8 @@ def _solve(self, timer: HierarchicalTimer): if config.time_limit is not None: self._solver_model.setParam('TimeLimit', config.time_limit) - if config.mip_gap is not None: - self._solver_model.setParam('MIPGap', config.mip_gap) + if config.rel_gap is not None: + self._solver_model.setParam('MIPGap', config.rel_gap) for key, option in options.items(): self._solver_model.setParam(key, option) diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index b5b2cc3b694..f62304563fc 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -21,7 +21,7 @@ from pyomo.common.dependencies import numpy as np from pyomo.core.staleflag import StaleFlagManager from pyomo.solver.base import PersistentSolverBase -from pyomo.solver.config import MIPInterfaceConfig +from pyomo.solver.config import BranchAndBoundConfig from pyomo.solver.results import TerminationCondition, Results from pyomo.solver.solution import PersistentSolutionLoader from pyomo.solver.util import PersistentSolverUtils @@ -35,7 +35,7 @@ class DegreeError(PyomoException): pass -class HighsConfig(MIPInterfaceConfig): +class HighsConfig(BranchAndBoundConfig): def __init__( self, description=None, @@ -219,8 +219,8 @@ def _solve(self, timer: HierarchicalTimer): if config.time_limit is not None: self._solver_model.setOptionValue('time_limit', config.time_limit) - if config.mip_gap is not None: - self._solver_model.setOptionValue('mip_rel_gap', config.mip_gap) + if config.rel_gap is not None: + self._solver_model.setOptionValue('mip_rel_gap', config.rel_gap) for key, option in options.items(): self._solver_model.setOptionValue(key, option) diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index b16ca4dc792..569bb98457f 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -27,7 +27,7 @@ from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager from pyomo.solver.base import PersistentSolverBase -from pyomo.solver.config import InterfaceConfig +from pyomo.solver.config import SolverConfig from pyomo.solver.results import TerminationCondition, Results from pyomo.solver.solution import PersistentSolutionLoader @@ -35,7 +35,7 @@ logger = logging.getLogger(__name__) -class IpoptConfig(InterfaceConfig): +class IpoptConfig(SolverConfig): def __init__( self, description=None, diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 2bd6245feda..ba25b23a354 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -130,7 +130,7 @@ def config(self): Returns ------- - InterfaceConfig + SolverConfig An object for configuring pyomo solve options such as the time limit. These options are mostly independent of the solver. """ diff --git a/pyomo/solver/config.py b/pyomo/solver/config.py index 22399caa35e..2f6f9b5bcc8 100644 --- a/pyomo/solver/config.py +++ b/pyomo/solver/config.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from typing import Optional - from pyomo.common.config import ( ConfigDict, ConfigValue, @@ -19,7 +17,7 @@ ) -class InterfaceConfig(ConfigDict): +class SolverConfig(ConfigDict): """ Attributes ---------- @@ -57,18 +55,24 @@ def __init__( visibility=visibility, ) - self.declare('tee', ConfigValue(domain=bool, default=False)) - self.declare('load_solution', ConfigValue(domain=bool, default=True)) - self.declare('symbolic_solver_labels', ConfigValue(domain=bool, default=False)) - self.declare('report_timing', ConfigValue(domain=bool, default=False)) - self.declare('threads', ConfigValue(domain=NonNegativeInt)) - - self.time_limit: Optional[float] = self.declare( + # TODO: Add in type-hinting everywhere + self.tee: bool = self.declare('tee', ConfigValue(domain=bool, default=False)) + self.load_solution: bool = self.declare( + 'load_solution', ConfigValue(domain=bool, default=True) + ) + self.symbolic_solver_labels: bool = self.declare( + 'symbolic_solver_labels', ConfigValue(domain=bool, default=False) + ) + self.report_timing: bool = self.declare( + 'report_timing', ConfigValue(domain=bool, default=False) + ) + self.threads = self.declare('threads', ConfigValue(domain=NonNegativeInt)) + self.time_limit: NonNegativeFloat = self.declare( 'time_limit', ConfigValue(domain=NonNegativeFloat) ) -class MIPInterfaceConfig(InterfaceConfig): +class BranchAndBoundConfig(SolverConfig): """ Attributes ---------- @@ -95,11 +99,15 @@ def __init__( visibility=visibility, ) - self.declare('mip_gap', ConfigValue(domain=NonNegativeFloat)) - self.declare('relax_integrality', ConfigValue(domain=bool)) - - self.mip_gap: Optional[float] = None - self.relax_integrality: bool = False + self.rel_gap: NonNegativeFloat = self.declare( + 'rel_gap', ConfigValue(domain=NonNegativeFloat) + ) + self.abs_gap: NonNegativeFloat = self.declare( + 'abs_gap', ConfigValue(domain=NonNegativeFloat) + ) + self.relax_integrality: bool = self.declare( + 'relax_integrality', ConfigValue(domain=bool, default=False) + ) class UpdateConfig(ConfigDict): diff --git a/pyomo/solver/plugins.py b/pyomo/solver/plugins.py index 229488742cd..5120bc9dd36 100644 --- a/pyomo/solver/plugins.py +++ b/pyomo/solver/plugins.py @@ -12,5 +12,6 @@ from .base import SolverFactory + def load(): pass diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index 17e9416862e..fa0080c5a7b 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -19,7 +19,6 @@ In, NonNegativeFloat, ) -from pyomo.solver.solution import SolutionLoader from pyomo.opt.results.solution import SolutionStatus as LegacySolutionStatus from pyomo.opt.results.solver import ( TerminationCondition as LegacyTerminationCondition, @@ -128,17 +127,14 @@ def __init__( visibility=visibility, ) - self.declare( - 'solution_loader', - ConfigValue(default=SolutionLoader(None, None, None, None)), - ) - self.declare( + self.solution_loader = self.declare('solution_loader', ConfigValue()) + self.termination_condition: In(TerminationCondition) = self.declare( 'termination_condition', ConfigValue( domain=In(TerminationCondition), default=TerminationCondition.unknown ), ) - self.declare( + self.solution_status: In(SolutionStatus) = self.declare( 'solution_status', ConfigValue(domain=In(SolutionStatus), default=SolutionStatus.noSolution), ) @@ -148,18 +144,28 @@ def __init__( self.objective_bound: Optional[float] = self.declare( 'objective_bound', ConfigValue(domain=float) ) - self.declare('solver_name', ConfigValue(domain=str)) - self.declare('solver_version', ConfigValue(domain=tuple)) - self.declare('termination_message', ConfigValue(domain=str)) - self.declare('iteration_count', ConfigValue(domain=NonNegativeInt)) - self.declare('timing_info', ConfigDict()) - # TODO: Set up type checking for start_time - self.timing_info.declare('start_time', ConfigValue()) - self.timing_info.declare('wall_time', ConfigValue(domain=NonNegativeFloat)) - self.timing_info.declare( + self.solver_name: Optional[str] = self.declare( + 'solver_name', ConfigValue(domain=str) + ) + self.solver_version: Optional[tuple] = self.declare( + 'solver_version', ConfigValue(domain=tuple) + ) + self.iteration_count: NonNegativeInt = self.declare( + 'iteration_count', ConfigValue(domain=NonNegativeInt) + ) + self.timing_info: ConfigDict = self.declare('timing_info', ConfigDict()) + self.timing_info.start_time = self.timing_info.declare( + 'start_time', ConfigValue() + ) + self.timing_info.wall_time: NonNegativeFloat = self.timing_info.declare( + 'wall_time', ConfigValue(domain=NonNegativeFloat) + ) + self.timing_info.solver_wall_time: NonNegativeFloat = self.timing_info.declare( 'solver_wall_time', ConfigValue(domain=NonNegativeFloat) ) - self.declare('extra_info', ConfigDict(implicit=True)) + self.extra_info: ConfigDict = self.declare( + 'extra_info', ConfigDict(implicit=True) + ) def __str__(self): s = '' diff --git a/pyomo/solver/solution.py b/pyomo/solver/solution.py index 1ef79050701..6c4b7431746 100644 --- a/pyomo/solver/solution.py +++ b/pyomo/solver/solution.py @@ -117,6 +117,8 @@ def get_reduced_costs( ) +# TODO: This is for development uses only; not to be released to the wild +# May turn into documentation someday class SolutionLoader(SolutionLoaderBase): def __init__( self, diff --git a/pyomo/solver/tests/test_config.py b/pyomo/solver/tests/test_config.py index 49d26513e2e..0686a3249d1 100644 --- a/pyomo/solver/tests/test_config.py +++ b/pyomo/solver/tests/test_config.py @@ -10,12 +10,12 @@ # ___________________________________________________________________________ from pyomo.common import unittest -from pyomo.solver.config import InterfaceConfig, MIPInterfaceConfig +from pyomo.solver.config import SolverConfig, BranchAndBoundConfig -class TestInterfaceConfig(unittest.TestCase): +class TestSolverConfig(unittest.TestCase): def test_interface_default_instantiation(self): - config = InterfaceConfig() + config = SolverConfig() self.assertEqual(config._description, None) self.assertEqual(config._visibility, 0) self.assertFalse(config.tee) @@ -24,7 +24,7 @@ def test_interface_default_instantiation(self): self.assertFalse(config.report_timing) def test_interface_custom_instantiation(self): - config = InterfaceConfig(description="A description") + config = SolverConfig(description="A description") config.tee = True self.assertTrue(config.tee) self.assertEqual(config._description, "A description") @@ -33,9 +33,9 @@ def test_interface_custom_instantiation(self): self.assertEqual(config.time_limit, 1.0) -class TestMIPInterfaceConfig(unittest.TestCase): +class TestBranchAndBoundConfig(unittest.TestCase): def test_interface_default_instantiation(self): - config = MIPInterfaceConfig() + config = BranchAndBoundConfig() self.assertEqual(config._description, None) self.assertEqual(config._visibility, 0) self.assertFalse(config.tee) @@ -46,12 +46,12 @@ def test_interface_default_instantiation(self): self.assertFalse(config.relax_integrality) def test_interface_custom_instantiation(self): - config = MIPInterfaceConfig(description="A description") + config = BranchAndBoundConfig(description="A description") config.tee = True self.assertTrue(config.tee) self.assertEqual(config._description, "A description") self.assertFalse(config.time_limit) config.time_limit = 1.0 self.assertEqual(config.time_limit, 1.0) - config.mip_gap = 2.5 - self.assertEqual(config.mip_gap, 2.5) + config.rel_gap = 2.5 + self.assertEqual(config.rel_gap, 2.5) From 5eb95a4f819751cce631a4bc114ee0e7bf90a29a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 31 Aug 2023 11:15:23 -0600 Subject: [PATCH 0079/1204] Fix problematic attribute changes --- pyomo/solver/tests/test_config.py | 3 ++- pyomo/solver/tests/test_results.py | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/solver/tests/test_config.py b/pyomo/solver/tests/test_config.py index 0686a3249d1..c705c7cb8ac 100644 --- a/pyomo/solver/tests/test_config.py +++ b/pyomo/solver/tests/test_config.py @@ -42,7 +42,8 @@ def test_interface_default_instantiation(self): self.assertTrue(config.load_solution) self.assertFalse(config.symbolic_solver_labels) self.assertFalse(config.report_timing) - self.assertEqual(config.mip_gap, None) + self.assertEqual(config.rel_gap, None) + self.assertEqual(config.abs_gap, None) self.assertFalse(config.relax_integrality) def test_interface_custom_instantiation(self): diff --git a/pyomo/solver/tests/test_results.py b/pyomo/solver/tests/test_results.py index 7dea76c856f..60b14d77521 100644 --- a/pyomo/solver/tests/test_results.py +++ b/pyomo/solver/tests/test_results.py @@ -12,6 +12,7 @@ from pyomo.common import unittest from pyomo.common.config import ConfigDict from pyomo.solver import results +from pyomo.solver import solution import pyomo.environ as pyo from pyomo.core.base.var import ScalarVar @@ -80,7 +81,6 @@ def test_declared_items(self): 'solver_name', 'solver_version', 'termination_condition', - 'termination_message', 'timing_info', } actual_declared = res._declared @@ -96,7 +96,6 @@ def test_uninitialized(self): self.assertEqual(res.solution_status, results.SolutionStatus.noSolution) self.assertIsNone(res.solver_name) self.assertIsNone(res.solver_version) - self.assertIsNone(res.termination_message) self.assertIsNone(res.iteration_count) self.assertIsInstance(res.timing_info, ConfigDict) self.assertIsInstance(res.extra_info, ConfigDict) @@ -142,7 +141,7 @@ def test_results(self): slacks[m.c2] = 8 res = results.Results() - res.solution_loader = results.SolutionLoader( + res.solution_loader = solution.SolutionLoader( primals=primals, duals=duals, slacks=slacks, reduced_costs=rc ) From 198d0b1fb8d37d791f4fd89270772d4e56387b4c Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 31 Aug 2023 11:25:07 -0600 Subject: [PATCH 0080/1204] Change type hinting for several config/results objects --- pyomo/solver/config.py | 44 +++++++++++++++++------------------------ pyomo/solver/results.py | 23 ++++++++++++--------- 2 files changed, 32 insertions(+), 35 deletions(-) diff --git a/pyomo/solver/config.py b/pyomo/solver/config.py index 2f6f9b5bcc8..efd2a1bac16 100644 --- a/pyomo/solver/config.py +++ b/pyomo/solver/config.py @@ -9,6 +9,8 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +from typing import Optional + from pyomo.common.config import ( ConfigDict, ConfigValue, @@ -55,7 +57,6 @@ def __init__( visibility=visibility, ) - # TODO: Add in type-hinting everywhere self.tee: bool = self.declare('tee', ConfigValue(domain=bool, default=False)) self.load_solution: bool = self.declare( 'load_solution', ConfigValue(domain=bool, default=True) @@ -66,8 +67,10 @@ def __init__( self.report_timing: bool = self.declare( 'report_timing', ConfigValue(domain=bool, default=False) ) - self.threads = self.declare('threads', ConfigValue(domain=NonNegativeInt)) - self.time_limit: NonNegativeFloat = self.declare( + self.threads: Optional[int] = self.declare( + 'threads', ConfigValue(domain=NonNegativeInt) + ) + self.time_limit: Optional[float] = self.declare( 'time_limit', ConfigValue(domain=NonNegativeFloat) ) @@ -99,10 +102,10 @@ def __init__( visibility=visibility, ) - self.rel_gap: NonNegativeFloat = self.declare( + self.rel_gap: Optional[float] = self.declare( 'rel_gap', ConfigValue(domain=NonNegativeFloat) ) - self.abs_gap: NonNegativeFloat = self.declare( + self.abs_gap: Optional[float] = self.declare( 'abs_gap', ConfigValue(domain=NonNegativeFloat) ) self.relax_integrality: bool = self.declare( @@ -143,7 +146,7 @@ def __init__( visibility=visibility, ) - self.declare( + self.check_for_new_or_removed_constraints: bool = self.declare( 'check_for_new_or_removed_constraints', ConfigValue( domain=bool, @@ -155,7 +158,7 @@ def __init__( added to/removed from the model.""", ), ) - self.declare( + self.check_for_new_or_removed_vars: bool = self.declare( 'check_for_new_or_removed_vars', ConfigValue( domain=bool, @@ -167,7 +170,7 @@ def __init__( removed from the model.""", ), ) - self.declare( + self.check_for_new_or_removed_params: bool = self.declare( 'check_for_new_or_removed_params', ConfigValue( domain=bool, @@ -179,7 +182,7 @@ def __init__( removed from the model.""", ), ) - self.declare( + self.check_for_new_objective: bool = self.declare( 'check_for_new_objective', ConfigValue( domain=bool, @@ -190,7 +193,7 @@ def __init__( when you are certain objectives are not being added to / removed from the model.""", ), ) - self.declare( + self.update_constraints: bool = self.declare( 'update_constraints', ConfigValue( domain=bool, @@ -203,7 +206,7 @@ def __init__( are not being modified.""", ), ) - self.declare( + self.update_vars: bool = self.declare( 'update_vars', ConfigValue( domain=bool, @@ -215,7 +218,7 @@ def __init__( opt.update_variables() or when you are certain variables are not being modified.""", ), ) - self.declare( + self.update_params: bool = self.declare( 'update_params', ConfigValue( domain=bool, @@ -226,7 +229,7 @@ def __init__( opt.update_params() or when you are certain parameters are not being modified.""", ), ) - self.declare( + self.update_named_expressions: bool = self.declare( 'update_named_expressions', ConfigValue( domain=bool, @@ -238,7 +241,7 @@ def __init__( Expressions are not being modified.""", ), ) - self.declare( + self.update_objective: bool = self.declare( 'update_objective', ConfigValue( domain=bool, @@ -250,7 +253,7 @@ def __init__( certain objectives are not being modified.""", ), ) - self.declare( + self.treat_fixed_vars_as_params: bool = self.declare( 'treat_fixed_vars_as_params', ConfigValue( domain=bool, @@ -267,14 +270,3 @@ def __init__( updating the values of fixed variables is much faster this way.""", ), ) - - self.check_for_new_or_removed_constraints: bool = True - self.check_for_new_or_removed_vars: bool = True - self.check_for_new_or_removed_params: bool = True - self.check_for_new_objective: bool = True - self.update_constraints: bool = True - self.update_vars: bool = True - self.update_params: bool = True - self.update_named_expressions: bool = True - self.update_objective: bool = True - self.treat_fixed_vars_as_params: bool = True diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index fa0080c5a7b..02a898f2df5 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -10,7 +10,8 @@ # ___________________________________________________________________________ import enum -from typing import Optional +from typing import Optional, Tuple +from datetime import datetime from pyomo.common.config import ( ConfigDict, @@ -24,6 +25,7 @@ TerminationCondition as LegacyTerminationCondition, SolverStatus as LegacySolverStatus, ) +from pyomo.solver.solution import SolutionLoaderBase class TerminationCondition(enum.Enum): @@ -127,14 +129,16 @@ def __init__( visibility=visibility, ) - self.solution_loader = self.declare('solution_loader', ConfigValue()) - self.termination_condition: In(TerminationCondition) = self.declare( + self.solution_loader: SolutionLoaderBase = self.declare( + 'solution_loader', ConfigValue() + ) + self.termination_condition: TerminationCondition = self.declare( 'termination_condition', ConfigValue( domain=In(TerminationCondition), default=TerminationCondition.unknown ), ) - self.solution_status: In(SolutionStatus) = self.declare( + self.solution_status: SolutionStatus = self.declare( 'solution_status', ConfigValue(domain=In(SolutionStatus), default=SolutionStatus.noSolution), ) @@ -147,20 +151,21 @@ def __init__( self.solver_name: Optional[str] = self.declare( 'solver_name', ConfigValue(domain=str) ) - self.solver_version: Optional[tuple] = self.declare( + self.solver_version: Optional[Tuple[int, ...]] = self.declare( 'solver_version', ConfigValue(domain=tuple) ) - self.iteration_count: NonNegativeInt = self.declare( + self.iteration_count: Optional[int] = self.declare( 'iteration_count', ConfigValue(domain=NonNegativeInt) ) self.timing_info: ConfigDict = self.declare('timing_info', ConfigDict()) - self.timing_info.start_time = self.timing_info.declare( + # TODO: Implement type checking for datetime + self.timing_info.start_time: datetime = self.timing_info.declare( 'start_time', ConfigValue() ) - self.timing_info.wall_time: NonNegativeFloat = self.timing_info.declare( + self.timing_info.wall_time: Optional[float] = self.timing_info.declare( 'wall_time', ConfigValue(domain=NonNegativeFloat) ) - self.timing_info.solver_wall_time: NonNegativeFloat = self.timing_info.declare( + self.timing_info.solver_wall_time: Optional[float] = self.timing_info.declare( 'solver_wall_time', ConfigValue(domain=NonNegativeFloat) ) self.extra_info: ConfigDict = self.declare( From 6dbc84d3f0064cb133fff46a62b0c14a0495dfd2 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 31 Aug 2023 11:35:34 -0600 Subject: [PATCH 0081/1204] Change un-init test to assign an empty SolutionLoader --- pyomo/solver/tests/test_results.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/solver/tests/test_results.py b/pyomo/solver/tests/test_results.py index 60b14d77521..f43b2b50ef4 100644 --- a/pyomo/solver/tests/test_results.py +++ b/pyomo/solver/tests/test_results.py @@ -102,6 +102,7 @@ def test_uninitialized(self): self.assertIsNone(res.timing_info.start_time) self.assertIsNone(res.timing_info.wall_time) self.assertIsNone(res.timing_info.solver_wall_time) + res.solution_loader = solution.SolutionLoader(None, None, None, None) with self.assertRaisesRegex( RuntimeError, '.*does not currently have a valid solution.*' From 60cc641008b9d501c50db0d44a84fb861450d16d Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Thu, 31 Aug 2023 13:39:38 -0600 Subject: [PATCH 0082/1204] printer is working --- pyomo/util/latex_printer.py | 474 ++++++++++++++++++++++++++++++++++++ 1 file changed, 474 insertions(+) create mode 100644 pyomo/util/latex_printer.py diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py new file mode 100644 index 00000000000..207c9501560 --- /dev/null +++ b/pyomo/util/latex_printer.py @@ -0,0 +1,474 @@ +import pyomo.environ as pe +from pyomo.core.expr.visitor import StreamBasedExpressionVisitor +from pyomo.core.expr import ( + NegationExpression, + ProductExpression, + DivisionExpression, + PowExpression, + AbsExpression, + UnaryFunctionExpression, + MonomialTermExpression, + LinearExpression, + SumExpression, + EqualityExpression, + InequalityExpression, + RangedExpression, + Expr_ifExpression, + ExternalFunctionExpression, +) + +from pyomo.core.expr.base import ExpressionBase +from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData +from pyomo.core.base.objective import ScalarObjective, _GeneralObjectiveData +import pyomo.core.kernel as kernel +from pyomo.core.expr.template_expr import GetItemExpression, GetAttrExpression, TemplateSumExpression, IndexTemplate +from pyomo.core.expr.template_expr import Numeric_GetItemExpression +from pyomo.core.expr.template_expr import templatize_constraint, resolve_template +from pyomo.core.base.var import ScalarVar, _GeneralVarData, IndexedVar +from pyomo.core.base.constraint import ScalarConstraint, IndexedConstraint + +from pyomo.core.base.external import _PythonCallbackFunctionID + +from pyomo.core.base.block import _BlockData + +from pyomo.repn.util import ExprType + +_CONSTANT = ExprType.CONSTANT +_MONOMIAL = ExprType.MONOMIAL +_GENERAL = ExprType.GENERAL + +# see: https://github.com/Pyomo/pyomo/blob/main/pyomo/repn/plugins/nl_writer.py + +def templatize_expression(expr): + expr, indices = templatize_rule(expr.parent_block(), expr._rule, expr.index_set()) + return (expr, indices) + +def templatize_passthrough(con): + return (con, []) + +def handle_negation_node(node, arg1): + return '-' + arg1 + +def handle_product_node(node, arg1, arg2): + childPrecedence = [] + for a in node.args: + if hasattr(a, 'PRECEDENCE'): + if a.PRECEDENCE is None: + childPrecedence.append(-1) + else: + childPrecedence.append(a.PRECEDENCE) + else: + childPrecedence.append(-1) + + if hasattr(node, 'PRECEDENCE'): + precedence = node.PRECEDENCE + else: + # Should never hit this + precedence = -1 + + if childPrecedence[0] > precedence: + arg1 = ' \\left( ' + arg1 + ' \\right) ' + + if childPrecedence[1] > precedence: + arg2 = ' \\left( ' + arg2 + ' \\right) ' + + return ''.join([arg1,arg2]) + +def handle_division_node(node, arg1, arg2): + # return '/'.join([arg1,arg2]) + return '\\frac{%s}{%s}'%(arg1,arg2) + +def handle_pow_node(node, arg1, arg2): + return "%s^{%s}"%(arg1, arg2) + +def handle_abs_node(node, arg1): + return ' \\left| ' + arg1 + ' \\right| ' + +def handle_unary_node(node, arg1): + fcn_handle = node.getname() + if fcn_handle == 'log10': + fcn_handle = 'log_{10}' + + if fcn_handle == 'sqrt': + return '\\sqrt { ' + arg1 + ' }' + else: + return '\\' + fcn_handle + ' \\left( ' + arg1 + ' \\right) ' + +def handle_equality_node(node, arg1, arg2): + return arg1 + ' = ' + arg2 + +def handle_inequality_node(node, arg1, arg2): + return arg1 + ' \leq ' + arg2 + +def handle_scalarVar_node(node): + return node.name + +def handle_num_node(node): + if isinstance(node,float): + if node.is_integer(): + node = int(node) + return str(node) + +def handle_sum_expression(node,*args): + rstr = args[0] + for i in range(1,len(args)): + if args[i][0]=='-': + rstr += ' - ' + args[i][1:] + else: + rstr += ' + ' + args[i] + return rstr + +def handle_monomialTermExpression_node(node, arg1, arg2): + if arg1 == '1': + return arg2 + elif arg1 == '-1': + return '-' + arg2 + else: + return arg1 + arg2 + +def handle_named_expression_node(node, arg1): + return arg1 + +def handle_ranged_inequality_node(node, arg1, arg2, arg3): + return arg1 + ' \\leq ' + arg2 + ' \\leq ' + arg3 + +def handle_exprif_node(node, arg1, arg2, arg3): + return 'f_{\\text{exprIf}}(' + arg1 + ',' + arg2 + ',' + arg3 + ')' + # raise NotImplementedError('Expr_if objects not supported by the Latex Printer') +# pstr = '' +# pstr += '\\begin{Bmatrix} ' +# pstr += arg2 + ' , & ' + arg1 + '\\\\ ' +# pstr += arg3 + ' , & \\text{otherwise}' + '\\\\ ' +# pstr += '\\end{Bmatrix}' +# return pstr + +def handle_external_function_node(node,*args): + pstr = '' + pstr += 'f(' + for i in range(0,len(args)-1): + pstr += args[i] + if i <= len(args)-3: + pstr += ',' + else: + pstr += ')' + return pstr + +def handle_functionID_node(node,*args): + # seems to just be a placeholder empty wrapper object + return '' + +def handle_indexedVar_node(node,*args): + return node.name + +def handle_indexTemplate_node(node,*args): + return '__INDEX_PLACEHOLDER_8675309_GROUP_%s_%s__'%(node._group, node._set) + +def handle_numericGIE_node(node,*args): + pstr = '' + pstr += args[0] + '_{' + for i in range(1,len(args)): + pstr += args[i] + if i <= len(args)-2: + pstr += ',' + else: + pstr += '}' + return pstr + +def handle_templateSumExpression_node(node,*args): + pstr = '' + pstr += '\\sum_{%s} %s'%('__SET_PLACEHOLDER_8675309_GROUP_%s_%s__'%( node._iters[0][0]._group, + str(node._iters[0][0]._set) ), args[0]) + return pstr + + +class _LatexVisitor(StreamBasedExpressionVisitor): + def __init__(self): + super().__init__() + + self._operator_handles = { + ScalarVar: handle_scalarVar_node, + int: handle_num_node, + float: handle_num_node, + NegationExpression: handle_negation_node, + ProductExpression: handle_product_node, + DivisionExpression: handle_division_node, + PowExpression: handle_pow_node, + AbsExpression: handle_abs_node, + UnaryFunctionExpression: handle_unary_node, + Expr_ifExpression: handle_exprif_node, + EqualityExpression: handle_equality_node, + InequalityExpression: handle_inequality_node, + RangedExpression: handle_ranged_inequality_node, + _GeneralExpressionData: handle_named_expression_node, + ScalarExpression: handle_named_expression_node, + kernel.expression.expression: handle_named_expression_node, + kernel.expression.noclone: handle_named_expression_node, + _GeneralObjectiveData: handle_named_expression_node, + _GeneralVarData: handle_scalarVar_node, + ScalarObjective: handle_named_expression_node, + kernel.objective.objective: handle_named_expression_node, + ExternalFunctionExpression: handle_external_function_node, + _PythonCallbackFunctionID: handle_functionID_node, + LinearExpression: handle_sum_expression, + SumExpression: handle_sum_expression, + MonomialTermExpression: handle_monomialTermExpression_node, + IndexedVar: handle_indexedVar_node, + IndexTemplate: handle_indexTemplate_node, + Numeric_GetItemExpression: handle_numericGIE_node, + TemplateSumExpression: handle_templateSumExpression_node, + } + + def exitNode(self,node,data): + return self._operator_handles[node.__class__](node,*data) + +def latex_printer(pyomoElement, filename=None, useAlignEnvironment=False, splitContinuousSets=False): + ''' + This function produces a string that can be rendered as LaTeX + + pyomoElement: The thing to be printed to LaTeX. Accepts Blocks (including models), Constraints, and Expressions + + filename: An optional file to write the LaTeX to. Default of None produces no file + + useAlignEnvironment: Default behavior uses equation/aligned and produces a single LaTeX Equation (ie, ==False). + Setting this input to True will instead use the align environment, and produce equation numbers for each + objective and constraint. Each objective and constraint will be labled with its name in the pyomo model. + This flag is only relevant for Models and Blocks. + + splitContinuous: Default behavior has all sum indicies be over "i \in I" or similar. Setting this flag to + True makes the sums go from: \sum_{i=1}^{5} if the set I is continuous and has 5 elements + + ''' + + # Various setup things + isSingle=False + if not isinstance(pyomoElement, _BlockData): + if isinstance(pyomoElement, pe.Objective): + objectives = [pyomoElement] + constraints = [] + expressions = [] + templatize_fcn = templatize_constraint + + if isinstance(pyomoElement, pe.Constraint): + objectives = [] + constraints = [pyomoElement] + expressions = [] + templatize_fcn = templatize_constraint + + if isinstance(pyomoElement, pe.Expression): + objectives = [] + constraints = [] + expressions = [pyomoElement] + templatize_fcn = templatize_expression + + + if isinstance(pyomoElement, ExpressionBase): + objectives = [] + constraints = [] + expressions = [pyomoElement] + templatize_fcn = templatize_passthrough + + useAlignEnvironment = False + isSingle = True + else: + objectives = [obj for obj in pyomoElement.component_data_objects(pe.Objective, descend_into=True, active=True)] + constraints = [con for con in pyomoElement.component_objects(pe.Constraint, descend_into=True, active=True)] + expressions = [] + templatize_fcn = templatize_constraint + + # In the case where just a single expression is passed, add this to the constraint list for printing + constraints = constraints + expressions + + # Declare a visitor/walker + visitor = _LatexVisitor() + + # starts building the output string + pstr = '' + if useAlignEnvironment: + pstr += '\\begin{align} \n' + tbSpc = 4 + else: + pstr += '\\begin{equation} \n' + if not isSingle: + pstr += ' \\begin{aligned} \n' + tbSpc = 8 + else: + tbSpc = 4 + + # Iterate over the objectives and print + for obj in objectives: + obj_template, obj_indices = templatize_fcn(obj) + if obj.sense == 1: + pstr += ' '*tbSpc + '& \\text{%s} \n'%('minimize') + else: + pstr += ' '*tbSpc + '& \\text{%s} \n'%('maximize') + + pstr += ' '*tbSpc + '& & %s '%(visitor.walk_expression(obj_template)) + if useAlignEnvironment: + pstr += '\\label{obj:' + m.name + '_' + obj.name + '} ' + pstr += '\\\\ \n' + + # Iterate over the constraints + if len(constraints) > 0: + # only print this if printing a full formulation + if not isSingle: + pstr += ' '*tbSpc + '& \\text{subject to} \n' + + # first constraint needs different alignment because of the 'subject to' + for i in range(0,len(constraints)): + if not isSingle: + if i==0: + algn = '& &' + else: + algn = '&&&' + else: + algn = '' + + tail = '\\\\ \n' + + # grab the constraint and templatize + con = constraints[i] + con_template, indices = templatize_fcn(con) + + # Walk the constraint + conLine = ' '*tbSpc + algn + ' %s '%(visitor.walk_expression(con_template)) + + # Multiple constraints are generated using a set + if len(indices) > 0: + nm = indices[0]._set + gp = indices[0]._group + + ixTag = '__INDEX_PLACEHOLDER_8675309_GROUP_%s_%s__'%(gp,nm) + stTag = '__SET_PLACEHOLDER_8675309_GROUP_%s_%s__'%( gp, nm ) + + conLine += ', \\quad %s \\in %s '%(ixTag,stTag) + pstr += conLine + + # Add labels as needed + if useAlignEnvironment: + pstr += '\\label{con:' + m.name + '_' + con.name + '} ' + + # prevents an emptly blank line from being at the end of the latex output + if i <= len(constraints)-2: + pstr += tail + else: + pstr += '\n' + + # close off the print string + if useAlignEnvironment: + pstr += '\\end{align} \n' + else: + if not isSingle: + pstr += ' \\end{aligned} \n' + pstr += ' \\label{%s} \n '%(m.name) + pstr += '\end{equation} \n' + + + # Handling the iterator indicies + + # preferential order for indices + setPreferenceOrder = ['I','J','K','M','N','P','Q','R','S','T','U','V','W'] + + # Go line by line and replace the placeholders with correct set names and index names + latexLines = pstr.split('\n') + for jj in range(0,len(latexLines)): + groupMap = {} + uniqueSets = [] + ln = latexLines[jj] + # only modify if there is a placeholder in the line + if "PLACEHOLDER_8675309_GROUP_" in ln: + splitLatex = ln.split('__') + # Find the unique combinations of group numbers and set names + for word in splitLatex: + if "PLACEHOLDER_8675309_GROUP_" in word: + ifo = word.split("PLACEHOLDER_8675309_GROUP_")[1] + gpNum, stNam = ifo.split('_') + if gpNum not in groupMap.keys(): + groupMap[gpNum] = [stNam] + if stNam not in uniqueSets: + uniqueSets.append(stNam) + + # Determine if the set is continuous + continuousSets = dict(zip(uniqueSets, [False for i in range(0,len(uniqueSets))] )) + for i in range(0,len(uniqueSets)): + st = getattr(m,uniqueSets[i]) + stData = st.data() + stCont = True + for ii in range(0,len(stData)): + if ii+stData[0] != stData[ii]: + stCont = False + break + continuousSets[uniqueSets[i]] = stCont + + # Add the continuous set data to the groupMap + for ky, vl in groupMap.items(): + groupMap[ky].append(continuousSets[vl[0]]) + + # Set up new names for duplicate sets + assignedSetNames = [] + gmk_list = list(groupMap.keys()) + for i in range(0,len(groupMap.keys())): + ix = gmk_list[i] + # set not already used + if groupMap[str(ix)][0] not in assignedSetNames: + assignedSetNames.append(groupMap[str(ix)][0]) + groupMap[str(ix)].append(groupMap[str(ix)][0]) + else: + # Pick a new set from the preference order + for j in range(0,len(setPreferenceOrder)): + stprf = setPreferenceOrder[j] + # must not be already used + if stprf not in assignedSetNames: + assignedSetNames.append(stprf) + groupMap[str(ix)].append(stprf) + break + + # set up the substitutions + setStrings = {} + indexStrings = {} + for ky, vl in groupMap.items(): + setStrings['__SET_PLACEHOLDER_8675309_GROUP_%s_%s__'%(ky,vl[0])] = [ vl[2], vl[1], vl[0] ] + indexStrings['__INDEX_PLACEHOLDER_8675309_GROUP_%s_%s__'%(ky,vl[0])] = vl[2].lower() + + # replace the indices + for ky, vl in indexStrings.items(): + ln = ln.replace(ky,vl) + + # replace the sets + for ky, vl in setStrings.items(): + # if the set is continuous and the flag has been set + if splitContinuousSets and vl[1]: + st = getattr(m,vl[2]) + stData = st.data() + bgn = stData[0] + ed = stData[-1] + ln = ln.replace('\\sum_{%s}'%(ky), '\\sum_{%s = %d}^{%d}'%(vl[0].lower(), bgn, ed)) + ln = ln.replace(ky,vl[2]) + else: + # if the set is not continuous or the flag has not been set + st = getattr(m,vl[2]) + stData = st.data() + bgn = stData[0] + ed = stData[-1] + ln = ln.replace('\\sum_{%s}'%(ky), '\\sum_{%s \\in %s}'%(vl[0].lower(),vl[0])) + ln = ln.replace(ky,vl[2]) + + # Assign the newly modified line + latexLines[jj] = ln + + # rejoin the corrected lines + pstr = '\n'.join(latexLines) + + # optional write to output file + if filename is not None: + fstr = '' + fstr += '\\documentclass{article} \n' + fstr += '\\usepackage{amsmath} \n' + fstr += '\\begin{document} \n' + fstr += pstr + fstr += '\\end{document} \n' + f = open(filename,'w') + f.write(fstr) + f.close() + + # return the latex string + return pstr \ No newline at end of file From 0783f41ac4a784f8a23d7e3cf98485a7c3443821 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Thu, 31 Aug 2023 13:47:23 -0600 Subject: [PATCH 0083/1204] doing typos and black --- pyomo/util/latex_printer.py | 328 ++++++++++++++++++++++-------------- 1 file changed, 202 insertions(+), 126 deletions(-) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index 207c9501560..ba3bcf378eb 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -21,7 +21,12 @@ from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData from pyomo.core.base.objective import ScalarObjective, _GeneralObjectiveData import pyomo.core.kernel as kernel -from pyomo.core.expr.template_expr import GetItemExpression, GetAttrExpression, TemplateSumExpression, IndexTemplate +from pyomo.core.expr.template_expr import ( + GetItemExpression, + GetAttrExpression, + TemplateSumExpression, + IndexTemplate, +) from pyomo.core.expr.template_expr import Numeric_GetItemExpression from pyomo.core.expr.template_expr import templatize_constraint, resolve_template from pyomo.core.base.var import ScalarVar, _GeneralVarData, IndexedVar @@ -39,16 +44,20 @@ # see: https://github.com/Pyomo/pyomo/blob/main/pyomo/repn/plugins/nl_writer.py + def templatize_expression(expr): expr, indices = templatize_rule(expr.parent_block(), expr._rule, expr.index_set()) return (expr, indices) - + + def templatize_passthrough(con): return (con, []) + def handle_negation_node(node, arg1): return '-' + arg1 + def handle_product_node(node, arg1, arg2): childPrecedence = [] for a in node.args: @@ -59,7 +68,7 @@ def handle_product_node(node, arg1, arg2): childPrecedence.append(a.PRECEDENCE) else: childPrecedence.append(-1) - + if hasattr(node, 'PRECEDENCE'): precedence = node.PRECEDENCE else: @@ -67,57 +76,67 @@ def handle_product_node(node, arg1, arg2): precedence = -1 if childPrecedence[0] > precedence: - arg1 = ' \\left( ' + arg1 + ' \\right) ' - + arg1 = ' \\left( ' + arg1 + ' \\right) ' + if childPrecedence[1] > precedence: - arg2 = ' \\left( ' + arg2 + ' \\right) ' - - return ''.join([arg1,arg2]) - + arg2 = ' \\left( ' + arg2 + ' \\right) ' + + return ''.join([arg1, arg2]) + + def handle_division_node(node, arg1, arg2): # return '/'.join([arg1,arg2]) - return '\\frac{%s}{%s}'%(arg1,arg2) + return '\\frac{%s}{%s}' % (arg1, arg2) + def handle_pow_node(node, arg1, arg2): - return "%s^{%s}"%(arg1, arg2) + return "%s^{%s}" % (arg1, arg2) + def handle_abs_node(node, arg1): - return ' \\left| ' + arg1 + ' \\right| ' + return ' \\left| ' + arg1 + ' \\right| ' + def handle_unary_node(node, arg1): fcn_handle = node.getname() if fcn_handle == 'log10': fcn_handle = 'log_{10}' - + if fcn_handle == 'sqrt': - return '\\sqrt { ' + arg1 + ' }' + return '\\sqrt { ' + arg1 + ' }' else: - return '\\' + fcn_handle + ' \\left( ' + arg1 + ' \\right) ' + return '\\' + fcn_handle + ' \\left( ' + arg1 + ' \\right) ' + def handle_equality_node(node, arg1, arg2): return arg1 + ' = ' + arg2 + def handle_inequality_node(node, arg1, arg2): return arg1 + ' \leq ' + arg2 + def handle_scalarVar_node(node): return node.name + def handle_num_node(node): - if isinstance(node,float): + if isinstance(node, float): if node.is_integer(): node = int(node) return str(node) -def handle_sum_expression(node,*args): + +def handle_sum_expression(node, *args): rstr = args[0] - for i in range(1,len(args)): - if args[i][0]=='-': + for i in range(1, len(args)): + if args[i][0] == '-': rstr += ' - ' + args[i][1:] else: rstr += ' + ' + args[i] return rstr + def handle_monomialTermExpression_node(node, arg1, arg2): if arg1 == '1': return arg2 @@ -125,66 +144,82 @@ def handle_monomialTermExpression_node(node, arg1, arg2): return '-' + arg2 else: return arg1 + arg2 - + + def handle_named_expression_node(node, arg1): return arg1 + def handle_ranged_inequality_node(node, arg1, arg2, arg3): return arg1 + ' \\leq ' + arg2 + ' \\leq ' + arg3 + def handle_exprif_node(node, arg1, arg2, arg3): return 'f_{\\text{exprIf}}(' + arg1 + ',' + arg2 + ',' + arg3 + ')' + + ## Raises not implemented error # raise NotImplementedError('Expr_if objects not supported by the Latex Printer') -# pstr = '' -# pstr += '\\begin{Bmatrix} ' -# pstr += arg2 + ' , & ' + arg1 + '\\\\ ' -# pstr += arg3 + ' , & \\text{otherwise}' + '\\\\ ' -# pstr += '\\end{Bmatrix}' -# return pstr - -def handle_external_function_node(node,*args): + + ## Puts cases in a bracketed matrix + # pstr = '' + # pstr += '\\begin{Bmatrix} ' + # pstr += arg2 + ' , & ' + arg1 + '\\\\ ' + # pstr += arg3 + ' , & \\text{otherwise}' + '\\\\ ' + # pstr += '\\end{Bmatrix}' + # return pstr + + +def handle_external_function_node(node, *args): pstr = '' pstr += 'f(' - for i in range(0,len(args)-1): - pstr += args[i] - if i <= len(args)-3: + for i in range(0, len(args) - 1): + pstr += args[i] + if i <= len(args) - 3: pstr += ',' else: pstr += ')' return pstr - -def handle_functionID_node(node,*args): + + +def handle_functionID_node(node, *args): # seems to just be a placeholder empty wrapper object return '' - -def handle_indexedVar_node(node,*args): + + +def handle_indexedVar_node(node, *args): return node.name -def handle_indexTemplate_node(node,*args): - return '__INDEX_PLACEHOLDER_8675309_GROUP_%s_%s__'%(node._group, node._set) -def handle_numericGIE_node(node,*args): +def handle_indexTemplate_node(node, *args): + return '__INDEX_PLACEHOLDER_8675309_GROUP_%s_%s__' % (node._group, node._set) + + +def handle_numericGIE_node(node, *args): pstr = '' pstr += args[0] + '_{' - for i in range(1,len(args)): - pstr += args[i] - if i <= len(args)-2: + for i in range(1, len(args)): + pstr += args[i] + if i <= len(args) - 2: pstr += ',' else: pstr += '}' return pstr -def handle_templateSumExpression_node(node,*args): + +def handle_templateSumExpression_node(node, *args): pstr = '' - pstr += '\\sum_{%s} %s'%('__SET_PLACEHOLDER_8675309_GROUP_%s_%s__'%( node._iters[0][0]._group, - str(node._iters[0][0]._set) ), args[0]) + pstr += '\\sum_{%s} %s' % ( + '__SET_PLACEHOLDER_8675309_GROUP_%s_%s__' + % (node._iters[0][0]._group, str(node._iters[0][0]._set)), + args[0], + ) return pstr - + class _LatexVisitor(StreamBasedExpressionVisitor): def __init__(self): super().__init__() - + self._operator_handles = { ScalarVar: handle_scalarVar_node, int: handle_num_node, @@ -214,33 +249,36 @@ def __init__(self): MonomialTermExpression: handle_monomialTermExpression_node, IndexedVar: handle_indexedVar_node, IndexTemplate: handle_indexTemplate_node, - Numeric_GetItemExpression: handle_numericGIE_node, + Numeric_GetItemExpression: handle_numericGIE_node, TemplateSumExpression: handle_templateSumExpression_node, } - - def exitNode(self,node,data): - return self._operator_handles[node.__class__](node,*data) -def latex_printer(pyomoElement, filename=None, useAlignEnvironment=False, splitContinuousSets=False): + def exitNode(self, node, data): + return self._operator_handles[node.__class__](node, *data) + + +def latex_printer( + pyomoElement, filename=None, useAlignEnvironment=False, splitContinuousSets=False +): ''' This function produces a string that can be rendered as LaTeX - + pyomoElement: The thing to be printed to LaTeX. Accepts Blocks (including models), Constraints, and Expressions - + filename: An optional file to write the LaTeX to. Default of None produces no file - - useAlignEnvironment: Default behavior uses equation/aligned and produces a single LaTeX Equation (ie, ==False). + + useAlignEnvironment: Default behavior uses equation/aligned and produces a single LaTeX Equation (ie, ==False). Setting this input to True will instead use the align environment, and produce equation numbers for each - objective and constraint. Each objective and constraint will be labled with its name in the pyomo model. + objective and constraint. Each objective and constraint will be labeled with its name in the pyomo model. This flag is only relevant for Models and Blocks. - - splitContinuous: Default behavior has all sum indicies be over "i \in I" or similar. Setting this flag to + + splitContinuous: Default behavior has all sum indices be over "i \in I" or similar. Setting this flag to True makes the sums go from: \sum_{i=1}^{5} if the set I is continuous and has 5 elements - + ''' - + # Various setup things - isSingle=False + isSingle = False if not isinstance(pyomoElement, _BlockData): if isinstance(pyomoElement, pe.Objective): objectives = [pyomoElement] @@ -253,38 +291,47 @@ def latex_printer(pyomoElement, filename=None, useAlignEnvironment=False, splitC constraints = [pyomoElement] expressions = [] templatize_fcn = templatize_constraint - + if isinstance(pyomoElement, pe.Expression): objectives = [] constraints = [] expressions = [pyomoElement] templatize_fcn = templatize_expression - if isinstance(pyomoElement, ExpressionBase): objectives = [] constraints = [] expressions = [pyomoElement] templatize_fcn = templatize_passthrough - + useAlignEnvironment = False isSingle = True else: - objectives = [obj for obj in pyomoElement.component_data_objects(pe.Objective, descend_into=True, active=True)] - constraints = [con for con in pyomoElement.component_objects(pe.Constraint, descend_into=True, active=True)] + objectives = [ + obj + for obj in pyomoElement.component_data_objects( + pe.Objective, descend_into=True, active=True + ) + ] + constraints = [ + con + for con in pyomoElement.component_objects( + pe.Constraint, descend_into=True, active=True + ) + ] expressions = [] templatize_fcn = templatize_constraint - + # In the case where just a single expression is passed, add this to the constraint list for printing constraints = constraints + expressions - + # Declare a visitor/walker visitor = _LatexVisitor() - + # starts building the output string pstr = '' if useAlignEnvironment: - pstr += '\\begin{align} \n' + pstr += '\\begin{align} \n' tbSpc = 4 else: pstr += '\\begin{equation} \n' @@ -293,84 +340,99 @@ def latex_printer(pyomoElement, filename=None, useAlignEnvironment=False, splitC tbSpc = 8 else: tbSpc = 4 - + # Iterate over the objectives and print for obj in objectives: obj_template, obj_indices = templatize_fcn(obj) if obj.sense == 1: - pstr += ' '*tbSpc + '& \\text{%s} \n'%('minimize') + pstr += ' ' * tbSpc + '& \\text{%s} \n' % ('minimize') else: - pstr += ' '*tbSpc + '& \\text{%s} \n'%('maximize') - - pstr += ' '*tbSpc + '& & %s '%(visitor.walk_expression(obj_template)) + pstr += ' ' * tbSpc + '& \\text{%s} \n' % ('maximize') + + pstr += ' ' * tbSpc + '& & %s ' % (visitor.walk_expression(obj_template)) if useAlignEnvironment: pstr += '\\label{obj:' + m.name + '_' + obj.name + '} ' pstr += '\\\\ \n' - + # Iterate over the constraints if len(constraints) > 0: # only print this if printing a full formulation if not isSingle: - pstr += ' '*tbSpc + '& \\text{subject to} \n' - + pstr += ' ' * tbSpc + '& \\text{subject to} \n' + # first constraint needs different alignment because of the 'subject to' - for i in range(0,len(constraints)): + for i in range(0, len(constraints)): if not isSingle: - if i==0: + if i == 0: algn = '& &' else: algn = '&&&' else: algn = '' - + tail = '\\\\ \n' - + # grab the constraint and templatize con = constraints[i] con_template, indices = templatize_fcn(con) - + # Walk the constraint - conLine = ' '*tbSpc + algn + ' %s '%(visitor.walk_expression(con_template)) - + conLine = ( + ' ' * tbSpc + algn + ' %s ' % (visitor.walk_expression(con_template)) + ) + # Multiple constraints are generated using a set if len(indices) > 0: nm = indices[0]._set gp = indices[0]._group - ixTag = '__INDEX_PLACEHOLDER_8675309_GROUP_%s_%s__'%(gp,nm) - stTag = '__SET_PLACEHOLDER_8675309_GROUP_%s_%s__'%( gp, nm ) + ixTag = '__INDEX_PLACEHOLDER_8675309_GROUP_%s_%s__' % (gp, nm) + stTag = '__SET_PLACEHOLDER_8675309_GROUP_%s_%s__' % (gp, nm) - conLine += ', \\quad %s \\in %s '%(ixTag,stTag) - pstr += conLine + conLine += ', \\quad %s \\in %s ' % (ixTag, stTag) + pstr += conLine # Add labels as needed if useAlignEnvironment: pstr += '\\label{con:' + m.name + '_' + con.name + '} ' # prevents an emptly blank line from being at the end of the latex output - if i <= len(constraints)-2: + if i <= len(constraints) - 2: pstr += tail else: pstr += '\n' - + # close off the print string if useAlignEnvironment: pstr += '\\end{align} \n' else: if not isSingle: pstr += ' \\end{aligned} \n' - pstr += ' \\label{%s} \n '%(m.name) + pstr += ' \\label{%s} \n ' % (m.name) pstr += '\end{equation} \n' - - # Handling the iterator indicies - + # Handling the iterator indices + # preferential order for indices - setPreferenceOrder = ['I','J','K','M','N','P','Q','R','S','T','U','V','W'] - + setPreferenceOrder = [ + 'I', + 'J', + 'K', + 'M', + 'N', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + ] + # Go line by line and replace the placeholders with correct set names and index names latexLines = pstr.split('\n') - for jj in range(0,len(latexLines)): + for jj in range(0, len(latexLines)): groupMap = {} uniqueSets = [] ln = latexLines[jj] @@ -381,24 +443,26 @@ def latex_printer(pyomoElement, filename=None, useAlignEnvironment=False, splitC for word in splitLatex: if "PLACEHOLDER_8675309_GROUP_" in word: ifo = word.split("PLACEHOLDER_8675309_GROUP_")[1] - gpNum, stNam = ifo.split('_') + gpNum, stName = ifo.split('_') if gpNum not in groupMap.keys(): - groupMap[gpNum] = [stNam] - if stNam not in uniqueSets: - uniqueSets.append(stNam) + groupMap[gpNum] = [stName] + if stName not in uniqueSets: + uniqueSets.append(stName) # Determine if the set is continuous - continuousSets = dict(zip(uniqueSets, [False for i in range(0,len(uniqueSets))] )) - for i in range(0,len(uniqueSets)): - st = getattr(m,uniqueSets[i]) + continuousSets = dict( + zip(uniqueSets, [False for i in range(0, len(uniqueSets))]) + ) + for i in range(0, len(uniqueSets)): + st = getattr(m, uniqueSets[i]) stData = st.data() stCont = True - for ii in range(0,len(stData)): - if ii+stData[0] != stData[ii]: + for ii in range(0, len(stData)): + if ii + stData[0] != stData[ii]: stCont = False break continuousSets[uniqueSets[i]] = stCont - + # Add the continuous set data to the groupMap for ky, vl in groupMap.items(): groupMap[ky].append(continuousSets[vl[0]]) @@ -406,7 +470,7 @@ def latex_printer(pyomoElement, filename=None, useAlignEnvironment=False, splitC # Set up new names for duplicate sets assignedSetNames = [] gmk_list = list(groupMap.keys()) - for i in range(0,len(groupMap.keys())): + for i in range(0, len(groupMap.keys())): ix = gmk_list[i] # set not already used if groupMap[str(ix)][0] not in assignedSetNames: @@ -414,7 +478,7 @@ def latex_printer(pyomoElement, filename=None, useAlignEnvironment=False, splitC groupMap[str(ix)].append(groupMap[str(ix)][0]) else: # Pick a new set from the preference order - for j in range(0,len(setPreferenceOrder)): + for j in range(0, len(setPreferenceOrder)): stprf = setPreferenceOrder[j] # must not be already used if stprf not in assignedSetNames: @@ -423,41 +487,53 @@ def latex_printer(pyomoElement, filename=None, useAlignEnvironment=False, splitC break # set up the substitutions - setStrings = {} + setStrings = {} indexStrings = {} for ky, vl in groupMap.items(): - setStrings['__SET_PLACEHOLDER_8675309_GROUP_%s_%s__'%(ky,vl[0])] = [ vl[2], vl[1], vl[0] ] - indexStrings['__INDEX_PLACEHOLDER_8675309_GROUP_%s_%s__'%(ky,vl[0])] = vl[2].lower() + setStrings['__SET_PLACEHOLDER_8675309_GROUP_%s_%s__' % (ky, vl[0])] = [ + vl[2], + vl[1], + vl[0], + ] + indexStrings[ + '__INDEX_PLACEHOLDER_8675309_GROUP_%s_%s__' % (ky, vl[0]) + ] = vl[2].lower() # replace the indices for ky, vl in indexStrings.items(): - ln = ln.replace(ky,vl) + ln = ln.replace(ky, vl) # replace the sets for ky, vl in setStrings.items(): # if the set is continuous and the flag has been set if splitContinuousSets and vl[1]: - st = getattr(m,vl[2]) + st = getattr(m, vl[2]) stData = st.data() bgn = stData[0] - ed = stData[-1] - ln = ln.replace('\\sum_{%s}'%(ky), '\\sum_{%s = %d}^{%d}'%(vl[0].lower(), bgn, ed)) - ln = ln.replace(ky,vl[2]) + ed = stData[-1] + ln = ln.replace( + '\\sum_{%s}' % (ky), + '\\sum_{%s = %d}^{%d}' % (vl[0].lower(), bgn, ed), + ) + ln = ln.replace(ky, vl[2]) else: # if the set is not continuous or the flag has not been set - st = getattr(m,vl[2]) + st = getattr(m, vl[2]) stData = st.data() bgn = stData[0] - ed = stData[-1] - ln = ln.replace('\\sum_{%s}'%(ky), '\\sum_{%s \\in %s}'%(vl[0].lower(),vl[0])) - ln = ln.replace(ky,vl[2]) + ed = stData[-1] + ln = ln.replace( + '\\sum_{%s}' % (ky), + '\\sum_{%s \\in %s}' % (vl[0].lower(), vl[0]), + ) + ln = ln.replace(ky, vl[2]) # Assign the newly modified line latexLines[jj] = ln - + # rejoin the corrected lines pstr = '\n'.join(latexLines) - + # optional write to output file if filename is not None: fstr = '' @@ -466,9 +542,9 @@ def latex_printer(pyomoElement, filename=None, useAlignEnvironment=False, splitC fstr += '\\begin{document} \n' fstr += pstr fstr += '\\end{document} \n' - f = open(filename,'w') + f = open(filename, 'w') f.write(fstr) f.close() # return the latex string - return pstr \ No newline at end of file + return pstr From d09c88040db806eeb91eac478b3267e07474ad01 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 31 Aug 2023 14:51:58 -0600 Subject: [PATCH 0084/1204] SAVE POINT: Starting work on IPOPT solver re-write --- pyomo/contrib/appsi/plugins.py | 2 +- pyomo/solver/IPOPT.py | 123 +++++++++++++++++++++++ pyomo/solver/__init__.py | 1 + pyomo/solver/base.py | 22 +--- pyomo/solver/config.py | 3 + pyomo/solver/factory.py | 33 ++++++ pyomo/solver/plugins.py | 2 +- pyomo/solver/tests/solvers/test_ipopt.py | 48 +++++++++ pyomo/solver/util.py | 9 ++ 9 files changed, 220 insertions(+), 23 deletions(-) create mode 100644 pyomo/solver/IPOPT.py create mode 100644 pyomo/solver/factory.py create mode 100644 pyomo/solver/tests/solvers/test_ipopt.py diff --git a/pyomo/contrib/appsi/plugins.py b/pyomo/contrib/appsi/plugins.py index 86dcd298a93..3a132b74395 100644 --- a/pyomo/contrib/appsi/plugins.py +++ b/pyomo/contrib/appsi/plugins.py @@ -1,5 +1,5 @@ from pyomo.common.extensions import ExtensionBuilderFactory -from pyomo.solver.base import SolverFactory +from pyomo.solver.factory import SolverFactory from .solvers import Gurobi, Ipopt, Cbc, Cplex, Highs from .build import AppsiBuilder diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py new file mode 100644 index 00000000000..3f5fa0e1df6 --- /dev/null +++ b/pyomo/solver/IPOPT.py @@ -0,0 +1,123 @@ +# ___________________________________________________________________________ +# +# 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 os +import subprocess + +from pyomo.common import Executable +from pyomo.common.config import ConfigValue +from pyomo.common.tempfiles import TempfileManager +from pyomo.opt import WriterFactory +from pyomo.solver.base import SolverBase +from pyomo.solver.config import SolverConfig +from pyomo.solver.factory import SolverFactory +from pyomo.solver.results import Results, TerminationCondition, SolutionStatus +from pyomo.solver.solution import SolutionLoaderBase +from pyomo.solver.util import SolverSystemError + +import logging + +logger = logging.getLogger(__name__) + + +class IPOPTConfig(SolverConfig): + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + super().__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, + ) + + self.executable = self.declare( + 'executable', ConfigValue(default=Executable('ipopt')) + ) + self.save_solver_io: bool = self.declare( + 'save_solver_io', ConfigValue(domain=bool, default=False) + ) + + +class IPOPTSolutionLoader(SolutionLoaderBase): + pass + + +@SolverFactory.register('ipopt', doc='The IPOPT NLP solver (new interface)') +class IPOPT(SolverBase): + CONFIG = IPOPTConfig() + + def __init__(self, **kwds): + self.config = self.CONFIG(kwds) + + def available(self): + if self.config.executable.path() is None: + return self.Availability.NotFound + return self.Availability.FullLicense + + def version(self): + results = subprocess.run( + [str(self.config.executable), '--version'], + timeout=1, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + ) + version = results.stdout.splitlines()[0] + version = version.split(' ')[1] + version = version.strip() + version = tuple(int(i) for i in version.split('.')) + return version + + @property + def config(self): + return self._config + + @config.setter + def config(self, val): + self._config = val + + def solve(self, model, **kwds): + # Check if solver is available + avail = self.available() + if not avail: + raise SolverSystemError( + f'Solver {self.__class__} is not available ({avail}).' + ) + # Update configuration options, based on keywords passed to solve + config = self.config(kwds.pop('options', {})) + config.set_value(kwds) + # Write the model to an nl file + nl_writer = WriterFactory('nl') + # Need to add check for symbolic_solver_labels; may need to generate up + # to three files for nl, row, col, if ssl == True + # What we have here may or may not work with IPOPT; will find out when + # we try to run it. + with TempfileManager.new_context() as tempfile: + dname = tempfile.mkdtemp() + with open(os.path.join(dname, model.name + '.nl')) as nl_file, open( + os.path.join(dname, model.name + '.row') + ) as row_file, open(os.path.join(dname, model.name + '.col')) as col_file: + info = nl_writer.write( + model, + nl_file, + row_file, + col_file, + symbolic_solver_labels=config.symbolic_solver_labels, + ) + # Call IPOPT - passing the files via the subprocess + subprocess.run() diff --git a/pyomo/solver/__init__.py b/pyomo/solver/__init__.py index e3eafa991cc..1ab9f975f0b 100644 --- a/pyomo/solver/__init__.py +++ b/pyomo/solver/__init__.py @@ -11,6 +11,7 @@ from . import base from . import config +from . import factory from . import results from . import solution from . import util diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index ba25b23a354..f7e5c4c58c5 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -21,8 +21,7 @@ from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.common.timing import HierarchicalTimer from pyomo.common.errors import ApplicationError -from pyomo.opt.base import SolverFactory as LegacySolverFactory -from pyomo.common.factory import Factory + from pyomo.opt.results.results_ import SolverResults as LegacySolverResults from pyomo.opt.results.solution import Solution as LegacySolution from pyomo.core.kernel.objective import minimize @@ -442,22 +441,3 @@ def __enter__(self): def __exit__(self, t, v, traceback): pass - - -class SolverFactoryClass(Factory): - def register(self, name, doc=None): - def decorator(cls): - self._cls[name] = cls - self._doc[name] = doc - - class LegacySolver(LegacySolverInterface, cls): - pass - - LegacySolverFactory.register(name, doc)(LegacySolver) - - return cls - - return decorator - - -SolverFactory = SolverFactoryClass() diff --git a/pyomo/solver/config.py b/pyomo/solver/config.py index efd2a1bac16..ed9008b7e1f 100644 --- a/pyomo/solver/config.py +++ b/pyomo/solver/config.py @@ -73,6 +73,9 @@ def __init__( self.time_limit: Optional[float] = self.declare( 'time_limit', ConfigValue(domain=NonNegativeFloat) ) + self.solver_options: ConfigDict = self.declare( + 'solver_options', ConfigDict(implicit=True) + ) class BranchAndBoundConfig(SolverConfig): diff --git a/pyomo/solver/factory.py b/pyomo/solver/factory.py new file mode 100644 index 00000000000..1a49ea92e40 --- /dev/null +++ b/pyomo/solver/factory.py @@ -0,0 +1,33 @@ +# ___________________________________________________________________________ +# +# 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.opt.base import SolverFactory as LegacySolverFactory +from pyomo.common.factory import Factory +from pyomo.solver.base import LegacySolverInterface + + +class SolverFactoryClass(Factory): + def register(self, name, doc=None): + def decorator(cls): + self._cls[name] = cls + self._doc[name] = doc + + class LegacySolver(LegacySolverInterface, cls): + pass + + LegacySolverFactory.register(name, doc)(LegacySolver) + + return cls + + return decorator + + +SolverFactory = SolverFactoryClass() diff --git a/pyomo/solver/plugins.py b/pyomo/solver/plugins.py index 5120bc9dd36..5dfd4bce1eb 100644 --- a/pyomo/solver/plugins.py +++ b/pyomo/solver/plugins.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ -from .base import SolverFactory +from .factory import SolverFactory def load(): diff --git a/pyomo/solver/tests/solvers/test_ipopt.py b/pyomo/solver/tests/solvers/test_ipopt.py new file mode 100644 index 00000000000..afe2dbbe531 --- /dev/null +++ b/pyomo/solver/tests/solvers/test_ipopt.py @@ -0,0 +1,48 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo +from pyomo.common.fileutils import ExecutableData +from pyomo.common.config import ConfigDict +from pyomo.solver.IPOPT import IPOPTConfig +from pyomo.solver.factory import SolverFactory +from pyomo.common import unittest + + +class TestIPOPT(unittest.TestCase): + def create_model(self): + model = pyo.ConcreteModel() + model.x = pyo.Var(initialize=1.5) + model.y = pyo.Var(initialize=1.5) + + def rosenbrock(m): + return (1.0 - m.x) ** 2 + 100.0 * (m.y - m.x**2) ** 2 + + model.obj = pyo.Objective(rule=rosenbrock, sense=pyo.minimize) + return model + + def test_IPOPT_config(self): + # Test default initialization + config = IPOPTConfig() + self.assertTrue(config.load_solution) + self.assertIsInstance(config.solver_options, ConfigDict) + print(type(config.executable)) + self.assertIsInstance(config.executable, ExecutableData) + + # Test custom initialization + solver = SolverFactory('ipopt', save_solver_io=True) + self.assertTrue(solver.config.save_solver_io) + self.assertFalse(solver.config.tee) + + # Change value on a solve call + # model = self.create_model() + # result = solver.solve(model, tee=True) diff --git a/pyomo/solver/util.py b/pyomo/solver/util.py index 1fb1738470b..79abee1b689 100644 --- a/pyomo/solver/util.py +++ b/pyomo/solver/util.py @@ -20,11 +20,20 @@ from pyomo.core.base.param import _ParamData, Param from pyomo.core.base.objective import Objective, _GeneralObjectiveData from pyomo.common.collections import ComponentMap +from pyomo.common.errors import PyomoException from pyomo.common.timing import HierarchicalTimer from pyomo.core.expr.numvalue import NumericConstant from pyomo.solver.config import UpdateConfig +class SolverSystemError(PyomoException): + """ + General exception to catch solver system errors + """ + + pass + + def get_objective(block): obj = None for o in block.component_data_objects( From f474d49008342b108e91bfde111beaf65434592f Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 31 Aug 2023 15:29:21 -0600 Subject: [PATCH 0085/1204] Change SolverFactory to remove legacy solver references --- pyomo/solver/factory.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/solver/factory.py b/pyomo/solver/factory.py index 1a49ea92e40..84b6cf02eac 100644 --- a/pyomo/solver/factory.py +++ b/pyomo/solver/factory.py @@ -20,10 +20,10 @@ def decorator(cls): self._cls[name] = cls self._doc[name] = doc - class LegacySolver(LegacySolverInterface, cls): - pass + # class LegacySolver(LegacySolverInterface, cls): + # pass - LegacySolverFactory.register(name, doc)(LegacySolver) + # LegacySolverFactory.register(name, doc)(LegacySolver) return cls From a697ff14e00f6ca15c956e9ab9726f36fd8e2d08 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Thu, 31 Aug 2023 15:39:03 -0600 Subject: [PATCH 0086/1204] adding docs and unit tests for the latex printer --- doc/.DS_Store | Bin 0 -> 6148 bytes doc/OnlineDocs/model_debugging/index.rst | 1 + .../model_debugging/latex_printing.rst | 130 ++++++++ pyomo/util/latex_printer.py | 37 +-- pyomo/util/tests/test_latex_printer.py | 306 ++++++++++++++++++ 5 files changed, 456 insertions(+), 18 deletions(-) create mode 100644 doc/.DS_Store create mode 100644 doc/OnlineDocs/model_debugging/latex_printing.rst create mode 100644 pyomo/util/tests/test_latex_printer.py diff --git a/doc/.DS_Store b/doc/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..dc8db9a690d7c42c02decdd7b94fc8b97d6a9d4b GIT binary patch literal 6148 zcmeHKyG{c^3>-s>h%_lF_ZRqsRdm!8`GH7)5K?rJKu}+m@8Z)KKZJ-b6cjXQEZMW` z_3Y`UIG+L7=I!AISOZwn9dYz9H$Qiu*+pfHNar0R9x>p6d%Vu7&nKLFg*!6$c>d0R z-@c6d!}yeUpC>CT1*Cu!kOERb3jA6D@4d9;Dp64iNC7GErGS4Q8r`uMPKoj9V2BZb zxL`Vr>zE~o%@f34I3+Sev!oJ}YBge5(wT2n*9)h_q{C|XuzIr9gkte@-ru4e))N(_ zfD|}Y;4-&s@Bg>-ALjp4l6F!+3j8YtY%y$y4PU8x>+I#c*Eaf&?lm8DH?D)i5bc;4 i?U);H$JbGmb>> # Note: this model is not mathematically sensible + + >>> import pyomo.environ as pe + >>> from pyomo.core.expr import Expr_if + >>> from pyomo.core.base import ExternalFunction + >>> from pyomo.util.latex_printer import latex_printer + + >>> m = pe.ConcreteModel(name = 'basicFormulation') + >>> m.x = pe.Var() + >>> m.y = pe.Var() + >>> m.z = pe.Var() + >>> m.objective = pe.Objective( expr = m.x + m.y + m.z ) + >>> m.constraint_1 = pe.Constraint(expr = m.x**2 + m.y**-2.0 - m.x*m.y*m.z + 1 == 2.0) + >>> m.constraint_2 = pe.Constraint(expr = abs(m.x/m.z**-2) * (m.x + m.y) <= 2.0) + >>> m.constraint_3 = pe.Constraint(expr = pe.sqrt(m.x/m.z**-2) <= 2.0) + >>> m.constraint_4 = pe.Constraint(expr = (1,m.x,2)) + >>> m.constraint_5 = pe.Constraint(expr = Expr_if(m.x<=1.0, m.z, m.y) <= 1.0) + + >>> def blackbox(a, b): + >>> return sin(a - b) + >>> m.bb = ExternalFunction(blackbox) + >>> m.constraint_6 = pe.Constraint(expr= m.x + m.bb(m.x,m.y) == 2 ) + + >>> m.I = pe.Set(initialize=[1,2,3,4,5]) + >>> m.J = pe.Set(initialize=[1,2,3]) + >>> m.u = pe.Var(m.I*m.I) + >>> m.v = pe.Var(m.I) + >>> m.w = pe.Var(m.J) + + >>> def ruleMaker(m,j): + >>> return (m.x + m.y) * sum( m.v[i] + m.u[i,j]**2 for i in m.I ) <= 0 + >>> m.constraint_7 = pe.Constraint(m.I, rule = ruleMaker) + + >>> def ruleMaker(m): + >>> return (m.x + m.y) * sum( m.w[j] for j in m.J ) + >>> m.objective_2 = pe.Objective(rule = ruleMaker) + + >>> pstr = latex_printer(m) + + +A Constraint +++++++++++++ + +.. doctest:: + + >>> import pyomo.environ as pe + >>> from pyomo.util.latex_printer import latex_printer + + >>> m = pe.ConcreteModel(name = 'basicFormulation') + >>> m.x = pe.Var() + >>> m.y = pe.Var() + + >>> m.constraint_1 = pe.Constraint(expr = m.x**2 + m.y**2 <= 1.0) + + >>> pstr = latex_printer(m.constraint_1) + + +An Expression ++++++++++++++ + +.. doctest:: + + >>> import pyomo.environ as pe + >>> from pyomo.util.latex_printer import latex_printer + + >>> m = pe.ConcreteModel(name = 'basicFormulation') + >>> m.x = pe.Var() + >>> m.y = pe.Var() + + >>> m.expression_1 = pe.Expression(expr = m.x**2 + m.y**2) + + >>> pstr = latex_printer(m.expression_1) + + +A Simple Expression ++++++++++++++++++++ + +.. doctest:: + + >>> import pyomo.environ as pe + >>> from pyomo.util.latex_printer import latex_printer + + >>> m = pe.ConcreteModel(name = 'basicFormulation') + >>> m.x = pe.Var() + >>> m.y = pe.Var() + + >>> pstr = latex_printer(m.x + m.y) + + + diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index ba3bcf378eb..e02b3b7491a 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -31,6 +31,7 @@ from pyomo.core.expr.template_expr import templatize_constraint, resolve_template from pyomo.core.base.var import ScalarVar, _GeneralVarData, IndexedVar from pyomo.core.base.constraint import ScalarConstraint, IndexedConstraint +from pyomo.core.expr.template_expr import templatize_rule from pyomo.core.base.external import _PythonCallbackFunctionID @@ -351,8 +352,11 @@ def latex_printer( pstr += ' ' * tbSpc + '& & %s ' % (visitor.walk_expression(obj_template)) if useAlignEnvironment: - pstr += '\\label{obj:' + m.name + '_' + obj.name + '} ' - pstr += '\\\\ \n' + pstr += '\\label{obj:' + pyomoElement.name + '_' + obj.name + '} ' + if not isSingle: + pstr += '\\\\ \n' + else: + pstr += '\n' # Iterate over the constraints if len(constraints) > 0: @@ -394,7 +398,7 @@ def latex_printer( # Add labels as needed if useAlignEnvironment: - pstr += '\\label{con:' + m.name + '_' + con.name + '} ' + pstr += '\\label{con:' + pyomoElement.name + '_' + con.name + '} ' # prevents an emptly blank line from being at the end of the latex output if i <= len(constraints) - 2: @@ -408,7 +412,7 @@ def latex_printer( else: if not isSingle: pstr += ' \\end{aligned} \n' - pstr += ' \\label{%s} \n ' % (m.name) + pstr += ' \\label{%s} \n' % (pyomoElement.name) pstr += '\end{equation} \n' # Handling the iterator indices @@ -453,15 +457,16 @@ def latex_printer( continuousSets = dict( zip(uniqueSets, [False for i in range(0, len(uniqueSets))]) ) - for i in range(0, len(uniqueSets)): - st = getattr(m, uniqueSets[i]) - stData = st.data() - stCont = True - for ii in range(0, len(stData)): - if ii + stData[0] != stData[ii]: - stCont = False - break - continuousSets[uniqueSets[i]] = stCont + if splitContinuousSets: + for i in range(0, len(uniqueSets)): + st = getattr(pyomoElement, uniqueSets[i]) + stData = st.data() + stCont = True + for ii in range(0, len(stData)): + if ii + stData[0] != stData[ii]: + stCont = False + break + continuousSets[uniqueSets[i]] = stCont # Add the continuous set data to the groupMap for ky, vl in groupMap.items(): @@ -507,7 +512,7 @@ def latex_printer( for ky, vl in setStrings.items(): # if the set is continuous and the flag has been set if splitContinuousSets and vl[1]: - st = getattr(m, vl[2]) + st = getattr(pyomoElement, vl[2]) stData = st.data() bgn = stData[0] ed = stData[-1] @@ -518,10 +523,6 @@ def latex_printer( ln = ln.replace(ky, vl[2]) else: # if the set is not continuous or the flag has not been set - st = getattr(m, vl[2]) - stData = st.data() - bgn = stData[0] - ed = stData[-1] ln = ln.replace( '\\sum_{%s}' % (ky), '\\sum_{%s \\in %s}' % (vl[0].lower(), vl[0]), diff --git a/pyomo/util/tests/test_latex_printer.py b/pyomo/util/tests/test_latex_printer.py new file mode 100644 index 00000000000..93bd589aa94 --- /dev/null +++ b/pyomo/util/tests/test_latex_printer.py @@ -0,0 +1,306 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2023 +# 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.common.unittest as unittest +from pyomo.util.latex_printer import latex_printer +import pyomo.environ as pe + + +def generate_model(): + import pyomo.environ as pe + from pyomo.core.expr import Expr_if + from pyomo.core.base import ExternalFunction + + m = pe.ConcreteModel(name='basicFormulation') + m.x = pe.Var() + m.y = pe.Var() + m.z = pe.Var() + m.objective_1 = pe.Objective(expr=m.x + m.y + m.z) + m.constraint_1 = pe.Constraint( + expr=m.x**2 + m.y**-2.0 - m.x * m.y * m.z + 1 == 2.0 + ) + m.constraint_2 = pe.Constraint(expr=abs(m.x / m.z**-2) * (m.x + m.y) <= 2.0) + m.constraint_3 = pe.Constraint(expr=pe.sqrt(m.x / m.z**-2) <= 2.0) + m.constraint_4 = pe.Constraint(expr=(1, m.x, 2)) + m.constraint_5 = pe.Constraint(expr=Expr_if(m.x <= 1.0, m.z, m.y) <= 1.0) + + def blackbox(a, b): + return sin(a - b) + + m.bb = ExternalFunction(blackbox) + m.constraint_6 = pe.Constraint(expr=m.x + m.bb(m.x, m.y) == 2) + + m.I = pe.Set(initialize=[1, 2, 3, 4, 5]) + m.J = pe.Set(initialize=[1, 2, 3]) + m.K = pe.Set(initialize=[1, 3, 5]) + m.u = pe.Var(m.I * m.I) + m.v = pe.Var(m.I) + m.w = pe.Var(m.J) + m.p = pe.Var(m.K) + + m.express = pe.Expression(expr=m.x**2 + m.y**2) + + def ruleMaker(m, j): + return (m.x + m.y) * sum(m.v[i] + m.u[i, j] ** 2 for i in m.I) <= 0 + + m.constraint_7 = pe.Constraint(m.I, rule=ruleMaker) + + def ruleMaker(m): + return sum(m.p[k] for k in m.K) == 1 + + m.constraint_8 = pe.Constraint(rule=ruleMaker) + + def ruleMaker(m): + return (m.x + m.y) * sum(m.w[j] for j in m.J) + + m.objective_2 = pe.Objective(rule=ruleMaker) + + m.objective_3 = pe.Objective(expr=m.x + m.y + m.z, sense=-1) + + return m + + +def generate_simple_model(): + import pyomo.environ as pe + + m = pe.ConcreteModel(name='basicFormulation') + m.x = pe.Var() + m.y = pe.Var() + m.objective_1 = pe.Objective(expr=m.x + m.y) + m.constraint_1 = pe.Constraint(expr=m.x**2 + m.y**2.0 <= 1.0) + m.constraint_2 = pe.Constraint(expr=m.x >= 0.0) + + m.I = pe.Set(initialize=[1, 2, 3, 4, 5]) + m.J = pe.Set(initialize=[1, 2, 3]) + m.K = pe.Set(initialize=[1, 3, 5]) + m.u = pe.Var(m.I * m.I) + m.v = pe.Var(m.I) + m.w = pe.Var(m.J) + m.p = pe.Var(m.K) + + def ruleMaker(m, j): + return (m.x + m.y) * sum(m.v[i] + m.u[i, j] ** 2 for i in m.I) <= 0 + + m.constraint_7 = pe.Constraint(m.I, rule=ruleMaker) + + def ruleMaker(m): + return sum(m.p[k] for k in m.K) == 1 + + m.constraint_8 = pe.Constraint(rule=ruleMaker) + + return m + + +class TestLatexPrinter(unittest.TestCase): + def test_latexPrinter_objective(self): + m = generate_model() + pstr = latex_printer(m.objective_1) + bstr = '' + bstr += '\\begin{equation} \n' + bstr += ' & \\text{minimize} \n' + bstr += ' & & x + y + z \n' + bstr += '\end{equation} \n' + self.assertEqual(pstr, bstr) + + pstr = latex_printer(m.objective_3) + bstr = '' + bstr += '\\begin{equation} \n' + bstr += ' & \\text{maximize} \n' + bstr += ' & & x + y + z \n' + bstr += '\end{equation} \n' + self.assertEqual(pstr, bstr) + + def test_latexPrinter_constraint(self): + m = generate_model() + pstr = latex_printer(m.constraint_1) + + bstr = '' + bstr += '\\begin{equation} \n' + bstr += ' x^{2} + y^{-2} - xyz + 1 = 2 \n' + bstr += '\end{equation} \n' + + self.assertEqual(pstr, bstr) + + def test_latexPrinter_expression(self): + m = generate_model() + + m.express = pe.Expression(expr=m.x + m.y) + + pstr = latex_printer(m.express) + + bstr = '' + bstr += '\\begin{equation} \n' + bstr += ' x + y \n' + bstr += '\end{equation} \n' + + self.assertEqual(pstr, bstr) + + def test_latexPrinter_simpleExpression(self): + m = generate_model() + + pstr = latex_printer(m.x - m.y) + bstr = '' + bstr += '\\begin{equation} \n' + bstr += ' x - y \n' + bstr += '\end{equation} \n' + self.assertEqual(pstr, bstr) + + pstr = latex_printer(m.x - 2 * m.y) + bstr = '' + bstr += '\\begin{equation} \n' + bstr += ' x - 2y \n' + bstr += '\end{equation} \n' + self.assertEqual(pstr, bstr) + + def test_latexPrinter_unary(self): + m = generate_model() + + pstr = latex_printer(m.constraint_2) + bstr = '' + bstr += '\\begin{equation} \n' + bstr += ( + ' \left| \\frac{x}{z^{-2}} \\right| \left( x + y \\right) \leq 2 \n' + ) + bstr += '\end{equation} \n' + self.assertEqual(pstr, bstr) + + pstr = latex_printer(pe.Constraint(expr=pe.sin(m.x) == 1)) + bstr = '' + bstr += '\\begin{equation} \n' + bstr += ' \sin \left( x \\right) = 1 \n' + bstr += '\end{equation} \n' + self.assertEqual(pstr, bstr) + + pstr = latex_printer(pe.Constraint(expr=pe.log10(m.x) == 1)) + bstr = '' + bstr += '\\begin{equation} \n' + bstr += ' \log_{10} \left( x \\right) = 1 \n' + bstr += '\end{equation} \n' + self.assertEqual(pstr, bstr) + + pstr = latex_printer(pe.Constraint(expr=pe.sqrt(m.x) == 1)) + bstr = '' + bstr += '\\begin{equation} \n' + bstr += ' \sqrt { x } = 1 \n' + bstr += '\end{equation} \n' + self.assertEqual(pstr, bstr) + + def test_latexPrinter_rangedConstraint(self): + m = generate_model() + + pstr = latex_printer(m.constraint_4) + bstr = '' + bstr += '\\begin{equation} \n' + bstr += ' 1 \leq x \leq 2 \n' + bstr += '\end{equation} \n' + self.assertEqual(pstr, bstr) + + def test_latexPrinter_exprIf(self): + m = generate_model() + + pstr = latex_printer(m.constraint_5) + bstr = '' + bstr += '\\begin{equation} \n' + bstr += ' f_{\\text{exprIf}}(x \leq 1,z,y) \leq 1 \n' + bstr += '\end{equation} \n' + self.assertEqual(pstr, bstr) + + def test_latexPrinter_blackBox(self): + m = generate_model() + + pstr = latex_printer(m.constraint_6) + bstr = '' + bstr += '\\begin{equation} \n' + bstr += ' x + f(x,y) = 2 \n' + bstr += '\end{equation} \n' + self.assertEqual(pstr, bstr) + + def test_latexPrinter_iteratedConstraints(self): + m = generate_model() + + pstr = latex_printer(m.constraint_7) + bstr = '' + bstr += '\\begin{equation} \n' + bstr += ' \left( x + y \\right) \sum_{i \in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I \n' + bstr += '\end{equation} \n' + self.assertEqual(pstr, bstr) + + pstr = latex_printer(m.constraint_8) + bstr = '' + bstr += '\\begin{equation} \n' + bstr += ' \sum_{k \in K} p_{k} = 1 \n' + bstr += '\end{equation} \n' + self.assertEqual(pstr, bstr) + + def test_latexPrinter_model(self): + m = generate_simple_model() + + pstr = latex_printer(m) + bstr = '' + bstr += '\\begin{equation} \n' + bstr += ' \\begin{aligned} \n' + bstr += ' & \\text{minimize} \n' + bstr += ' & & x + y \\\\ \n' + bstr += ' & \\text{subject to} \n' + bstr += ' & & x^{2} + y^{2} \leq 1 \\\\ \n' + bstr += ' &&& 0 \leq x \\\\ \n' + bstr += ' &&& \left( x + y \\right) \sum_{i \\in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \\in I \\\\ \n' + bstr += ' &&& \sum_{k \\in K} p_{k} = 1 \n' + bstr += ' \end{aligned} \n' + bstr += ' \label{basicFormulation} \n' + bstr += '\end{equation} \n' + self.assertEqual(pstr, bstr) + + pstr = latex_printer(m, None, True) + bstr = '' + bstr += '\\begin{align} \n' + bstr += ' & \\text{minimize} \n' + bstr += ' & & x + y \label{obj:basicFormulation_objective_1} \\\\ \n' + bstr += ' & \\text{subject to} \n' + bstr += ' & & x^{2} + y^{2} \leq 1 \label{con:basicFormulation_constraint_1} \\\\ \n' + bstr += ' &&& 0 \leq x \label{con:basicFormulation_constraint_2} \\\\ \n' + bstr += ' &&& \left( x + y \\right) \sum_{i \\in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \\in I \label{con:basicFormulation_constraint_7} \\\\ \n' + bstr += ' &&& \sum_{k \\in K} p_{k} = 1 \label{con:basicFormulation_constraint_8} \n' + bstr += '\end{align} \n' + self.assertEqual(pstr, bstr) + + pstr = latex_printer(m, None, False, True) + bstr = '' + bstr += '\\begin{equation} \n' + bstr += ' \\begin{aligned} \n' + bstr += ' & \\text{minimize} \n' + bstr += ' & & x + y \\\\ \n' + bstr += ' & \\text{subject to} \n' + bstr += ' & & x^{2} + y^{2} \leq 1 \\\\ \n' + bstr += ' &&& 0 \leq x \\\\ \n' + bstr += ' &&& \left( x + y \\right) \sum_{i = 1}^{5} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \\in I \\\\ \n' + bstr += ' &&& \sum_{k \\in K} p_{k} = 1 \n' + bstr += ' \end{aligned} \n' + bstr += ' \label{basicFormulation} \n' + bstr += '\end{equation} \n' + self.assertEqual(pstr, bstr) + + pstr = latex_printer(m, None, True, True) + bstr = '' + bstr += '\\begin{align} \n' + bstr += ' & \\text{minimize} \n' + bstr += ' & & x + y \label{obj:basicFormulation_objective_1} \\\\ \n' + bstr += ' & \\text{subject to} \n' + bstr += ' & & x^{2} + y^{2} \leq 1 \label{con:basicFormulation_constraint_1} \\\\ \n' + bstr += ' &&& 0 \leq x \label{con:basicFormulation_constraint_2} \\\\ \n' + bstr += ' &&& \left( x + y \\right) \sum_{i = 1}^{5} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \\in I \label{con:basicFormulation_constraint_7} \\\\ \n' + bstr += ' &&& \sum_{k \in K} p_{k} = 1 \label{con:basicFormulation_constraint_8} \n' + bstr += '\end{align} \n' + self.assertEqual(pstr, bstr) + + +if __name__ == '__main__': + unittest.main() From 312bad48002abe5f6f27e693a9fd6d4b927fc2f2 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Thu, 31 Aug 2023 15:43:20 -0600 Subject: [PATCH 0087/1204] removing DS_store --- .DS_Store | Bin 6148 -> 0 bytes doc/.DS_Store | Bin 6148 -> 0 bytes pyomo/.DS_Store | Bin 6148 -> 0 bytes pyomo/contrib/.DS_Store | Bin 6148 -> 0 bytes pyomo/contrib/edi/.DS_Store | Bin 6148 -> 0 bytes 5 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store delete mode 100644 doc/.DS_Store delete mode 100644 pyomo/.DS_Store delete mode 100644 pyomo/contrib/.DS_Store delete mode 100644 pyomo/contrib/edi/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index a6006a4ad1ba49dc60d16a61c045eaa5a228aacf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5Z>*NZ7D(y3VK`cTCk;uw0H@zzJL)usMLfM4W`-Bq!uZK-1UWg5ueAI z-3_!DJc`&E*!^bbXE*af_J=XX-9^}A%wmi+p&@cqN(9ZNu8Ijp zbu?Lyt%Gx!WPX%P<|-iyClGRX6D6Tsx^j_(smk@VqXjayj#sPR&>nQepug^jRj)s= zJHoby>vgMncyfAnIew0(iG0&ca-dwvzQF?CLD?wj#hWLwOdi2nWE7Ev!~iis3=ji5 z$$&Wyn(dveo{A<0h=HFN!2Ll$Lv#%08r9YT9bTW&UqeIz9p4g&!k}X?*9aaEu2TVZ zDmPCIuG7ITOq^pd*QnDOS1ZFjX654X!qw_v7b=`_MP|R7$64z83VjF@6T?pgllC!MGe15YV?S0WiRQ-s>h%_lF_ZRqsRdm!8`GH7)5K?rJKu}+m@8Z)KKZJ-b6cjXQEZMW` z_3Y`UIG+L7=I!AISOZwn9dYz9H$Qiu*+pfHNar0R9x>p6d%Vu7&nKLFg*!6$c>d0R z-@c6d!}yeUpC>CT1*Cu!kOERb3jA6D@4d9;Dp64iNC7GErGS4Q8r`uMPKoj9V2BZb zxL`Vr>zE~o%@f34I3+Sev!oJ}YBge5(wT2n*9)h_q{C|XuzIr9gkte@-ru4e))N(_ zfD|}Y;4-&s@Bg>-ALjp4l6F!+3j8YtY%y$y4PU8x>+I#c*Eaf&?lm8DH?D)i5bc;4 i?U);H$JbGmb01p5v*a!tFYlO^eT?H3RD9juwXi(97Jc^Pv z6a7UKeR~;(pkM|ueENPJq310cCGmLDXuOL;v9z^aMyZwW!bd$1C;iEE-07z`G`iF} ziE_OkUB$zB&)YrIYSNF@Ff|GBV2B~N*RdMtc}GvxU~F!_miyo1HUZ#R$Y(r>hu zb-D2U)=6EqoBncHt?V5honG{wl4qq~ESUm%H?rd}hgVd-)HMrJm1y;Vo;)j$W@HAK z0cL<1*dzwrDNw0xQqf#1Gr$b|hymIkBsRjpVP?^69oW(JnfxU}64dD}K`0#t4l|4B zK@m0;(WVOb#1J+e?b5{s4l|239fVmK=W#3Nj~8K9N4qrPAOefrGXu=PDg#A3^yvIQ z$6sdcBY!o8N6Y{-@Xr_!rEb{mU{UUD{Z<~GwG!JsHWG@s#|-=e10NI;O*jAm diff --git a/pyomo/contrib/.DS_Store b/pyomo/contrib/.DS_Store deleted file mode 100644 index 851319072b84f232eac3559cc3f19a730f4f9680..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKJ5Iwu5S=9{EYYN-+$*G`Tq1LVTmS_lK#Juw^i~=Uz&*G{lxuJl-hB8(D_l}U zZ={*$ozJfQiXD%Lq}5Be6j_K!167n)HMMA5wUUeQ%z;VwSg!Afepls9Ika{r57No= z_OYsuNI$ggW;<+<+q@m$_aE1Xo1eOV=q94Or)t-!_hF0-kO4A42FSpi3QFBCHH9}Ii~ D`!y$X diff --git a/pyomo/contrib/edi/.DS_Store b/pyomo/contrib/edi/.DS_Store deleted file mode 100644 index 7c829d887e4d0c83c53ced83c678d4a5de433a25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKK}y6>3{A!nDsBecxcpbBE0>+2o}d@daRyzqw4eimn~F#B3?9G(xbP;v{DiT! z9dISXkU;W&^OOGH_e;|d5id5YlxRjo2~==$0y82qFFKHkd1R8~JsK)$O%LT=S`4Dy zv5ySs;jZb4Zm6Qp`Q6r4)7fx>bM3`cb)GNFdWo3i^W);>>+*dr<6+$DPjStCTKrn` zm>%VAf{ky~?%D2M-p-z1Z7-ets{Yx2vEVyuvLto4w%>i0H<(A!B~0;$q9y;VXKH42x}@(Q`uS!)^zxT#bt)AqNWpD z^TD<Z^cgtP%bC>wtKI#7KgqA00cYT#7~pAM Date: Thu, 31 Aug 2023 15:54:36 -0600 Subject: [PATCH 0088/1204] SAVE POINT: Stopping for end of sprin --- pyomo/solver/IPOPT.py | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index 3f5fa0e1df6..384b2173840 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -101,6 +101,14 @@ def solve(self, model, **kwds): # Update configuration options, based on keywords passed to solve config = self.config(kwds.pop('options', {})) config.set_value(kwds) + # Get a copy of the environment to pass to the subprocess + env = os.environ.copy() + if 'PYOMO_AMPLFUNC' in env: + env['AMPLFUNC'] = "\n".join( + filter( + None, (env.get('AMPLFUNC', None), env.get('PYOMO_AMPLFUNC', None)) + ) + ) # Write the model to an nl file nl_writer = WriterFactory('nl') # Need to add check for symbolic_solver_labels; may need to generate up @@ -112,7 +120,7 @@ def solve(self, model, **kwds): with open(os.path.join(dname, model.name + '.nl')) as nl_file, open( os.path.join(dname, model.name + '.row') ) as row_file, open(os.path.join(dname, model.name + '.col')) as col_file: - info = nl_writer.write( + self.info = nl_writer.write( model, nl_file, row_file, @@ -120,4 +128,29 @@ def solve(self, model, **kwds): symbolic_solver_labels=config.symbolic_solver_labels, ) # Call IPOPT - passing the files via the subprocess - subprocess.run() + cmd = [str(config.executable), nl_file, '-AMPL'] + if config.time_limit is not None: + config.solver_options['max_cpu_time'] = config.time_limit + for key, val in config.solver_options.items(): + cmd.append(key + '=' + val) + process = subprocess.run(cmd, timeout=config.time_limit, + env=env, + universal_newlines=True) + + if process.returncode != 0: + if self.config.load_solution: + raise RuntimeError( + 'A feasible solution was not found, so no solution can be loaded.' + 'Please set config.load_solution=False and check ' + 'results.termination_condition and ' + 'results.incumbent_objective before loading a solution.' + ) + results = Results() + results.termination_condition = TerminationCondition.error + else: + results = self._parse_solution() + + def _parse_solution(self): + # STOPPING POINT: The suggestion here is to look at the original + # parser, which hasn't failed yet, and rework it to be ... better? + pass From cfc45dd86ffe0b79087470393a2823bc18be3e05 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Fri, 1 Sep 2023 05:01:30 -0600 Subject: [PATCH 0089/1204] adding variable features, fixing some bugs --- .../model_debugging/latex_printing.rst | 9 +- pyomo/util/latex_printer.py | 91 +++++++++++++++++-- pyomo/util/tests/test_latex_printer.py | 37 ++++++++ 3 files changed, 121 insertions(+), 16 deletions(-) diff --git a/doc/OnlineDocs/model_debugging/latex_printing.rst b/doc/OnlineDocs/model_debugging/latex_printing.rst index 9374e43bf3f..b9ac07af25d 100644 --- a/doc/OnlineDocs/model_debugging/latex_printing.rst +++ b/doc/OnlineDocs/model_debugging/latex_printing.rst @@ -56,8 +56,7 @@ A Model >>> m.constraint_4 = pe.Constraint(expr = (1,m.x,2)) >>> m.constraint_5 = pe.Constraint(expr = Expr_if(m.x<=1.0, m.z, m.y) <= 1.0) - >>> def blackbox(a, b): - >>> return sin(a - b) + >>> def blackbox(a, b): return sin(a - b) >>> m.bb = ExternalFunction(blackbox) >>> m.constraint_6 = pe.Constraint(expr= m.x + m.bb(m.x,m.y) == 2 ) @@ -67,12 +66,10 @@ A Model >>> m.v = pe.Var(m.I) >>> m.w = pe.Var(m.J) - >>> def ruleMaker(m,j): - >>> return (m.x + m.y) * sum( m.v[i] + m.u[i,j]**2 for i in m.I ) <= 0 + >>> def ruleMaker(m,j): return (m.x + m.y) * sum( m.v[i] + m.u[i,j]**2 for i in m.I ) <= 0 >>> m.constraint_7 = pe.Constraint(m.I, rule = ruleMaker) - >>> def ruleMaker(m): - >>> return (m.x + m.y) * sum( m.w[j] for j in m.J ) + >>> def ruleMaker(m): return (m.x + m.y) * sum( m.w[j] for j in m.J ) >>> m.objective_2 = pe.Objective(rule = ruleMaker) >>> pstr = latex_printer(m) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index e02b3b7491a..426f7f51074 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -56,8 +56,26 @@ def templatize_passthrough(con): def handle_negation_node(node, arg1): - return '-' + arg1 + childPrecedence = [] + for a in node.args: + if hasattr(a, 'PRECEDENCE'): + if a.PRECEDENCE is None: + childPrecedence.append(-1) + else: + childPrecedence.append(a.PRECEDENCE) + else: + childPrecedence.append(-1) + + if hasattr(node, 'PRECEDENCE'): + precedence = node.PRECEDENCE + else: + # Should never hit this + precedence = -1 + if childPrecedence[0] > precedence: + arg1 = ' \\left( ' + arg1 + ' \\right) ' + + return '-' + arg1 def handle_product_node(node, arg1, arg2): childPrecedence = [] @@ -91,6 +109,28 @@ def handle_division_node(node, arg1, arg2): def handle_pow_node(node, arg1, arg2): + childPrecedence = [] + for a in node.args: + if hasattr(a, 'PRECEDENCE'): + if a.PRECEDENCE is None: + childPrecedence.append(-1) + else: + childPrecedence.append(a.PRECEDENCE) + else: + childPrecedence.append(-1) + + if hasattr(node, 'PRECEDENCE'): + precedence = node.PRECEDENCE + else: + # Should never hit this + precedence = -1 + + if childPrecedence[0] > precedence: + arg1 = ' \\left( ' + arg1 + ' \\right) ' + + if childPrecedence[1] > precedence: + arg2 = ' \\left( ' + arg2 + ' \\right) ' + return "%s^{%s}" % (arg1, arg2) @@ -117,8 +157,43 @@ def handle_inequality_node(node, arg1, arg2): return arg1 + ' \leq ' + arg2 -def handle_scalarVar_node(node): - return node.name +def handle_var_node(node): + name = node.name + + splitName = name.split('_') + + filteredName = [] + + prfx = '' + psfx = '' + for i in range(0,len(splitName)): + se = splitName[i] + if se != 0: + if se == 'dot': + prfx = '\dot{' + psfx = '}' + elif se == 'hat': + prfx = '\hat{' + psfx = '}' + elif se == 'bar': + prfx = '\\bar{' + psfx = '}' + elif se == 'star': + prfx = '' + psfx = '^{*}' + else: + filteredName.append(se) + else: + filteredName.append(se) + + + joinedName = prfx + filteredName[0] + psfx + for i in range(1,len(filteredName)): + joinedName += '_{' + filteredName[i] + + joinedName += '}'*(len(filteredName)-1) + + return joinedName def handle_num_node(node): @@ -187,10 +262,6 @@ def handle_functionID_node(node, *args): return '' -def handle_indexedVar_node(node, *args): - return node.name - - def handle_indexTemplate_node(node, *args): return '__INDEX_PLACEHOLDER_8675309_GROUP_%s_%s__' % (node._group, node._set) @@ -222,7 +293,7 @@ def __init__(self): super().__init__() self._operator_handles = { - ScalarVar: handle_scalarVar_node, + ScalarVar: handle_var_node, int: handle_num_node, float: handle_num_node, NegationExpression: handle_negation_node, @@ -240,7 +311,7 @@ def __init__(self): kernel.expression.expression: handle_named_expression_node, kernel.expression.noclone: handle_named_expression_node, _GeneralObjectiveData: handle_named_expression_node, - _GeneralVarData: handle_scalarVar_node, + _GeneralVarData: handle_var_node, ScalarObjective: handle_named_expression_node, kernel.objective.objective: handle_named_expression_node, ExternalFunctionExpression: handle_external_function_node, @@ -248,7 +319,7 @@ def __init__(self): LinearExpression: handle_sum_expression, SumExpression: handle_sum_expression, MonomialTermExpression: handle_monomialTermExpression_node, - IndexedVar: handle_indexedVar_node, + IndexedVar: handle_var_node, IndexTemplate: handle_indexTemplate_node, Numeric_GetItemExpression: handle_numericGIE_node, TemplateSumExpression: handle_templateSumExpression_node, diff --git a/pyomo/util/tests/test_latex_printer.py b/pyomo/util/tests/test_latex_printer.py index 93bd589aa94..6385b7e864a 100644 --- a/pyomo/util/tests/test_latex_printer.py +++ b/pyomo/util/tests/test_latex_printer.py @@ -99,6 +99,24 @@ def ruleMaker(m): return m +def generate_simple_model_2(): + import pyomo.environ as pe + + m = pe.ConcreteModel(name = 'basicFormulation') + m.x_dot = pe.Var() + m.x_bar = pe.Var() + m.x_star = pe.Var() + m.x_hat = pe.Var() + m.x_hat_1 = pe.Var() + m.y_sub1_sub2_sub3 = pe.Var() + m.objective_1 = pe.Objective( expr = m.y_sub1_sub2_sub3 ) + m.constraint_1 = pe.Constraint(expr = (m.x_dot + m.x_bar + m.x_star + m.x_hat + m.x_hat_1)**2 <= m.y_sub1_sub2_sub3 ) + m.constraint_2 = pe.Constraint(expr = (m.x_dot + m.x_bar)**-(m.x_star + m.x_hat) <= m.y_sub1_sub2_sub3 ) + m.constraint_3 = pe.Constraint(expr = -(m.x_dot + m.x_bar)+ -(m.x_star + m.x_hat) <= m.y_sub1_sub2_sub3 ) + + return m + + class TestLatexPrinter(unittest.TestCase): def test_latexPrinter_objective(self): m = generate_model() @@ -301,6 +319,25 @@ def test_latexPrinter_model(self): bstr += '\end{align} \n' self.assertEqual(pstr, bstr) + def test_latexPrinter_advancedVariables(self): + m = generate_simple_model_2() + + pstr = latex_printer(m) + bstr = '' + bstr += '\\begin{equation} \n' + bstr += ' \\begin{aligned} \n' + bstr += ' & \\text{minimize} \n' + bstr += ' & & y_{sub1_{sub2_{sub3}}} \\\\ \n' + bstr += ' & \\text{subject to} \n' + bstr += ' & & \left( \dot{x} + \\bar{x} + x^{*} + \hat{x} + \hat{x}_{1} \\right) ^{2} \leq y_{sub1_{sub2_{sub3}}} \\\\ \n' + bstr += ' &&& \left( \dot{x} + \\bar{x} \\right) ^{ \left( - \left( x^{*} + \hat{x} \\right) \\right) } \leq y_{sub1_{sub2_{sub3}}} \\\\ \n' + bstr += ' &&& - \left( \dot{x} + \\bar{x} \\right) - \left( x^{*} + \hat{x} \\right) \leq y_{sub1_{sub2_{sub3}}} \n' + bstr += ' \\end{aligned} \n' + bstr += ' \label{basicFormulation} \n' + bstr += '\\end{equation} \n' + self.assertEqual(pstr, bstr) + + if __name__ == '__main__': unittest.main() From bb2082a2360efb7e975ec6e7e15a1bda0e80fb2e Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Fri, 1 Sep 2023 05:09:26 -0600 Subject: [PATCH 0090/1204] removing the star capability and updating documentation --- doc/OnlineDocs/model_debugging/latex_printing.rst | 7 +++++++ pyomo/util/latex_printer.py | 3 --- pyomo/util/tests/test_latex_printer.py | 6 +++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/doc/OnlineDocs/model_debugging/latex_printing.rst b/doc/OnlineDocs/model_debugging/latex_printing.rst index b9ac07af25d..0bdb0de735c 100644 --- a/doc/OnlineDocs/model_debugging/latex_printing.rst +++ b/doc/OnlineDocs/model_debugging/latex_printing.rst @@ -29,6 +29,13 @@ Pyomo models can be printed to a LaTeX compatible format using the ``pyomo.util. ``display(Math(latex_printer(m))`` +The LaTeX printer will auto detect the following structures in variable names: + + * ``_``: underscores will get rendered as subscripts, ie ``x_var`` is rendered as ``x_{var}`` + * ``_dot``: will format as a ``\dot{}`` and remove from the underscore formatting. Ex: ``x_dot_1`` becomes ``\dot{x}_1`` + * ``_hat``: will format as a ``\hat{}`` and remove from the underscore formatting. Ex: ``x_hat_1`` becomes ``\hat{x}_1`` + * ``_bar``: will format as a ``\bar{}`` and remove from the underscore formatting. Ex: ``x_bar_1`` becomes ``\bar{x}_1`` + Examples -------- diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index 426f7f51074..765a4791ff2 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -178,9 +178,6 @@ def handle_var_node(node): elif se == 'bar': prfx = '\\bar{' psfx = '}' - elif se == 'star': - prfx = '' - psfx = '^{*}' else: filteredName.append(se) else: diff --git a/pyomo/util/tests/test_latex_printer.py b/pyomo/util/tests/test_latex_printer.py index 6385b7e864a..80bb0ee6b34 100644 --- a/pyomo/util/tests/test_latex_printer.py +++ b/pyomo/util/tests/test_latex_printer.py @@ -329,9 +329,9 @@ def test_latexPrinter_advancedVariables(self): bstr += ' & \\text{minimize} \n' bstr += ' & & y_{sub1_{sub2_{sub3}}} \\\\ \n' bstr += ' & \\text{subject to} \n' - bstr += ' & & \left( \dot{x} + \\bar{x} + x^{*} + \hat{x} + \hat{x}_{1} \\right) ^{2} \leq y_{sub1_{sub2_{sub3}}} \\\\ \n' - bstr += ' &&& \left( \dot{x} + \\bar{x} \\right) ^{ \left( - \left( x^{*} + \hat{x} \\right) \\right) } \leq y_{sub1_{sub2_{sub3}}} \\\\ \n' - bstr += ' &&& - \left( \dot{x} + \\bar{x} \\right) - \left( x^{*} + \hat{x} \\right) \leq y_{sub1_{sub2_{sub3}}} \n' + bstr += ' & & \left( \dot{x} + \\bar{x} + x_{star} + \hat{x} + \hat{x}_{1} \\right) ^{2} \leq y_{sub1_{sub2_{sub3}}} \\\\ \n' + bstr += ' &&& \left( \dot{x} + \\bar{x} \\right) ^{ \left( - \left( x_{star} + \hat{x} \\right) \\right) } \leq y_{sub1_{sub2_{sub3}}} \\\\ \n' + bstr += ' &&& - \left( \dot{x} + \\bar{x} \\right) - \left( x_{star} + \hat{x} \\right) \leq y_{sub1_{sub2_{sub3}}} \n' bstr += ' \\end{aligned} \n' bstr += ' \label{basicFormulation} \n' bstr += '\\end{equation} \n' From c9c3a64b682938fbf33b564a31fae11ee43e57c4 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Fri, 1 Sep 2023 05:17:13 -0600 Subject: [PATCH 0091/1204] adding black --- pyomo/util/latex_printer.py | 16 ++++++++-------- pyomo/util/tests/test_latex_printer.py | 20 +++++++++++++------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index 765a4791ff2..f22c6d0d12f 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -74,9 +74,10 @@ def handle_negation_node(node, arg1): if childPrecedence[0] > precedence: arg1 = ' \\left( ' + arg1 + ' \\right) ' - + return '-' + arg1 + def handle_product_node(node, arg1, arg2): childPrecedence = [] for a in node.args: @@ -129,8 +130,8 @@ def handle_pow_node(node, arg1, arg2): arg1 = ' \\left( ' + arg1 + ' \\right) ' if childPrecedence[1] > precedence: - arg2 = ' \\left( ' + arg2 + ' \\right) ' - + arg2 = ' \\left( ' + arg2 + ' \\right) ' + return "%s^{%s}" % (arg1, arg2) @@ -166,7 +167,7 @@ def handle_var_node(node): prfx = '' psfx = '' - for i in range(0,len(splitName)): + for i in range(0, len(splitName)): se = splitName[i] if se != 0: if se == 'dot': @@ -183,12 +184,11 @@ def handle_var_node(node): else: filteredName.append(se) - - joinedName = prfx + filteredName[0] + psfx - for i in range(1,len(filteredName)): + joinedName = prfx + filteredName[0] + psfx + for i in range(1, len(filteredName)): joinedName += '_{' + filteredName[i] - joinedName += '}'*(len(filteredName)-1) + joinedName += '}' * (len(filteredName) - 1) return joinedName diff --git a/pyomo/util/tests/test_latex_printer.py b/pyomo/util/tests/test_latex_printer.py index 80bb0ee6b34..c2e2036ccf2 100644 --- a/pyomo/util/tests/test_latex_printer.py +++ b/pyomo/util/tests/test_latex_printer.py @@ -101,18 +101,25 @@ def ruleMaker(m): def generate_simple_model_2(): import pyomo.environ as pe - - m = pe.ConcreteModel(name = 'basicFormulation') + + m = pe.ConcreteModel(name='basicFormulation') m.x_dot = pe.Var() m.x_bar = pe.Var() m.x_star = pe.Var() m.x_hat = pe.Var() m.x_hat_1 = pe.Var() m.y_sub1_sub2_sub3 = pe.Var() - m.objective_1 = pe.Objective( expr = m.y_sub1_sub2_sub3 ) - m.constraint_1 = pe.Constraint(expr = (m.x_dot + m.x_bar + m.x_star + m.x_hat + m.x_hat_1)**2 <= m.y_sub1_sub2_sub3 ) - m.constraint_2 = pe.Constraint(expr = (m.x_dot + m.x_bar)**-(m.x_star + m.x_hat) <= m.y_sub1_sub2_sub3 ) - m.constraint_3 = pe.Constraint(expr = -(m.x_dot + m.x_bar)+ -(m.x_star + m.x_hat) <= m.y_sub1_sub2_sub3 ) + m.objective_1 = pe.Objective(expr=m.y_sub1_sub2_sub3) + m.constraint_1 = pe.Constraint( + expr=(m.x_dot + m.x_bar + m.x_star + m.x_hat + m.x_hat_1) ** 2 + <= m.y_sub1_sub2_sub3 + ) + m.constraint_2 = pe.Constraint( + expr=(m.x_dot + m.x_bar) ** -(m.x_star + m.x_hat) <= m.y_sub1_sub2_sub3 + ) + m.constraint_3 = pe.Constraint( + expr=-(m.x_dot + m.x_bar) + -(m.x_star + m.x_hat) <= m.y_sub1_sub2_sub3 + ) return m @@ -338,6 +345,5 @@ def test_latexPrinter_advancedVariables(self): self.assertEqual(pstr, bstr) - if __name__ == '__main__': unittest.main() From 6526d77ddcf380dc95700c17a1534c8dd0077c96 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Tue, 5 Sep 2023 21:48:26 -0600 Subject: [PATCH 0092/1204] fixes and adding features --- pyomo/util/latex_printer.py | 536 ++++++++++++++++++------- pyomo/util/tests/test_latex_printer.py | 488 ++++++++++++---------- 2 files changed, 676 insertions(+), 348 deletions(-) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index f22c6d0d12f..5d3c0943e79 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -1,4 +1,16 @@ -import pyomo.environ as pe +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2023 +# 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 math +import pyomo.environ as pyo from pyomo.core.expr.visitor import StreamBasedExpressionVisitor from pyomo.core.expr import ( NegationExpression, @@ -26,12 +38,13 @@ GetAttrExpression, TemplateSumExpression, IndexTemplate, + Numeric_GetItemExpression, + templatize_constraint, + resolve_template, + templatize_rule, ) -from pyomo.core.expr.template_expr import Numeric_GetItemExpression -from pyomo.core.expr.template_expr import templatize_constraint, resolve_template from pyomo.core.base.var import ScalarVar, _GeneralVarData, IndexedVar from pyomo.core.base.constraint import ScalarConstraint, IndexedConstraint -from pyomo.core.expr.template_expr import templatize_rule from pyomo.core.base.external import _PythonCallbackFunctionID @@ -43,7 +56,83 @@ _MONOMIAL = ExprType.MONOMIAL _GENERAL = ExprType.GENERAL -# see: https://github.com/Pyomo/pyomo/blob/main/pyomo/repn/plugins/nl_writer.py + +def decoder(num, base): + if isinstance(base, float): + if not base.is_integer(): + raise ValueError('Invalid base') + else: + base = int(base) + + if base <= 1: + raise ValueError('Invalid base') + + if num == 0: + numDigs = 1 + else: + numDigs = math.ceil(math.log(num, base)) + if math.log(num, base).is_integer(): + numDigs += 1 + digs = [0.0 for i in range(0, numDigs)] + rem = num + for i in range(0, numDigs): + ix = numDigs - i - 1 + dg = math.floor(rem / base**ix) + rem = rem % base**ix + digs[i] = dg + return digs + + +def indexCorrector(ixs, base): + for i in range(0, len(ixs)): + ix = ixs[i] + if i + 1 < len(ixs): + if ixs[i + 1] == 0: + ixs[i] -= 1 + ixs[i + 1] = base + if ixs[i] == 0: + ixs = indexCorrector(ixs, base) + return ixs + + +def alphabetStringGenerator(num): + alphabet = [ + '.', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + ] + ixs = decoder(num + 1, 26) + pstr = '' + ixs = indexCorrector(ixs, 26) + for i in range(0, len(ixs)): + ix = ixs[i] + pstr += alphabet[ix] + pstr = pstr.replace('.', '') + return pstr def templatize_expression(expr): @@ -55,7 +144,7 @@ def templatize_passthrough(con): return (con, []) -def handle_negation_node(node, arg1): +def precedenceChecker(node, arg1, arg2=None): childPrecedence = [] for a in node.args: if hasattr(a, 'PRECEDENCE'): @@ -70,76 +159,44 @@ def handle_negation_node(node, arg1): precedence = node.PRECEDENCE else: # Should never hit this - precedence = -1 + raise RuntimeError( + 'This error should never be thrown, node does not have a precedence. Report to developers' + ) if childPrecedence[0] > precedence: arg1 = ' \\left( ' + arg1 + ' \\right) ' - return '-' + arg1 + if arg2 is not None: + if childPrecedence[1] > precedence: + arg2 = ' \\left( ' + arg2 + ' \\right) ' + return arg1, arg2 -def handle_product_node(node, arg1, arg2): - childPrecedence = [] - for a in node.args: - if hasattr(a, 'PRECEDENCE'): - if a.PRECEDENCE is None: - childPrecedence.append(-1) - else: - childPrecedence.append(a.PRECEDENCE) - else: - childPrecedence.append(-1) - - if hasattr(node, 'PRECEDENCE'): - precedence = node.PRECEDENCE - else: - # Should never hit this - precedence = -1 - if childPrecedence[0] > precedence: - arg1 = ' \\left( ' + arg1 + ' \\right) ' - - if childPrecedence[1] > precedence: - arg2 = ' \\left( ' + arg2 + ' \\right) ' - - return ''.join([arg1, arg2]) - - -def handle_division_node(node, arg1, arg2): - # return '/'.join([arg1,arg2]) - return '\\frac{%s}{%s}' % (arg1, arg2) +def handle_negation_node(visitor, node, arg1): + arg1, tsh = precedenceChecker(node, arg1) + return '-' + arg1 -def handle_pow_node(node, arg1, arg2): - childPrecedence = [] - for a in node.args: - if hasattr(a, 'PRECEDENCE'): - if a.PRECEDENCE is None: - childPrecedence.append(-1) - else: - childPrecedence.append(a.PRECEDENCE) - else: - childPrecedence.append(-1) +def handle_product_node(visitor, node, arg1, arg2): + arg1, arg2 = precedenceChecker(node, arg1, arg2) + return ' '.join([arg1, arg2]) - if hasattr(node, 'PRECEDENCE'): - precedence = node.PRECEDENCE - else: - # Should never hit this - precedence = -1 - if childPrecedence[0] > precedence: - arg1 = ' \\left( ' + arg1 + ' \\right) ' +def handle_pow_node(visitor, node, arg1, arg2): + arg1, arg2 = precedenceChecker(node, arg1, arg2) + return "%s^{%s}" % (arg1, arg2) - if childPrecedence[1] > precedence: - arg2 = ' \\left( ' + arg2 + ' \\right) ' - return "%s^{%s}" % (arg1, arg2) +def handle_division_node(visitor, node, arg1, arg2): + return '\\frac{%s}{%s}' % (arg1, arg2) -def handle_abs_node(node, arg1): +def handle_abs_node(visitor, node, arg1): return ' \\left| ' + arg1 + ' \\right| ' -def handle_unary_node(node, arg1): +def handle_unary_node(visitor, node, arg1): fcn_handle = node.getname() if fcn_handle == 'log10': fcn_handle = 'log_{10}' @@ -150,57 +207,87 @@ def handle_unary_node(node, arg1): return '\\' + fcn_handle + ' \\left( ' + arg1 + ' \\right) ' -def handle_equality_node(node, arg1, arg2): +def handle_equality_node(visitor, node, arg1, arg2): return arg1 + ' = ' + arg2 -def handle_inequality_node(node, arg1, arg2): +def handle_inequality_node(visitor, node, arg1, arg2): return arg1 + ' \leq ' + arg2 -def handle_var_node(node): +def handle_var_node(visitor, node): + # if self.disableSmartVariables: + # if self.xOnlyMode: + overwriteDict = visitor.overwriteDict + # varList = visitor.variableList + name = node.name - splitName = name.split('_') - - filteredName = [] - - prfx = '' - psfx = '' - for i in range(0, len(splitName)): - se = splitName[i] - if se != 0: - if se == 'dot': - prfx = '\dot{' - psfx = '}' - elif se == 'hat': - prfx = '\hat{' - psfx = '}' - elif se == 'bar': - prfx = '\\bar{' - psfx = '}' + declaredIndex = None + if '[' in name: + openBracketIndex = name.index('[') + closeBracketIndex = name.index(']') + if closeBracketIndex != len(name) - 1: + # I dont think this can happen, but possibly through a raw string and a user + # who is really hacking the variable name setter + raise ValueError( + 'Variable %s has a close brace not at the end of the string' % (name) + ) + declaredIndex = name[openBracketIndex + 1 : closeBracketIndex] + name = name[0:openBracketIndex] + + if name in overwriteDict.keys(): + name = overwriteDict[name] + + if not visitor.disableSmartVariables: + splitName = name.split('_') + if declaredIndex is not None: + splitName.append(declaredIndex) + + filteredName = [] + + prfx = '' + psfx = '' + for i in range(0, len(splitName)): + se = splitName[i] + if se != 0: + if se == 'dot': + prfx = '\dot{' + psfx = '}' + elif se == 'hat': + prfx = '\hat{' + psfx = '}' + elif se == 'bar': + prfx = '\\bar{' + psfx = '}' + else: + filteredName.append(se) else: filteredName.append(se) - else: - filteredName.append(se) - joinedName = prfx + filteredName[0] + psfx - for i in range(1, len(filteredName)): - joinedName += '_{' + filteredName[i] + joinedName = prfx + filteredName[0] + psfx + for i in range(1, len(filteredName)): + joinedName += '_{' + filteredName[i] + + joinedName += '}' * (len(filteredName) - 1) - joinedName += '}' * (len(filteredName) - 1) + else: + if declaredIndex is not None: + joinedName = name + '[' + declaredIndex + ']' + else: + joinedName = name return joinedName -def handle_num_node(node): +def handle_num_node(visitor, node): if isinstance(node, float): if node.is_integer(): node = int(node) return str(node) -def handle_sum_expression(node, *args): +def handle_sumExpression_node(visitor, node, *args): rstr = args[0] for i in range(1, len(args)): if args[i][0] == '-': @@ -210,24 +297,26 @@ def handle_sum_expression(node, *args): return rstr -def handle_monomialTermExpression_node(node, arg1, arg2): +def handle_monomialTermExpression_node(visitor, node, arg1, arg2): if arg1 == '1': return arg2 elif arg1 == '-1': return '-' + arg2 else: - return arg1 + arg2 + return arg1 + ' ' + arg2 -def handle_named_expression_node(node, arg1): +def handle_named_expression_node(visitor, node, arg1): + # needed to preserve consistencency with the exitNode function call + # prevents the need to type check in the exitNode function return arg1 -def handle_ranged_inequality_node(node, arg1, arg2, arg3): +def handle_ranged_inequality_node(visitor, node, arg1, arg2, arg3): return arg1 + ' \\leq ' + arg2 + ' \\leq ' + arg3 -def handle_exprif_node(node, arg1, arg2, arg3): +def handle_exprif_node(visitor, node, arg1, arg2, arg3): return 'f_{\\text{exprIf}}(' + arg1 + ',' + arg2 + ',' + arg3 + ')' ## Raises not implemented error @@ -242,7 +331,7 @@ def handle_exprif_node(node, arg1, arg2, arg3): # return pstr -def handle_external_function_node(node, *args): +def handle_external_function_node(visitor, node, *args): pstr = '' pstr += 'f(' for i in range(0, len(args) - 1): @@ -254,28 +343,41 @@ def handle_external_function_node(node, *args): return pstr -def handle_functionID_node(node, *args): +def handle_functionID_node(visitor, node, *args): # seems to just be a placeholder empty wrapper object return '' -def handle_indexTemplate_node(node, *args): +def handle_indexTemplate_node(visitor, node, *args): return '__INDEX_PLACEHOLDER_8675309_GROUP_%s_%s__' % (node._group, node._set) -def handle_numericGIE_node(node, *args): +def handle_numericGIE_node(visitor, node, *args): + addFinalBrace = False + if '_' in args[0]: + splitName = args[0].split('_') + joinedName = splitName[0] + for i in range(1, len(splitName)): + joinedName += '_{' + splitName[i] + joinedName += '}' * (len(splitName) - 2) + addFinalBrace = True + else: + joinedName = args[0] + pstr = '' - pstr += args[0] + '_{' + pstr += joinedName + '_{' for i in range(1, len(args)): pstr += args[i] if i <= len(args) - 2: pstr += ',' else: pstr += '}' + if addFinalBrace: + pstr += '}' return pstr -def handle_templateSumExpression_node(node, *args): +def handle_templateSumExpression_node(visitor, node, *args): pstr = '' pstr += '\\sum_{%s} %s' % ( '__SET_PLACEHOLDER_8675309_GROUP_%s_%s__' @@ -288,6 +390,9 @@ def handle_templateSumExpression_node(node, *args): class _LatexVisitor(StreamBasedExpressionVisitor): def __init__(self): super().__init__() + self.disableSmartVariables = False + self.xOnlyMode = False + self.overwriteDict = {} self._operator_handles = { ScalarVar: handle_var_node, @@ -313,8 +418,8 @@ def __init__(self): kernel.objective.objective: handle_named_expression_node, ExternalFunctionExpression: handle_external_function_node, _PythonCallbackFunctionID: handle_functionID_node, - LinearExpression: handle_sum_expression, - SumExpression: handle_sum_expression, + LinearExpression: handle_sumExpression_node, + SumExpression: handle_sumExpression_node, MonomialTermExpression: handle_monomialTermExpression_node, IndexedVar: handle_var_node, IndexTemplate: handle_indexTemplate_node, @@ -323,79 +428,208 @@ def __init__(self): } def exitNode(self, node, data): - return self._operator_handles[node.__class__](node, *data) + return self._operator_handles[node.__class__](self, node, *data) + + +def number_to_letterStack(num): + alphabet = [ + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + ] def latex_printer( - pyomoElement, filename=None, useAlignEnvironment=False, splitContinuousSets=False + pyomoElement, + filename=None, + useAlignEnvironment=False, + splitContinuousSets=False, + disableSmartVariables=False, + xOnlyMode=0, + overwriteDict={}, ): - ''' - This function produces a string that can be rendered as LaTeX + """This function produces a string that can be rendered as LaTeX + + As described, this function produces a string that can be rendered as LaTeX - pyomoElement: The thing to be printed to LaTeX. Accepts Blocks (including models), Constraints, and Expressions + Parameters + ---------- + pyomoElement: _BlockData or Model or Constraint or Expression or Objective + The thing to be printed to LaTeX. Accepts Blocks (including models), Constraints, and Expressions - filename: An optional file to write the LaTeX to. Default of None produces no file + filename: str + An optional file to write the LaTeX to. Default of None produces no file - useAlignEnvironment: Default behavior uses equation/aligned and produces a single LaTeX Equation (ie, ==False). + useAlignEnvironment: bool + Default behavior uses equation/aligned and produces a single LaTeX Equation (ie, ==False). Setting this input to True will instead use the align environment, and produce equation numbers for each objective and constraint. Each objective and constraint will be labeled with its name in the pyomo model. This flag is only relevant for Models and Blocks. - splitContinuous: Default behavior has all sum indices be over "i \in I" or similar. Setting this flag to + splitContinuous: bool + Default behavior has all sum indices be over "i \in I" or similar. Setting this flag to True makes the sums go from: \sum_{i=1}^{5} if the set I is continuous and has 5 elements - ''' + Returns + ------- + str + A LaTeX string of the pyomoElement + + """ # Various setup things + + # is Single implies Objective, constraint, or expression + # these objects require a slight modification of behavior + # isSingle==False means a model or block isSingle = False - if not isinstance(pyomoElement, _BlockData): - if isinstance(pyomoElement, pe.Objective): - objectives = [pyomoElement] - constraints = [] - expressions = [] - templatize_fcn = templatize_constraint - - if isinstance(pyomoElement, pe.Constraint): - objectives = [] - constraints = [pyomoElement] - expressions = [] - templatize_fcn = templatize_constraint - - if isinstance(pyomoElement, pe.Expression): - objectives = [] - constraints = [] - expressions = [pyomoElement] - templatize_fcn = templatize_expression - - if isinstance(pyomoElement, ExpressionBase): - objectives = [] - constraints = [] - expressions = [pyomoElement] - templatize_fcn = templatize_passthrough + if isinstance(pyomoElement, pyo.Objective): + objectives = [pyomoElement] + constraints = [] + expressions = [] + templatize_fcn = templatize_constraint useAlignEnvironment = False isSingle = True - else: + + elif isinstance(pyomoElement, pyo.Constraint): + objectives = [] + constraints = [pyomoElement] + expressions = [] + templatize_fcn = templatize_constraint + useAlignEnvironment = False + isSingle = True + + elif isinstance(pyomoElement, pyo.Expression): + objectives = [] + constraints = [] + expressions = [pyomoElement] + templatize_fcn = templatize_expression + useAlignEnvironment = False + isSingle = True + + elif isinstance(pyomoElement, ExpressionBase): + objectives = [] + constraints = [] + expressions = [pyomoElement] + templatize_fcn = templatize_passthrough + useAlignEnvironment = False + isSingle = True + + elif isinstance(pyomoElement, _BlockData): objectives = [ obj for obj in pyomoElement.component_data_objects( - pe.Objective, descend_into=True, active=True + pyo.Objective, descend_into=True, active=True ) ] constraints = [ con for con in pyomoElement.component_objects( - pe.Constraint, descend_into=True, active=True + pyo.Constraint, descend_into=True, active=True ) ] expressions = [] templatize_fcn = templatize_constraint + else: + raise ValueError( + "Invalid type %s passed into the latex printer" % (str(type(pyomoElement))) + ) + # In the case where just a single expression is passed, add this to the constraint list for printing constraints = constraints + expressions # Declare a visitor/walker visitor = _LatexVisitor() + visitor.disableSmartVariables = disableSmartVariables + # visitor.xOnlyMode = xOnlyMode + + # # Only x modes + # # Mode 0 : dont use + # # Mode 1 : indexed variables become x_{_{ix}} + # # Mode 2 : uses standard alphabet [a,...,z,aa,...,az,...,aaa,...] with subscripts for indices, ex: abcd_{ix} + # # Mode 3 : unwrap everything into an x_{} list, WILL NOT WORK ON TEMPLATED CONSTRAINTS + + nameReplacementDict = {} + if not isSingle: + # only works if you can get the variables from a block + variableList = [ + vr + for vr in pyomoElement.component_objects( + pyo.Var, descend_into=True, active=True + ) + ] + if xOnlyMode == 1: + newVariableList = ['x' for i in range(0, len(variableList))] + for i in range(0, len(variableList)): + newVariableList[i] += '_' + str(i + 1) + overwriteDict = dict(zip([v.name for v in variableList], newVariableList)) + elif xOnlyMode == 2: + newVariableList = [ + alphabetStringGenerator(i) for i in range(0, len(variableList)) + ] + overwriteDict = dict(zip([v.name for v in variableList], newVariableList)) + elif xOnlyMode == 3: + newVariableList = ['x' for i in range(0, len(variableList))] + for i in range(0, len(variableList)): + newVariableList[i] += '_' + str(i + 1) + overwriteDict = dict(zip([v.name for v in variableList], newVariableList)) + + unwrappedVarCounter = 0 + wrappedVarCounter = 0 + for v in variableList: + setData = v.index_set().data() + if setData[0] is None: + unwrappedVarCounter += 1 + wrappedVarCounter += 1 + nameReplacementDict['x_{' + str(wrappedVarCounter) + '}'] = ( + 'x_{' + str(unwrappedVarCounter) + '}' + ) + else: + wrappedVarCounter += 1 + for dta in setData: + dta_str = str(dta) + if '(' not in dta_str: + dta_str = '(' + dta_str + ')' + subsetString = dta_str.replace('(', '{') + subsetString = subsetString.replace(')', '}') + subsetString = subsetString.replace(' ', '') + unwrappedVarCounter += 1 + nameReplacementDict[ + 'x_{' + str(wrappedVarCounter) + '_' + subsetString + '}' + ] = ('x_{' + str(unwrappedVarCounter) + '}') + # for ky, vl in nameReplacementDict.items(): + # print(ky,vl) + # print(nameReplacementDict) + else: + # default to the standard mode where pyomo names are used + overwriteDict = {} + + visitor.overwriteDict = overwriteDict # starts building the output string pstr = '' @@ -432,7 +666,14 @@ def latex_printer( if not isSingle: pstr += ' ' * tbSpc + '& \\text{subject to} \n' - # first constraint needs different alignment because of the 'subject to' + # first constraint needs different alignment because of the 'subject to': + # & minimize & & [Objective] + # & subject to & & [Constraint 1] + # & & & [Constraint 2] + # & & & [Constraint N] + + # The double '& &' renders better for some reason + for i in range(0, len(constraints)): if not isSingle: if i == 0: @@ -455,13 +696,16 @@ def latex_printer( # Multiple constraints are generated using a set if len(indices) > 0: - nm = indices[0]._set - gp = indices[0]._group - - ixTag = '__INDEX_PLACEHOLDER_8675309_GROUP_%s_%s__' % (gp, nm) - stTag = '__SET_PLACEHOLDER_8675309_GROUP_%s_%s__' % (gp, nm) - - conLine += ', \\quad %s \\in %s ' % (ixTag, stTag) + idxTag = '__INDEX_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( + indices[0]._group, + indices[0]._set, + ) + setTag = '__SET_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( + indices[0]._group, + indices[0]._set, + ) + + conLine += ', \\quad %s \\in %s ' % (idxTag, setTag) pstr += conLine # Add labels as needed @@ -615,5 +859,9 @@ def latex_printer( f.write(fstr) f.close() + # Catch up on only x mode 3 and replace + for ky, vl in nameReplacementDict.items(): + pstr = pstr.replace(ky, vl) + # return the latex string return pstr diff --git a/pyomo/util/tests/test_latex_printer.py b/pyomo/util/tests/test_latex_printer.py index c2e2036ccf2..ab2dbfb3c33 100644 --- a/pyomo/util/tests/test_latex_printer.py +++ b/pyomo/util/tests/test_latex_printer.py @@ -11,113 +11,115 @@ import pyomo.common.unittest as unittest from pyomo.util.latex_printer import latex_printer -import pyomo.environ as pe +import pyomo.environ as pyo +from textwrap import dedent +from pyomo.common.tempfiles import TempfileManager def generate_model(): - import pyomo.environ as pe + import pyomo.environ as pyo from pyomo.core.expr import Expr_if from pyomo.core.base import ExternalFunction - m = pe.ConcreteModel(name='basicFormulation') - m.x = pe.Var() - m.y = pe.Var() - m.z = pe.Var() - m.objective_1 = pe.Objective(expr=m.x + m.y + m.z) - m.constraint_1 = pe.Constraint( + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var() + m.y = pyo.Var() + m.z = pyo.Var() + m.objective_1 = pyo.Objective(expr=m.x + m.y + m.z) + m.constraint_1 = pyo.Constraint( expr=m.x**2 + m.y**-2.0 - m.x * m.y * m.z + 1 == 2.0 ) - m.constraint_2 = pe.Constraint(expr=abs(m.x / m.z**-2) * (m.x + m.y) <= 2.0) - m.constraint_3 = pe.Constraint(expr=pe.sqrt(m.x / m.z**-2) <= 2.0) - m.constraint_4 = pe.Constraint(expr=(1, m.x, 2)) - m.constraint_5 = pe.Constraint(expr=Expr_if(m.x <= 1.0, m.z, m.y) <= 1.0) + m.constraint_2 = pyo.Constraint(expr=abs(m.x / m.z**-2) * (m.x + m.y) <= 2.0) + m.constraint_3 = pyo.Constraint(expr=pyo.sqrt(m.x / m.z**-2) <= 2.0) + m.constraint_4 = pyo.Constraint(expr=(1, m.x, 2)) + m.constraint_5 = pyo.Constraint(expr=Expr_if(m.x <= 1.0, m.z, m.y) <= 1.0) def blackbox(a, b): return sin(a - b) m.bb = ExternalFunction(blackbox) - m.constraint_6 = pe.Constraint(expr=m.x + m.bb(m.x, m.y) == 2) + m.constraint_6 = pyo.Constraint(expr=m.x + m.bb(m.x, m.y) == 2) - m.I = pe.Set(initialize=[1, 2, 3, 4, 5]) - m.J = pe.Set(initialize=[1, 2, 3]) - m.K = pe.Set(initialize=[1, 3, 5]) - m.u = pe.Var(m.I * m.I) - m.v = pe.Var(m.I) - m.w = pe.Var(m.J) - m.p = pe.Var(m.K) + m.I = pyo.Set(initialize=[1, 2, 3, 4, 5]) + m.J = pyo.Set(initialize=[1, 2, 3]) + m.K = pyo.Set(initialize=[1, 3, 5]) + m.u = pyo.Var(m.I * m.I) + m.v = pyo.Var(m.I) + m.w = pyo.Var(m.J) + m.p = pyo.Var(m.K) - m.express = pe.Expression(expr=m.x**2 + m.y**2) + m.express = pyo.Expression(expr=m.x**2 + m.y**2) def ruleMaker(m, j): return (m.x + m.y) * sum(m.v[i] + m.u[i, j] ** 2 for i in m.I) <= 0 - m.constraint_7 = pe.Constraint(m.I, rule=ruleMaker) + m.constraint_7 = pyo.Constraint(m.I, rule=ruleMaker) def ruleMaker(m): return sum(m.p[k] for k in m.K) == 1 - m.constraint_8 = pe.Constraint(rule=ruleMaker) + m.constraint_8 = pyo.Constraint(rule=ruleMaker) def ruleMaker(m): return (m.x + m.y) * sum(m.w[j] for j in m.J) - m.objective_2 = pe.Objective(rule=ruleMaker) + m.objective_2 = pyo.Objective(rule=ruleMaker) - m.objective_3 = pe.Objective(expr=m.x + m.y + m.z, sense=-1) + m.objective_3 = pyo.Objective(expr=m.x + m.y + m.z, sense=-1) return m def generate_simple_model(): - import pyomo.environ as pe - - m = pe.ConcreteModel(name='basicFormulation') - m.x = pe.Var() - m.y = pe.Var() - m.objective_1 = pe.Objective(expr=m.x + m.y) - m.constraint_1 = pe.Constraint(expr=m.x**2 + m.y**2.0 <= 1.0) - m.constraint_2 = pe.Constraint(expr=m.x >= 0.0) - - m.I = pe.Set(initialize=[1, 2, 3, 4, 5]) - m.J = pe.Set(initialize=[1, 2, 3]) - m.K = pe.Set(initialize=[1, 3, 5]) - m.u = pe.Var(m.I * m.I) - m.v = pe.Var(m.I) - m.w = pe.Var(m.J) - m.p = pe.Var(m.K) + import pyomo.environ as pyo + + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var() + m.y = pyo.Var() + m.objective_1 = pyo.Objective(expr=m.x + m.y) + m.constraint_1 = pyo.Constraint(expr=m.x**2 + m.y**2.0 <= 1.0) + m.constraint_2 = pyo.Constraint(expr=m.x >= 0.0) + + m.I = pyo.Set(initialize=[1, 2, 3, 4, 5]) + m.J = pyo.Set(initialize=[1, 2, 3]) + m.K = pyo.Set(initialize=[1, 3, 5]) + m.u = pyo.Var(m.I * m.I) + m.v = pyo.Var(m.I) + m.w = pyo.Var(m.J) + m.p = pyo.Var(m.K) def ruleMaker(m, j): return (m.x + m.y) * sum(m.v[i] + m.u[i, j] ** 2 for i in m.I) <= 0 - m.constraint_7 = pe.Constraint(m.I, rule=ruleMaker) + m.constraint_7 = pyo.Constraint(m.I, rule=ruleMaker) def ruleMaker(m): return sum(m.p[k] for k in m.K) == 1 - m.constraint_8 = pe.Constraint(rule=ruleMaker) + m.constraint_8 = pyo.Constraint(rule=ruleMaker) return m def generate_simple_model_2(): - import pyomo.environ as pe - - m = pe.ConcreteModel(name='basicFormulation') - m.x_dot = pe.Var() - m.x_bar = pe.Var() - m.x_star = pe.Var() - m.x_hat = pe.Var() - m.x_hat_1 = pe.Var() - m.y_sub1_sub2_sub3 = pe.Var() - m.objective_1 = pe.Objective(expr=m.y_sub1_sub2_sub3) - m.constraint_1 = pe.Constraint( + import pyomo.environ as pyo + + m = pyo.ConcreteModel(name='basicFormulation') + m.x_dot = pyo.Var() + m.x_bar = pyo.Var() + m.x_star = pyo.Var() + m.x_hat = pyo.Var() + m.x_hat_1 = pyo.Var() + m.y_sub1_sub2_sub3 = pyo.Var() + m.objective_1 = pyo.Objective(expr=m.y_sub1_sub2_sub3) + m.constraint_1 = pyo.Constraint( expr=(m.x_dot + m.x_bar + m.x_star + m.x_hat + m.x_hat_1) ** 2 <= m.y_sub1_sub2_sub3 ) - m.constraint_2 = pe.Constraint( + m.constraint_2 = pyo.Constraint( expr=(m.x_dot + m.x_bar) ** -(m.x_star + m.x_hat) <= m.y_sub1_sub2_sub3 ) - m.constraint_3 = pe.Constraint( + m.constraint_3 = pyo.Constraint( expr=-(m.x_dot + m.x_bar) + -(m.x_star + m.x_hat) <= m.y_sub1_sub2_sub3 ) @@ -128,221 +130,299 @@ class TestLatexPrinter(unittest.TestCase): def test_latexPrinter_objective(self): m = generate_model() pstr = latex_printer(m.objective_1) - bstr = '' - bstr += '\\begin{equation} \n' - bstr += ' & \\text{minimize} \n' - bstr += ' & & x + y + z \n' - bstr += '\end{equation} \n' - self.assertEqual(pstr, bstr) + bstr = dedent( + r""" + \begin{equation} + & \text{minimize} + & & x + y + z + \end{equation} + """ + ) + self.assertEqual('\n' + pstr, bstr) pstr = latex_printer(m.objective_3) - bstr = '' - bstr += '\\begin{equation} \n' - bstr += ' & \\text{maximize} \n' - bstr += ' & & x + y + z \n' - bstr += '\end{equation} \n' - self.assertEqual(pstr, bstr) + bstr = dedent( + r""" + \begin{equation} + & \text{maximize} + & & x + y + z + \end{equation} + """ + ) + self.assertEqual('\n' + pstr, bstr) def test_latexPrinter_constraint(self): m = generate_model() pstr = latex_printer(m.constraint_1) - bstr = '' - bstr += '\\begin{equation} \n' - bstr += ' x^{2} + y^{-2} - xyz + 1 = 2 \n' - bstr += '\end{equation} \n' + bstr = dedent( + r""" + \begin{equation} + x^{2} + y^{-2} - x y z + 1 = 2 + \end{equation} + """ + ) - self.assertEqual(pstr, bstr) + self.assertEqual('\n' + pstr, bstr) def test_latexPrinter_expression(self): m = generate_model() - m.express = pe.Expression(expr=m.x + m.y) + m.express = pyo.Expression(expr=m.x + m.y) pstr = latex_printer(m.express) - bstr = '' - bstr += '\\begin{equation} \n' - bstr += ' x + y \n' - bstr += '\end{equation} \n' + bstr = dedent( + r""" + \begin{equation} + x + y + \end{equation} + """ + ) - self.assertEqual(pstr, bstr) + self.assertEqual('\n' + pstr, bstr) def test_latexPrinter_simpleExpression(self): m = generate_model() pstr = latex_printer(m.x - m.y) - bstr = '' - bstr += '\\begin{equation} \n' - bstr += ' x - y \n' - bstr += '\end{equation} \n' - self.assertEqual(pstr, bstr) + bstr = dedent( + r""" + \begin{equation} + x - y + \end{equation} + """ + ) + self.assertEqual('\n' + pstr, bstr) pstr = latex_printer(m.x - 2 * m.y) - bstr = '' - bstr += '\\begin{equation} \n' - bstr += ' x - 2y \n' - bstr += '\end{equation} \n' - self.assertEqual(pstr, bstr) + bstr = dedent( + r""" + \begin{equation} + x - 2 y + \end{equation} + """ + ) + self.assertEqual('\n' + pstr, bstr) def test_latexPrinter_unary(self): m = generate_model() pstr = latex_printer(m.constraint_2) - bstr = '' - bstr += '\\begin{equation} \n' - bstr += ( - ' \left| \\frac{x}{z^{-2}} \\right| \left( x + y \\right) \leq 2 \n' + bstr = dedent( + r""" + \begin{equation} + \left| \frac{x}{z^{-2}} \right| \left( x + y \right) \leq 2 + \end{equation} + """ + ) + self.assertEqual('\n' + pstr, bstr) + + pstr = latex_printer(pyo.Constraint(expr=pyo.sin(m.x) == 1)) + bstr = dedent( + r""" + \begin{equation} + \sin \left( x \right) = 1 + \end{equation} + """ ) - bstr += '\end{equation} \n' - self.assertEqual(pstr, bstr) - - pstr = latex_printer(pe.Constraint(expr=pe.sin(m.x) == 1)) - bstr = '' - bstr += '\\begin{equation} \n' - bstr += ' \sin \left( x \\right) = 1 \n' - bstr += '\end{equation} \n' - self.assertEqual(pstr, bstr) - - pstr = latex_printer(pe.Constraint(expr=pe.log10(m.x) == 1)) - bstr = '' - bstr += '\\begin{equation} \n' - bstr += ' \log_{10} \left( x \\right) = 1 \n' - bstr += '\end{equation} \n' - self.assertEqual(pstr, bstr) - - pstr = latex_printer(pe.Constraint(expr=pe.sqrt(m.x) == 1)) - bstr = '' - bstr += '\\begin{equation} \n' - bstr += ' \sqrt { x } = 1 \n' - bstr += '\end{equation} \n' - self.assertEqual(pstr, bstr) + self.assertEqual('\n' + pstr, bstr) + + pstr = latex_printer(pyo.Constraint(expr=pyo.log10(m.x) == 1)) + bstr = dedent( + r""" + \begin{equation} + \log_{10} \left( x \right) = 1 + \end{equation} + """ + ) + self.assertEqual('\n' + pstr, bstr) + + pstr = latex_printer(pyo.Constraint(expr=pyo.sqrt(m.x) == 1)) + bstr = dedent( + r""" + \begin{equation} + \sqrt { x } = 1 + \end{equation} + """ + ) + self.assertEqual('\n' + pstr, bstr) def test_latexPrinter_rangedConstraint(self): m = generate_model() pstr = latex_printer(m.constraint_4) - bstr = '' - bstr += '\\begin{equation} \n' - bstr += ' 1 \leq x \leq 2 \n' - bstr += '\end{equation} \n' - self.assertEqual(pstr, bstr) + bstr = dedent( + r""" + \begin{equation} + 1 \leq x \leq 2 + \end{equation} + """ + ) + self.assertEqual('\n' + pstr, bstr) def test_latexPrinter_exprIf(self): m = generate_model() pstr = latex_printer(m.constraint_5) - bstr = '' - bstr += '\\begin{equation} \n' - bstr += ' f_{\\text{exprIf}}(x \leq 1,z,y) \leq 1 \n' - bstr += '\end{equation} \n' - self.assertEqual(pstr, bstr) + bstr = dedent( + r""" + \begin{equation} + f_{\text{exprIf}}(x \leq 1,z,y) \leq 1 + \end{equation} + """ + ) + self.assertEqual('\n' + pstr, bstr) def test_latexPrinter_blackBox(self): m = generate_model() pstr = latex_printer(m.constraint_6) - bstr = '' - bstr += '\\begin{equation} \n' - bstr += ' x + f(x,y) = 2 \n' - bstr += '\end{equation} \n' - self.assertEqual(pstr, bstr) + bstr = dedent( + r""" + \begin{equation} + x + f(x,y) = 2 + \end{equation} + """ + ) + self.assertEqual('\n' + pstr, bstr) def test_latexPrinter_iteratedConstraints(self): m = generate_model() pstr = latex_printer(m.constraint_7) - bstr = '' - bstr += '\\begin{equation} \n' - bstr += ' \left( x + y \\right) \sum_{i \in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I \n' - bstr += '\end{equation} \n' - self.assertEqual(pstr, bstr) + bstr = dedent( + r""" + \begin{equation} + \left( x + y \right) \sum_{i \in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I + \end{equation} + """ + ) + self.assertEqual('\n' + pstr, bstr) pstr = latex_printer(m.constraint_8) - bstr = '' - bstr += '\\begin{equation} \n' - bstr += ' \sum_{k \in K} p_{k} = 1 \n' - bstr += '\end{equation} \n' - self.assertEqual(pstr, bstr) + bstr = dedent( + r""" + \begin{equation} + \sum_{k \in K} p_{k} = 1 + \end{equation} + """ + ) + self.assertEqual('\n' + pstr, bstr) def test_latexPrinter_model(self): m = generate_simple_model() pstr = latex_printer(m) - bstr = '' - bstr += '\\begin{equation} \n' - bstr += ' \\begin{aligned} \n' - bstr += ' & \\text{minimize} \n' - bstr += ' & & x + y \\\\ \n' - bstr += ' & \\text{subject to} \n' - bstr += ' & & x^{2} + y^{2} \leq 1 \\\\ \n' - bstr += ' &&& 0 \leq x \\\\ \n' - bstr += ' &&& \left( x + y \\right) \sum_{i \\in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \\in I \\\\ \n' - bstr += ' &&& \sum_{k \\in K} p_{k} = 1 \n' - bstr += ' \end{aligned} \n' - bstr += ' \label{basicFormulation} \n' - bstr += '\end{equation} \n' - self.assertEqual(pstr, bstr) + bstr = dedent( + r""" + \begin{equation} + \begin{aligned} + & \text{minimize} + & & x + y \\ + & \text{subject to} + & & x^{2} + y^{2} \leq 1 \\ + &&& 0 \leq x \\ + &&& \left( x + y \right) \sum_{i \in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I \\ + &&& \sum_{k \in K} p_{k} = 1 + \end{aligned} + \label{basicFormulation} + \end{equation} + """ + ) + self.assertEqual('\n' + pstr, bstr) pstr = latex_printer(m, None, True) - bstr = '' - bstr += '\\begin{align} \n' - bstr += ' & \\text{minimize} \n' - bstr += ' & & x + y \label{obj:basicFormulation_objective_1} \\\\ \n' - bstr += ' & \\text{subject to} \n' - bstr += ' & & x^{2} + y^{2} \leq 1 \label{con:basicFormulation_constraint_1} \\\\ \n' - bstr += ' &&& 0 \leq x \label{con:basicFormulation_constraint_2} \\\\ \n' - bstr += ' &&& \left( x + y \\right) \sum_{i \\in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \\in I \label{con:basicFormulation_constraint_7} \\\\ \n' - bstr += ' &&& \sum_{k \\in K} p_{k} = 1 \label{con:basicFormulation_constraint_8} \n' - bstr += '\end{align} \n' - self.assertEqual(pstr, bstr) + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x + y \label{obj:basicFormulation_objective_1} \\ + & \text{subject to} + & & x^{2} + y^{2} \leq 1 \label{con:basicFormulation_constraint_1} \\ + &&& 0 \leq x \label{con:basicFormulation_constraint_2} \\ + &&& \left( x + y \right) \sum_{i \in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I \label{con:basicFormulation_constraint_7} \\ + &&& \sum_{k \in K} p_{k} = 1 \label{con:basicFormulation_constraint_8} + \end{align} + """ + ) + self.assertEqual('\n' + pstr, bstr) pstr = latex_printer(m, None, False, True) - bstr = '' - bstr += '\\begin{equation} \n' - bstr += ' \\begin{aligned} \n' - bstr += ' & \\text{minimize} \n' - bstr += ' & & x + y \\\\ \n' - bstr += ' & \\text{subject to} \n' - bstr += ' & & x^{2} + y^{2} \leq 1 \\\\ \n' - bstr += ' &&& 0 \leq x \\\\ \n' - bstr += ' &&& \left( x + y \\right) \sum_{i = 1}^{5} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \\in I \\\\ \n' - bstr += ' &&& \sum_{k \\in K} p_{k} = 1 \n' - bstr += ' \end{aligned} \n' - bstr += ' \label{basicFormulation} \n' - bstr += '\end{equation} \n' - self.assertEqual(pstr, bstr) + bstr = dedent( + r""" + \begin{equation} + \begin{aligned} + & \text{minimize} + & & x + y \\ + & \text{subject to} + & & x^{2} + y^{2} \leq 1 \\ + &&& 0 \leq x \\ + &&& \left( x + y \right) \sum_{i = 1}^{5} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I \\ + &&& \sum_{k \in K} p_{k} = 1 + \end{aligned} + \label{basicFormulation} + \end{equation} + """ + ) + self.assertEqual('\n' + pstr, bstr) pstr = latex_printer(m, None, True, True) - bstr = '' - bstr += '\\begin{align} \n' - bstr += ' & \\text{minimize} \n' - bstr += ' & & x + y \label{obj:basicFormulation_objective_1} \\\\ \n' - bstr += ' & \\text{subject to} \n' - bstr += ' & & x^{2} + y^{2} \leq 1 \label{con:basicFormulation_constraint_1} \\\\ \n' - bstr += ' &&& 0 \leq x \label{con:basicFormulation_constraint_2} \\\\ \n' - bstr += ' &&& \left( x + y \\right) \sum_{i = 1}^{5} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \\in I \label{con:basicFormulation_constraint_7} \\\\ \n' - bstr += ' &&& \sum_{k \in K} p_{k} = 1 \label{con:basicFormulation_constraint_8} \n' - bstr += '\end{align} \n' - self.assertEqual(pstr, bstr) + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x + y \label{obj:basicFormulation_objective_1} \\ + & \text{subject to} + & & x^{2} + y^{2} \leq 1 \label{con:basicFormulation_constraint_1} \\ + &&& 0 \leq x \label{con:basicFormulation_constraint_2} \\ + &&& \left( x + y \right) \sum_{i = 1}^{5} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I \label{con:basicFormulation_constraint_7} \\ + &&& \sum_{k \in K} p_{k} = 1 \label{con:basicFormulation_constraint_8} + \end{align} + """ + ) + self.assertEqual('\n' + pstr, bstr) def test_latexPrinter_advancedVariables(self): m = generate_simple_model_2() pstr = latex_printer(m) - bstr = '' - bstr += '\\begin{equation} \n' - bstr += ' \\begin{aligned} \n' - bstr += ' & \\text{minimize} \n' - bstr += ' & & y_{sub1_{sub2_{sub3}}} \\\\ \n' - bstr += ' & \\text{subject to} \n' - bstr += ' & & \left( \dot{x} + \\bar{x} + x_{star} + \hat{x} + \hat{x}_{1} \\right) ^{2} \leq y_{sub1_{sub2_{sub3}}} \\\\ \n' - bstr += ' &&& \left( \dot{x} + \\bar{x} \\right) ^{ \left( - \left( x_{star} + \hat{x} \\right) \\right) } \leq y_{sub1_{sub2_{sub3}}} \\\\ \n' - bstr += ' &&& - \left( \dot{x} + \\bar{x} \\right) - \left( x_{star} + \hat{x} \\right) \leq y_{sub1_{sub2_{sub3}}} \n' - bstr += ' \\end{aligned} \n' - bstr += ' \label{basicFormulation} \n' - bstr += '\\end{equation} \n' - self.assertEqual(pstr, bstr) + bstr = dedent( + r""" + \begin{equation} + \begin{aligned} + & \text{minimize} + & & y_{sub1_{sub2_{sub3}}} \\ + & \text{subject to} + & & \left( \dot{x} + \bar{x} + x_{star} + \hat{x} + \hat{x}_{1} \right) ^{2} \leq y_{sub1_{sub2_{sub3}}} \\ + &&& \left( \dot{x} + \bar{x} \right) ^{ \left( - \left( x_{star} + \hat{x} \right) \right) } \leq y_{sub1_{sub2_{sub3}}} \\ + &&& - \left( \dot{x} + \bar{x} \right) - \left( x_{star} + \hat{x} \right) \leq y_{sub1_{sub2_{sub3}}} + \end{aligned} + \label{basicFormulation} + \end{equation} + """ + ) + self.assertEqual('\n' + pstr, bstr) + + def test_latexPrinter_fileWriter(self): + m = generate_simple_model() + + with TempfileManager.new_context() as tempfile: + fd, fname = tempfile.mkstemp() + pstr = latex_printer(m, fname) + + f = open(fname) + bstr = f.read() + f.close() + + bstr_split = bstr.split('\n') + bstr_stripped = bstr_split[3:-2] + bstr = '\n'.join(bstr_stripped) + '\n' + + self.assertEqual(pstr, bstr) + + def test_latexPrinter_inputError(self): + self.assertRaises(ValueError, latex_printer, **{'pyomoElement': 'errorString'}) if __name__ == '__main__': From 79b95d646141960bced1b89db45548972b96c0a3 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Tue, 5 Sep 2023 22:17:05 -0600 Subject: [PATCH 0093/1204] adding an exception --- pyomo/util/latex_printer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index 5d3c0943e79..a90f380a5a2 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -754,6 +754,10 @@ def latex_printer( ln = latexLines[jj] # only modify if there is a placeholder in the line if "PLACEHOLDER_8675309_GROUP_" in ln: + if xOnlyMode == 2: + raise RuntimeError( + 'Unwrapping indexed variables when an indexed constraint is present yields incorrect results' + ) splitLatex = ln.split('__') # Find the unique combinations of group numbers and set names for word in splitLatex: From 5ec0858a87ce15590a0f4860222e0fe093df7b8d Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Wed, 6 Sep 2023 09:53:12 -0600 Subject: [PATCH 0094/1204] minor changes --- pyomo/util/latex_printer.py | 10 +++--- pyomo/util/tests/test_latex_printer.py | 45 +++++++++++++------------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index a90f380a5a2..4a652b5dadd 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -216,8 +216,6 @@ def handle_inequality_node(visitor, node, arg1, arg2): def handle_var_node(visitor, node): - # if self.disableSmartVariables: - # if self.xOnlyMode: overwriteDict = visitor.overwriteDict # varList = visitor.variableList @@ -239,7 +237,7 @@ def handle_var_node(visitor, node): if name in overwriteDict.keys(): name = overwriteDict[name] - if not visitor.disableSmartVariables: + if visitor.useSmartVariables: splitName = name.split('_') if declaredIndex is not None: splitName.append(declaredIndex) @@ -390,7 +388,7 @@ def handle_templateSumExpression_node(visitor, node, *args): class _LatexVisitor(StreamBasedExpressionVisitor): def __init__(self): super().__init__() - self.disableSmartVariables = False + self.useSmartVariables = False self.xOnlyMode = False self.overwriteDict = {} @@ -467,7 +465,7 @@ def latex_printer( filename=None, useAlignEnvironment=False, splitContinuousSets=False, - disableSmartVariables=False, + useSmartVariables=False, xOnlyMode=0, overwriteDict={}, ): @@ -565,7 +563,7 @@ def latex_printer( # Declare a visitor/walker visitor = _LatexVisitor() - visitor.disableSmartVariables = disableSmartVariables + visitor.useSmartVariables = useSmartVariables # visitor.xOnlyMode = xOnlyMode # # Only x modes diff --git a/pyomo/util/tests/test_latex_printer.py b/pyomo/util/tests/test_latex_printer.py index ab2dbfb3c33..fdb1abf57f1 100644 --- a/pyomo/util/tests/test_latex_printer.py +++ b/pyomo/util/tests/test_latex_printer.py @@ -131,7 +131,7 @@ def test_latexPrinter_objective(self): m = generate_model() pstr = latex_printer(m.objective_1) bstr = dedent( - r""" + r""" \begin{equation} & \text{minimize} & & x + y + z @@ -142,7 +142,7 @@ def test_latexPrinter_objective(self): pstr = latex_printer(m.objective_3) bstr = dedent( - r""" + r""" \begin{equation} & \text{maximize} & & x + y + z @@ -156,7 +156,7 @@ def test_latexPrinter_constraint(self): pstr = latex_printer(m.constraint_1) bstr = dedent( - r""" + r""" \begin{equation} x^{2} + y^{-2} - x y z + 1 = 2 \end{equation} @@ -173,7 +173,7 @@ def test_latexPrinter_expression(self): pstr = latex_printer(m.express) bstr = dedent( - r""" + r""" \begin{equation} x + y \end{equation} @@ -187,7 +187,7 @@ def test_latexPrinter_simpleExpression(self): pstr = latex_printer(m.x - m.y) bstr = dedent( - r""" + r""" \begin{equation} x - y \end{equation} @@ -197,7 +197,7 @@ def test_latexPrinter_simpleExpression(self): pstr = latex_printer(m.x - 2 * m.y) bstr = dedent( - r""" + r""" \begin{equation} x - 2 y \end{equation} @@ -210,7 +210,7 @@ def test_latexPrinter_unary(self): pstr = latex_printer(m.constraint_2) bstr = dedent( - r""" + r""" \begin{equation} \left| \frac{x}{z^{-2}} \right| \left( x + y \right) \leq 2 \end{equation} @@ -220,7 +220,7 @@ def test_latexPrinter_unary(self): pstr = latex_printer(pyo.Constraint(expr=pyo.sin(m.x) == 1)) bstr = dedent( - r""" + r""" \begin{equation} \sin \left( x \right) = 1 \end{equation} @@ -230,7 +230,7 @@ def test_latexPrinter_unary(self): pstr = latex_printer(pyo.Constraint(expr=pyo.log10(m.x) == 1)) bstr = dedent( - r""" + r""" \begin{equation} \log_{10} \left( x \right) = 1 \end{equation} @@ -240,7 +240,7 @@ def test_latexPrinter_unary(self): pstr = latex_printer(pyo.Constraint(expr=pyo.sqrt(m.x) == 1)) bstr = dedent( - r""" + r""" \begin{equation} \sqrt { x } = 1 \end{equation} @@ -253,7 +253,7 @@ def test_latexPrinter_rangedConstraint(self): pstr = latex_printer(m.constraint_4) bstr = dedent( - r""" + r""" \begin{equation} 1 \leq x \leq 2 \end{equation} @@ -266,7 +266,7 @@ def test_latexPrinter_exprIf(self): pstr = latex_printer(m.constraint_5) bstr = dedent( - r""" + r""" \begin{equation} f_{\text{exprIf}}(x \leq 1,z,y) \leq 1 \end{equation} @@ -279,7 +279,7 @@ def test_latexPrinter_blackBox(self): pstr = latex_printer(m.constraint_6) bstr = dedent( - r""" + r""" \begin{equation} x + f(x,y) = 2 \end{equation} @@ -292,7 +292,7 @@ def test_latexPrinter_iteratedConstraints(self): pstr = latex_printer(m.constraint_7) bstr = dedent( - r""" + r""" \begin{equation} \left( x + y \right) \sum_{i \in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I \end{equation} @@ -302,7 +302,7 @@ def test_latexPrinter_iteratedConstraints(self): pstr = latex_printer(m.constraint_8) bstr = dedent( - r""" + r""" \begin{equation} \sum_{k \in K} p_{k} = 1 \end{equation} @@ -315,7 +315,7 @@ def test_latexPrinter_model(self): pstr = latex_printer(m) bstr = dedent( - r""" + r""" \begin{equation} \begin{aligned} & \text{minimize} @@ -334,7 +334,7 @@ def test_latexPrinter_model(self): pstr = latex_printer(m, None, True) bstr = dedent( - r""" + r""" \begin{align} & \text{minimize} & & x + y \label{obj:basicFormulation_objective_1} \\ @@ -350,7 +350,7 @@ def test_latexPrinter_model(self): pstr = latex_printer(m, None, False, True) bstr = dedent( - r""" + r""" \begin{equation} \begin{aligned} & \text{minimize} @@ -369,7 +369,7 @@ def test_latexPrinter_model(self): pstr = latex_printer(m, None, True, True) bstr = dedent( - r""" + r""" \begin{align} & \text{minimize} & & x + y \label{obj:basicFormulation_objective_1} \\ @@ -386,9 +386,9 @@ def test_latexPrinter_model(self): def test_latexPrinter_advancedVariables(self): m = generate_simple_model_2() - pstr = latex_printer(m) + pstr = latex_printer(m,useSmartVariables=True) bstr = dedent( - r""" + r""" \begin{equation} \begin{aligned} & \text{minimize} @@ -400,8 +400,7 @@ def test_latexPrinter_advancedVariables(self): \end{aligned} \label{basicFormulation} \end{equation} - """ - ) + """) self.assertEqual('\n' + pstr, bstr) def test_latexPrinter_fileWriter(self): From 7e15c954f4ecfeacb84a543b394b624a2ef5edb3 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Wed, 6 Sep 2023 19:13:38 -0600 Subject: [PATCH 0095/1204] working on new features --- .gitignore | 5 +- pyomo/util/latex_printer.py | 382 ++++++++++++++++++++----- pyomo/util/tests/test_latex_printer.py | 69 ++--- 3 files changed, 356 insertions(+), 100 deletions(-) diff --git a/.gitignore b/.gitignore index 09069552990..638dc70d13e 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,7 @@ gurobi.log # Jupyterhub/Jupyterlab checkpoints .ipynb_checkpoints -cplex.log \ No newline at end of file +cplex.log + +# Mac tracking files +*.DS_Store* diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index 4a652b5dadd..dc73930cbeb 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -52,6 +52,8 @@ from pyomo.repn.util import ExprType +from pyomo.common import DeveloperError + _CONSTANT = ExprType.CONSTANT _MONOMIAL = ExprType.MONOMIAL _GENERAL = ExprType.GENERAL @@ -159,7 +161,7 @@ def precedenceChecker(node, arg1, arg2=None): precedence = node.PRECEDENCE else: # Should never hit this - raise RuntimeError( + raise DeveloperError( 'This error should never be thrown, node does not have a precedence. Report to developers' ) @@ -212,11 +214,11 @@ def handle_equality_node(visitor, node, arg1, arg2): def handle_inequality_node(visitor, node, arg1, arg2): - return arg1 + ' \leq ' + arg2 + return arg1 + ' \\leq ' + arg2 def handle_var_node(visitor, node): - overwriteDict = visitor.overwriteDict + overwrite_dict = visitor.overwrite_dict # varList = visitor.variableList name = node.name @@ -234,10 +236,10 @@ def handle_var_node(visitor, node): declaredIndex = name[openBracketIndex + 1 : closeBracketIndex] name = name[0:openBracketIndex] - if name in overwriteDict.keys(): - name = overwriteDict[name] + if name in overwrite_dict.keys(): + name = overwrite_dict[name] - if visitor.useSmartVariables: + if visitor.use_smart_variables: splitName = name.split('_') if declaredIndex is not None: splitName.append(declaredIndex) @@ -250,10 +252,10 @@ def handle_var_node(visitor, node): se = splitName[i] if se != 0: if se == 'dot': - prfx = '\dot{' + prfx = '\\dot{' psfx = '}' elif se == 'hat': - prfx = '\hat{' + prfx = '\\hat{' psfx = '}' elif se == 'bar': prfx = '\\bar{' @@ -317,6 +319,8 @@ def handle_ranged_inequality_node(visitor, node, arg1, arg2, arg3): def handle_exprif_node(visitor, node, arg1, arg2, arg3): return 'f_{\\text{exprIf}}(' + arg1 + ',' + arg2 + ',' + arg3 + ')' + ## Could be handled in the future using cases or similar + ## Raises not implemented error # raise NotImplementedError('Expr_if objects not supported by the Latex Printer') @@ -388,9 +392,9 @@ def handle_templateSumExpression_node(visitor, node, *args): class _LatexVisitor(StreamBasedExpressionVisitor): def __init__(self): super().__init__() - self.useSmartVariables = False - self.xOnlyMode = False - self.overwriteDict = {} + self.use_smart_variables = False + self.x_only_mode = False + self.overwrite_dict = {} self._operator_handles = { ScalarVar: handle_var_node, @@ -461,13 +465,15 @@ def number_to_letterStack(num): def latex_printer( - pyomoElement, + pyomo_component, filename=None, - useAlignEnvironment=False, - splitContinuousSets=False, - useSmartVariables=False, - xOnlyMode=0, - overwriteDict={}, + use_align_environment=False, + split_continuous_sets=False, + use_smart_variables=False, + x_only_mode=0, + use_short_descriptors=False, + use_forall=False, + overwrite_dict={}, ): """This function produces a string that can be rendered as LaTeX @@ -475,13 +481,13 @@ def latex_printer( Parameters ---------- - pyomoElement: _BlockData or Model or Constraint or Expression or Objective + pyomo_component: _BlockData or Model or Constraint or Expression or Objective The thing to be printed to LaTeX. Accepts Blocks (including models), Constraints, and Expressions filename: str An optional file to write the LaTeX to. Default of None produces no file - useAlignEnvironment: bool + use_align_environment: bool Default behavior uses equation/aligned and produces a single LaTeX Equation (ie, ==False). Setting this input to True will instead use the align environment, and produce equation numbers for each objective and constraint. Each objective and constraint will be labeled with its name in the pyomo model. @@ -494,7 +500,7 @@ def latex_printer( Returns ------- str - A LaTeX string of the pyomoElement + A LaTeX string of the pyomo_component """ @@ -505,48 +511,48 @@ def latex_printer( # isSingle==False means a model or block isSingle = False - if isinstance(pyomoElement, pyo.Objective): - objectives = [pyomoElement] + if isinstance(pyomo_component, pyo.Objective): + objectives = [pyomo_component] constraints = [] expressions = [] templatize_fcn = templatize_constraint - useAlignEnvironment = False + use_align_environment = False isSingle = True - elif isinstance(pyomoElement, pyo.Constraint): + elif isinstance(pyomo_component, pyo.Constraint): objectives = [] - constraints = [pyomoElement] + constraints = [pyomo_component] expressions = [] templatize_fcn = templatize_constraint - useAlignEnvironment = False + use_align_environment = False isSingle = True - elif isinstance(pyomoElement, pyo.Expression): + elif isinstance(pyomo_component, pyo.Expression): objectives = [] constraints = [] - expressions = [pyomoElement] + expressions = [pyomo_component] templatize_fcn = templatize_expression - useAlignEnvironment = False + use_align_environment = False isSingle = True - elif isinstance(pyomoElement, ExpressionBase): + elif isinstance(pyomo_component, (ExpressionBase, pyo.Var)): objectives = [] constraints = [] - expressions = [pyomoElement] + expressions = [pyomo_component] templatize_fcn = templatize_passthrough - useAlignEnvironment = False + use_align_environment = False isSingle = True - elif isinstance(pyomoElement, _BlockData): + elif isinstance(pyomo_component, _BlockData): objectives = [ obj - for obj in pyomoElement.component_data_objects( + for obj in pyomo_component.component_data_objects( pyo.Objective, descend_into=True, active=True ) ] constraints = [ con - for con in pyomoElement.component_objects( + for con in pyomo_component.component_objects( pyo.Constraint, descend_into=True, active=True ) ] @@ -555,16 +561,32 @@ def latex_printer( else: raise ValueError( - "Invalid type %s passed into the latex printer" % (str(type(pyomoElement))) + "Invalid type %s passed into the latex printer" + % (str(type(pyomo_component))) ) + if use_forall: + forallTag = ' \\forall' + else: + forallTag = ', \\quad' + + descriptorDict = {} + if use_short_descriptors: + descriptorDict['minimize'] = '\\min' + descriptorDict['maximize'] = '\\max' + descriptorDict['subject to'] = '\\text{s.t.}' + else: + descriptorDict['minimize'] = '\\text{minimize}' + descriptorDict['maximize'] = '\\text{maximize}' + descriptorDict['subject to'] = '\\text{subject to}' + # In the case where just a single expression is passed, add this to the constraint list for printing constraints = constraints + expressions # Declare a visitor/walker visitor = _LatexVisitor() - visitor.useSmartVariables = useSmartVariables - # visitor.xOnlyMode = xOnlyMode + visitor.use_smart_variables = use_smart_variables + # visitor.x_only_mode = x_only_mode # # Only x modes # # Mode 0 : dont use @@ -577,25 +599,25 @@ def latex_printer( # only works if you can get the variables from a block variableList = [ vr - for vr in pyomoElement.component_objects( + for vr in pyomo_component.component_objects( pyo.Var, descend_into=True, active=True ) ] - if xOnlyMode == 1: + if x_only_mode == 1: newVariableList = ['x' for i in range(0, len(variableList))] for i in range(0, len(variableList)): newVariableList[i] += '_' + str(i + 1) - overwriteDict = dict(zip([v.name for v in variableList], newVariableList)) - elif xOnlyMode == 2: + overwrite_dict = dict(zip([v.name for v in variableList], newVariableList)) + elif x_only_mode == 2: newVariableList = [ alphabetStringGenerator(i) for i in range(0, len(variableList)) ] - overwriteDict = dict(zip([v.name for v in variableList], newVariableList)) - elif xOnlyMode == 3: + overwrite_dict = dict(zip([v.name for v in variableList], newVariableList)) + elif x_only_mode == 3: newVariableList = ['x' for i in range(0, len(variableList))] for i in range(0, len(variableList)): newVariableList[i] += '_' + str(i + 1) - overwriteDict = dict(zip([v.name for v in variableList], newVariableList)) + overwrite_dict = dict(zip([v.name for v in variableList], newVariableList)) unwrappedVarCounter = 0 wrappedVarCounter = 0 @@ -625,15 +647,16 @@ def latex_printer( # print(nameReplacementDict) else: # default to the standard mode where pyomo names are used - overwriteDict = {} + overwrite_dict = {} - visitor.overwriteDict = overwriteDict + visitor.overwrite_dict = overwrite_dict # starts building the output string pstr = '' - if useAlignEnvironment: + if use_align_environment: pstr += '\\begin{align} \n' tbSpc = 4 + trailingAligner = '& ' else: pstr += '\\begin{equation} \n' if not isSingle: @@ -641,18 +664,22 @@ def latex_printer( tbSpc = 8 else: tbSpc = 4 + trailingAligner = '' # Iterate over the objectives and print for obj in objectives: obj_template, obj_indices = templatize_fcn(obj) if obj.sense == 1: - pstr += ' ' * tbSpc + '& \\text{%s} \n' % ('minimize') + pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['minimize']) else: - pstr += ' ' * tbSpc + '& \\text{%s} \n' % ('maximize') + pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['maximize']) - pstr += ' ' * tbSpc + '& & %s ' % (visitor.walk_expression(obj_template)) - if useAlignEnvironment: - pstr += '\\label{obj:' + pyomoElement.name + '_' + obj.name + '} ' + pstr += ' ' * tbSpc + '& & %s %s' % ( + visitor.walk_expression(obj_template), + trailingAligner, + ) + if use_align_environment: + pstr += '\\label{obj:' + pyomo_component.name + '_' + obj.name + '} ' if not isSingle: pstr += '\\\\ \n' else: @@ -662,7 +689,7 @@ def latex_printer( if len(constraints) > 0: # only print this if printing a full formulation if not isSingle: - pstr += ' ' * tbSpc + '& \\text{subject to} \n' + pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['subject to']) # first constraint needs different alignment because of the 'subject to': # & minimize & & [Objective] @@ -689,7 +716,9 @@ def latex_printer( # Walk the constraint conLine = ( - ' ' * tbSpc + algn + ' %s ' % (visitor.walk_expression(con_template)) + ' ' * tbSpc + + algn + + ' %s %s' % (visitor.walk_expression(con_template), trailingAligner) ) # Multiple constraints are generated using a set @@ -703,12 +732,26 @@ def latex_printer( indices[0]._set, ) - conLine += ', \\quad %s \\in %s ' % (idxTag, setTag) + if use_forall: + conLine += '%s %s \\in %s ' % (forallTag, idxTag, setTag) + else: + if trailingAligner == '': + conLine = ( + conLine + + '%s %s \\in %s ' % (forallTag, idxTag, setTag) + + trailingAligner + ) + else: + conLine = ( + conLine[0:-2] + + '%s %s \\in %s ' % (forallTag, idxTag, setTag) + + trailingAligner + ) pstr += conLine # Add labels as needed - if useAlignEnvironment: - pstr += '\\label{con:' + pyomoElement.name + '_' + con.name + '} ' + if use_align_environment: + pstr += '\\label{con:' + pyomo_component.name + '_' + con.name + '} ' # prevents an emptly blank line from being at the end of the latex output if i <= len(constraints) - 2: @@ -716,14 +759,221 @@ def latex_printer( else: pstr += '\n' + # Print bounds and sets + if not isSingle: + domainMap = { + 'Reals': '\\mathcal{R}', + 'PositiveReals': '\\mathcal{R}_{> 0}', + 'NonPositiveReals': '\\mathcal{R}_{\\leq 0}', + 'NegativeReals': '\\mathcal{R}_{< 0}', + 'NonNegativeReals': '\\mathcal{R}_{\\geq 0}', + 'Integers': '\\mathcal{Z}', + 'PositiveIntegers': '\\mathcal{Z}_{> 0}', + 'NonPositiveIntegers': '\\mathcal{Z}_{\\leq 0}', + 'NegativeIntegers': '\\mathcal{Z}_{< 0}', + 'NonNegativeIntegers': '\\mathcal{Z}_{\\geq 0}', + 'Boolean': '\\left{ 0 , 1 \\right }', + 'Binary': '\\left{ 0 , 1 \\right }', + 'Any': None, + 'AnyWithNone': None, + 'EmptySet': '\\varnothing', + 'UnitInterval': '\\left[ 0 , 1 \\right ]', + 'PercentFraction': '\\left[ 0 , 1 \\right ]', + # 'RealInterval' : None , + # 'IntegerInterval' : None , + } + + variableList = [ + vr + for vr in pyomo_component.component_objects( + pyo.Var, descend_into=True, active=True + ) + ] + + varBoundData = [[] for v in variableList] + for i in range(0, len(variableList)): + vr = variableList[i] + if isinstance(vr, ScalarVar): + domainName = vr.domain.name + varBounds = vr.bounds + lowerBoundValue = varBounds[0] + upperBoundValue = varBounds[1] + + varLatexName = visitor.walk_expression(vr) + if varLatexName in overwrite_dict.keys(): + varReplaceName = overwrite_dict[varLatexName] + else: + varReplaceName = varLatexName + + if domainName in ['Reals', 'Integers']: + if lowerBoundValue is not None: + lowerBound = str(lowerBoundValue) + ' \\leq ' + else: + lowerBound = '' + + if upperBoundValue is not None: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = '' + + elif domainName in ['PositiveReals', 'PositiveIntegers']: + if lowerBoundValue is not None: + if lowerBoundValue > 0: + lowerBound = str(lowerBoundValue) + ' \\leq ' + else: + lowerBound = ' 0 < ' + else: + lowerBound = ' 0 < ' + + if upperBoundValue is not None: + if upperBoundValue <= 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' + % (vr.name) + ) + else: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = '' + + elif domainName in ['NonPositiveReals', 'NonPositiveIntegers']: + if lowerBoundValue is not None: + if lowerBoundValue > 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' + % (vr.name) + ) + elif lowerBoundValue == 0: + lowerBound = ' 0 = ' + else: + lowerBound = str(upperBoundValue) + ' \\leq ' + else: + lowerBound = '' + + if upperBoundValue is not None: + if upperBoundValue >= 0: + upperBound = ' \\leq 0 ' + else: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = ' \\leq 0 ' + + elif domainName in ['NegativeReals', 'NegativeIntegers']: + if lowerBoundValue is not None: + if lowerBoundValue >= 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' + % (vr.name) + ) + else: + lowerBound = str(upperBoundValue) + ' \\leq ' + else: + lowerBound = '' + + if upperBoundValue is not None: + if upperBoundValue >= 0: + upperBound = ' < 0 ' + else: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = ' < 0 ' + + elif domainName in ['NonNegativeReals', 'NonNegativeIntegers']: + if lowerBoundValue is not None: + if lowerBoundValue > 0: + lowerBound = str(lowerBoundValue) + ' \\leq ' + else: + lowerBound = ' 0 \\leq ' + else: + lowerBound = ' 0 \\leq ' + + if upperBoundValue is not None: + if upperBoundValue < 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' + % (vr.name) + ) + elif upperBoundValue == 0: + upperBound = ' = 0 ' + else: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = '' + + elif domainName in [ + 'Boolean', + 'Binary', + 'Any', + 'AnyWithNone', + 'EmptySet', + ]: + lowerBound = '' + upperBound = '' + + elif domainName in ['UnitInterval', 'PercentFraction']: + if lowerBoundValue is not None: + if lowerBoundValue > 0: + upperBound = str(lowerBoundValue) + ' \\leq ' + elif lowerBoundValue > 1: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' + % (vr.name) + ) + elif lowerBoundValue == 1: + lowerBound = ' = 1 ' + else: + lowerBoundValue = ' 0 \\leq ' + else: + lowerBound = ' 0 \\leq ' + + if upperBoundValue is not None: + if upperBoundValue < 1: + upperBound = ' \\leq ' + str(upperBoundValue) + elif upperBoundValue < 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' + % (vr.name) + ) + elif upperBoundValue == 0: + upperBound = ' = 0 ' + else: + upperBound = ' \\leq 1 ' + else: + upperBound = ' \\leq 1 ' + + else: + raise ValueError( + 'Domain %s not supported by the latex printer' % (domainName) + ) + + varBoundData[i] = [ + vr, + varLatexName, + varReplaceName, + lowerBound, + upperBound, + domainName, + domainMap[domainName], + ] + elif isinstance(vr, IndexedVar): + # need to wrap in function and do individually + # Check on the final variable after all the indices are processed + pass + else: + raise DeveloperError( + 'Variable is not a variable. Should not happen. Contact developers' + ) + + # print the accumulated data to the string + # close off the print string - if useAlignEnvironment: + if use_align_environment: pstr += '\\end{align} \n' else: if not isSingle: pstr += ' \\end{aligned} \n' - pstr += ' \\label{%s} \n' % (pyomoElement.name) - pstr += '\end{equation} \n' + pstr += ' \\label{%s} \n' % (pyomo_component.name) + pstr += '\\end{equation} \n' # Handling the iterator indices @@ -752,7 +1002,7 @@ def latex_printer( ln = latexLines[jj] # only modify if there is a placeholder in the line if "PLACEHOLDER_8675309_GROUP_" in ln: - if xOnlyMode == 2: + if x_only_mode == 2: raise RuntimeError( 'Unwrapping indexed variables when an indexed constraint is present yields incorrect results' ) @@ -771,9 +1021,9 @@ def latex_printer( continuousSets = dict( zip(uniqueSets, [False for i in range(0, len(uniqueSets))]) ) - if splitContinuousSets: + if split_continuous_sets: for i in range(0, len(uniqueSets)): - st = getattr(pyomoElement, uniqueSets[i]) + st = getattr(pyomo_component, uniqueSets[i]) stData = st.data() stCont = True for ii in range(0, len(stData)): @@ -825,8 +1075,8 @@ def latex_printer( # replace the sets for ky, vl in setStrings.items(): # if the set is continuous and the flag has been set - if splitContinuousSets and vl[1]: - st = getattr(pyomoElement, vl[2]) + if split_continuous_sets and vl[1]: + st = getattr(pyomo_component, vl[2]) stData = st.data() bgn = stData[0] ed = stData[-1] diff --git a/pyomo/util/tests/test_latex_printer.py b/pyomo/util/tests/test_latex_printer.py index fdb1abf57f1..aff6932616e 100644 --- a/pyomo/util/tests/test_latex_printer.py +++ b/pyomo/util/tests/test_latex_printer.py @@ -131,7 +131,7 @@ def test_latexPrinter_objective(self): m = generate_model() pstr = latex_printer(m.objective_1) bstr = dedent( - r""" + r""" \begin{equation} & \text{minimize} & & x + y + z @@ -142,7 +142,7 @@ def test_latexPrinter_objective(self): pstr = latex_printer(m.objective_3) bstr = dedent( - r""" + r""" \begin{equation} & \text{maximize} & & x + y + z @@ -156,7 +156,7 @@ def test_latexPrinter_constraint(self): pstr = latex_printer(m.constraint_1) bstr = dedent( - r""" + r""" \begin{equation} x^{2} + y^{-2} - x y z + 1 = 2 \end{equation} @@ -173,7 +173,7 @@ def test_latexPrinter_expression(self): pstr = latex_printer(m.express) bstr = dedent( - r""" + r""" \begin{equation} x + y \end{equation} @@ -187,7 +187,7 @@ def test_latexPrinter_simpleExpression(self): pstr = latex_printer(m.x - m.y) bstr = dedent( - r""" + r""" \begin{equation} x - y \end{equation} @@ -197,7 +197,7 @@ def test_latexPrinter_simpleExpression(self): pstr = latex_printer(m.x - 2 * m.y) bstr = dedent( - r""" + r""" \begin{equation} x - 2 y \end{equation} @@ -210,7 +210,7 @@ def test_latexPrinter_unary(self): pstr = latex_printer(m.constraint_2) bstr = dedent( - r""" + r""" \begin{equation} \left| \frac{x}{z^{-2}} \right| \left( x + y \right) \leq 2 \end{equation} @@ -220,7 +220,7 @@ def test_latexPrinter_unary(self): pstr = latex_printer(pyo.Constraint(expr=pyo.sin(m.x) == 1)) bstr = dedent( - r""" + r""" \begin{equation} \sin \left( x \right) = 1 \end{equation} @@ -230,7 +230,7 @@ def test_latexPrinter_unary(self): pstr = latex_printer(pyo.Constraint(expr=pyo.log10(m.x) == 1)) bstr = dedent( - r""" + r""" \begin{equation} \log_{10} \left( x \right) = 1 \end{equation} @@ -240,7 +240,7 @@ def test_latexPrinter_unary(self): pstr = latex_printer(pyo.Constraint(expr=pyo.sqrt(m.x) == 1)) bstr = dedent( - r""" + r""" \begin{equation} \sqrt { x } = 1 \end{equation} @@ -253,7 +253,7 @@ def test_latexPrinter_rangedConstraint(self): pstr = latex_printer(m.constraint_4) bstr = dedent( - r""" + r""" \begin{equation} 1 \leq x \leq 2 \end{equation} @@ -266,7 +266,7 @@ def test_latexPrinter_exprIf(self): pstr = latex_printer(m.constraint_5) bstr = dedent( - r""" + r""" \begin{equation} f_{\text{exprIf}}(x \leq 1,z,y) \leq 1 \end{equation} @@ -279,7 +279,7 @@ def test_latexPrinter_blackBox(self): pstr = latex_printer(m.constraint_6) bstr = dedent( - r""" + r""" \begin{equation} x + f(x,y) = 2 \end{equation} @@ -292,7 +292,7 @@ def test_latexPrinter_iteratedConstraints(self): pstr = latex_printer(m.constraint_7) bstr = dedent( - r""" + r""" \begin{equation} \left( x + y \right) \sum_{i \in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I \end{equation} @@ -302,7 +302,7 @@ def test_latexPrinter_iteratedConstraints(self): pstr = latex_printer(m.constraint_8) bstr = dedent( - r""" + r""" \begin{equation} \sum_{k \in K} p_{k} = 1 \end{equation} @@ -315,7 +315,7 @@ def test_latexPrinter_model(self): pstr = latex_printer(m) bstr = dedent( - r""" + r""" \begin{equation} \begin{aligned} & \text{minimize} @@ -334,15 +334,15 @@ def test_latexPrinter_model(self): pstr = latex_printer(m, None, True) bstr = dedent( - r""" + r""" \begin{align} & \text{minimize} - & & x + y \label{obj:basicFormulation_objective_1} \\ + & & x + y & \label{obj:basicFormulation_objective_1} \\ & \text{subject to} - & & x^{2} + y^{2} \leq 1 \label{con:basicFormulation_constraint_1} \\ - &&& 0 \leq x \label{con:basicFormulation_constraint_2} \\ - &&& \left( x + y \right) \sum_{i \in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I \label{con:basicFormulation_constraint_7} \\ - &&& \sum_{k \in K} p_{k} = 1 \label{con:basicFormulation_constraint_8} + & & x^{2} + y^{2} \leq 1 & \label{con:basicFormulation_constraint_1} \\ + &&& 0 \leq x & \label{con:basicFormulation_constraint_2} \\ + &&& \left( x + y \right) \sum_{i \in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I & \label{con:basicFormulation_constraint_7} \\ + &&& \sum_{k \in K} p_{k} = 1 & \label{con:basicFormulation_constraint_8} \end{align} """ ) @@ -350,7 +350,7 @@ def test_latexPrinter_model(self): pstr = latex_printer(m, None, False, True) bstr = dedent( - r""" + r""" \begin{equation} \begin{aligned} & \text{minimize} @@ -369,15 +369,15 @@ def test_latexPrinter_model(self): pstr = latex_printer(m, None, True, True) bstr = dedent( - r""" + r""" \begin{align} & \text{minimize} - & & x + y \label{obj:basicFormulation_objective_1} \\ + & & x + y & \label{obj:basicFormulation_objective_1} \\ & \text{subject to} - & & x^{2} + y^{2} \leq 1 \label{con:basicFormulation_constraint_1} \\ - &&& 0 \leq x \label{con:basicFormulation_constraint_2} \\ - &&& \left( x + y \right) \sum_{i = 1}^{5} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I \label{con:basicFormulation_constraint_7} \\ - &&& \sum_{k \in K} p_{k} = 1 \label{con:basicFormulation_constraint_8} + & & x^{2} + y^{2} \leq 1 & \label{con:basicFormulation_constraint_1} \\ + &&& 0 \leq x & \label{con:basicFormulation_constraint_2} \\ + &&& \left( x + y \right) \sum_{i = 1}^{5} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I & \label{con:basicFormulation_constraint_7} \\ + &&& \sum_{k \in K} p_{k} = 1 & \label{con:basicFormulation_constraint_8} \end{align} """ ) @@ -386,9 +386,9 @@ def test_latexPrinter_model(self): def test_latexPrinter_advancedVariables(self): m = generate_simple_model_2() - pstr = latex_printer(m,useSmartVariables=True) + pstr = latex_printer(m, use_smart_variables=True) bstr = dedent( - r""" + r""" \begin{equation} \begin{aligned} & \text{minimize} @@ -400,7 +400,8 @@ def test_latexPrinter_advancedVariables(self): \end{aligned} \label{basicFormulation} \end{equation} - """) + """ + ) self.assertEqual('\n' + pstr, bstr) def test_latexPrinter_fileWriter(self): @@ -421,7 +422,9 @@ def test_latexPrinter_fileWriter(self): self.assertEqual(pstr, bstr) def test_latexPrinter_inputError(self): - self.assertRaises(ValueError, latex_printer, **{'pyomoElement': 'errorString'}) + self.assertRaises( + ValueError, latex_printer, **{'pyomo_component': 'errorString'} + ) if __name__ == '__main__': From b90a03f634ef89cc4bdbe36c91b81759ceab59c7 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Sep 2023 04:28:51 -0600 Subject: [PATCH 0096/1204] defer compilation/warning for unused suffixes --- pyomo/common/collections/component_map.py | 7 + pyomo/core/base/suffix.py | 2 +- pyomo/repn/plugins/nl_writer.py | 194 +++++++++------------- 3 files changed, 91 insertions(+), 112 deletions(-) diff --git a/pyomo/common/collections/component_map.py b/pyomo/common/collections/component_map.py index ceb4174ecca..47b88ce5914 100644 --- a/pyomo/common/collections/component_map.py +++ b/pyomo/common/collections/component_map.py @@ -91,6 +91,13 @@ def __len__(self): # Overload MutableMapping default implementations # + # We want a specialization of update() to avoid unnecessary calls to + # id() when copying / merging ComponentMaps + def update(self, *args, **kwargs): + if len(args) == 1 and not kwargs and isinstance(args[0], ComponentMap): + return self._dict.update(args[0]._dict) + return super().update(*args, **kwargs) + # We want to avoid generating Pyomo expressions due to # comparison of values, so we convert both objects to a # plain dictionary mapping key->(type(val), id(val)) and diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index 8806bc7abcd..130da451b1c 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -542,8 +542,8 @@ def __init__(self, name, default=None): """ self.name = name self.default = default - self._suffixes_by_block = ComponentMap({None: []}) self.all_suffixes = [] + self._suffixes_by_block = ComponentMap({None: []}) def find(self, component_data): """Find suffix value for a given component data object in model tree diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index d3846580c52..9e363d69313 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -9,12 +9,14 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +import itertools import logging import os from collections import deque, defaultdict from operator import itemgetter, attrgetter, setitem from pyomo.common.backports import nullcontext +from pyomo.common.collections import ComponentMap, ComponentSet from pyomo.common.config import ( ConfigBlock, ConfigValue, @@ -64,6 +66,7 @@ from pyomo.core.base.component import ActiveComponent from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData from pyomo.core.base.objective import ScalarObjective, _GeneralObjectiveData +from pyomo.core.base.suffix import SuffixFinder import pyomo.core.kernel as kernel from pyomo.core.pyomoobject import PyomoObject from pyomo.opt import WriterFactory @@ -342,97 +345,72 @@ def _RANGE_TYPE(lb, ub): class _SuffixData(object): - def __init__(self, name, column_order, row_order, obj_order, model_id): - self._name = name - self._column_order = column_order - self._row_order = row_order - self._obj_order = obj_order - self._model_id = model_id + def __init__(self, name): + self.name = name self.obj = {} self.con = {} self.var = {} self.prob = {} self.datatype = set() + self.values = ComponentMap() def update(self, suffix): - missing_component = missing_other = 0 self.datatype.add(suffix.datatype) - for obj, val in suffix.items(): - missing = self._store(obj, val) - if missing: - if missing > 0: - missing_component += missing + self.values.update(suffix) + + def store(self, obj, val): + self.values[obj] = val + + def compile(self, column_order, row_order, obj_order, model_id): + missing_component_data = ComponentSet() + unknown_data = ComponentSet() + queue = [self.values.items()] + while queue: + for obj, val in queue.pop(0): + if val.__class__ not in int_float: + val = float(val) + _id = id(obj) + if _id in column_order: + self.var[column_order[_id]] = val + elif _id in row_order: + self.con[row_order[_id]] = val + elif _id in obj_order: + self.obj[obj_order[_id]] = val + elif _id == model_id: + self.prob[0] = val + elif isinstance(obj, (_VarData, _ConstraintData, _ObjectiveData)): + missing_component_data.add(obj) + elif isinstance(obj, (Var, Constraint, Objective)): + # Expand this indexed component to store the + # individual ComponentDatas, but ONLY if the + # component data is not in the original dictionary + # of values that we extracted from the Suffixes + queue.append( + itertools.product( + itertools.filterfalse( + self.values.__contains__, obj.values() + ), + (val,), + ) + ) else: - missing_other -= missing - if missing_component: + unknown_data.add(obj) + if missing_component_data: logger.warning( - f"model contains export suffix '{suffix.name}' that " - f"contains {missing_component} component keys that are " + f"model contains export suffix '{self.name}' that " + f"contains {len(missing_component_data)} component keys that are " "not exported as part of the NL file. " "Skipping." ) - if missing_other: + if unknown_data: logger.warning( - f"model contains export suffix '{suffix.name}' that " - f"contains {missing_other} keys that are not " + f"model contains export suffix '{self.name}' that " + f"contains {len(unknown_data)} keys that are not " "Var, Constraint, Objective, or the model. Skipping." ) - def store(self, obj, val): - missing = self._store(obj, val) - if not missing: - return - if missing == 1: - logger.warning( - f"model contains export suffix '{self._name}' with " - f"{obj.ctype.__name__} key '{obj.name}', but that " - "object is not exported as part of the NL file. " - "Skipping." - ) - elif missing > 1: - logger.warning( - f"model contains export suffix '{self._name}' with " - f"{obj.ctype.__name__} key '{obj.name}', but that " - "object contained {missing} data objects that are " - "not exported as part of the NL file. " - "Skipping." - ) - else: - logger.warning( - f"model contains export suffix '{self._name}' with " - f"{obj.__class__.__name__} key '{obj}' that is not " - "a Var, Constraint, Objective, or the model. Skipping." - ) - def _store(self, obj, val): _id = id(obj) - if _id in self._column_order: - obj = self.var - key = self._column_order[_id] - elif _id in self._row_order: - obj = self.con - key = self._row_order[_id] - elif _id in self._obj_order: - obj = self.obj - key = self._obj_order[_id] - elif _id == self._model_id: - obj = self.prob - key = 0 - else: - missing_ct = 0 - if isinstance(obj, PyomoObject): - if obj.is_indexed(): - for o in obj.values(): - missing_ct += self._store(o, val) - else: - missing_ct = 1 - else: - missing_ct = -1 - return missing_ct - if val.__class__ not in int_float: - val = float(val) - obj[key] = val - return 0 class _NLWriter_impl(object): @@ -526,6 +504,23 @@ def write(self, model): initialize_var_map_from_column_order(model, self.config, var_map) timer.toc('Initialized column order', level=logging.DEBUG) + # Collect all defined EXPORT suffixes on the model + suffix_data = {} + if component_map[Suffix]: + # Note: reverse the block list so that higher-level Suffix + # components override lower level ones. + for block in reversed(component_map[Suffix]): + for suffix in block.component_objects( + Suffix, active=True, descend_into=False, sort=sorter + ): + if not suffix.export_enabled() or not suffix: + continue + name = suffix.local_name + if name not in suffix_data: + suffix_data[name] = _SuffixData(name) + suffix_data[name].update(suffix) + timer.toc("Collected suffixes", level=logging.DEBUG) + # # Tabulate the model expressions # @@ -829,36 +824,8 @@ def write(self, model): variables[idx] = (v, _id, _RANGE_TYPE(lb, ub), lb, ub) timer.toc("Computed variable bounds", level=logging.DEBUG) - # Collect all defined EXPORT suffixes on the model - suffix_data = {} - if component_map[Suffix]: - if not row_order: - row_order = {id(con[0]): i for i, con in enumerate(constraints)} - obj_order = {id(obj[0]): i for i, obj in enumerate(objectives)} - model_id = id(model) - # Note: reverse the block list so that higher-level Suffix - # components override lower level ones. - for block in reversed(component_map[Suffix]): - for suffix in block.component_objects( - Suffix, active=True, descend_into=False, sort=sorter - ): - if not (suffix.direction & Suffix.EXPORT): - continue - name = suffix.local_name - if name not in suffix_data: - suffix_data[name] = _SuffixData( - name, column_order, row_order, obj_order, model_id - ) - suffix_data[name].update(suffix) - timer.toc("Collected suffixes", level=logging.DEBUG) - # Collect all defined SOSConstraints on the model if component_map[SOSConstraint]: - if not row_order: - row_order = {id(con[0]): i for i, con in enumerate(constraints)} - if not component_map[Suffix]: - obj_order = {id(obj[0]): i for i, obj in enumerate(objectives)} - model_id = id(model) for name in ('sosno', 'ref'): # I am choosing not to allow a user to mix the use of the Pyomo # SOSConstraint component and manual sosno declarations within @@ -879,9 +846,7 @@ def write(self, model): "model. To avoid this error please use only one of " "these methods to define special ordered sets." ) - suffix_data[name] = _SuffixData( - name, column_order, row_order, obj_order, model_id - ) + suffix_data[name] = _SuffixData(name) suffix_data[name].datatype.add(Suffix.INT) sos_id = 0 sosno = suffix_data['sosno'] @@ -910,6 +875,11 @@ def write(self, model): sosno.store(v, tag) ref.store(v, r) + if suffix_data: + row_order = {id(con[0]): i for i, con in enumerate(constraints)} + obj_order = {id(obj[0]): i for i, obj in enumerate(objectives)} + model_id = id(model) + if symbolic_solver_labels: labeler = NameLabeler() row_labels = [labeler(info[0]) for info in constraints] + [ @@ -1106,6 +1076,7 @@ def write(self, model): for name, data in suffix_data.items(): if name == 'dual': continue + data.compile(column_order, row_order, obj_order, model_id) if len(data.datatype) > 1: raise ValueError( "The NL file writer found multiple active export " @@ -1129,7 +1100,7 @@ def write(self, model): if not _vals: continue ostream.write(f"S{_field|_float} {len(_vals)} {name}\n") - # Note: _SuffixData store/update guarantee the value is int/float + # Note: _SuffixData.compile() guarantees the value is int/float ostream.write( ''.join(f"{_id} {_vals[_id]!r}\n" for _id in sorted(_vals)) ) @@ -1210,18 +1181,19 @@ def write(self, model): # "d" lines (dual initialization) # if 'dual' in suffix_data: - _data = suffix_data['dual'] - if _data.var: + data = suffix_data['dual'] + data.compile(column_order, row_order, obj_order, model_id) + if data.var: logger.warning("ignoring 'dual' suffix for Var types") - if _data.obj: + if data.obj: logger.warning("ignoring 'dual' suffix for Objective types") - if _data.prob: + if data.prob: logger.warning("ignoring 'dual' suffix for Model") - if _data.con: + if data.con: ostream.write(f"d{len(_data.con)}\n") - # Note: _SuffixData store/update guarantee the value is int/float + # Note: _SuffixData.compile() guarantees the value is int/float ostream.write( - ''.join(f"{_id} {_data.con[_id]!r}\n" for _id in sorted(_data.con)) + ''.join(f"{_id} {_data.con[_id]!r}\n" for _id in sorted(data.con)) ) # From 08007e142beea435833f344be6901142fbba658b Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Sep 2023 04:33:26 -0600 Subject: [PATCH 0097/1204] checkpoint initial implementation of nl scaling --- pyomo/repn/plugins/nl_writer.py | 129 +++++++++++++++++++++++++++----- 1 file changed, 110 insertions(+), 19 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 9e363d69313..cc2d465d73e 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -199,6 +199,18 @@ class NLWriter(object): description='Write the corresponding .row and .col files', ), ) + CONFIG.declare( + 'scale_model', + ConfigValue( + default=True, + domain=bool, + description="Write variables and constraints in scaled space", + doc=""" + If True, then the writer will output the model constraints and + variables in 'scaled space' using the scaling from the + 'scaling_factor' Suffix, if provided.""", + ), + ) CONFIG.declare( 'export_nonlinear_variables', ConfigValue( @@ -262,6 +274,7 @@ def __call__(self, model, filename, solver_capability, io_options): col_fname = filename_base + '.col' config = self.config(io_options) + config.scale_model = False if config.symbolic_solver_labels: _open = lambda fname: open(fname, 'w') else: @@ -410,7 +423,29 @@ def compile(self, column_order, row_order, obj_order, model_id): ) +class CachingNumericSuffixFinder(SuffixFinder): + scale = True + + def __init__(self, name, default=None): + super().__init__(name, default) + self.suffix_cache = {} + + def __call__(self, obj): _id = id(obj) + if _id in self.suffix_cache: + return self.suffix_cache[_id] + ans = self.find(obj) + if ans.__class__ not in int_float: + ans = float(ans) + self.suffix_cache[_id] = ans + return ans + + +class _NoScalingFactor(object): + scale = False + + def __call__(self, obj): + return 1 class _NLWriter_impl(object): @@ -521,6 +556,12 @@ def write(self, model): suffix_data[name].update(suffix) timer.toc("Collected suffixes", level=logging.DEBUG) + if self.config.scale_model and 'scaling_factor' in suffix_data: + scaling_factor = CachingNumericSuffixFinder('scaling_factor', 1) + del suffix_data['scaling_factor'] + else: + scaling_factor = _NoScalingFactor() + # # Tabulate the model expressions # @@ -531,7 +572,7 @@ def write(self, model): if with_debug_timing and obj.parent_component() is not last_parent: timer.toc('Objective %s', last_parent, level=logging.DEBUG) last_parent = obj.parent_component() - expr = visitor.walk_expression((obj.expr, obj, 1)) + expr = visitor.walk_expression((obj.expr, obj, 1, scaling_factor(obj))) if expr.named_exprs: self._record_named_expression_usage(expr.named_exprs, obj, 1) if expr.nonlinear: @@ -563,20 +604,30 @@ def write(self, model): if with_debug_timing and con.parent_component() is not last_parent: timer.toc('Constraint %s', last_parent, level=logging.DEBUG) last_parent = con.parent_component() - expr = visitor.walk_expression((con.body, con, 0)) + scale = scaling_factor(con) + expr = visitor.walk_expression((con.body, con, 0, scale)) if expr.named_exprs: self._record_named_expression_usage(expr.named_exprs, con, 0) + # Note: Constraint.lb/ub guarantee a return value that is # either a (finite) native_numeric_type, or None const = expr.const if const.__class__ not in int_float: const = float(const) lb = con.lb - if lb is not None: - lb = repr(lb - const) ub = con.ub - if ub is not None: - ub = repr(ub - const) + if scale != 1: + if lb is not None: + lb = repr(lb * scale - const) + if ub is not None: + ub = repr(ub * scale - const) + if scale < 0: + lb, ub = ub, lb + else: + if lb is not None: + lb = repr(lb - const) + if ub is not None: + ub = repr(ub - const) _type = _RANGE_TYPE(lb, ub) if _type == 4: n_equality += 1 @@ -817,10 +868,19 @@ def write(self, model): # Note: Var.bounds guarantees the values are either (finite) # native_numeric_types or None lb, ub = v.bounds - if lb is not None: - lb = repr(lb) - if ub is not None: - ub = repr(ub) + scale = scaling_factor(v) + if scale != 1: + if lb is not None: + lb = repr(lb * scale) + if ub is not None: + ub = repr(ub * scale) + if scale < 0: + lb, ub = ub, lb + else: + if lb is not None: + lb = repr(lb) + if ub is not None: + ub = repr(ub) variables[idx] = (v, _id, _RANGE_TYPE(lb, ub), lb, ub) timer.toc("Computed variable bounds", level=logging.DEBUG) @@ -888,10 +948,26 @@ def write(self, model): row_comments = [f'\t#{lbl}' for lbl in row_labels] col_labels = [labeler(info[0]) for info in variables] col_comments = [f'\t#{lbl}' for lbl in col_labels] - self.var_id_to_nl = { - info[1]: f'{var_idx}{col_comments[var_idx]}' - for var_idx, info in enumerate(variables) - } + if scaling_factor.scale: + self.var_id_to_nl = _map = {} + for var_idx, info in enumerate(variables): + _id = info[1] + scale = scaling_factor.suffix_cache[_id] + if scale == 1: + _map[_id] = f'v{var_idx}{col_comments[var_idx]}' + else: + _map[_id] = ( + template.division + + f'v{var_idx}' + + col_comments[var_idx] + + '\n' + + template.const % scale + ).rstrip() + else: + self.var_id_to_nl = { + info[1]: f'v{var_idx}{col_comments[var_idx]}' + for var_idx, info in enumerate(variables) + } # Write out the .row and .col data if self.rowstream is not None: self.rowstream.write('\n'.join(row_labels)) @@ -902,9 +978,21 @@ def write(self, model): else: row_labels = row_comments = [''] * (n_cons + n_objs) col_labels = col_comments = [''] * len(variables) - self.var_id_to_nl = { - info[1]: var_idx for var_idx, info in enumerate(variables) - } + if scaling_factor.scale: + self.var_id_to_nl = _map = {} + for var_idx, info in enumerate(variables): + _id = info[1] + scale = scaling_factor.suffix_cache[_id] + if scale == 1: + _map[_id] = f"v{var_idx}" + else: + _map[_id] = ( + template.division + f'v{var_idx}\n' + template.const % scale + ).rstrip() + else: + self.var_id_to_nl = { + info[1]: f"v{var_idx}" for var_idx, info in enumerate(variables) + } timer.toc("Generated row/col labels & comments", level=logging.DEBUG) # @@ -1805,7 +1893,7 @@ class text_nl_debug_template(object): less_equal = 'o23\t# le\n' equality = 'o24\t# eq\n' external_fcn = 'f%d %d%s\n' - var = 'v%s\n' + var = '%s\n' # NOTE: to support scaling, we do NOT include the 'v' here const = 'n%r\n' string = 'h%d:%s\n' monomial = product + const + var.replace('%', '%%') @@ -2518,7 +2606,7 @@ def _eval_expr(self, expr): return ans def initializeWalker(self, expr): - expr, src, src_idx = expr + expr, src, src_idx, self.expression_scaling_factor = expr self.active_expression_source = (src_idx, id(src)) walk, result = self.beforeChild(None, expr, 0) if not walk: @@ -2553,6 +2641,9 @@ def exitNode(self, node, data): def finalizeResult(self, result): ans = node_result_to_amplrepn(result) + # Multiply the expression by the scaling factor provided by the caller + ans.mult *= self.expression_scaling_factor + # If this was a nonlinear named expression, and that expression # has no linear portion, then we will directly use this as a # named expression. We need to mark that the expression was From ff9fda2011fd3b832f79350a5971a5515ddf77a8 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Sep 2023 04:35:44 -0600 Subject: [PATCH 0098/1204] removing unneeded code --- pyomo/repn/plugins/nl_writer.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index cc2d465d73e..0149387fde5 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -684,9 +684,6 @@ def write(self, model): constraints.extend(linear_cons) n_cons = len(constraints) - # initialize an empty row order, to be populated later if we need it - row_order = {} - # # Collect constraints and objectives into the groupings # necessary for AMPL From 00932b848dedcca4ce45cf8ded7028f41851b507 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Sep 2023 04:37:19 -0600 Subject: [PATCH 0099/1204] Update tests to reflect adding scaling_factor option --- pyomo/repn/tests/ampl/test_nlv2.py | 135 +++++++++++++++-------------- 1 file changed, 68 insertions(+), 67 deletions(-) diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index fe04847b6cb..8320c37b20e 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -85,19 +85,19 @@ def test_divide(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((m.x**2 / m.p, None, None)) + repn = info.visitor.walk_expression((m.x**2 / m.p, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) self.assertEqual(repn.const, 0) self.assertEqual(repn.linear, {}) - self.assertEqual(repn.nonlinear, ('o5\nv%s\nn2\n', [id(m.x)])) + self.assertEqual(repn.nonlinear, ('o5\n%s\nn2\n', [id(m.x)])) m.p = 2 info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((4 / m.p, None, None)) + repn = info.visitor.walk_expression((4 / m.p, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -107,7 +107,7 @@ def test_divide(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((m.x / m.p, None, None)) + repn = info.visitor.walk_expression((m.x / m.p, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -117,7 +117,7 @@ def test_divide(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression(((4 * m.x) / m.p, None, None)) + repn = info.visitor.walk_expression(((4 * m.x) / m.p, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -127,7 +127,7 @@ def test_divide(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((4 * (m.x + 2) / m.p, None, None)) + repn = info.visitor.walk_expression((4 * (m.x + 2) / m.p, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -137,23 +137,23 @@ def test_divide(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((m.x**2 / m.p, None, None)) + repn = info.visitor.walk_expression((m.x**2 / m.p, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) self.assertEqual(repn.const, 0) self.assertEqual(repn.linear, {}) - self.assertEqual(repn.nonlinear, ('o2\nn0.5\no5\nv%s\nn2\n', [id(m.x)])) + self.assertEqual(repn.nonlinear, ('o2\nn0.5\no5\n%s\nn2\n', [id(m.x)])) info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((log(m.x) / m.x, None, None)) + repn = info.visitor.walk_expression((log(m.x) / m.x, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) self.assertEqual(repn.const, 0) self.assertEqual(repn.linear, {}) - self.assertEqual(repn.nonlinear, ('o3\no43\nv%s\nv%s\n', [id(m.x), id(m.x)])) + self.assertEqual(repn.nonlinear, ('o3\no43\n%s\n%s\n', [id(m.x), id(m.x)])) def test_errors_divide_by_0(self): m = ConcreteModel() @@ -162,7 +162,7 @@ def test_errors_divide_by_0(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((1 / m.p, None, None)) + repn = info.visitor.walk_expression((1 / m.p, None, None, 1)) self.assertEqual( LOG.getvalue(), "Exception encountered evaluating expression 'div(1, 0)'\n" @@ -177,7 +177,7 @@ def test_errors_divide_by_0(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((m.x / m.p, None, None)) + repn = info.visitor.walk_expression((m.x / m.p, None, None, 1)) self.assertEqual( LOG.getvalue(), "Exception encountered evaluating expression 'div(1, 0)'\n" @@ -192,7 +192,7 @@ def test_errors_divide_by_0(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression(((3 * m.x) / m.p, None, None)) + repn = info.visitor.walk_expression(((3 * m.x) / m.p, None, None, 1)) self.assertEqual( LOG.getvalue(), "Exception encountered evaluating expression 'div(3, 0)'\n" @@ -207,7 +207,7 @@ def test_errors_divide_by_0(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((3 * (m.x + 2) / m.p, None, None)) + repn = info.visitor.walk_expression((3 * (m.x + 2) / m.p, None, None, 1)) self.assertEqual( LOG.getvalue(), "Exception encountered evaluating expression 'div(3, 0)'\n" @@ -222,7 +222,7 @@ def test_errors_divide_by_0(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((m.x**2 / m.p, None, None)) + repn = info.visitor.walk_expression((m.x**2 / m.p, None, None, 1)) self.assertEqual( LOG.getvalue(), "Exception encountered evaluating expression 'div(1, 0)'\n" @@ -242,18 +242,18 @@ def test_pow(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((m.x**m.p, None, None)) + repn = info.visitor.walk_expression((m.x**m.p, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) self.assertEqual(repn.const, 0) self.assertEqual(repn.linear, {}) - self.assertEqual(repn.nonlinear, ('o5\nv%s\nn2\n', [id(m.x)])) + self.assertEqual(repn.nonlinear, ('o5\n%s\nn2\n', [id(m.x)])) m.p = 1 info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((m.x**m.p, None, None)) + repn = info.visitor.walk_expression((m.x**m.p, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -264,7 +264,7 @@ def test_pow(self): m.p = 0 info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((m.x**m.p, None, None)) + repn = info.visitor.walk_expression((m.x**m.p, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -281,7 +281,7 @@ def test_errors_divide_by_0_mult_by_0(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((m.p * (1 / m.p), None, None)) + repn = info.visitor.walk_expression((m.p * (1 / m.p), None, None, 1)) self.assertIn( "Exception encountered evaluating expression 'div(1, 0)'\n" "\tmessage: division by zero\n" @@ -296,7 +296,7 @@ def test_errors_divide_by_0_mult_by_0(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression(((1 / m.p) * m.p, None, None)) + repn = info.visitor.walk_expression(((1 / m.p) * m.p, None, None, 1)) self.assertIn( "Exception encountered evaluating expression 'div(1, 0)'\n" "\tmessage: division by zero\n" @@ -311,7 +311,7 @@ def test_errors_divide_by_0_mult_by_0(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((m.p * (m.x / m.p), None, None)) + repn = info.visitor.walk_expression((m.p * (m.x / m.p), None, None, 1)) self.assertIn( "Exception encountered evaluating expression 'div(1, 0)'\n" "\tmessage: division by zero\n" @@ -327,7 +327,7 @@ def test_errors_divide_by_0_mult_by_0(self): info = INFO() with LoggingIntercept() as LOG: repn = info.visitor.walk_expression( - (m.p * (3 * (m.x + 2) / m.p), None, None) + (m.p * (3 * (m.x + 2) / m.p), None, None, 1) ) self.assertIn( "Exception encountered evaluating expression 'div(3, 0)'\n" @@ -343,7 +343,7 @@ def test_errors_divide_by_0_mult_by_0(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((m.p * (m.x**2 / m.p), None, None)) + repn = info.visitor.walk_expression((m.p * (m.x**2 / m.p), None, None, 1)) self.assertIn( "Exception encountered evaluating expression 'div(1, 0)'\n" "\tmessage: division by zero\n" @@ -368,7 +368,7 @@ def test_errors_divide_by_0_halt(self): try: info = INFO() with LoggingIntercept() as LOG, self.assertRaises(ZeroDivisionError): - info.visitor.walk_expression((1 / m.p, None, None)) + info.visitor.walk_expression((1 / m.p, None, None, 1)) self.assertEqual( LOG.getvalue(), "Exception encountered evaluating expression 'div(1, 0)'\n" @@ -378,7 +378,7 @@ def test_errors_divide_by_0_halt(self): info = INFO() with LoggingIntercept() as LOG, self.assertRaises(ZeroDivisionError): - info.visitor.walk_expression((m.x / m.p, None, None)) + info.visitor.walk_expression((m.x / m.p, None, None, 1)) self.assertEqual( LOG.getvalue(), "Exception encountered evaluating expression 'div(1, 0)'\n" @@ -388,7 +388,7 @@ def test_errors_divide_by_0_halt(self): info = INFO() with LoggingIntercept() as LOG, self.assertRaises(ZeroDivisionError): - info.visitor.walk_expression((3 * (m.x + 2) / m.p, None, None)) + info.visitor.walk_expression((3 * (m.x + 2) / m.p, None, None, 1)) self.assertEqual( LOG.getvalue(), "Exception encountered evaluating expression 'div(3, 0)'\n" @@ -398,7 +398,7 @@ def test_errors_divide_by_0_halt(self): info = INFO() with LoggingIntercept() as LOG, self.assertRaises(ZeroDivisionError): - info.visitor.walk_expression((m.x**2 / m.p, None, None)) + info.visitor.walk_expression((m.x**2 / m.p, None, None, 1)) self.assertEqual( LOG.getvalue(), "Exception encountered evaluating expression 'div(1, 0)'\n" @@ -415,7 +415,7 @@ def test_errors_negative_frac_pow(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((m.p ** (0.5), None, None)) + repn = info.visitor.walk_expression((m.p ** (0.5), None, None, 1)) self.assertEqual( LOG.getvalue(), "Complex number returned from expression\n" @@ -431,7 +431,7 @@ def test_errors_negative_frac_pow(self): m.x.fix(0.5) info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((m.p**m.x, None, None)) + repn = info.visitor.walk_expression((m.p**m.x, None, None, 1)) self.assertEqual( LOG.getvalue(), "Complex number returned from expression\n" @@ -451,7 +451,7 @@ def test_errors_unary_func(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((log(m.p), None, None)) + repn = info.visitor.walk_expression((log(m.p), None, None, 1)) self.assertEqual( LOG.getvalue(), "Exception encountered evaluating expression 'log(0)'\n" @@ -476,7 +476,7 @@ def test_errors_propagate_nan(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((expr, None, None)) + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual( LOG.getvalue(), "Exception encountered evaluating expression 'div(3, 0)'\n" @@ -491,7 +491,7 @@ def test_errors_propagate_nan(self): m.y.fix(None) expr = log(m.y) + 3 - repn = info.visitor.walk_expression((expr, None, None)) + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) self.assertEqual(str(repn.const), 'InvalidNumber(nan)') @@ -499,7 +499,7 @@ def test_errors_propagate_nan(self): self.assertEqual(repn.nonlinear, None) expr = 3 * m.y - repn = info.visitor.walk_expression((expr, None, None)) + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) self.assertEqual(repn.const, InvalidNumber(None)) @@ -508,7 +508,7 @@ def test_errors_propagate_nan(self): m.p.value = None expr = 5 * (m.p * m.x + 2 * m.z) - repn = info.visitor.walk_expression((expr, None, None)) + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) self.assertEqual(repn.const, 0) @@ -516,7 +516,7 @@ def test_errors_propagate_nan(self): self.assertEqual(repn.nonlinear, None) expr = m.y * m.x - repn = info.visitor.walk_expression((expr, None, None)) + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) self.assertEqual(repn.const, 0) @@ -527,17 +527,17 @@ def test_errors_propagate_nan(self): m.z[1].fix(None) expr = m.z[1] - ((m.z[2] * m.z[3]) * m.z[4]) with INFO() as info: - repn = info.visitor.walk_expression((expr, None, None)) + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) self.assertEqual(repn.const, InvalidNumber(None)) self.assertEqual(repn.linear, {}) - self.assertEqual(repn.nonlinear[0], 'o16\no2\no2\nv%s\nv%s\nv%s\n') + self.assertEqual(repn.nonlinear[0], 'o16\no2\no2\n%s\n%s\n%s\n') self.assertEqual(repn.nonlinear[1], [id(m.z[2]), id(m.z[3]), id(m.z[4])]) m.z[3].fix(float('nan')) with INFO() as info: - repn = info.visitor.walk_expression((expr, None, None)) + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) self.assertEqual(repn.const, InvalidNumber(None)) @@ -560,6 +560,7 @@ def test_linearexpression_npv(self): ), None, None, + 1, ) ) self.assertEqual(LOG.getvalue(), "") @@ -575,18 +576,18 @@ def test_eval_pow(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((m.x ** (0.5), None, None)) + repn = info.visitor.walk_expression((m.x ** (0.5), None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) self.assertEqual(repn.const, 0) self.assertEqual(repn.linear, {}) - self.assertEqual(repn.nonlinear, ('o5\nv%s\nn0.5\n', [id(m.x)])) + self.assertEqual(repn.nonlinear, ('o5\n%s\nn0.5\n', [id(m.x)])) m.x.fix() info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((m.x ** (0.5), None, None)) + repn = info.visitor.walk_expression((m.x ** (0.5), None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -600,18 +601,18 @@ def test_eval_abs(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((abs(m.x), None, None)) + repn = info.visitor.walk_expression((abs(m.x), None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) self.assertEqual(repn.const, 0) self.assertEqual(repn.linear, {}) - self.assertEqual(repn.nonlinear, ('o15\nv%s\n', [id(m.x)])) + self.assertEqual(repn.nonlinear, ('o15\n%s\n', [id(m.x)])) m.x.fix() info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((abs(m.x), None, None)) + repn = info.visitor.walk_expression((abs(m.x), None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -625,18 +626,18 @@ def test_eval_unary_func(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((log(m.x), None, None)) + repn = info.visitor.walk_expression((log(m.x), None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) self.assertEqual(repn.const, 0) self.assertEqual(repn.linear, {}) - self.assertEqual(repn.nonlinear, ('o43\nv%s\n', [id(m.x)])) + self.assertEqual(repn.nonlinear, ('o43\n%s\n', [id(m.x)])) m.x.fix() info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((log(m.x), None, None)) + repn = info.visitor.walk_expression((log(m.x), None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -652,7 +653,7 @@ def test_eval_expr_if_lessEq(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((expr, None, None)) + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -660,13 +661,13 @@ def test_eval_expr_if_lessEq(self): self.assertEqual(repn.linear, {}) self.assertEqual( repn.nonlinear, - ('o35\no23\nv%s\nn4\no5\nv%s\nn2\nv%s\n', [id(m.x), id(m.x), id(m.y)]), + ('o35\no23\n%s\nn4\no5\n%s\nn2\n%s\n', [id(m.x), id(m.x), id(m.y)]), ) m.x.fix() info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((expr, None, None)) + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -677,7 +678,7 @@ def test_eval_expr_if_lessEq(self): m.x.fix(5) info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((expr, None, None)) + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -693,7 +694,7 @@ def test_eval_expr_if_Eq(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((expr, None, None)) + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -701,13 +702,13 @@ def test_eval_expr_if_Eq(self): self.assertEqual(repn.linear, {}) self.assertEqual( repn.nonlinear, - ('o35\no24\nv%s\nn4\no5\nv%s\nn2\nv%s\n', [id(m.x), id(m.x), id(m.y)]), + ('o35\no24\n%s\nn4\no5\n%s\nn2\n%s\n', [id(m.x), id(m.x), id(m.y)]), ) m.x.fix() info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((expr, None, None)) + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -718,7 +719,7 @@ def test_eval_expr_if_Eq(self): m.x.fix(5) info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((expr, None, None)) + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -734,7 +735,7 @@ def test_eval_expr_if_ranged(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((expr, None, None)) + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -743,7 +744,7 @@ def test_eval_expr_if_ranged(self): self.assertEqual( repn.nonlinear, ( - 'o35\no21\no23\nn1\nv%s\no23\nv%s\nn4\no5\nv%s\nn2\nv%s\n', + 'o35\no21\no23\nn1\n%s\no23\n%s\nn4\no5\n%s\nn2\n%s\n', [id(m.x), id(m.x), id(m.x), id(m.y)], ), ) @@ -751,7 +752,7 @@ def test_eval_expr_if_ranged(self): m.x.fix() info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((expr, None, None)) + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -762,7 +763,7 @@ def test_eval_expr_if_ranged(self): m.x.fix(5) info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((expr, None, None)) + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -773,7 +774,7 @@ def test_eval_expr_if_ranged(self): m.x.fix(0) info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((expr, None, None)) + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -793,7 +794,7 @@ class CustomExpression(ScalarExpression): expr = m.e + m.e info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((expr, None, None)) + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) @@ -804,7 +805,7 @@ class CustomExpression(ScalarExpression): self.assertEqual(len(info.subexpression_cache), 1) obj, repn, info = info.subexpression_cache[id(m.e)] self.assertIs(obj, m.e) - self.assertEqual(repn.nl, ('v%s\n', (id(m.e),))) + self.assertEqual(repn.nl, ('%s\n', (id(m.e),))) self.assertEqual(repn.mult, 1) self.assertEqual(repn.const, 3) self.assertEqual(repn.linear, {id(m.x): 1}) @@ -825,13 +826,13 @@ def test_nested_operator_zero_arg(self): info = INFO() with LoggingIntercept() as LOG: - repn = info.visitor.walk_expression((expr, None, None)) + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) self.assertEqual(repn.const, 0) self.assertEqual(repn.linear, {}) - self.assertEqual(repn.nonlinear, ('o24\no3\nn1\nv%s\nn0\n', [id(m.x)])) + self.assertEqual(repn.nonlinear, ('o24\no3\nn1\n%s\nn0\n', [id(m.x)])) def test_duplicate_shared_linear_expressions(self): # This tests an issue where AMPLRepn.duplicate() was not copying @@ -848,8 +849,8 @@ def test_duplicate_shared_linear_expressions(self): info = INFO() with LoggingIntercept() as LOG: - repn1 = info.visitor.walk_expression((expr1, None, None)) - repn2 = info.visitor.walk_expression((expr2, None, None)) + repn1 = info.visitor.walk_expression((expr1, None, None, 1)) + repn2 = info.visitor.walk_expression((expr2, None, None, 1)) self.assertEqual(LOG.getvalue(), "") self.assertEqual(repn1.nl, None) self.assertEqual(repn1.mult, 1) From 0f152254b43ff8e48706cbaac6fa644110150d21 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Sep 2023 04:41:04 -0600 Subject: [PATCH 0100/1204] Adding missing import --- pyomo/repn/plugins/nl_writer.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 0149387fde5..38a94a17495 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -64,9 +64,15 @@ minimize, ) from pyomo.core.base.component import ActiveComponent +from pyomo.core.base.constraint import _ConstraintData from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData -from pyomo.core.base.objective import ScalarObjective, _GeneralObjectiveData +from pyomo.core.base.objective import ( + ScalarObjective, + _GeneralObjectiveData, + _ObjectiveData, +) from pyomo.core.base.suffix import SuffixFinder +from pyomo.core.base.var import _VarData import pyomo.core.kernel as kernel from pyomo.core.pyomoobject import PyomoObject from pyomo.opt import WriterFactory From 1073c3eb88e640182ef270ab5767d0537e441c71 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Sep 2023 04:49:17 -0600 Subject: [PATCH 0101/1204] Add centralized beforeChild/exitNode dispatcher classes --- pyomo/repn/linear.py | 377 ++++++++++++-------------------- pyomo/repn/plugins/nl_writer.py | 358 ++++++++++++------------------ pyomo/repn/quadratic.py | 8 +- pyomo/repn/util.py | 139 +++++++++++- 4 files changed, 429 insertions(+), 453 deletions(-) diff --git a/pyomo/repn/linear.py b/pyomo/repn/linear.py index 8bffbf1d49b..cd962b4f3ac 100644 --- a/pyomo/repn/linear.py +++ b/pyomo/repn/linear.py @@ -8,7 +8,7 @@ # rights in this software. # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -import collections + import logging import sys from operator import itemgetter @@ -37,10 +37,11 @@ ) from pyomo.core.expr.visitor import StreamBasedExpressionVisitor, _EvaluationVisitor from pyomo.core.expr import is_fixed, value -from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData -from pyomo.core.base.objective import ScalarObjective, _GeneralObjectiveData +from pyomo.core.base.expression import Expression import pyomo.core.kernel as kernel from pyomo.repn.util import ( + BeforeChildDispatcher, + ExitNodeDispatcher, ExprType, InvalidNumber, apply_node_operation, @@ -416,23 +417,12 @@ def _handle_named_ANY(visitor, node, arg1): return _type, arg1.duplicate() -_exit_node_handlers[ScalarExpression] = { +_exit_node_handlers[Expression] = { (_CONSTANT,): _handle_named_constant, (_LINEAR,): _handle_named_ANY, (_GENERAL,): _handle_named_ANY, } -_named_subexpression_types = [ - ScalarExpression, - _GeneralExpressionData, - kernel.expression.expression, - kernel.expression.noclone, - # Note: objectives are special named expressions - _GeneralObjectiveData, - ScalarObjective, - kernel.objective.objective, -] - # # EXPR_IF handlers # @@ -578,246 +568,159 @@ def _handle_ranged_general(visitor, node, arg1, arg2, arg3): ] = _handle_ranged_const -def _before_native(visitor, child): - return False, (_CONSTANT, child) - - -def _before_invalid(visitor, child): - return False, ( - _CONSTANT, - InvalidNumber(child, "'{child}' is not a valid numeric type"), - ) - - -def _before_complex(visitor, child): - return False, (_CONSTANT, complex_number_error(child, visitor, child)) - - -def _before_var(visitor, child): - _id = id(child) - if _id not in visitor.var_map: - if child.fixed: - return False, (_CONSTANT, visitor._eval_fixed(child)) - visitor.var_map[_id] = child - visitor.var_order[_id] = len(visitor.var_order) - ans = visitor.Result() - ans.linear[_id] = 1 - return False, (_LINEAR, ans) - - -def _before_param(visitor, child): - return False, (_CONSTANT, visitor._eval_fixed(child)) - +class LinearBeforeChildDispatcher(BeforeChildDispatcher): + def __init__(self): + # Special handling for external functions: will be handled + # as terminal nodes from the point of view of the visitor + self[ExternalFunctionExpression] = self._before_external + # Special linear / summation expressions + self[MonomialTermExpression] = self._before_monomial + self[LinearExpression] = self._before_linear + self[SumExpression] = self._before_general_expression + + @staticmethod + def _before_var(visitor, child): + _id = id(child) + if _id not in visitor.var_map: + if child.fixed: + return False, (_CONSTANT, visitor._eval_fixed(child)) + visitor.var_map[_id] = child + visitor.var_order[_id] = len(visitor.var_order) + ans = visitor.Result() + ans.linear[_id] = 1 + return False, (_LINEAR, ans) -def _before_npv(visitor, child): - try: - return False, (_CONSTANT, visitor._eval_expr(child)) - except (ValueError, ArithmeticError): - return True, None - - -def _before_monomial(visitor, child): - # - # The following are performance optimizations for common - # situations (Monomial terms and Linear expressions) - # - arg1, arg2 = child._args_ - if arg1.__class__ not in native_types: - try: - arg1 = visitor._eval_expr(arg1) - except (ValueError, ArithmeticError): - return True, None + @staticmethod + def _before_monomial(visitor, child): + # + # The following are performance optimizations for common + # situations (Monomial terms and Linear expressions) + # + arg1, arg2 = child._args_ + if arg1.__class__ not in native_types: + try: + arg1 = visitor._eval_expr(arg1) + except (ValueError, ArithmeticError): + return True, None - # We want to check / update the var_map before processing "0" - # coefficients so that we are consistent with what gets added to the - # var_map (e.g., 0*x*y: y is processed by _before_var and will - # always be added, but x is processed here) - _id = id(arg2) - if _id not in visitor.var_map: - if arg2.fixed: - return False, (_CONSTANT, arg1 * visitor._eval_fixed(arg2)) - visitor.var_map[_id] = arg2 - visitor.var_order[_id] = len(visitor.var_order) - - # Trap multiplication by 0 and nan. - if not arg1: - if arg2.fixed: - arg2 = visitor._eval_fixed(arg2) - if arg2 != arg2: - deprecation_warning( - f"Encountered {arg1}*{str(arg2.value)} in expression " - "tree. Mapping the NaN result to 0 for compatibility " - "with the lp_v1 writer. In the future, this NaN " - "will be preserved/emitted to comply with IEEE-754.", - version='6.6.0', - ) - return False, (_CONSTANT, arg1) + # We want to check / update the var_map before processing "0" + # coefficients so that we are consistent with what gets added to the + # var_map (e.g., 0*x*y: y is processed by _before_var and will + # always be added, but x is processed here) + _id = id(arg2) + if _id not in visitor.var_map: + if arg2.fixed: + return False, (_CONSTANT, arg1 * visitor._eval_fixed(arg2)) + visitor.var_map[_id] = arg2 + visitor.var_order[_id] = len(visitor.var_order) + + # Trap multiplication by 0 and nan. + if not arg1: + if arg2.fixed: + arg2 = visitor._eval_fixed(arg2) + if arg2 != arg2: + deprecation_warning( + f"Encountered {arg1}*{str(arg2.value)} in expression " + "tree. Mapping the NaN result to 0 for compatibility " + "with the lp_v1 writer. In the future, this NaN " + "will be preserved/emitted to comply with IEEE-754.", + version='6.6.0', + ) + return False, (_CONSTANT, arg1) - ans = visitor.Result() - ans.linear[_id] = arg1 - return False, (_LINEAR, ans) + ans = visitor.Result() + ans.linear[_id] = arg1 + return False, (_LINEAR, ans) + @staticmethod + def _before_linear(visitor, child): + var_map = visitor.var_map + var_order = visitor.var_order + next_i = len(var_order) + ans = visitor.Result() + const = 0 + linear = ans.linear + for arg in child.args: + if arg.__class__ is MonomialTermExpression: + arg1, arg2 = arg._args_ + if arg1.__class__ not in native_types: + try: + arg1 = visitor._eval_expr(arg1) + except (ValueError, ArithmeticError): + return True, None + + # Trap multiplication by 0 and nan. + if not arg1: + if arg2.fixed: + arg2 = visitor._eval_fixed(arg2) + if arg2 != arg2: + deprecation_warning( + f"Encountered {arg1}*{str(arg2.value)} in expression " + "tree. Mapping the NaN result to 0 for compatibility " + "with the lp_v1 writer. In the future, this NaN " + "will be preserved/emitted to comply with IEEE-754.", + version='6.6.0', + ) + continue -def _before_linear(visitor, child): - var_map = visitor.var_map - var_order = visitor.var_order - next_i = len(var_order) - ans = visitor.Result() - const = 0 - linear = ans.linear - for arg in child.args: - if arg.__class__ is MonomialTermExpression: - arg1, arg2 = arg._args_ - if arg1.__class__ not in native_types: + _id = id(arg2) + if _id not in var_map: + if arg2.fixed: + const += arg1 * visitor._eval_fixed(arg2) + continue + var_map[_id] = arg2 + var_order[_id] = next_i + next_i += 1 + linear[_id] = arg1 + elif _id in linear: + linear[_id] += arg1 + else: + linear[_id] = arg1 + elif arg.__class__ in native_numeric_types: + const += arg + else: try: - arg1 = visitor._eval_expr(arg1) + const += visitor._eval_expr(arg) except (ValueError, ArithmeticError): return True, None - - # Trap multiplication by 0 and nan. - if not arg1: - if arg2.fixed: - arg2 = visitor._eval_fixed(arg2) - if arg2 != arg2: - deprecation_warning( - f"Encountered {arg1}*{str(arg2.value)} in expression " - "tree. Mapping the NaN result to 0 for compatibility " - "with the lp_v1 writer. In the future, this NaN " - "will be preserved/emitted to comply with IEEE-754.", - version='6.6.0', - ) - continue - - _id = id(arg2) - if _id not in var_map: - if arg2.fixed: - const += arg1 * visitor._eval_fixed(arg2) - continue - var_map[_id] = arg2 - var_order[_id] = next_i - next_i += 1 - linear[_id] = arg1 - elif _id in linear: - linear[_id] += arg1 - else: - linear[_id] = arg1 - elif arg.__class__ in native_numeric_types: - const += arg + if linear: + ans.constant = const + return False, (_LINEAR, ans) else: - try: - const += visitor._eval_expr(arg) - except (ValueError, ArithmeticError): - return True, None - if linear: - ans.constant = const - return False, (_LINEAR, ans) - else: - return False, (_CONSTANT, const) - - -def _before_named_expression(visitor, child): - _id = id(child) - if _id in visitor.subexpression_cache: - _type, expr = visitor.subexpression_cache[_id] - if _type is _CONSTANT: - return False, (_type, expr) + return False, (_CONSTANT, const) + + @staticmethod + def _before_named_expression(visitor, child): + _id = id(child) + if _id in visitor.subexpression_cache: + _type, expr = visitor.subexpression_cache[_id] + if _type is _CONSTANT: + return False, (_type, expr) + else: + return False, (_type, expr.duplicate()) else: - return False, (_type, expr.duplicate()) - else: - return True, None - + return True, None -def _before_external(visitor, child): - ans = visitor.Result() - if all(is_fixed(arg) for arg in child.args): - try: - ans.constant = visitor._eval_expr(child) - return False, (_CONSTANT, ans) - except: - pass - ans.nonlinear = child - return False, (_GENERAL, ans) - - -def _before_general_expression(visitor, child): - return True, None - - -def _register_new_before_child_dispatcher(visitor, child): - dispatcher = _before_child_dispatcher - child_type = child.__class__ - if child_type in native_numeric_types: - if issubclass(child_type, complex): - _complex_types.add(child_type) - dispatcher[child_type] = _before_complex - else: - dispatcher[child_type] = _before_native - elif child_type in native_types: - dispatcher[child_type] = _before_invalid - elif not child.is_expression_type(): - if child.is_potentially_variable(): - dispatcher[child_type] = _before_var - else: - dispatcher[child_type] = _before_param - elif not child.is_potentially_variable(): - dispatcher[child_type] = _before_npv - # If we descend into the named expression (because of an - # evaluation error), then on the way back out, we will use - # the potentially variable handler to process the result. - pv_base_type = child.potentially_variable_base_class() - if pv_base_type not in dispatcher: + @staticmethod + def _before_external(visitor, child): + ans = visitor.Result() + if all(is_fixed(arg) for arg in child.args): try: - child.__class__ = pv_base_type - _register_new_before_child_dispatcher(visitor, child) - finally: - child.__class__ = child_type - if pv_base_type in visitor.exit_node_handlers: - visitor.exit_node_handlers[child_type] = visitor.exit_node_handlers[ - pv_base_type - ] - for args, fcn in visitor.exit_node_handlers[child_type].items(): - visitor.exit_node_dispatcher[(child_type, *args)] = fcn - elif id(child) in visitor.subexpression_cache or issubclass( - child_type, _GeneralExpressionData - ): - dispatcher[child_type] = _before_named_expression - visitor.exit_node_handlers[child_type] = visitor.exit_node_handlers[ - ScalarExpression - ] - for args, fcn in visitor.exit_node_handlers[child_type].items(): - visitor.exit_node_dispatcher[(child_type, *args)] = fcn - else: - dispatcher[child_type] = _before_general_expression - return dispatcher[child_type](visitor, child) + ans.constant = visitor._eval_expr(child) + return False, (_CONSTANT, ans) + except: + pass + ans.nonlinear = child + return False, (_GENERAL, ans) -_before_child_dispatcher = collections.defaultdict( - lambda: _register_new_before_child_dispatcher -) - -# For efficiency reasons, we will maintain a separate list of all -# complex number types -_complex_types = set((complex,)) - -# We do not support writing complex numbers out -_before_child_dispatcher[complex] = _before_complex -# Special handling for external functions: will be handled -# as terminal nodes from the point of view of the visitor -_before_child_dispatcher[ExternalFunctionExpression] = _before_external -# Special linear / summation expressions -_before_child_dispatcher[MonomialTermExpression] = _before_monomial -_before_child_dispatcher[LinearExpression] = _before_linear -_before_child_dispatcher[SumExpression] = _before_general_expression +_before_child_dispatcher = LinearBeforeChildDispatcher() # # Initialize the _exit_node_dispatcher # def _initialize_exit_node_dispatcher(exit_handlers): - # expand the knowns set of named expressiosn - for expr in _named_subexpression_types: - exit_handlers[expr] = exit_handlers[ScalarExpression] - exit_dispatcher = {} for cls, handlers in exit_handlers.items(): for args, fcn in handlers.items(): @@ -828,7 +731,9 @@ def _initialize_exit_node_dispatcher(exit_handlers): class LinearRepnVisitor(StreamBasedExpressionVisitor): Result = LinearRepn exit_node_handlers = _exit_node_handlers - exit_node_dispatcher = _initialize_exit_node_dispatcher(_exit_node_handlers) + exit_node_dispatcher = ExitNodeDispatcher( + _initialize_exit_node_dispatcher(_exit_node_handlers) + ) expand_nonlinear_products = False max_exponential_expansion = 1 diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index d3846580c52..aa820945ff9 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -11,7 +11,7 @@ import logging import os -from collections import deque, defaultdict +from collections import deque from operator import itemgetter, attrgetter, setitem from pyomo.common.backports import nullcontext @@ -69,6 +69,8 @@ from pyomo.opt import WriterFactory from pyomo.repn.util import ( + BeforeChildDispatcher, + ExitNodeDispatcher, ExprType, FileDeterminism, FileDeterminism_to_SortComponents, @@ -2230,232 +2232,162 @@ def handle_external_function_node(visitor, node, *args): return (_GENERAL, AMPLRepn(0, None, nonlin)) -_operator_handles = { - NegationExpression: handle_negation_node, - ProductExpression: handle_product_node, - DivisionExpression: handle_division_node, - PowExpression: handle_pow_node, - AbsExpression: handle_abs_node, - UnaryFunctionExpression: handle_unary_node, - Expr_ifExpression: handle_exprif_node, - EqualityExpression: handle_equality_node, - InequalityExpression: handle_inequality_node, - RangedExpression: handle_ranged_inequality_node, - _GeneralExpressionData: handle_named_expression_node, - ScalarExpression: handle_named_expression_node, - kernel.expression.expression: handle_named_expression_node, - kernel.expression.noclone: handle_named_expression_node, - # Note: objectives are special named expressions - _GeneralObjectiveData: handle_named_expression_node, - ScalarObjective: handle_named_expression_node, - kernel.objective.objective: handle_named_expression_node, - ExternalFunctionExpression: handle_external_function_node, - # These are handled explicitly in beforeChild(): - # LinearExpression: handle_linear_expression, - # SumExpression: handle_sum_expression, - # - # Note: MonomialTermExpression is only hit when processing NPV - # subexpressions that raise errors (e.g., log(0) * m.x), so no - # special processing is needed [it is just a product expression] - MonomialTermExpression: handle_product_node, -} - - -def _before_native(visitor, child): - return False, (_CONSTANT, child) - - -def _before_complex(visitor, child): - return False, (_CONSTANT, complex_number_error(child, visitor, child)) - - -def _before_string(visitor, child): - visitor.encountered_string_arguments = True - ans = AMPLRepn(child, None, None) - ans.nl = (visitor.template.string % (len(child), child), ()) - return False, (_GENERAL, ans) - - -def _before_var(visitor, child): - _id = id(child) - if _id not in visitor.var_map: - if child.fixed: - return False, (_CONSTANT, visitor._eval_fixed(child)) - visitor.var_map[_id] = child - return False, (_MONOMIAL, _id, 1) - - -def _before_param(visitor, child): - return False, (_CONSTANT, visitor._eval_fixed(child)) - - -def _before_npv(visitor, child): - try: - return False, (_CONSTANT, visitor._eval_expr(child)) - except (ValueError, ArithmeticError): - return True, None - - -def _before_monomial(visitor, child): - # - # The following are performance optimizations for common - # situations (Monomial terms and Linear expressions) - # - arg1, arg2 = child._args_ - if arg1.__class__ not in native_types: - try: - arg1 = visitor._eval_expr(arg1) - except (ValueError, ArithmeticError): - return True, None - - # Trap multiplication by 0 and nan. - if not arg1: - if arg2.fixed: - arg2 = visitor._eval_fixed(arg2) - if arg2 != arg2: - deprecation_warning( - f"Encountered {arg1}*{arg2} in expression tree. " - "Mapping the NaN result to 0 for compatibility " - "with the nl_v1 writer. In the future, this NaN " - "will be preserved/emitted to comply with IEEE-754.", - version='6.4.3', - ) - return False, (_CONSTANT, arg1) - - _id = id(arg2) - if _id not in visitor.var_map: - if arg2.fixed: - return False, (_CONSTANT, arg1 * visitor._eval_fixed(arg2)) - visitor.var_map[_id] = arg2 - return False, (_MONOMIAL, _id, arg1) - +_operator_handles = ExitNodeDispatcher( + { + NegationExpression: handle_negation_node, + ProductExpression: handle_product_node, + DivisionExpression: handle_division_node, + PowExpression: handle_pow_node, + AbsExpression: handle_abs_node, + UnaryFunctionExpression: handle_unary_node, + Expr_ifExpression: handle_exprif_node, + EqualityExpression: handle_equality_node, + InequalityExpression: handle_inequality_node, + RangedExpression: handle_ranged_inequality_node, + Expression: handle_named_expression_node, + ExternalFunctionExpression: handle_external_function_node, + # These are handled explicitly in beforeChild(): + # LinearExpression: handle_linear_expression, + # SumExpression: handle_sum_expression, + # + # Note: MonomialTermExpression is only hit when processing NPV + # subexpressions that raise errors (e.g., log(0) * m.x), so no + # special processing is needed [it is just a product expression] + MonomialTermExpression: handle_product_node, + } +) -def _before_linear(visitor, child): - # Because we are going to modify the LinearExpression in this - # walker, we need to make a copy of the arg list from the original - # expression tree. - var_map = visitor.var_map - const = 0 - linear = {} - for arg in child.args: - if arg.__class__ is MonomialTermExpression: - arg1, arg2 = arg._args_ - if arg1.__class__ not in native_types: - try: - arg1 = visitor._eval_expr(arg1) - except (ValueError, ArithmeticError): - return True, None - # Trap multiplication by 0 and nan. - if not arg1: - if arg2.fixed: - arg2 = visitor._eval_fixed(arg2) - if arg2 != arg2: - deprecation_warning( - f"Encountered {arg1}*{str(arg2.value)} in expression " - "tree. Mapping the NaN result to 0 for compatibility " - "with the nl_v1 writer. In the future, this NaN " - "will be preserved/emitted to comply with IEEE-754.", - version='6.4.3', - ) - continue +class AMPLBeforeChildDispatcher(BeforeChildDispatcher): + operator_handles = _operator_handles - _id = id(arg2) - if _id not in var_map: - if arg2.fixed: - const += arg1 * visitor._eval_fixed(arg2) - continue - var_map[_id] = arg2 - linear[_id] = arg1 - elif _id in linear: - linear[_id] += arg1 - else: - linear[_id] = arg1 - elif arg.__class__ in native_types: - const += arg - else: + def __init__(self): + # Special linear / summation expressions + self[MonomialTermExpression] = self._before_monomial + self[LinearExpression] = self._before_linear + self[SumExpression] = self._before_general_expression + + @staticmethod + def _before_string(visitor, child): + visitor.encountered_string_arguments = True + ans = AMPLRepn(child, None, None) + ans.nl = (visitor.template.string % (len(child), child), ()) + return False, (_GENERAL, ans) + + @staticmethod + def _before_var(visitor, child): + _id = id(child) + if _id not in visitor.var_map: + if child.fixed: + return False, (_CONSTANT, visitor._eval_fixed(child)) + visitor.var_map[_id] = child + return False, (_MONOMIAL, _id, 1) + + @staticmethod + def _before_monomial(visitor, child): + # + # The following are performance optimizations for common + # situations (Monomial terms and Linear expressions) + # + arg1, arg2 = child._args_ + if arg1.__class__ not in native_types: try: - const += visitor._eval_expr(arg) + arg1 = visitor._eval_expr(arg1) except (ValueError, ArithmeticError): return True, None - if linear: - return False, (_GENERAL, AMPLRepn(const, linear, None)) - else: - return False, (_CONSTANT, const) - + # Trap multiplication by 0 and nan. + if not arg1: + if arg2.fixed: + arg2 = visitor._eval_fixed(arg2) + if arg2 != arg2: + deprecation_warning( + f"Encountered {arg1}*{arg2} in expression tree. " + "Mapping the NaN result to 0 for compatibility " + "with the nl_v1 writer. In the future, this NaN " + "will be preserved/emitted to comply with IEEE-754.", + version='6.4.3', + ) + return False, (_CONSTANT, arg1) + + _id = id(arg2) + if _id not in visitor.var_map: + if arg2.fixed: + return False, (_CONSTANT, arg1 * visitor._eval_fixed(arg2)) + visitor.var_map[_id] = arg2 + return False, (_MONOMIAL, _id, arg1) + + @staticmethod + def _before_linear(visitor, child): + # Because we are going to modify the LinearExpression in this + # walker, we need to make a copy of the arg list from the original + # expression tree. + var_map = visitor.var_map + const = 0 + linear = {} + for arg in child.args: + if arg.__class__ is MonomialTermExpression: + arg1, arg2 = arg._args_ + if arg1.__class__ not in native_types: + try: + arg1 = visitor._eval_expr(arg1) + except (ValueError, ArithmeticError): + return True, None + + # Trap multiplication by 0 and nan. + if not arg1: + if arg2.fixed: + arg2 = visitor._eval_fixed(arg2) + if arg2 != arg2: + deprecation_warning( + f"Encountered {arg1}*{str(arg2.value)} in expression " + "tree. Mapping the NaN result to 0 for compatibility " + "with the nl_v1 writer. In the future, this NaN " + "will be preserved/emitted to comply with IEEE-754.", + version='6.4.3', + ) + continue -def _before_named_expression(visitor, child): - _id = id(child) - if _id in visitor.subexpression_cache: - obj, repn, info = visitor.subexpression_cache[_id] - if info[2]: - if repn.linear: - return False, (_MONOMIAL, next(iter(repn.linear)), 1) + _id = id(arg2) + if _id not in var_map: + if arg2.fixed: + const += arg1 * visitor._eval_fixed(arg2) + continue + var_map[_id] = arg2 + linear[_id] = arg1 + elif _id in linear: + linear[_id] += arg1 + else: + linear[_id] = arg1 + elif arg.__class__ in native_types: + const += arg else: - return False, (_CONSTANT, repn.const) - return False, (_GENERAL, repn.duplicate()) - else: - return True, None - - -def _before_general_expression(visitor, child): - return True, None - + try: + const += visitor._eval_expr(arg) + except (ValueError, ArithmeticError): + return True, None -def _register_new_before_child_handler(visitor, child): - handlers = _before_child_handlers - child_type = child.__class__ - if child_type in native_numeric_types: - if isinstance(child_type, complex): - _complex_types.add(child_type) - handlers[child_type] = _before_complex + if linear: + return False, (_GENERAL, AMPLRepn(const, linear, None)) else: - handlers[child_type] = _before_native - elif issubclass(child_type, str): - handlers[child_type] = _before_string - elif child_type in native_types: - handlers[child_type] = _before_native - elif not child.is_expression_type(): - if child.is_potentially_variable(): - handlers[child_type] = _before_var - else: - handlers[child_type] = _before_param - elif not child.is_potentially_variable(): - handlers[child_type] = _before_npv - # If we descend into the named expression (because of an - # evaluation error), then on the way back out, we will use - # the potentially variable handler to process the result. - pv_base_type = child.potentially_variable_base_class() - if pv_base_type not in handlers: - try: - child.__class__ = pv_base_type - _register_new_before_child_handler(visitor, child) - finally: - child.__class__ = child_type - if pv_base_type in _operator_handles: - _operator_handles[child_type] = _operator_handles[pv_base_type] - elif id(child) in visitor.subexpression_cache or issubclass( - child_type, _GeneralExpressionData - ): - handlers[child_type] = _before_named_expression - _operator_handles[child_type] = handle_named_expression_node - else: - handlers[child_type] = _before_general_expression - return handlers[child_type](visitor, child) + return False, (_CONSTANT, const) + @staticmethod + def _before_named_expression(visitor, child): + _id = id(child) + if _id in visitor.subexpression_cache: + obj, repn, info = visitor.subexpression_cache[_id] + if info[2]: + if repn.linear: + return False, (_MONOMIAL, next(iter(repn.linear)), 1) + else: + return False, (_CONSTANT, repn.const) + return False, (_GENERAL, repn.duplicate()) + else: + return True, None -_before_child_handlers = defaultdict(lambda: _register_new_before_child_handler) -_complex_types = set((complex,)) -_before_child_handlers[complex] = _before_complex -for _type in native_types: - if issubclass(_type, str): - _before_child_handlers[_type] = _before_string -# Special linear / summation expressions -_before_child_handlers[MonomialTermExpression] = _before_monomial -_before_child_handlers[LinearExpression] = _before_linear -_before_child_handlers[SumExpression] = _before_general_expression +_before_child_handlers = AMPLBeforeChildDispatcher() class AMPLRepnVisitor(StreamBasedExpressionVisitor): diff --git a/pyomo/repn/quadratic.py b/pyomo/repn/quadratic.py index 4195d95e2e7..9ae29168b05 100644 --- a/pyomo/repn/quadratic.py +++ b/pyomo/repn/quadratic.py @@ -28,7 +28,7 @@ InequalityExpression, RangedExpression, ) -from pyomo.core.base.expression import ScalarExpression +from pyomo.core.base.expression import Expression from . import linear from .linear import _merge_dict, to_expression @@ -339,7 +339,7 @@ def _handle_product_nonlinear(visitor, node, arg1, arg2): # # NAMED EXPRESSION handlers # -_exit_node_handlers[ScalarExpression][(_QUADRATIC,)] = linear._handle_named_ANY +_exit_node_handlers[Expression][(_QUADRATIC,)] = linear._handle_named_ANY # # EXPR_IF handlers @@ -399,5 +399,7 @@ def _handle_product_nonlinear(visitor, node, arg1, arg2): class QuadraticRepnVisitor(linear.LinearRepnVisitor): Result = QuadraticRepn exit_node_handlers = _exit_node_handlers - exit_node_dispatcher = linear._initialize_exit_node_dispatcher(_exit_node_handlers) + exit_node_dispatcher = linear.ExitNodeDispatcher( + linear._initialize_exit_node_dispatcher(_exit_node_handlers) + ) max_exponential_expansion = 2 diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index c660abdf7a1..37055156a44 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -9,7 +9,9 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +import collections import enum +import functools import itertools import logging import operator @@ -18,6 +20,7 @@ from pyomo.common.collections import Sequence, ComponentMap from pyomo.common.deprecation import deprecation_warning from pyomo.common.errors import DeveloperError, InvalidValueError +from pyomo.common.numeric_types import native_types, native_numeric_types from pyomo.core.pyomoobject import PyomoObject from pyomo.core.base import ( Var, @@ -26,11 +29,13 @@ Objective, Block, Constraint, + Expression, Suffix, SortComponents, ) from pyomo.core.base.component import ActiveComponent -from pyomo.core.expr.numvalue import native_numeric_types, is_fixed, value +from pyomo.core.base.expression import _GeneralExpressionData +from pyomo.core.expr.numvalue import is_fixed, value import pyomo.core.expr as EXPR import pyomo.core.kernel as kernel @@ -222,6 +227,138 @@ def __rpow__(self, other): return self._op(operator.pow, other, self) +_CONSTANT = ExprType.CONSTANT + + +class BeforeChildDispatcher(collections.defaultdict): + def __missing__(self, key): + return self.register_child_dispatcher + + def register_child_dispatcher(self, visitor, child): + child_type = child.__class__ + if issubclass(child_type, complex): + self[child_type] = self._before_complex + elif child_type in native_numeric_types: + self[child_type] = self._before_native + elif issubclass(child_type, str): + self[child_type] = self._before_string + elif child_type in native_types: + self[child_type] = self._before_invalid + elif not hasattr(child, 'is_expression_type'): + if check_if_numeric_type(child): + self[child_type] = self._before_native + else: + self[child_type] = self._before_invalid + elif not child.is_expression_type(): + if child.is_potentially_variable(): + self[child_type] = self._before_var + else: + self[child_type] = self._before_param + elif not child.is_potentially_variable(): + self[child_type] = self._before_npv + pv_base_type = child.potentially_variable_base_class() + if pv_base_type not in self: + try: + child.__class__ = pv_base_type + self.register_child_handler(visitor, child) + finally: + child.__class__ = child_type + elif issubclass(child_type, _GeneralExpressionData): + self[child_type] = self._before_named_expression + else: + self[child_type] = self._before_general_expression + return self[child_type](visitor, child) + + @staticmethod + def _before_general_expression(visitor, child): + return True, None + + @staticmethod + def _before_native(visitor, child): + return False, (_CONSTANT, child) + + @staticmethod + def _before_complex(visitor, child): + return False, (_CONSTANT, complex_number_error(child, visitor, child)) + + @staticmethod + def _before_invalid(visitor, child): + return False, ( + _CONSTANT, + InvalidNumber(child, "'{child}' is not a valid numeric type"), + ) + + @staticmethod + def _before_string(visitor, child): + return False, ( + _CONSTANT, + InvalidNumber(child, "'{child}' is not a valid numeric type"), + ) + + @staticmethod + def _before_npv(visitor, child): + try: + return False, (_CONSTANT, visitor._eval_expr(child)) + except (ValueError, ArithmeticError): + return True, None + + @staticmethod + def _before_param(visitor, child): + return False, (_CONSTANT, visitor._eval_fixed(child)) + + # + # The following methods must be defined by derivative classes + # + + # @staticmethod + # def _before_var(visitor, child): + # pass + + # @staticmethod + # def _before_named_expression(visitor, child): + # pass + + +class ExitNodeDispatcher(collections.defaultdict): + _named_subexpression_types = ( + _GeneralExpressionData, + kernel.expression.expression, + kernel.objective.objective, + ) + + def __init__(self, *args, **kwargs): + super().__init__(None, *args, **kwargs) + + def __missing__(self, key): + return functools.partial(self.register_dispatcher, key=key) + + def register_dispatcher(self, visitor, node, *data, key=None): + node_type = node.__class__ + if ( + issubclass(node_type, self._named_subexpression_types) + or node_type is kernel.expression.noclone + ): + base_type = Expression + elif not node.is_potentially_variable(): + base_type = node.potentially_variable_base_class() + else: + raise DeveloperError( + f"Unexpected expression node type '{type(node).__name__}' " + "found when walking expression tree." + ) + if isinstance(key, tuple): + base_key = (base_type,) + key[1:] + else: + base_key = base_type + if base_key not in self: + raise DeveloperError( + f"Base expression key '{key}' not found when inserting dispatcher " + f"for node '{type(node).__class__}' when walking expression tree." + ) + self[key] = self[base_key] + return self[key](visitor, node, *data) + + def apply_node_operation(node, args): try: ans = node._apply_operation(args) From d4434bdb54307d49f8f96ce45d5fdb5a2692d708 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Sep 2023 05:15:46 -0600 Subject: [PATCH 0102/1204] Standardize handling of named expression types --- pyomo/repn/util.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index 37055156a44..074b5b8cb32 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -48,6 +48,11 @@ EXPR.LinearExpression, EXPR.NPV_SumExpression, } +_named_subexpression_types = ( + _GeneralExpressionData, + kernel.expression.expression, + kernel.objective.objective, +) HALT_ON_EVALUATION_ERROR = False nan = float('nan') @@ -263,7 +268,10 @@ def register_child_dispatcher(self, visitor, child): self.register_child_handler(visitor, child) finally: child.__class__ = child_type - elif issubclass(child_type, _GeneralExpressionData): + elif ( + issubclass(child_type, _named_subexpression_types): + or node_type is kernel.expression.noclone + ): self[child_type] = self._before_named_expression else: self[child_type] = self._before_general_expression @@ -333,10 +341,9 @@ def __missing__(self, key): return functools.partial(self.register_dispatcher, key=key) def register_dispatcher(self, visitor, node, *data, key=None): - node_type = node.__class__ if ( - issubclass(node_type, self._named_subexpression_types) - or node_type is kernel.expression.noclone + isinstance(node, _named_subexpression_types) + or type(node) is kernel.expression.noclone ): base_type = Expression elif not node.is_potentially_variable(): From 1a3e34a2dfeb94f7e400c340bb6bbde2a41ca2f0 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Sep 2023 05:16:08 -0600 Subject: [PATCH 0103/1204] Add docs, improve method naming --- pyomo/repn/util.py | 50 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index 074b5b8cb32..cd6a4c86704 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -236,11 +236,27 @@ def __rpow__(self, other): class BeforeChildDispatcher(collections.defaultdict): + """Dispatcher for handling the :py:class:`StreamBasedExpressionVisitor` + `beforeChild` callback + + This dispatcher implements a specialization of :py:`defaultdict` + that supports automatic type registration. Any missing types will + return the :py:meth:`register_dispatcher` method, which (when called + as a callback) will interrogate the type, identify the appropriate + callback, add the callback to the dict, and return the result of + calling the callback. As the callback is added to the dict, no type + will incur the overhead of `register_dispatcher` more than once. + + Note that all dispatchers are implemented as `staticmethod` + functions to avoid the (unnecessary) overhead of binding to the + dispatcher object. + + """ def __missing__(self, key): - return self.register_child_dispatcher + return self.register_dispatcher - def register_child_dispatcher(self, visitor, child): - child_type = child.__class__ + def register_dispatcher(self, visitor, child): + child_type = type(child) if issubclass(child_type, complex): self[child_type] = self._before_complex elif child_type in native_numeric_types: @@ -315,7 +331,10 @@ def _before_param(visitor, child): return False, (_CONSTANT, visitor._eval_fixed(child)) # - # The following methods must be defined by derivative classes + # The following methods must be defined by derivative classes (along + # with any other special-case handling they want to implement; + # usually including handling for Monomial, Linear, and + # ExternalFunction # # @staticmethod @@ -328,11 +347,24 @@ def _before_param(visitor, child): class ExitNodeDispatcher(collections.defaultdict): - _named_subexpression_types = ( - _GeneralExpressionData, - kernel.expression.expression, - kernel.objective.objective, - ) + """Dispatcher for handling the :py:class:`StreamBasedExpressionVisitor` + `exitNode` callback + + This dispatcher implements a specialization of :py:`defaultdict` + that supports automatic type registration. Any missing types will + return the :py:meth:`register_dispatcher` method, which (when called + as a callback) will interrogate the type, identify the appropriate + callback, add the callback to the dict, and return the result of + calling the callback. As the callback is added to the dict, no type + will incur the overhead of `register_dispatcher` more than once. + + Note that in this case, the client is expected to register all + non-NPV expression types. The auto-registration is designed to only + handle two cases: + - Auto-detection of user-defined Named Expression types + - Automatic mappimg of NPV expressions to their equivalent non-NPV handlers + + """ def __init__(self, *args, **kwargs): super().__init__(None, *args, **kwargs) From 10a3b95b2f07fdc1feab09751c40576740ecc1c6 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Sep 2023 05:21:22 -0600 Subject: [PATCH 0104/1204] NFC: remove unreachable code; update comments --- pyomo/repn/plugins/nl_writer.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index aa820945ff9..386bc1e2d9d 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -591,7 +591,6 @@ def write(self, model): n_ranges += 1 elif _type == 3: # and self.config.skip_trivial_constraints: continue - pass # FIXME: this is a HACK to be compatible with the NLv1 # writer. In the future, this writer should be expanded to # look for and process Complementarity components (assuming @@ -613,7 +612,8 @@ def write(self, model): linear_cons.append((con, expr, _type, lb, ub)) elif not self.config.skip_trivial_constraints: linear_cons.append((con, expr, _type, lb, ub)) - else: # constant constraint and skip_trivial_constraints + else: + # constant constraint and skip_trivial_constraints # # TODO: skip_trivial_constraints should be an # enum that also accepts "Exception" so that @@ -1323,7 +1323,7 @@ def write(self, model): for row_idx, info in enumerate(constraints): linear = info[1].linear # ASL will fail on "J 0", so if there are no coefficients - # (i.e., a constant objective), then skip this entry + # (e.g., a nonlinear-only constraint), then skip this entry if not linear: continue ostream.write(f'J{row_idx} {len(linear)}{row_comments[row_idx]}\n') @@ -1339,7 +1339,7 @@ def write(self, model): for obj_idx, info in enumerate(objectives): linear = info[1].linear # ASL will fail on "G 0", so if there are no coefficients - # (i.e., a constant objective), then skip this entry + # (e.g., a constant objective), then skip this entry if not linear: continue ostream.write(f'G{obj_idx} {len(linear)}{row_comments[obj_idx + n_cons]}\n') @@ -2544,7 +2544,6 @@ def finalizeResult(self, result): # variables are not accidentally re-characterized as # nonlinear. pass - # ans.nonlinear = orig.nonlinear ans.nl = None if ans.nonlinear.__class__ is list: @@ -2552,8 +2551,8 @@ def finalizeResult(self, result): if not ans.linear: ans.linear = {} - linear = ans.linear if ans.mult != 1: + linear = ans.linear mult, ans.mult = ans.mult, 1 ans.const *= mult if linear: From ea1dc2929d80ef4c7e75c44d5f74f69b535db6b6 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Sep 2023 05:42:36 -0600 Subject: [PATCH 0105/1204] Fix typos --- pyomo/repn/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index cd6a4c86704..67af6bb4bcb 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -285,8 +285,8 @@ def register_dispatcher(self, visitor, child): finally: child.__class__ = child_type elif ( - issubclass(child_type, _named_subexpression_types): - or node_type is kernel.expression.noclone + issubclass(child_type, _named_subexpression_types) + or child_type is kernel.expression.noclone ): self[child_type] = self._before_named_expression else: From 8504c05a0699bcb7f0d5740c59055e7543b7094b Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Sep 2023 06:55:24 -0600 Subject: [PATCH 0106/1204] Split complex types out from native_numeric_types --- pyomo/common/numeric_types.py | 33 ++++++++++++++++++++++++++------- pyomo/repn/linear.py | 8 +++++--- pyomo/repn/plugins/nl_writer.py | 13 +++++++++---- pyomo/repn/util.py | 11 ++++++----- pyomo/util/calc_var_value.py | 16 +++++++++------- 5 files changed, 55 insertions(+), 26 deletions(-) diff --git a/pyomo/common/numeric_types.py b/pyomo/common/numeric_types.py index dbad3ef0853..d2b75152140 100644 --- a/pyomo/common/numeric_types.py +++ b/pyomo/common/numeric_types.py @@ -41,9 +41,14 @@ #: Python set used to identify numeric constants. This set includes #: native Python types as well as numeric types from Python packages #: like numpy, which may be registered by users. -native_numeric_types = {int, float, complex} +#: +#: Note that :data:`native_numeric_types` does NOT include +#: :py:`complex`, as that is not a valid constant in Pyomo numeric +#: expressions. +native_numeric_types = {int, float} native_integer_types = {int} native_logical_types = {bool} +native_complex_types = {complex} pyomo_constant_types = set() # includes NumericConstant _native_boolean_types = {int, bool, str, bytes} @@ -64,11 +69,12 @@ #: like numpy. #: #: :data:`native_types` = :data:`native_numeric_types ` + { str } -native_types = set([bool, str, type(None), slice, bytes]) +native_types = {bool, str, type(None), slice, bytes} native_types.update(native_numeric_types) native_types.update(native_integer_types) -native_types.update(_native_boolean_types) +native_types.update(native_complex_types) native_types.update(native_logical_types) +native_types.update(_native_boolean_types) nonpyomo_leaf_types.update(native_types) @@ -117,11 +123,24 @@ def RegisterBooleanType(new_type): nonpyomo_leaf_types.add(new_type) -def RegisterLogicalType(new_type): +def RegisterComplexType(new_type): + """A utility function for updating the set of types that are recognized + as handling complex values. This function does not add the type + with the integer or numeric sets. + + + The argument should be a class (e.g., numpy.complex_). + """ - A utility function for updating the set of types that are - recognized as handling boolean values. This function does not - register the type of integer or numeric. + native_types.add(new_type) + native_complex_types.add(new_type) + nonpyomo_leaf_types.add(new_type) + + +def RegisterLogicalType(new_type): + """A utility function for updating the set of types that are recognized + as handling boolean values. This function does not add the type + with the integer or numeric sets. The argument should be a class (e.g., numpy.bool_). """ diff --git a/pyomo/repn/linear.py b/pyomo/repn/linear.py index cd962b4f3ac..b0b1640ea98 100644 --- a/pyomo/repn/linear.py +++ b/pyomo/repn/linear.py @@ -15,7 +15,7 @@ from itertools import filterfalse from pyomo.common.deprecation import deprecation_warning -from pyomo.common.numeric_types import native_types, native_numeric_types +from pyomo.common.numeric_types import native_types, native_numeric_types, native_complex_types from pyomo.core.expr.numeric_expr import ( NegationExpression, ProductExpression, @@ -332,7 +332,7 @@ def _handle_division_nonlinear(visitor, node, arg1, arg2): def _handle_pow_constant_constant(visitor, node, *args): arg1, arg2 = args ans = apply_node_operation(node, (arg1[1], arg2[1])) - if ans.__class__ in _complex_types: + if ans.__class__ in native_complex_types: ans = complex_number_error(ans, visitor, node) return _CONSTANT, ans @@ -381,7 +381,7 @@ def _handle_pow_nonlinear(visitor, node, arg1, arg2): def _handle_unary_constant(visitor, node, arg): ans = apply_node_operation(node, (arg[1],)) # Unary includes sqrt() which can return complex numbers - if ans.__class__ in _complex_types: + if ans.__class__ in native_complex_types: ans = complex_number_error(ans, visitor, node) return _CONSTANT, ans @@ -754,6 +754,8 @@ def _eval_fixed(self, obj): ) if ans.__class__ is InvalidNumber: return ans + elif ans.__class__ in native_complex_types: + return complex_number_error(ans, self, obj) else: # It is possible to get other non-numeric types. Most # common are bool and 1-element numpy.array(). We will diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 386bc1e2d9d..fe7a7436398 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -24,6 +24,12 @@ from pyomo.common.deprecation import deprecation_warning from pyomo.common.errors import DeveloperError from pyomo.common.gc_manager import PauseGC +from pyomo.common.numeric_types import ( + native_complex_types, + native_numeric_types, + native_types, + value, +) from pyomo.common.timing import TicTocTimer from pyomo.core.expr import ( @@ -41,9 +47,6 @@ RangedExpression, Expr_ifExpression, ExternalFunctionExpression, - native_types, - native_numeric_types, - value, ) from pyomo.core.expr.visitor import StreamBasedExpressionVisitor, _EvaluationVisitor from pyomo.core.base import ( @@ -1985,7 +1988,7 @@ def handle_pow_node(visitor, node, arg1, arg2): if arg2[0] is _CONSTANT: if arg1[0] is _CONSTANT: ans = apply_node_operation(node, (arg1[1], arg2[1])) - if ans.__class__ in _complex_types: + if ans.__class__ in native_complex_types: ans = complex_number_error(ans, visitor, node) return _CONSTANT, ans elif not arg2[1]: @@ -2425,6 +2428,8 @@ def _eval_fixed(self, obj): ) if ans.__class__ is InvalidNumber: return ans + elif ans.__class__ in native_complex_types: + return complex_number_error(ans, self, obj) else: # It is possible to get other non-numeric types. Most # common are bool and 1-element numpy.array(). We will diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index 67af6bb4bcb..a98ab70c902 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -20,7 +20,7 @@ from pyomo.common.collections import Sequence, ComponentMap from pyomo.common.deprecation import deprecation_warning from pyomo.common.errors import DeveloperError, InvalidValueError -from pyomo.common.numeric_types import native_types, native_numeric_types +from pyomo.common.numeric_types import native_types, native_numeric_types, native_complex_types from pyomo.core.pyomoobject import PyomoObject from pyomo.core.base import ( Var, @@ -257,14 +257,15 @@ def __missing__(self, key): def register_dispatcher(self, visitor, child): child_type = type(child) - if issubclass(child_type, complex): - self[child_type] = self._before_complex - elif child_type in native_numeric_types: + if child_type in native_numeric_types: self[child_type] = self._before_native elif issubclass(child_type, str): self[child_type] = self._before_string elif child_type in native_types: - self[child_type] = self._before_invalid + if issubclass(child_type, tuple(native_complex_types)): + self[child_type] = self._before_complex + else: + self[child_type] = self._before_invalid elif not hasattr(child, 'is_expression_type'): if check_if_numeric_type(child): self[child_type] = self._before_native diff --git a/pyomo/util/calc_var_value.py b/pyomo/util/calc_var_value.py index 81bbd285dd2..03b5b4d1de3 100644 --- a/pyomo/util/calc_var_value.py +++ b/pyomo/util/calc_var_value.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ from pyomo.common.errors import IterationLimitError -from pyomo.core.expr.numvalue import native_numeric_types, value, is_fixed +from pyomo.common.numeric_types import native_numeric_types, native_complex_types, value, is_fixed from pyomo.core.expr.calculus.derivatives import differentiate from pyomo.core.base.constraint import Constraint, _ConstraintData @@ -92,6 +92,9 @@ def calculate_variable_from_constraint( if lower != upper: raise ValueError(f"Constraint '{constraint}' must be an equality constraint") + _invalid_types = set(native_complex_types) + _invalid_types.add(type(None)) + if variable.value is None: # Note that we use "skip_validation=True" here as well, as the # variable domain may not admit the calculated initial guesses, @@ -151,7 +154,7 @@ def calculate_variable_from_constraint( # to using Newton's method. residual_2 = None - if residual_2 is not None and type(residual_2) is not complex: + if residual_2.__class__ not in _invalid_types: # if the variable appears linearly with a coefficient of 1, then we # are done if abs(residual_2 - upper) < eps: @@ -168,8 +171,7 @@ def calculate_variable_from_constraint( variable.set_value(-intercept / slope, skip_validation=True) body_val = value(body, exception=False) if ( - body_val is not None - and body_val.__class__ is not complex + body_val.__class__ not in _invalid_types and abs(body_val - upper) < eps ): # Re-set the variable value to trigger any warnings WRT @@ -234,7 +236,7 @@ def calculate_variable_from_constraint( xk = value(variable) try: fk = value(expr) - if type(fk) is complex: + if fk.__class__ in _invalid_types and fk is not None: raise ValueError("Complex numbers are not allowed in Newton's method.") except: # We hit numerical problems with the last step (possible if @@ -275,7 +277,7 @@ def calculate_variable_from_constraint( # HACK for Python3 support, pending resolution of #879 # Issue #879 also pertains to other checks for "complex" # in this method. - if type(fkp1) is complex: + if fkp1.__class__ in _invalid_types: # We cannot perform computations on complex numbers fkp1 = None if fkp1 is not None and fkp1**2 < c1 * fk**2: @@ -289,7 +291,7 @@ def calculate_variable_from_constraint( if alpha <= alpha_min: residual = value(expr, exception=False) - if residual is None or type(residual) is complex: + if residual.__class__ in _invalid_types: residual = "{function evaluation error}" raise IterationLimitError( f"Linesearch iteration limit reached solving for " From 9143204011f14b8ba87be1ee820c023d506ea7ee Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Sep 2023 06:57:39 -0600 Subject: [PATCH 0107/1204] Update list of numpy types --- pyomo/common/dependencies.py | 6 +++++- pyomo/core/kernel/register_numpy_types.py | 2 +- pyomo/core/tests/unit/test_kernel_register_numpy_types.py | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 4ddbe1c9ee8..5d0da11765a 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -778,6 +778,8 @@ def _finalize_matplotlib(module, available): def _finalize_numpy(np, available): if not available: return + # Register ndarray as a native type to prevent 1-element ndarrays + # from accidentally registering ndarray as a native_numeric_type. numeric_types.native_types.add(np.ndarray) numeric_types.RegisterLogicalType(np.bool_) for t in ( @@ -798,12 +800,14 @@ def _finalize_numpy(np, available): # registration here (to bypass the deprecation warning) until we # finally remove all support for it numeric_types._native_boolean_types.add(t) - for t in (np.float_, np.float16, np.float32, np.float64): + for t in (np.float_, np.float16, np.float32, np.float64, np.float128): numeric_types.RegisterNumericType(t) # We have deprecated RegisterBooleanType, so we will mock up the # registration here (to bypass the deprecation warning) until we # finally remove all support for it numeric_types._native_boolean_types.add(t) + for t in (np.complex_, np.complex64, np.complex128, np.complex256): + numeric_types.RegisterComplexType(t) dill, dill_available = attempt_import('dill') diff --git a/pyomo/core/kernel/register_numpy_types.py b/pyomo/core/kernel/register_numpy_types.py index b9205930512..0570684e7e3 100644 --- a/pyomo/core/kernel/register_numpy_types.py +++ b/pyomo/core/kernel/register_numpy_types.py @@ -66,7 +66,7 @@ numpy_complex_names = [] numpy_complex = [] if _has_numpy: - numpy_complex_names.extend(('complex_', 'complex64', 'complex128')) + numpy_complex_names.extend(('complex_', 'complex64', 'complex128', 'complex256')) for _type_name in numpy_complex_names: try: _type = getattr(numpy, _type_name) diff --git a/pyomo/core/tests/unit/test_kernel_register_numpy_types.py b/pyomo/core/tests/unit/test_kernel_register_numpy_types.py index 0a9e3ab08f9..1aef71bf632 100644 --- a/pyomo/core/tests/unit/test_kernel_register_numpy_types.py +++ b/pyomo/core/tests/unit/test_kernel_register_numpy_types.py @@ -38,12 +38,14 @@ numpy_float_names.append('float16') numpy_float_names.append('float32') numpy_float_names.append('float64') + numpy_float_names.append('float128') # Complex numpy_complex_names = [] if numpy_available: numpy_complex_names.append('complex_') numpy_complex_names.append('complex64') numpy_complex_names.append('complex128') + numpy_complex_names.append('complex256') class TestNumpyRegistration(unittest.TestCase): From e0729dbcaf074ffcfe071d809c1a0e5c89ae2e85 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Sep 2023 07:02:04 -0600 Subject: [PATCH 0108/1204] Remove repeated constant resolution code --- pyomo/repn/linear.py | 63 +++++++++------------------------ pyomo/repn/plugins/nl_writer.py | 59 +++++++----------------------- pyomo/repn/util.py | 4 +-- 3 files changed, 31 insertions(+), 95 deletions(-) diff --git a/pyomo/repn/linear.py b/pyomo/repn/linear.py index b0b1640ea98..e1180256c52 100644 --- a/pyomo/repn/linear.py +++ b/pyomo/repn/linear.py @@ -583,7 +583,7 @@ def _before_var(visitor, child): _id = id(child) if _id not in visitor.var_map: if child.fixed: - return False, (_CONSTANT, visitor._eval_fixed(child)) + return False, (_CONSTANT, visitor.handle_constant(child.value, child)) visitor.var_map[_id] = child visitor.var_order[_id] = len(visitor.var_order) ans = visitor.Result() @@ -599,7 +599,7 @@ def _before_monomial(visitor, child): arg1, arg2 = child._args_ if arg1.__class__ not in native_types: try: - arg1 = visitor._eval_expr(arg1) + arg1 = visitor.handle_constant(visitor.evaluate(arg1), arg1) except (ValueError, ArithmeticError): return True, None @@ -610,14 +610,14 @@ def _before_monomial(visitor, child): _id = id(arg2) if _id not in visitor.var_map: if arg2.fixed: - return False, (_CONSTANT, arg1 * visitor._eval_fixed(arg2)) + return False, (_CONSTANT, arg1 * visitor.handle_constant(arg2.value, arg2)) visitor.var_map[_id] = arg2 visitor.var_order[_id] = len(visitor.var_order) # Trap multiplication by 0 and nan. if not arg1: if arg2.fixed: - arg2 = visitor._eval_fixed(arg2) + arg2 = visitor.handle_constant(arg2.value, arg2) if arg2 != arg2: deprecation_warning( f"Encountered {arg1}*{str(arg2.value)} in expression " @@ -645,14 +645,14 @@ def _before_linear(visitor, child): arg1, arg2 = arg._args_ if arg1.__class__ not in native_types: try: - arg1 = visitor._eval_expr(arg1) + arg1 = visitor.handle_constant(visitor.evaluate(arg1), arg1) except (ValueError, ArithmeticError): return True, None # Trap multiplication by 0 and nan. if not arg1: if arg2.fixed: - arg2 = visitor._eval_fixed(arg2) + arg2 = visitor.handle_constant(arg2.value, arg2) if arg2 != arg2: deprecation_warning( f"Encountered {arg1}*{str(arg2.value)} in expression " @@ -666,7 +666,7 @@ def _before_linear(visitor, child): _id = id(arg2) if _id not in var_map: if arg2.fixed: - const += arg1 * visitor._eval_fixed(arg2) + const += arg1 * visitor.handle_constant(arg2.value, arg2) continue var_map[_id] = arg2 var_order[_id] = next_i @@ -680,7 +680,7 @@ def _before_linear(visitor, child): const += arg else: try: - const += visitor._eval_expr(arg) + const += visitor.handle_constant(visitor.evaluate(arg), arg) except (ValueError, ArithmeticError): return True, None if linear: @@ -706,7 +706,7 @@ def _before_external(visitor, child): ans = visitor.Result() if all(is_fixed(arg) for arg in child.args): try: - ans.constant = visitor._eval_expr(child) + ans.constant = visitor.handle_constant(visitor.evaluate(child), child) return False, (_CONSTANT, ans) except: pass @@ -743,14 +743,14 @@ def __init__(self, subexpression_cache, var_map, var_order): self.var_map = var_map self.var_order = var_order self._eval_expr_visitor = _EvaluationVisitor(True) + self.evaluate = self._eval_expr_visitor.dfs_postorder_stack - def _eval_fixed(self, obj): - ans = obj.value + def handle_constant(self, ans, obj): if ans.__class__ not in native_numeric_types: # None can be returned from uninitialized Var/Param objects if ans is None: return InvalidNumber( - None, f"'{obj}' contains a nonnumeric value '{ans}'" + None, f"'{obj}' evaluated to a nonnumeric value '{ans}'" ) if ans.__class__ is InvalidNumber: return ans @@ -769,43 +769,12 @@ def _eval_fixed(self, obj): ans = float(ans) except: return InvalidNumber( - ans, f"'{obj}' contains a nonnumeric value '{ans}'" + ans, f"'{obj}' evaluated to a nonnumeric value '{ans}'" ) if ans != ans: - return InvalidNumber(nan, f"'{obj}' contains a nonnumeric value '{ans}'") - if ans.__class__ in _complex_types: - return complex_number_error(ans, self, obj) - return ans - - def _eval_expr(self, expr): - ans = self._eval_expr_visitor.dfs_postorder_stack(expr) - if ans.__class__ not in native_numeric_types: - # None can be returned from uninitialized Expression objects - if ans is None: - return InvalidNumber( - ans, f"'{expr}' evaluated to nonnumeric value '{ans}'" - ) - if ans.__class__ is InvalidNumber: - return ans - else: - # It is possible to get other non-numeric types. Most - # common are bool and 1-element numpy.array(). We will - # attempt to convert the value to a float before - # proceeding. - # - # TODO: we should check bool and warn/error (while bool is - # convertible to float in Python, they have very - # different semantic meanings in Pyomo). - try: - ans = float(ans) - except: - return InvalidNumber( - ans, f"'{expr}' evaluated to nonnumeric value '{ans}'" - ) - if ans != ans: - return InvalidNumber(ans, f"'{expr}' evaluated to nonnumeric value '{ans}'") - if ans.__class__ in _complex_types: - return complex_number_error(ans, self, expr) + return InvalidNumber( + nan, f"'{obj}' evaluated to a nonnumeric value '{ans}'" + ) return ans def initializeWalker(self, expr): diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index fe7a7436398..d4c255def52 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -2282,7 +2282,7 @@ def _before_var(visitor, child): _id = id(child) if _id not in visitor.var_map: if child.fixed: - return False, (_CONSTANT, visitor._eval_fixed(child)) + return False, (_CONSTANT, visitor.handle_constant(child.value, child)) visitor.var_map[_id] = child return False, (_MONOMIAL, _id, 1) @@ -2295,14 +2295,14 @@ def _before_monomial(visitor, child): arg1, arg2 = child._args_ if arg1.__class__ not in native_types: try: - arg1 = visitor._eval_expr(arg1) + arg1 = visitor.handle_constant(visitor.evaluate(arg1), arg1) except (ValueError, ArithmeticError): return True, None # Trap multiplication by 0 and nan. if not arg1: if arg2.fixed: - arg2 = visitor._eval_fixed(arg2) + arg2 = visitor.handle_constant(arg2.value, arg2) if arg2 != arg2: deprecation_warning( f"Encountered {arg1}*{arg2} in expression tree. " @@ -2316,7 +2316,7 @@ def _before_monomial(visitor, child): _id = id(arg2) if _id not in visitor.var_map: if arg2.fixed: - return False, (_CONSTANT, arg1 * visitor._eval_fixed(arg2)) + return False, (_CONSTANT, arg1 * visitor.handle_constant(arg2.value, arg2)) visitor.var_map[_id] = arg2 return False, (_MONOMIAL, _id, arg1) @@ -2333,14 +2333,14 @@ def _before_linear(visitor, child): arg1, arg2 = arg._args_ if arg1.__class__ not in native_types: try: - arg1 = visitor._eval_expr(arg1) + arg1 = visitor.handle_constant(visitor.evaluate(arg1), arg1) except (ValueError, ArithmeticError): return True, None # Trap multiplication by 0 and nan. if not arg1: if arg2.fixed: - arg2 = visitor._eval_fixed(arg2) + arg2 = visitor.handle_constant(arg2.value, arg2) if arg2 != arg2: deprecation_warning( f"Encountered {arg1}*{str(arg2.value)} in expression " @@ -2354,7 +2354,7 @@ def _before_linear(visitor, child): _id = id(arg2) if _id not in var_map: if arg2.fixed: - const += arg1 * visitor._eval_fixed(arg2) + const += arg1 * visitor.handle_constant(arg2.value, arg2) continue var_map[_id] = arg2 linear[_id] = arg1 @@ -2366,7 +2366,7 @@ def _before_linear(visitor, child): const += arg else: try: - const += visitor._eval_expr(arg) + const += visitor.handle_constant(visitor.evaluate(arg), arg) except (ValueError, ArithmeticError): return True, None @@ -2417,14 +2417,14 @@ def __init__( self.use_named_exprs = use_named_exprs self.encountered_string_arguments = False self._eval_expr_visitor = _EvaluationVisitor(True) + self.evaluate = self._eval_expr_visitor.dfs_postorder_stack - def _eval_fixed(self, obj): - ans = obj.value + def handle_constant(self, ans, obj): if ans.__class__ not in native_numeric_types: # None can be returned from uninitialized Var/Param objects if ans is None: return InvalidNumber( - None, f"'{obj}' contains a nonnumeric value '{ans}'" + None, f"'{obj}' evaluated to a nonnumeric value '{ans}'" ) if ans.__class__ is InvalidNumber: return ans @@ -2443,43 +2443,10 @@ def _eval_fixed(self, obj): ans = float(ans) except: return InvalidNumber( - ans, f"'{obj}' contains a nonnumeric value '{ans}'" + ans, f"'{obj}' evaluated to a nonnumeric value '{ans}'" ) if ans != ans: - return InvalidNumber(nan, f"'{obj}' contains a nonnumeric value '{ans}'") - if ans.__class__ in _complex_types: - return complex_number_error(ans, self, obj) - return ans - - def _eval_expr(self, expr): - ans = self._eval_expr_visitor.dfs_postorder_stack(expr) - if ans.__class__ not in native_numeric_types: - # None can be returned from uninitialized Expression objects - if ans is None: - return InvalidNumber( - ans, f"'{expr}' evaluated to nonnumeric value '{ans}'" - ) - if ans.__class__ is InvalidNumber: - return ans - else: - # It is possible to get other non-numeric types. Most - # common are bool and 1-element numpy.array(). We will - # attempt to convert the value to a float before - # proceeding. - # - # TODO: we should check bool and warn/error (while bool is - # convertible to float in Python, they have very - # different semantic meanings in Pyomo). - try: - ans = float(ans) - except: - return InvalidNumber( - ans, f"'{expr}' evaluated to nonnumeric value '{ans}'" - ) - if ans != ans: - return InvalidNumber(ans, f"'{expr}' evaluated to nonnumeric value '{ans}'") - if ans.__class__ in _complex_types: - return complex_number_error(ans, self, expr) + return InvalidNumber(nan, f"'{obj}' evaluated to a nonnumeric value '{ans}'") return ans def initializeWalker(self, expr): diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index a98ab70c902..27c8974daab 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -323,13 +323,13 @@ def _before_string(visitor, child): @staticmethod def _before_npv(visitor, child): try: - return False, (_CONSTANT, visitor._eval_expr(child)) + return False, (_CONSTANT, visitor.handle_constant(visitor.evaluate(child), child)) except (ValueError, ArithmeticError): return True, None @staticmethod def _before_param(visitor, child): - return False, (_CONSTANT, visitor._eval_fixed(child)) + return False, (_CONSTANT, visitor.handle_constant(child.value, child)) # # The following methods must be defined by derivative classes (along From e80bf23b1d7b797b4a8771889a20eb2932d897cb Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Sep 2023 07:04:13 -0600 Subject: [PATCH 0109/1204] NFC: comments --- pyomo/common/numeric_types.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/pyomo/common/numeric_types.py b/pyomo/common/numeric_types.py index d2b75152140..7b23bdf716f 100644 --- a/pyomo/common/numeric_types.py +++ b/pyomo/common/numeric_types.py @@ -80,11 +80,11 @@ def RegisterNumericType(new_type): - """ - A utility function for updating the set of types that are - recognized to handle numeric values. + """A utility function for updating the set of types that are recognized + to handle numeric values. The argument should be a class (e.g, numpy.float64). + """ native_numeric_types.add(new_type) native_types.add(new_type) @@ -92,12 +92,12 @@ def RegisterNumericType(new_type): def RegisterIntegerType(new_type): - """ - A utility function for updating the set of types that are - recognized to handle integer values. This also registers the type - as numeric but does not register it as boolean. + """A utility function for updating the set of types that are recognized + to handle integer values. This also adds the type to the numeric + and native type sets (but not the Boolean / logical sets). The argument should be a class (e.g., numpy.int64). + """ native_numeric_types.add(new_type) native_integer_types.add(new_type) @@ -111,12 +111,12 @@ def RegisterIntegerType(new_type): version='6.6.0', ) def RegisterBooleanType(new_type): - """ - A utility function for updating the set of types that are - recognized as handling boolean values. This function does not - register the type of integer or numeric. + """A utility function for updating the set of types that are recognized + as handling boolean values. This function does not add the type + with the integer or numeric sets. The argument should be a class (e.g., numpy.bool_). + """ _native_boolean_types.add(new_type) native_types.add(new_type) @@ -143,6 +143,7 @@ def RegisterLogicalType(new_type): with the integer or numeric sets. The argument should be a class (e.g., numpy.bool_). + """ _native_boolean_types.add(new_type) native_logical_types.add(new_type) From 1d893808686cc0fa6df2a01db3df24a227f1f87a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Sep 2023 09:03:57 -0600 Subject: [PATCH 0110/1204] Remove redundant checks for int/float domains --- pyomo/repn/plugins/lp_writer.py | 6 ----- pyomo/repn/plugins/nl_writer.py | 42 +++++++-------------------------- 2 files changed, 8 insertions(+), 40 deletions(-) diff --git a/pyomo/repn/plugins/lp_writer.py b/pyomo/repn/plugins/lp_writer.py index fab94d313d5..23f5c82280a 100644 --- a/pyomo/repn/plugins/lp_writer.py +++ b/pyomo/repn/plugins/lp_writer.py @@ -427,8 +427,6 @@ def write(self, model): # Pull out the constant: we will move it to the bounds offset = repn.constant - if offset.__class__ not in int_float: - offset = float(offset) repn.constant = 0 if repn.linear or getattr(repn, 'quadratic', None): @@ -584,8 +582,6 @@ def write_expression(self, ostream, expr, is_objective): for vid, coef in sorted( expr.linear.items(), key=lambda x: getVarOrder(x[0]) ): - if coef.__class__ not in int_float: - coef = float(coef) if coef < 0: ostream.write(f'{coef!r} {getSymbol(getVar(vid))}\n') else: @@ -607,8 +603,6 @@ def _normalize_constraint(data): else: col = c1, c2 sym = f' {getSymbol(getVar(vid1))} * {getSymbol(getVar(vid2))}\n' - if coef.__class__ not in int_float: - coef = float(coef) if coef < 0: return col, repr(coef) + sym else: diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index d4c255def52..d8d0633497f 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -579,8 +579,6 @@ def write(self, model): # Note: Constraint.lb/ub guarantee a return value that is # either a (finite) native_numeric_type, or None const = expr.const - if const.__class__ not in int_float: - const = float(const) lb = con.lb if lb is not None: lb = repr(lb - const) @@ -1331,10 +1329,7 @@ def write(self, model): continue ostream.write(f'J{row_idx} {len(linear)}{row_comments[row_idx]}\n') for _id in sorted(linear.keys(), key=column_order.__getitem__): - val = linear[_id] - if val.__class__ not in int_float: - val = float(val) - ostream.write(f'{column_order[_id]} {val!r}\n') + ostream.write(f'{column_order[_id]} {linear[_id]!r}\n') # # "G" lines (non-empty terms in the Objective) @@ -1347,10 +1342,7 @@ def write(self, model): continue ostream.write(f'G{obj_idx} {len(linear)}{row_comments[obj_idx + n_cons]}\n') for _id in sorted(linear.keys(), key=column_order.__getitem__): - val = linear[_id] - if val.__class__ not in int_float: - val = float(val) - ostream.write(f'{column_order[_id]} {val!r}\n') + ostream.write(f'{column_order[_id]} {linear[_id]!r}\n') # Generate the return information info = NLWriterInfo( @@ -1502,33 +1494,18 @@ def _write_nl_expression(self, repn, include_const): # compiled before this point). Omitting the assertion for # efficiency. # assert repn.mult == 1 + # + # Note that repn.const should always be a int/float (it has + # already been compiled) if repn.nonlinear: nl, args = repn.nonlinear if include_const and repn.const: # Add the constant to the NL expression. AMPL adds the # constant as the second argument, so we will too. - nl = ( - self.template.binary_sum - + nl - + ( - self.template.const - % ( - repn.const - if repn.const.__class__ in int_float - else float(repn.const) - ) - ) - ) + nl = self.template.binary_sum + nl + self.template.const % repn.const self.ostream.write(nl % tuple(map(self.var_id_to_nl.__getitem__, args))) elif include_const: - self.ostream.write( - self.template.const - % ( - repn.const - if repn.const.__class__ in int_float - else float(repn.const) - ) - ) + self.ostream.write(self.template.const % repn.const) else: self.ostream.write(self.template.const % 0) @@ -1548,10 +1525,7 @@ def _write_v_line(self, expr_id, k): # ostream.write(f'V{self.next_V_line_id} {len(linear)} {k}{lbl}\n') for _id in sorted(linear, key=column_order.__getitem__): - val = linear[_id] - if val.__class__ not in int_float: - val = float(val) - ostream.write(f'{column_order[_id]} {val!r}\n') + ostream.write(f'{column_order[_id]} {linear[_id]!r}\n') self._write_nl_expression(info[1], True) self.next_V_line_id += 1 From 8876e25b0b8ba728438eec79c6175dc5e0469484 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Sep 2023 09:04:26 -0600 Subject: [PATCH 0111/1204] Apply black --- pyomo/repn/linear.py | 11 +++++++++-- pyomo/repn/plugins/nl_writer.py | 13 ++++++++----- pyomo/repn/util.py | 12 ++++++++++-- pyomo/util/calc_var_value.py | 12 +++++++----- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/pyomo/repn/linear.py b/pyomo/repn/linear.py index e1180256c52..a0060da6e9f 100644 --- a/pyomo/repn/linear.py +++ b/pyomo/repn/linear.py @@ -15,7 +15,11 @@ from itertools import filterfalse from pyomo.common.deprecation import deprecation_warning -from pyomo.common.numeric_types import native_types, native_numeric_types, native_complex_types +from pyomo.common.numeric_types import ( + native_types, + native_numeric_types, + native_complex_types, +) from pyomo.core.expr.numeric_expr import ( NegationExpression, ProductExpression, @@ -610,7 +614,10 @@ def _before_monomial(visitor, child): _id = id(arg2) if _id not in visitor.var_map: if arg2.fixed: - return False, (_CONSTANT, arg1 * visitor.handle_constant(arg2.value, arg2)) + return False, ( + _CONSTANT, + arg1 * visitor.handle_constant(arg2.value, arg2), + ) visitor.var_map[_id] = arg2 visitor.var_order[_id] = len(visitor.var_order) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index d8d0633497f..d532bceb768 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1650,9 +1650,7 @@ def compile_repn(self, visitor, prefix='', args=None, named_exprs=None): args.extend(self.nonlinear[1]) if self.const: nterms += 1 - nl_sum += template.const % ( - self.const if self.const.__class__ in int_float else float(self.const) - ) + nl_sum += template.const % self.const if nterms > 2: return (prefix + (template.nary_sum % nterms) + nl_sum, args, named_exprs) @@ -2290,7 +2288,10 @@ def _before_monomial(visitor, child): _id = id(arg2) if _id not in visitor.var_map: if arg2.fixed: - return False, (_CONSTANT, arg1 * visitor.handle_constant(arg2.value, arg2)) + return False, ( + _CONSTANT, + arg1 * visitor.handle_constant(arg2.value, arg2), + ) visitor.var_map[_id] = arg2 return False, (_MONOMIAL, _id, arg1) @@ -2420,7 +2421,9 @@ def handle_constant(self, ans, obj): ans, f"'{obj}' evaluated to a nonnumeric value '{ans}'" ) if ans != ans: - return InvalidNumber(nan, f"'{obj}' evaluated to a nonnumeric value '{ans}'") + return InvalidNumber( + nan, f"'{obj}' evaluated to a nonnumeric value '{ans}'" + ) return ans def initializeWalker(self, expr): diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index 27c8974daab..0b63db1bedd 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -20,7 +20,11 @@ from pyomo.common.collections import Sequence, ComponentMap from pyomo.common.deprecation import deprecation_warning from pyomo.common.errors import DeveloperError, InvalidValueError -from pyomo.common.numeric_types import native_types, native_numeric_types, native_complex_types +from pyomo.common.numeric_types import ( + native_types, + native_numeric_types, + native_complex_types, +) from pyomo.core.pyomoobject import PyomoObject from pyomo.core.base import ( Var, @@ -252,6 +256,7 @@ class BeforeChildDispatcher(collections.defaultdict): dispatcher object. """ + def __missing__(self, key): return self.register_dispatcher @@ -323,7 +328,10 @@ def _before_string(visitor, child): @staticmethod def _before_npv(visitor, child): try: - return False, (_CONSTANT, visitor.handle_constant(visitor.evaluate(child), child)) + return False, ( + _CONSTANT, + visitor.handle_constant(visitor.evaluate(child), child), + ) except (ValueError, ArithmeticError): return True, None diff --git a/pyomo/util/calc_var_value.py b/pyomo/util/calc_var_value.py index 03b5b4d1de3..85e8d4abd22 100644 --- a/pyomo/util/calc_var_value.py +++ b/pyomo/util/calc_var_value.py @@ -10,7 +10,12 @@ # ___________________________________________________________________________ from pyomo.common.errors import IterationLimitError -from pyomo.common.numeric_types import native_numeric_types, native_complex_types, value, is_fixed +from pyomo.common.numeric_types import ( + native_numeric_types, + native_complex_types, + value, + is_fixed, +) from pyomo.core.expr.calculus.derivatives import differentiate from pyomo.core.base.constraint import Constraint, _ConstraintData @@ -170,10 +175,7 @@ def calculate_variable_from_constraint( if slope: variable.set_value(-intercept / slope, skip_validation=True) body_val = value(body, exception=False) - if ( - body_val.__class__ not in _invalid_types - and abs(body_val - upper) < eps - ): + if body_val.__class__ not in _invalid_types and abs(body_val - upper) < eps: # Re-set the variable value to trigger any warnings WRT # the final variable state variable.set_value(variable.value) From 0c52ea02dccdcd97b42464a90d9a1c042988de65 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Sep 2023 09:13:11 -0600 Subject: [PATCH 0112/1204] update tests to track dispatcher api changes --- pyomo/repn/tests/test_linear.py | 29 +++++++++++++++-------------- pyomo/repn/util.py | 2 +- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/pyomo/repn/tests/test_linear.py b/pyomo/repn/tests/test_linear.py index 1501ecfcc9d..7334703f1d5 100644 --- a/pyomo/repn/tests/test_linear.py +++ b/pyomo/repn/tests/test_linear.py @@ -1492,53 +1492,54 @@ def test_type_registrations(self): visitor = LinearRepnVisitor(*cfg) _orig_dispatcher = linear._before_child_dispatcher - linear._before_child_dispatcher = bcd = {} + linear._before_child_dispatcher = bcd = _orig_dispatcher.__class__() + bcd.clear() try: # native type self.assertEqual( - linear._register_new_before_child_dispatcher(visitor, 5), + bcd.register_dispatcher(visitor, 5), (False, (linear._CONSTANT, 5)), ) self.assertEqual(len(bcd), 1) - self.assertIs(bcd[int], linear._before_native) + self.assertIs(bcd[int], bcd._before_native) # complex type self.assertEqual( - linear._register_new_before_child_dispatcher(visitor, 5j), + bcd.register_dispatcher(visitor, 5j), (False, (linear._CONSTANT, 5j)), ) self.assertEqual(len(bcd), 2) - self.assertIs(bcd[complex], linear._before_complex) + self.assertIs(bcd[complex], bcd._before_complex) # ScalarParam m.p = Param(initialize=5) self.assertEqual( - linear._register_new_before_child_dispatcher(visitor, m.p), + bcd.register_dispatcher(visitor, m.p), (False, (linear._CONSTANT, 5)), ) self.assertEqual(len(bcd), 3) - self.assertIs(bcd[m.p.__class__], linear._before_param) + self.assertIs(bcd[m.p.__class__], bcd._before_param) # ParamData m.q = Param([0], initialize=6, mutable=True) self.assertEqual( - linear._register_new_before_child_dispatcher(visitor, m.q[0]), + bcd.register_dispatcher(visitor, m.q[0]), (False, (linear._CONSTANT, 6)), ) self.assertEqual(len(bcd), 4) - self.assertIs(bcd[m.q[0].__class__], linear._before_param) + self.assertIs(bcd[m.q[0].__class__], bcd._before_param) # NPV_SumExpression self.assertEqual( - linear._register_new_before_child_dispatcher(visitor, m.p + m.q[0]), + bcd.register_dispatcher(visitor, m.p + m.q[0]), (False, (linear._CONSTANT, 11)), ) self.assertEqual(len(bcd), 6) - self.assertIs(bcd[NPV_SumExpression], linear._before_npv) - self.assertIs(bcd[LinearExpression], linear._before_general_expression) + self.assertIs(bcd[NPV_SumExpression], bcd._before_npv) + self.assertIs(bcd[LinearExpression], bcd._before_general_expression) # Named expression m.e = Expression(expr=m.p + m.q[0]) self.assertEqual( - linear._register_new_before_child_dispatcher(visitor, m.e), (True, None) + bcd.register_dispatcher(visitor, m.e), (True, None) ) self.assertEqual(len(bcd), 7) - self.assertIs(bcd[m.e.__class__], linear._before_named_expression) + self.assertIs(bcd[m.e.__class__], bcd._before_named_expression) finally: linear._before_child_dispatcher = _orig_dispatcher diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index 0b63db1bedd..4161796d2fb 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -287,7 +287,7 @@ def register_dispatcher(self, visitor, child): if pv_base_type not in self: try: child.__class__ = pv_base_type - self.register_child_handler(visitor, child) + self.register_dispatcher(visitor, child) finally: child.__class__ = child_type elif ( From d9e3be53340cb339200ea4cb15b3a61add311a03 Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 7 Sep 2023 15:32:19 -0400 Subject: [PATCH 0113/1204] Make PyROS solver log more detailed --- pyomo/contrib/pyros/master_problem_methods.py | 7 +- pyomo/contrib/pyros/pyros.py | 237 ++++++++++++- .../contrib/pyros/pyros_algorithm_methods.py | 334 ++++++++++++++++-- pyomo/contrib/pyros/solve_data.py | 16 + pyomo/contrib/pyros/util.py | 148 ++++++++ 5 files changed, 704 insertions(+), 38 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index a0e2245cab1..df9d4e1fbcb 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -298,6 +298,9 @@ def minimize_dr_vars(model_data, config): ------- results : SolverResults Subordinate solver results for the polishing problem. + polishing_successful : bool + True if polishing model was solved to acceptable level, + False otherwise. """ # config.progress_logger.info("Executing decision rule variable polishing solve.") model = model_data.master_model @@ -493,7 +496,7 @@ def minimize_dr_vars(model_data, config): acceptable = {tc.globallyOptimal, tc.optimal, tc.locallyOptimal, tc.feasible} if results.solver.termination_condition not in acceptable: # continue with "unpolished" master model solution - return results + return results, False # update master model second-stage, state, and decision rule # variables to polishing model solution @@ -520,7 +523,7 @@ def minimize_dr_vars(model_data, config): for mvar, pvar in zip(master_dr.values(), polish_dr.values()): mvar.set_value(value(pvar), skip_validation=True) - return results + return results, True def add_p_robust_constraint(model_data, config): diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 34db54b64e6..b67cf5c3df6 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -37,17 +37,52 @@ transform_to_standard_form, turn_bounds_to_constraints, replace_uncertain_bounds_with_constraints, - output_logger, ) from pyomo.contrib.pyros.solve_data import ROSolveResults from pyomo.contrib.pyros.pyros_algorithm_methods import ROSolver_iterative_solve from pyomo.contrib.pyros.uncertainty_sets import uncertainty_sets from pyomo.core.base import Constraint +from pyomo.common.timing import TicTocTimer +from pyomo.contrib.pyros.util import IterationLogRecord + +from datetime import datetime +import logging __version__ = "1.2.7" +def _get_pyomo_git_info(): + """ + Get Pyomo git commit hash. + """ + import os + import subprocess + + pyros_dir = os.path.join(*os.path.split(__file__)[:-1]) + + git_info_dict = {} + commands_dict = { + "branch": [ + "git", "-C", f"{pyros_dir}", "rev-parse", "--abbrev-ref", "HEAD" + ], + "commit hash": [ + "git", "-C", f"{pyros_dir}", "rev-parse", "--short", "HEAD" + ], + } + for field, command in commands_dict.items(): + try: + field_val = ( + subprocess.check_output(command).decode("ascii").strip() + ) + except subprocess.CalledProcessError: + field_val = "unknown" + + git_info_dict[field] = field_val + + return git_info_dict + + def NonNegIntOrMinusOne(obj): ''' if obj is a non-negative int, return the non-negative int @@ -642,6 +677,7 @@ class PyROS(object): ''' CONFIG = pyros_config() + _LOG_LINE_LENGTH = 78 def available(self, exception_flag=True): """Check if solver is available.""" @@ -663,6 +699,113 @@ def __enter__(self): def __exit__(self, et, ev, tb): pass + def _log_intro(self, logger, **log_kwargs): + """ + Log PyROS solver introductory messages. + + Parameters + ---------- + logger : logging.Logger + Logger through which to emit messages. + **log_kwargs : dict, optional + Keyword arguments to ``logger.log()`` callable. + Should not include `msg`. + """ + logger.log(msg="=" * self._LOG_LINE_LENGTH, **log_kwargs) + logger.log( + msg="PyROS: The Pyomo Robust Optimization Solver.", + **log_kwargs, + ) + + git_info_str = ", ".join( + f"{field}: {val}" for field, val in _get_pyomo_git_info().items() + ) + logger.log( + msg=( + f"{' ' * len('PyROS:')} Version {self.version()} | " + f"Git {git_info_str}" + ), + **log_kwargs, + ) + logger.log( + msg=( + f"{' ' * len('PyROS:')} " + f"Invoked at UTC {datetime.utcnow().isoformat()}" + ), + **log_kwargs, + ) + logger.log(msg="", **log_kwargs) + logger.log( + msg=("Developed by: Natalie M. Isenberg (1), Jason A. F. Sherman (1),"), + **log_kwargs, + ) + logger.log( + msg=( + f"{' ' * len('Developed by:')} " + "John D. Siirola (2), Chrysanthos E. Gounaris (1)" + ), + **log_kwargs, + ) + logger.log( + msg=( + "(1) Carnegie Mellon University, " + "Department of Chemical Engineering" + ), + **log_kwargs, + ) + logger.log( + msg="(2) Sandia National Laboratories, Center for Computing Research", + **log_kwargs, + ) + logger.log(msg="", **log_kwargs) + logger.log( + msg=( + "The developers gratefully acknowledge support " + "from the U.S. Department" + ), + **log_kwargs, + ) + logger.log( + msg=( + "of Energy's " + "Institute for the Design of Advanced Energy Systems (IDAES)." + ), + **log_kwargs, + ) + logger.log(msg="=" * self._LOG_LINE_LENGTH, **log_kwargs) + + def _log_disclaimer(self, logger, **log_kwargs): + """ + Log PyROS solver disclaimer messages. + + Parameters + ---------- + logger : logging.Logger + Logger through which to emit messages. + **log_kwargs : dict, optional + Keyword arguments to ``logger.log()`` callable. + Should not include `msg`. + """ + disclaimer_header = " DISCLAIMER ".center(self._LOG_LINE_LENGTH, "=") + + logger.log(msg=disclaimer_header, **log_kwargs) + logger.log( + msg="PyROS is still under development. ", + **log_kwargs, + ) + logger.log( + msg=( + "Please provide feedback and/or report any issues by creating " + "a ticket at" + ), + **log_kwargs, + ) + logger.log( + msg="https://github.com/Pyomo/pyomo/issues/new/choose", + **log_kwargs, + ) + logger.log(msg="=" * self._LOG_LINE_LENGTH, **log_kwargs) + def solve( self, model, @@ -742,15 +885,42 @@ def solve( model_data = ROSolveResults() model_data.timing = Bunch() - # === Set up logger for logging results - with time_code(model_data.timing, 'total', is_main_timer=True): - config.progress_logger.setLevel(logging.INFO) - - # === PREAMBLE - output_logger(config=config, preamble=True, version=str(self.version())) + # === Start timer, run the algorithm + model_data.timing = Bunch() + with time_code( + timing_data_obj=model_data.timing, + code_block_name='total', + is_main_timer=True, + ): + tt_timer = model_data.timing.tic_toc_timer + # output intro and disclaimer + self._log_intro( + config.progress_logger, + level=logging.INFO, + ) + self._log_disclaimer( + config.progress_logger, + level=logging.INFO, + ) - # === DISCLAIMER - output_logger(config=config, disclaimer=True) + # log solver options + excl_from_config_display = [ + "first_stage_variables", + "second_stage_variables", + "uncertain_params", + "uncertainty_set", + "local_solver", + "global_solver", + ] + config.progress_logger.info("Solver options:") + for key, val in config.items(): + if key not in excl_from_config_display: + config.progress_logger.info(f" {key}={val!r}") + config.progress_logger.info("-" * self._LOG_LINE_LENGTH) + + # begin preprocessing + config.progress_logger.info("Preprocessing...") + tt_timer.toc(msg=None) # === A block to hold list-type data to make cloning easy util = Block(concrete=True) @@ -831,10 +1001,16 @@ def solve( if "bound_con" in c.name: wm_util.ssv_bounds.append(c) + config.progress_logger.info( + f"Done preprocessing; required wall time of " + f"{tt_timer.toc(msg=None, delta=True):.2f}s." + ) + # === Solve and load solution into model pyros_soln, final_iter_separation_solns = ROSolver_iterative_solve( model_data, config ) + IterationLogRecord.log_header_rule(config.progress_logger.info) return_soln = ROSolveResults() if pyros_soln is not None and final_iter_separation_solns is not None: @@ -884,6 +1060,49 @@ def solve( return_soln.final_objective_value = None return_soln.time = get_main_elapsed_time(model_data.timing) return_soln.iterations = 0 + + termination_msg_dict = { + pyrosTerminationCondition.robust_optimal: ( + "Robust optimal solution identified." + ), + pyrosTerminationCondition.robust_feasible: ( + "Robust feasible solution identified." + ), + pyrosTerminationCondition.robust_infeasible: ( + "Problem is robust infeasible." + ), + pyrosTerminationCondition.time_out: ( + "Maximum allowable time exceeded." + ), + pyrosTerminationCondition.max_iter: ( + "Maximum number of iterations reached." + ), + pyrosTerminationCondition.subsolver_error: ( + "Subordinate optimizer(s) could not solve a subproblem " + "to an acceptable status." + ), + } + config.progress_logger.info( + termination_msg_dict[return_soln.pyros_termination_condition] + ) + config.progress_logger.info("-" * self._LOG_LINE_LENGTH) + config.progress_logger.info("Termination stats:") + config.progress_logger.info( + f" {'Iterations':<22s}: {return_soln.iterations}" + ) + config.progress_logger.info( + f" {'Solve time (wall s)':<22s}: {return_soln.time:.4f}" + ) + config.progress_logger.info( + f" {'Final objective value':<22s}: " + f"{return_soln.final_objective_value}" + ) + config.progress_logger.info( + f" {'Termination condition':<22s}: " + f"{return_soln.pyros_termination_condition}" + ) + config.progress_logger.info("=" * self._LOG_LINE_LENGTH) + return return_soln diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index 7a0c990d549..6787b49ca6f 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -11,14 +11,16 @@ ObjectiveType, get_time_from_solver, pyrosTerminationCondition, + IterationLogRecord, ) from pyomo.contrib.pyros.util import ( get_main_elapsed_time, - output_logger, coefficient_matching, ) from pyomo.core.base import value -from pyomo.common.collections import ComponentSet +from pyomo.common.collections import ComponentSet, ComponentMap +from pyomo.core.base.var import _VarData as VarData +from itertools import chain def update_grcs_solve_data( @@ -47,6 +49,162 @@ def update_grcs_solve_data( return +def get_dr_var_to_scaled_expr_map( + decision_rule_eqns, + second_stage_vars, + uncertain_params, + decision_rule_vars, + ): + """ + Generate mapping from decision rule variables + to their terms in a model's DR expression. + """ + var_to_scaled_expr_map = ComponentMap() + ssv_dr_eq_zip = zip( + second_stage_vars, + decision_rule_eqns, + ) + for ssv_idx, (ssv, dr_eq) in enumerate(ssv_dr_eq_zip): + for term in dr_eq.body.args: + is_ssv_term = ( + isinstance(term.args[0], int) + and term.args[0] == -1 + and isinstance(term.args[1], VarData) + ) + if not is_ssv_term: + dr_var = term.args[1] + var_to_scaled_expr_map[dr_var] = term + + return var_to_scaled_expr_map + + +def evaluate_and_log_component_stats(model_data, separation_model, config): + """ + Evaluate and log model component statistics. + """ + config.progress_logger.info( + "Model statistics:" + ) + # print model statistics + dr_var_set = ComponentSet(chain(*tuple( + indexed_dr_var.values() + for indexed_dr_var in model_data.working_model.util.decision_rule_vars + ))) + first_stage_vars = [ + var for var in model_data.working_model.util.first_stage_variables + if var not in dr_var_set + ] + + # account for epigraph constraint + sep_model_epigraph_con = getattr(separation_model, "epigraph_constr", None) + has_epigraph_con = sep_model_epigraph_con is not None + + num_fsv = len(first_stage_vars) + num_ssv = len(model_data.working_model.util.second_stage_variables) + num_sv = len(model_data.working_model.util.state_vars) + num_dr_vars = len(dr_var_set) + num_vars = int(has_epigraph_con) + num_fsv + num_ssv + num_sv + num_dr_vars + + eq_cons = [ + con for con in + model_data.working_model.component_data_objects( + Constraint, + active=True, + ) + if con.equality + ] + dr_eq_set = ComponentSet(chain(*tuple( + indexed_dr_eq.values() + for indexed_dr_eq in model_data.working_model.util.decision_rule_eqns + ))) + num_eq_cons = len(eq_cons) + num_dr_cons = len(dr_eq_set) + num_coefficient_matching_cons = len(getattr( + model_data.working_model, + "coefficient_matching_constraints", + [], + )) + num_other_eq_cons = num_eq_cons - num_dr_cons - num_coefficient_matching_cons + + # get performance constraints as referenced in the separation + # model object + new_sep_con_map = separation_model.util.map_new_constraint_list_to_original_con + perf_con_set = ComponentSet( + new_sep_con_map.get(con, con) + for con in separation_model.util.performance_constraints + ) + is_epigraph_con_first_stage = ( + has_epigraph_con + and sep_model_epigraph_con not in perf_con_set + ) + working_model_perf_con_set = ComponentSet( + model_data.working_model.find_component(new_sep_con_map.get(con, con)) + for con in separation_model.util.performance_constraints + if con is not None + ) + + num_perf_cons = len(separation_model.util.performance_constraints) + num_fsv_bounds = sum( + int(var.lower is not None) + int(var.upper is not None) + for var in first_stage_vars + ) + ineq_con_set = [ + con for con in + model_data.working_model.component_data_objects( + Constraint, + active=True, + ) + if not con.equality + ] + num_fsv_ineqs = num_fsv_bounds + len( + [con for con in ineq_con_set if con not in working_model_perf_con_set] + ) + is_epigraph_con_first_stage + num_ineq_cons = ( + len(ineq_con_set) + + has_epigraph_con + + num_fsv_bounds + ) + + config.progress_logger.info( + f"{' Number of variables'} : {num_vars}" + ) + config.progress_logger.info( + f"{' Epigraph variable'} : {int(has_epigraph_con)}" + ) + config.progress_logger.info(f"{' First-stage variables'} : {num_fsv}") + config.progress_logger.info(f"{' Second-stage variables'} : {num_ssv}") + config.progress_logger.info(f"{' State variables'} : {num_sv}") + config.progress_logger.info(f"{' Decision rule variables'} : {num_dr_vars}") + config.progress_logger.info( + f"{' Number of constraints'} : " + f"{num_ineq_cons + num_eq_cons}" + ) + config.progress_logger.info( + f"{' Equality constraints'} : {num_eq_cons}" + ) + config.progress_logger.info( + f"{' Coefficient matching constraints'} : " + f"{num_coefficient_matching_cons}" + ) + config.progress_logger.info( + f"{' Decision rule equations'} : {num_dr_cons}" + ) + config.progress_logger.info( + f"{' All other equality constraints'} : " + f"{num_other_eq_cons}" + ) + config.progress_logger.info( + f"{' Inequality constraints'} : {num_ineq_cons}" + ) + config.progress_logger.info( + f"{' First-stage inequalities (incl. certain var bounds)'} : " + f"{num_fsv_ineqs}" + ) + config.progress_logger.info( + f"{' Performance constraints (incl. var bounds)'} : {num_perf_cons}" + ) + + def ROSolver_iterative_solve(model_data, config): ''' GRCS algorithm implementation @@ -75,20 +233,23 @@ def ROSolver_iterative_solve(model_data, config): config=config, ) if not coeff_matching_success and not robust_infeasible: - raise ValueError( - "Equality constraint \"%s\" cannot be guaranteed to be robustly feasible, " - "given the current partitioning between first-stage, second-stage and state variables. " - "You might consider editing this constraint to reference some second-stage " - "and/or state variable(s)." % c.name + config.progress_logger.error( + f"Equality constraint {c.name!r} cannot be guaranteed to " + "be robustly feasible, given the current partitioning " + "between first-stage, second-stage, and state variables. " + "Consider editing this constraint to reference some " + "second-stage and/or state variable(s)." ) + raise ValueError("Coefficient matching unsuccessful. See the solver logs.") elif not coeff_matching_success and robust_infeasible: config.progress_logger.info( "PyROS has determined that the model is robust infeasible. " - "One reason for this is that equality constraint \"%s\" cannot be satisfied " - "against all realizations of uncertainty, " - "given the current partitioning between first-stage, second-stage and state variables. " - "You might consider editing this constraint to reference some (additional) second-stage " - "and/or state variable(s)." % c.name + f"One reason for this is that the equality constraint {c.name} " + "cannot be satisfied against all realizations of uncertainty, " + "given the current partitioning between " + "first-stage, second-stage, and state variables. " + "Consider editing this constraint to reference some (additional) " + "second-stage and/or state variable(s)." ) return None, None else: @@ -156,6 +317,12 @@ def ROSolver_iterative_solve(model_data, config): model_data=master_data, config=config ) + evaluate_and_log_component_stats( + model_data=model_data, + separation_model=separation_model, + config=config, + ) + # === Create separation problem data container object and add information to catalog during solve separation_data = SeparationProblemData() separation_data.separation_model = separation_model @@ -204,6 +371,33 @@ def ROSolver_iterative_solve(model_data, config): dr_var_lists_original = [] dr_var_lists_polished = [] + # set up first-stage variable and DR variable sets + master_dr_var_set = ComponentSet(chain(*tuple( + indexed_var.values() + for indexed_var + in master_data.master_model.scenarios[0, 0].util.decision_rule_vars + ))) + master_fsv_set = ComponentSet( + var for var in + master_data.master_model.scenarios[0, 0].util.first_stage_variables + if var not in master_dr_var_set + ) + previous_master_fsv_vals = ComponentMap( + (var, None) for var in master_fsv_set + ) + previous_master_dr_var_vals = ComponentMap( + (var, None) for var in master_dr_var_set + ) + + nom_master_util_blk = master_data.master_model.scenarios[0, 0].util + dr_var_scaled_expr_map = get_dr_var_to_scaled_expr_map( + decision_rule_vars=nom_master_util_blk.decision_rule_vars, + decision_rule_eqns=nom_master_util_blk.decision_rule_eqns, + second_stage_vars=nom_master_util_blk.second_stage_variables, + uncertain_params=nom_master_util_blk.uncertain_params, + ) + + IterationLogRecord.log_header(config.progress_logger.info) k = 0 master_statuses = [] while config.max_iter == -1 or k < config.max_iter: @@ -216,7 +410,7 @@ def ROSolver_iterative_solve(model_data, config): ) # === Solve Master Problem - config.progress_logger.info("PyROS working on iteration %s..." % k) + config.progress_logger.debug(f"PyROS working on iteration {k}...") master_soln = master_problem_methods.solve_master( model_data=master_data, config=config ) @@ -239,7 +433,6 @@ def ROSolver_iterative_solve(model_data, config): is pyrosTerminationCondition.robust_infeasible ): term_cond = pyrosTerminationCondition.robust_infeasible - output_logger(config=config, robust_infeasible=True) elif ( master_soln.pyros_termination_condition is pyrosTerminationCondition.subsolver_error @@ -257,6 +450,18 @@ def ROSolver_iterative_solve(model_data, config): pyrosTerminationCondition.time_out, pyrosTerminationCondition.robust_infeasible, }: + log_record = IterationLogRecord( + iteration=k, + objective=None, + first_stage_var_shift=None, + dr_var_shift=None, + num_violated_cons=None, + max_violation=None, + dr_polishing_failed=None, + all_sep_problems_solved=None, + elapsed_time=get_main_elapsed_time(model_data.timing), + ) + log_record.log(config.progress_logger.info) update_grcs_solve_data( pyros_soln=model_data, k=k, @@ -280,6 +485,7 @@ def ROSolver_iterative_solve(model_data, config): nominal_data.nom_second_stage_cost = master_soln.second_stage_objective nominal_data.nom_obj = value(master_data.master_model.obj) + polishing_successful = True if ( config.decision_rule_order != 0 and len(config.second_stage_variables) > 0 @@ -294,8 +500,11 @@ def ROSolver_iterative_solve(model_data, config): vals.append(dvar.value) dr_var_lists_original.append(vals) - polishing_results = master_problem_methods.minimize_dr_vars( - model_data=master_data, config=config + polishing_results, polishing_successful = ( + master_problem_methods.minimize_dr_vars( + model_data=master_data, + config=config, + ) ) timing_data.total_dr_polish_time += get_time_from_solver(polishing_results) @@ -308,11 +517,45 @@ def ROSolver_iterative_solve(model_data, config): vals.append(dvar.value) dr_var_lists_polished.append(vals) - # === Check if time limit reached - elapsed = get_main_elapsed_time(model_data.timing) + # get current first-stage and DR variable values + # and compare with previous first-stage and DR variable + # values + current_master_fsv_vals = ComponentMap( + (var, value(var)) + for var in master_fsv_set + ) + current_master_dr_var_vals = ComponentMap( + (var, value(var)) + for var, expr in dr_var_scaled_expr_map.items() + ) + if k > 0: + first_stage_var_shift = max( + abs(current_master_fsv_vals[var] - previous_master_fsv_vals[var]) + for var in previous_master_fsv_vals + ) if current_master_fsv_vals else None + dr_var_shift = max( + abs(current_master_dr_var_vals[var] - previous_master_dr_var_vals[var]) + for var in previous_master_dr_var_vals + ) if current_master_dr_var_vals else None + else: + first_stage_var_shift = None + dr_var_shift = None + + # === Check if time limit reached after polishing if config.time_limit: + elapsed = get_main_elapsed_time(model_data.timing) if elapsed >= config.time_limit: - output_logger(config=config, time_out=True, elapsed=elapsed) + iter_log_record = IterationLogRecord( + iteration=k, + objective=value(master_data.master_model.obj), + first_stage_var_shift=first_stage_var_shift, + dr_var_shift=dr_var_shift, + num_violated_cons=None, + max_violation=None, + dr_polishing_failed=not polishing_successful, + all_sep_problems_solved=None, + elapsed_time=elapsed, + ) update_grcs_solve_data( pyros_soln=model_data, k=k, @@ -322,6 +565,7 @@ def ROSolver_iterative_solve(model_data, config): separation_data=separation_data, master_soln=master_soln, ) + iter_log_record.log(config.progress_logger.info) return model_data, [] # === Set up for the separation problem @@ -376,10 +620,39 @@ def ROSolver_iterative_solve(model_data, config): separation_results.violating_param_realization ) + scaled_violations = [ + solve_call_res.scaled_violations[con] + for con, solve_call_res + in separation_results.main_loop_results.solver_call_results.items() + if solve_call_res.scaled_violations is not None + ] + if scaled_violations: + max_sep_con_violation = max(scaled_violations) + else: + max_sep_con_violation = None + num_violated_cons = len( + separation_results.violated_performance_constraints + ) + all_sep_problems_solved = ( + len(scaled_violations) + == len(separation_model.util.performance_constraints) + ) + + iter_log_record = IterationLogRecord( + iteration=k, + objective=value(master_data.master_model.obj), + first_stage_var_shift=first_stage_var_shift, + dr_var_shift=dr_var_shift, + num_violated_cons=num_violated_cons, + max_violation=max_sep_con_violation, + dr_polishing_failed=not polishing_successful, + all_sep_problems_solved=all_sep_problems_solved, + elapsed_time=get_main_elapsed_time(model_data.timing), + ) + # terminate on time limit elapsed = get_main_elapsed_time(model_data.timing) if separation_results.time_out: - output_logger(config=config, time_out=True, elapsed=elapsed) termination_condition = pyrosTerminationCondition.time_out update_grcs_solve_data( pyros_soln=model_data, @@ -390,6 +663,7 @@ def ROSolver_iterative_solve(model_data, config): separation_data=separation_data, master_soln=master_soln, ) + iter_log_record.log(config.progress_logger.info) return model_data, separation_results # terminate on separation subsolver error @@ -404,24 +678,26 @@ def ROSolver_iterative_solve(model_data, config): separation_data=separation_data, master_soln=master_soln, ) + iter_log_record.log(config.progress_logger.info) return model_data, separation_results # === Check if we terminate due to robust optimality or feasibility, # or in the event of bypassing global separation, no violations robustness_certified = separation_results.robustness_certified if robustness_certified: - output_logger( - config=config, bypass_global_separation=config.bypass_global_separation - ) + if config.bypass_global_separation: + config.progress_logger.info( + "NOTE: Option to bypass global separation was chosen. " + "Robust feasibility and optimality of the reported " + "solution are not guaranteed." + ) robust_optimal = ( config.solve_master_globally and config.objective_focus is ObjectiveType.worst_case ) if robust_optimal: - output_logger(config=config, robust_optimal=True) termination_condition = pyrosTerminationCondition.robust_optimal else: - output_logger(config=config, robust_feasible=True) termination_condition = pyrosTerminationCondition.robust_feasible update_grcs_solve_data( pyros_soln=model_data, @@ -432,6 +708,7 @@ def ROSolver_iterative_solve(model_data, config): separation_data=separation_data, master_soln=master_soln, ) + iter_log_record.log(config.progress_logger.info) return model_data, separation_results # === Add block to master at violation @@ -445,11 +722,14 @@ def ROSolver_iterative_solve(model_data, config): k += 1 + iter_log_record.log(config.progress_logger.info) + previous_master_fsv_vals = current_master_fsv_vals + previous_master_dr_var_vals = current_master_dr_var_vals + # Iteration limit reached - output_logger(config=config, max_iter=True) update_grcs_solve_data( pyros_soln=model_data, - k=k, + k=k - 1, # remove last increment to fix iteration count term_cond=pyrosTerminationCondition.max_iter, nominal_data=nominal_data, timing_data=timing_data, diff --git a/pyomo/contrib/pyros/solve_data.py b/pyomo/contrib/pyros/solve_data.py index 63e7fdd7ebd..511c042e48e 100644 --- a/pyomo/contrib/pyros/solve_data.py +++ b/pyomo/contrib/pyros/solve_data.py @@ -541,6 +541,22 @@ def get_violating_attr(self, attr_name): return attr_val + @property + def worst_case_perf_con(self): + """ + ... + """ + return self.get_violating_attr("worst_case_perf_con") + + @property + def main_loop_results(self): + """ + Get main separation loop results. + """ + if self.global_separation_loop_results is not None: + return self.global_separation_loop_results + return self.local_separation_loop_results + @property def found_violation(self): """ diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index 2c1a309ced3..485744d2ec9 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -41,6 +41,7 @@ import logging from pprint import pprint import math +from pyomo.common.timing import TicTocTimer # Tolerances used in the code @@ -63,6 +64,10 @@ def time_code(timing_data_obj, code_block_name, is_main_timer=False): allowing calculation of total elapsed time 'on the fly' (e.g. to enforce a time limit) using `get_main_elapsed_time(timing_data_obj)`. """ + # initialize tic toc timer + timing_data_obj.tic_toc_timer = TicTocTimer() + timing_data_obj.tic_toc_timer.tic(msg=None) + start_time = timeit.default_timer() if is_main_timer: timing_data_obj.main_timer_start_time = start_time @@ -1383,6 +1388,149 @@ def process_termination_condition_master_problem(config, results): ) +class IterationLogRecord: + """ + PyROS solver iteration log record. + + Attributes + ---------- + iteration : int or None + Iteration number. + objective : int or None + Master problem objective value. + Note: if the sense of the original model is maximization, + then this is the negative of the objective value. + first_stage_var_shift : float or None + Infinity norm of the difference between first-stage + variable vectors for the current and previous iterations. + dr_var_shift : float or None + Infinity norm of the difference between decision rule + variable vectors for the current and previous iterations. + num_violated_cons : int or None + Number of performance constraints found to be violated + during separation step. + max_violation : int or None + Maximum scaled violation of any performance constraint + found during separation step. + """ + + _LINE_LENGTH = 78 + _ATTR_FORMAT_LENGTHS = { + "iteration": 5, + "objective": 13, + "first_stage_var_shift": 13, + "dr_var_shift": 13, + "num_violated_cons": 8, + "max_violation": 12, + "elapsed_time": 14, + } + _ATTR_HEADER_NAMES = { + "iteration": "Itn", + "objective": "Objective", + "first_stage_var_shift": "1-Stg Shift", + "dr_var_shift": "DR Shift", + "num_violated_cons": "#CViol", + "max_violation": "Max Viol", + "elapsed_time": "Wall Time (s)", + } + + def __init__( + self, + iteration, + objective, + first_stage_var_shift, + dr_var_shift, + dr_polishing_failed, + num_violated_cons, + all_sep_problems_solved, + max_violation, + elapsed_time, + ): + """Initialize self (see class docstring).""" + self.iteration = iteration + self.objective = objective + self.first_stage_var_shift = first_stage_var_shift + self.dr_var_shift = dr_var_shift + self.dr_polishing_failed = dr_polishing_failed + self.num_violated_cons = num_violated_cons + self.all_sep_problems_solved = all_sep_problems_solved + self.max_violation = max_violation + self.elapsed_time = elapsed_time + + def get_log_str(self): + """Get iteration log string.""" + attrs = [ + "iteration", + "objective", + "first_stage_var_shift", + "dr_var_shift", + "num_violated_cons", + "max_violation", + "elapsed_time", + ] + return "".join(self._format_record_attr(attr) for attr in attrs) + + def _format_record_attr(self, attr_name): + """Format attribute record for logging.""" + attr_val = getattr(self, attr_name) + if attr_val is None: + fmt_str = f"<{self._ATTR_FORMAT_LENGTHS[attr_name]}s" + return f"{'-':{fmt_str}}" + else: + attr_val_fstrs = { + "iteration": "f'{attr_val:d}'", + "objective": "f'{attr_val: .4e}'", + "first_stage_var_shift": "f'{attr_val:.4e}'", + "dr_var_shift": "f'{attr_val:.4e}'", + "num_violated_cons": "f'{attr_val:d}'", + "max_violation": "f'{attr_val:.4e}'", + "elapsed_time": "f'{attr_val:.2f}'", + } + + # qualifier for DR polishing and separation columns + if attr_name == "dr_var_shift": + qual = "*" if self.dr_polishing_failed else "" + elif attr_name == "num_violated_cons": + qual = "+" if not self.all_sep_problems_solved else "" + else: + qual = "" + + attr_val_str = f"{eval(attr_val_fstrs[attr_name])}{qual}" + + return ( + f"{attr_val_str:{f'<{self._ATTR_FORMAT_LENGTHS[attr_name]}'}}" + ) + + def log(self, log_func, **log_func_kwargs): + """Log self.""" + log_str = self.get_log_str() + log_func(log_str, **log_func_kwargs) + + @staticmethod + def get_log_header_str(): + """Get string for iteration log header.""" + fmt_lengths_dict = IterationLogRecord._ATTR_FORMAT_LENGTHS + header_names_dict = IterationLogRecord._ATTR_HEADER_NAMES + return "".join( + f"{header_names_dict[attr]:<{fmt_lengths_dict[attr]}s}" + for attr in fmt_lengths_dict + ) + + @staticmethod + def log_header(log_func, with_rules=True, **log_func_kwargs): + """Log header.""" + if with_rules: + IterationLogRecord.log_header_rule(log_func, **log_func_kwargs) + log_func(IterationLogRecord.get_log_header_str(), **log_func_kwargs) + if with_rules: + IterationLogRecord.log_header_rule(log_func, **log_func_kwargs) + + @staticmethod + def log_header_rule(log_func, fillchar="-", **log_func_kwargs): + """Log header rule.""" + log_func(fillchar * IterationLogRecord._LINE_LENGTH, **log_func_kwargs) + + def output_logger(config, **kwargs): ''' All user returned messages (termination conditions, runtime errors) are here From 8dca4071b619d167c301697a1a9da7faf5beb9e8 Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 7 Sep 2023 15:50:08 -0400 Subject: [PATCH 0114/1204] Reconfigure default PyROS progress logger --- pyomo/contrib/pyros/pyros.py | 7 +++---- pyomo/contrib/pyros/util.py | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index b67cf5c3df6..c5efbb8dadb 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -37,16 +37,15 @@ transform_to_standard_form, turn_bounds_to_constraints, replace_uncertain_bounds_with_constraints, + IterationLogRecord, + DEFAULT_LOGGER_NAME, ) from pyomo.contrib.pyros.solve_data import ROSolveResults from pyomo.contrib.pyros.pyros_algorithm_methods import ROSolver_iterative_solve from pyomo.contrib.pyros.uncertainty_sets import uncertainty_sets from pyomo.core.base import Constraint -from pyomo.common.timing import TicTocTimer -from pyomo.contrib.pyros.util import IterationLogRecord from datetime import datetime -import logging __version__ = "1.2.7" @@ -520,7 +519,7 @@ def pyros_config(): CONFIG.declare( "progress_logger", PyROSConfigValue( - default="pyomo.contrib.pyros", + default=DEFAULT_LOGGER_NAME, domain=a_logger, doc=( """ diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index 485744d2ec9..9a7eb4e2f76 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -51,6 +51,7 @@ COEFF_MATCH_ABS_TOL = 0 ABS_CON_CHECK_FEAS_TOL = 1e-5 TIC_TOC_SOLVE_TIME_ATTR = "pyros_tic_toc_time" +DEFAULT_LOGGER_NAME = "pyomo.contrib.pyros" '''Code borrowed from gdpopt: time_code, get_main_elapsed_time, a_logger.''' @@ -229,11 +230,42 @@ def revert_solver_max_time_adjustment( def a_logger(str_or_logger): - """Returns a logger when passed either a logger name or logger object.""" + """ + Standardize a string or logger object to a logger object. + + Parameters + ---------- + str_or_logger : str or logging.Logger + String or logger object to normalize. + + Returns + ------- + logging.Logger + If `str_or_logger` is of type `logging.Logger`,then + `str_or_logger` is returned. + Otherwise, a logger with name `str_or_logger`, INFO level, + ``propagate=False``, and handlers reduced to just a single + stream handler, is returned. + """ if isinstance(str_or_logger, logging.Logger): return str_or_logger else: - return logging.getLogger(str_or_logger) + logger = logging.getLogger(str_or_logger) + + if str_or_logger == DEFAULT_LOGGER_NAME: + # turn off propagate to remove possible influence + # of overarching Pyomo logger settings + logger.propagate = False + + # clear handlers, want just a single stream handler + logger.handlers.clear() + ch = logging.StreamHandler() + logger.addHandler(ch) + + # info level logger + logger.setLevel(logging.INFO) + + return logger def ValidEnum(enum_class): From 4b6cd10137975c320b621399bf1372aef75f5607 Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 7 Sep 2023 16:17:47 -0400 Subject: [PATCH 0115/1204] Add detailed subsolver failure warning messages --- pyomo/contrib/pyros/master_problem_methods.py | 42 ++++++++++++++---- .../pyros/separation_problem_methods.py | 44 +++++++++++++++---- 2 files changed, 68 insertions(+), 18 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index df9d4e1fbcb..bfc8bafa8fb 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -657,7 +657,7 @@ def solver_call_master(model_data, config, solver, solve_data): # errors, etc.) config.progress_logger.error( f"Solver {repr(opt)} encountered exception attempting to " - f"optimize master problem in iteration {model_data.iteration}" + f"optimize master problem in iteration {model_data.iteration}." ) raise else: @@ -718,6 +718,20 @@ def solver_call_master(model_data, config, solver, solve_data): nlp_model.scenarios[0, 0].first_stage_objective ) + # debugging: log breakdown of master objective + config.progress_logger.debug("Master objective") + config.progress_logger.debug( + f" First-stage objective {master_soln.first_stage_objective}" + ) + config.progress_logger.debug( + f" Second-stage objective {master_soln.second_stage_objective}" + ) + master_obj = ( + master_soln.first_stage_objective + + master_soln.second_stage_objective + ) + config.progress_logger.debug(f" Objective {master_obj}") + master_soln.nominal_block = nlp_model.scenarios[0, 0] master_soln.results = results master_soln.master_model = nlp_model @@ -745,8 +759,9 @@ def solver_call_master(model_data, config, solver, solve_data): # NOTE: subproblem is written with variables set to their # initial values (not the final subsolver iterate) save_dir = config.subproblem_file_directory + serialization_msg = "" if save_dir and config.keepfiles: - name = os.path.join( + output_problem_path = os.path.join( save_dir, ( config.uncertainty_set.type @@ -757,15 +772,24 @@ def solver_call_master(model_data, config, solver, solve_data): + ".bar" ), ) - nlp_model.write(name, io_options={'symbolic_solver_labels': True}) - output_logger( - config=config, - master_error=True, - status_dict=solver_term_cond_dict, - filename=name, - iteration=model_data.iteration, + nlp_model.write( + output_problem_path, + io_options={'symbolic_solver_labels': True}, ) + serialization_msg = ( + f" Problem has been serialized to path {output_problem_path!r}." + ) + master_soln.pyros_termination_condition = pyrosTerminationCondition.subsolver_error + solve_mode = "global" if config.solve_master_globally else "local" + config.progress_logger.warning( + f"Could not successfully solve master problem of iteration " + f"{model_data.iteration} with any of the " + f"provided subordinate {solve_mode} optimizers. " + f"(Termination statuses: " + f"{[term_cond for term_cond in solver_term_cond_dict.values()]}.)" + f"{ serialization_msg}" + ) return master_soln diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index 2c41c869474..872c2f0edb5 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -929,6 +929,26 @@ def solver_call_separation( solver_status_dict = {} nlp_model = model_data.separation_model + # get name of constraint for loggers + orig_con = ( + nlp_model.util.map_new_constraint_list_to_original_con.get( + perf_con_to_maximize, + perf_con_to_maximize, + ) + ) + if orig_con is perf_con_to_maximize: + con_name_repr = ( + f"{perf_con_to_maximize.name!r} " + f"(mapped to objective {separation_obj.name!r})" + ) + else: + con_name_repr = ( + f"{perf_con_to_maximize.name!r} " + f"(originally named {orig_con.name!r}, " + f"mapped to objective {separation_obj.name!r})" + ) + solve_mode = "global" if solve_globally else "local" + # === Initialize separation problem; fix first-stage variables initialize_separation(model_data, config) @@ -1020,9 +1040,10 @@ def solver_call_separation( # error. At this point, export model if desired solve_call_results.subsolver_error = True save_dir = config.subproblem_file_directory + serialization_msg = "" if save_dir and config.keepfiles: objective = separation_obj.name - name = os.path.join( + output_problem_path = os.path.join( save_dir, ( config.uncertainty_set.type @@ -1035,15 +1056,20 @@ def solver_call_separation( + ".bar" ), ) - nlp_model.write(name, io_options={'symbolic_solver_labels': True}) - output_logger( - config=config, - separation_error=True, - filename=name, - iteration=model_data.iteration, - objective=objective, - status_dict=solver_status_dict, + nlp_model.write(output_problem_path, io_options={'symbolic_solver_labels': True}) + serialization_msg = ( + f"Problem has been serialized to path {output_problem_path!r}." ) + solve_call_results.message = ( + "Could not successfully solve separation problem of iteration " + f"{model_data.iteration} " + f"for performance constraint {con_name_repr} with any of the " + f"provided subordinate {solve_mode} optimizers. " + f"(Termination statuses: " + f"{[str(term_cond) for term_cond in solver_status_dict.values()]}.)" + f"{ serialization_msg}" + ) + config.progress_logger.warning(solve_call_results.message) separation_obj.deactivate() From 46dfa5d3f8a6267f9da1488fc65733bf83823625 Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 7 Sep 2023 16:29:42 -0400 Subject: [PATCH 0116/1204] Standardize subsolver exception log messages --- pyomo/contrib/pyros/master_problem_methods.py | 8 ++++---- pyomo/contrib/pyros/separation_problem_methods.py | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index bfc8bafa8fb..81ae38dc955 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -253,8 +253,8 @@ def solve_master_feasibility_problem(model_data, config): # (such as segmentation faults, function evaluation # errors, etc.) config.progress_logger.error( - f"Solver {repr(solver)} encountered exception attempting to " - "optimize master feasibility problem in iteration " + f"Optimizer {repr(solver)} encountered exception " + "attempting to solve master feasibility problem in iteration " f"{model_data.iteration}" ) raise @@ -656,8 +656,8 @@ def solver_call_master(model_data, config, solver, solve_data): # (such as segmentation faults, function evaluation # errors, etc.) config.progress_logger.error( - f"Solver {repr(opt)} encountered exception attempting to " - f"optimize master problem in iteration {model_data.iteration}." + f"Optimizer {repr(opt)} encountered exception attempting to " + f"solve master problem in iteration {model_data.iteration}." ) raise else: diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index 872c2f0edb5..f4a3d6df7af 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -978,10 +978,11 @@ def solver_call_separation( # account for possible external subsolver errors # (such as segmentation faults, function evaluation # errors, etc.) + adverb = "globally" if solve_globally else "locally" config.progress_logger.error( - f"Solver {repr(opt)} encountered exception attempting to " - "optimize separation problem in iteration " - f"{model_data.iteration}" + f"Optimizer {repr(opt)} encountered exception attempting " + f"to {adverb} solve separation problem for constraint " + f"{con_name_repr} in iteration {model_data.iteration}." ) raise else: From 7bcdb98b43afd7cc6b9cbe95c4b84cdf985bb67b Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 7 Sep 2023 17:26:09 -0400 Subject: [PATCH 0117/1204] Add backup solver warning level messages --- pyomo/contrib/pyros/master_problem_methods.py | 13 +++++++++---- pyomo/contrib/pyros/separation_problem_methods.py | 9 ++++++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index 81ae38dc955..c50fba746d7 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -631,15 +631,20 @@ def solver_call_master(model_data, config, solver, solve_data): solver_term_cond_dict = {} if config.solve_master_globally: - backup_solvers = deepcopy(config.backup_global_solvers) + solvers = [solver] + config.backup_global_solvers else: - backup_solvers = deepcopy(config.backup_local_solvers) - backup_solvers.insert(0, solver) + solvers = [solver] + config.backup_local_solvers higher_order_decision_rule_efficiency(config, model_data) timer = TicTocTimer() - for opt in backup_solvers: + for idx, opt in enumerate(solvers): + if idx > 0: + config.progress_logger.warning( + f"Invoking backup solver {opt!r} " + f"(solver {idx + 1} of {len(solvers)}) for " + f"master problem of iteration {model_data.iteration}." + ) orig_setting, custom_setting_present = adjust_solver_time_settings( model_data.timing, opt, config ) diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index f4a3d6df7af..3536001baf4 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -962,7 +962,14 @@ def solver_call_separation( subsolver_error=False, ) timer = TicTocTimer() - for opt in solvers: + for idx, opt in enumerate(solvers): + if idx > 0: + config.progress_logger.warning( + f"Invoking backup solver {opt!r} " + f"(solver {idx + 1} of {len(solvers)}) for {solve_mode} " + f"separation of performance constraint {con_name_repr} " + f"in iteration {model_data.iteration}." + ) orig_setting, custom_setting_present = adjust_solver_time_settings( model_data.timing, opt, config ) From 2119c21090f2be280467286ee3fdee58ec386849 Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 7 Sep 2023 17:34:22 -0400 Subject: [PATCH 0118/1204] Tweak subsolver exception log messages --- pyomo/contrib/pyros/master_problem_methods.py | 3 ++- pyomo/contrib/pyros/separation_problem_methods.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index c50fba746d7..1e3a95835e1 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -661,7 +661,8 @@ def solver_call_master(model_data, config, solver, solve_data): # (such as segmentation faults, function evaluation # errors, etc.) config.progress_logger.error( - f"Optimizer {repr(opt)} encountered exception attempting to " + f"Optimizer {repr(opt)} ({idx + 1} of {len(solvers)}) " + "encountered exception attempting to " f"solve master problem in iteration {model_data.iteration}." ) raise diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index 3536001baf4..b58871e2b10 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -987,7 +987,8 @@ def solver_call_separation( # errors, etc.) adverb = "globally" if solve_globally else "locally" config.progress_logger.error( - f"Optimizer {repr(opt)} encountered exception attempting " + f"Optimizer {repr(opt)} ({idx + 1} of {len(solvers)}) " + f"encountered exception attempting " f"to {adverb} solve separation problem for constraint " f"{con_name_repr} in iteration {model_data.iteration}." ) From 7fff720eb10490d7f218d3bb80464530304fdedb Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 7 Sep 2023 18:33:47 -0400 Subject: [PATCH 0119/1204] Add more debug level log messages --- pyomo/contrib/pyros/master_problem_methods.py | 52 +++++++ .../contrib/pyros/pyros_algorithm_methods.py | 6 + .../pyros/separation_problem_methods.py | 127 ++++++++++++++---- 3 files changed, 157 insertions(+), 28 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index 1e3a95835e1..f925313806e 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -470,6 +470,15 @@ def minimize_dr_vars(model_data, config): else: solver = config.local_solver + config.progress_logger.debug("Solving DR polishing problem") + + # NOTE: this objective evalaution may not be accurate, due + # to the current initialization scheme for the auxiliary + # variables. new initialization will be implemented in the + # near future. + polishing_obj = polishing_model.scenarios[0, 0].polishing_obj + config.progress_logger.debug(f" Initial DR norm: {value(polishing_obj)}") + # === Solve the polishing model timer = TicTocTimer() orig_setting, custom_setting_present = adjust_solver_time_settings( @@ -492,6 +501,16 @@ def minimize_dr_vars(model_data, config): solver, orig_setting, custom_setting_present, config ) + # interested in the time and termination status for debugging + # purposes + config.progress_logger.debug(" Done solving DR polishing problem") + config.progress_logger.debug( + f" Solve time: {getattr(results.solver, TIC_TOC_SOLVE_TIME_ATTR)} s" + ) + config.progress_logger.debug( + f" Termination status: {results.solver.termination_condition} " + ) + # === Process solution by termination condition acceptable = {tc.globallyOptimal, tc.optimal, tc.locallyOptimal, tc.feasible} if results.solver.termination_condition not in acceptable: @@ -523,6 +542,37 @@ def minimize_dr_vars(model_data, config): for mvar, pvar in zip(master_dr.values(), polish_dr.values()): mvar.set_value(value(pvar), skip_validation=True) + config.progress_logger.debug(f" Optimized DDR norm: {value(polishing_obj)}") + config.progress_logger.debug("Polished Master objective:") + + # print master solution + if config.objective_focus == ObjectiveType.worst_case: + worst_blk_idx = max( + model_data.master_model.scenarios.keys(), + key=lambda idx: value( + model_data.master_model.scenarios[idx] + .second_stage_objective + ) + ) + else: + worst_blk_idx = (0, 0) + + # debugging: summarize objective breakdown + worst_master_blk = model_data.master_model.scenarios[worst_blk_idx] + config.progress_logger.debug( + " First-stage objective " + f"{value(worst_master_blk.first_stage_objective)}" + ) + config.progress_logger.debug( + " Second-stage objective " + f"{value(worst_master_blk.second_stage_objective)}" + ) + polished_master_obj = value( + worst_master_blk.first_stage_objective + + worst_master_blk.second_stage_objective + ) + config.progress_logger.debug(f" Objective {polished_master_obj}") + return results, True @@ -637,6 +687,8 @@ def solver_call_master(model_data, config, solver, solve_data): higher_order_decision_rule_efficiency(config, model_data) + config.progress_logger.debug("Solving master problem") + timer = TicTocTimer() for idx, opt in enumerate(solvers): if idx > 0: diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index 6787b49ca6f..d9a05425135 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -21,6 +21,7 @@ from pyomo.common.collections import ComponentSet, ComponentMap from pyomo.core.base.var import _VarData as VarData from itertools import chain +import numpy as np def update_grcs_solve_data( @@ -720,6 +721,11 @@ def ROSolver_iterative_solve(model_data, config): separation_results.violating_param_realization ) + config.progress_logger.debug("Points added to master:") + config.progress_logger.debug( + np.array([pt for pt in separation_data.points_added_to_master]), + ) + k += 1 iter_log_record.log(config.progress_logger.info) diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index b58871e2b10..61bc514f933 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -536,6 +536,61 @@ def get_worst_discrete_separation_solution( ) +def get_con_name_repr( + separation_model, + perf_con, + with_orig_name=True, + with_obj_name=True, + ): + """ + Get string representation of performance constraint + and any other modeling components to which it has + been mapped. + + Parameters + ---------- + separation_model : ConcreteModel + Separation model. + perf_con : ScalarConstraint or ConstraintData + Performance constraint for which to get the + representation + with_orig_name : bool, optional + If constraint was added during construction of the + separation problem (i.e. if the constraint is a member of + in `separation_model.util.new_constraints`), + include the name of the original constraint from which + `perf_con` was created. + with_obj_name : bool, optional + Include name of separation model objective to which + performance constraint is mapped. + + Returns + ------- + str + Constraint name representation. + """ + + qual_strs = [] + if with_orig_name: + # check performance constraint was not added + # at construction of separation problem + orig_con = ( + separation_model + .util + .map_new_constraint_list_to_original_con.get(perf_con, perf_con) + ) + if orig_con is not perf_con: + qual_strs.append(f"originally {orig_con.name!r}") + if with_obj_name: + objectives_map = separation_model.util.map_obj_to_constr + separation_obj = objectives_map[perf_con] + qual_strs.append(f"mapped to objective {separation_obj.name!r}") + + final_qual_str = f"({', '.join(qual_strs)})" if qual_strs else "" + + return f"{perf_con.name!r} {final_qual_str}" + + def perform_separation_loop(model_data, config, solve_globally): """ Loop through, and solve, PyROS separation problems to @@ -635,7 +690,15 @@ def perform_separation_loop(model_data, config, solve_globally): all_solve_call_results = ComponentMap() for priority, perf_constraints in sorted_priority_groups.items(): priority_group_solve_call_results = ComponentMap() - for perf_con in perf_constraints: + for idx, perf_con in enumerate(perf_constraints): + solve_adverb = "Globally" if solve_globally else "Locally" + config.progress_logger.debug( + f"{solve_adverb} separating constraint " + f"{get_con_name_repr(model_data.separation_model, perf_con)} " + f"(group priority {priority}, " + f"constraint {idx + 1} of {len(perf_constraints)})" + ) + # config.progress_logger.info( # f"Separating constraint {perf_con.name}" # ) @@ -689,17 +752,27 @@ def perform_separation_loop(model_data, config, solve_globally): ) # # auxiliary log messages - # objectives_map = ( - # model_data.separation_model.util.map_obj_to_constr - # ) - # violated_con_name = list(objectives_map.keys())[ - # worst_case_perf_con - # ] - # config.progress_logger.info( - # f"Violation found for constraint {violated_con_name} " - # "under realization " - # f"{worst_case_res.violating_param_realization}" - # ) + violated_con_names = "\n ".join( + get_con_name_repr(model_data.separation_model, con) + for con, res in all_solve_call_results.items() + if res.found_violation + ) + config.progress_logger.debug( + f"Violated constraints:\n {violated_con_names} " + ) + config.progress_logger.debug( + "Worst-case constraint: " + f"{get_con_name_repr(model_data.separation_model, worst_case_perf_con)} " + "under realization " + f"{worst_case_res.violating_param_realization}." + ) + config.progress_logger.debug( + f"Maximal scaled violation " + f"{worst_case_res.scaled_violations[worst_case_perf_con]} " + "from this constraint " + "exceeds the robust feasibility tolerance " + f"{config.robust_feasibility_tolerance}" + ) # violating separation problem solution now chosen. # exit loop @@ -930,23 +1003,12 @@ def solver_call_separation( nlp_model = model_data.separation_model # get name of constraint for loggers - orig_con = ( - nlp_model.util.map_new_constraint_list_to_original_con.get( - perf_con_to_maximize, - perf_con_to_maximize, - ) + con_name_repr = get_con_name_repr( + separation_model=nlp_model, + perf_con=perf_con_to_maximize, + with_orig_name=True, + with_obj_name=True, ) - if orig_con is perf_con_to_maximize: - con_name_repr = ( - f"{perf_con_to_maximize.name!r} " - f"(mapped to objective {separation_obj.name!r})" - ) - else: - con_name_repr = ( - f"{perf_con_to_maximize.name!r} " - f"(originally named {orig_con.name!r}, " - f"mapped to objective {separation_obj.name!r})" - ) solve_mode = "global" if solve_globally else "local" # === Initialize separation problem; fix first-stage variables @@ -1043,6 +1105,15 @@ def solver_call_separation( separation_obj.deactivate() return solve_call_results + else: + config.progress_logger.debug( + f"Solver {opt} ({idx + 1} of {len(solvers)}) " + f"failed for {solve_mode} separation of performance " + f"constraint {con_name_repr} in iteration " + f"{model_data.iteration}. Termination condition: " + f"{results.solver.termination_condition!r}." + ) + config.progress_logger.debug(f"Results:\n{results.solver}") # All subordinate solvers failed to optimize model to appropriate # termination condition. PyROS will terminate with subsolver From 696a3b78a1787e1930e1c013238641c7a555394e Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 7 Sep 2023 18:47:39 -0400 Subject: [PATCH 0120/1204] Fix all testing issues --- pyomo/contrib/pyros/tests/test_grcs.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 0d24b799b99..0c9351835ec 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -59,6 +59,7 @@ sqrt, value, ) +import logging if not (numpy_available and scipy_available): @@ -3589,6 +3590,7 @@ def test_solve_master(self): ) config.declare("subproblem_file_directory", ConfigValue(default=None)) config.declare("time_limit", ConfigValue(default=None)) + config.declare("progress_logger", ConfigValue(default=logging.getLogger(__name__))) with time_code(master_data.timing, "total", is_main_timer=True): master_soln = solve_master(master_data, config) @@ -3866,7 +3868,7 @@ def test_minimize_dr_norm(self): m.working_model.util.state_vars = [] m.working_model.util.first_stage_variables = [] - config = Block() + config = Bunch() config.decision_rule_order = 1 config.objective_focus = ObjectiveType.nominal config.global_solver = SolverFactory('baron') @@ -3874,6 +3876,7 @@ def test_minimize_dr_norm(self): config.tee = False config.solve_master_globally = True config.time_limit = None + config.progress_logger = logging.getLogger(__name__) add_decision_rule_variables(model_data=m, config=config) add_decision_rule_constraints(model_data=m, config=config) @@ -3895,12 +3898,16 @@ def test_minimize_dr_norm(self): master_data.timing = Bunch() with time_code(master_data.timing, "total", is_main_timer=True): - results = minimize_dr_vars(model_data=master_data, config=config) + results, success = minimize_dr_vars(model_data=master_data, config=config) self.assertEqual( results.solver.termination_condition, TerminationCondition.optimal, msg="Minimize dr norm did not solve to optimality.", ) + self.assertTrue( + success, + msg=f"DR polishing {success=}, expected True." + ) @unittest.skipUnless( baron_license_is_valid, "Global NLP solver is not available and licensed." @@ -4867,8 +4874,7 @@ def test_coefficient_matching_raises_error_4_3(self): with self.assertRaisesRegex( ValueError, expected_regex=( - "Equality constraint.*cannot be guaranteed to be robustly " - "feasible.*" + "Coefficient matching unsuccessful. See the solver logs." ), ): res = pyros_solver.solve( From 673b5498112d5e86dd6d8919072b93d5ca6187b0 Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 7 Sep 2023 19:14:27 -0400 Subject: [PATCH 0121/1204] Add spacing for serialization messages --- pyomo/contrib/pyros/master_problem_methods.py | 4 ++-- pyomo/contrib/pyros/separation_problem_methods.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index f925313806e..1fc65a552a8 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -845,8 +845,8 @@ def solver_call_master(model_data, config, solver, solve_data): f"{model_data.iteration} with any of the " f"provided subordinate {solve_mode} optimizers. " f"(Termination statuses: " - f"{[term_cond for term_cond in solver_term_cond_dict.values()]}.)" - f"{ serialization_msg}" + f"{[term_cond for term_cond in solver_term_cond_dict.values()]}.) " + f"{serialization_msg}" ) return master_soln diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index 61bc514f933..6efa0f66071 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -1146,8 +1146,8 @@ def solver_call_separation( f"for performance constraint {con_name_repr} with any of the " f"provided subordinate {solve_mode} optimizers. " f"(Termination statuses: " - f"{[str(term_cond) for term_cond in solver_status_dict.values()]}.)" - f"{ serialization_msg}" + f"{[str(term_cond) for term_cond in solver_status_dict.values()]}.) " + f"{serialization_msg}" ) config.progress_logger.warning(solve_call_results.message) From 46f31dd2df15e73041bffb0a152a5fc17f793202 Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 7 Sep 2023 19:39:05 -0400 Subject: [PATCH 0122/1204] Apply black formatting --- pyomo/contrib/pyros/master_problem_methods.py | 20 +- pyomo/contrib/pyros/pyros.py | 59 ++---- .../contrib/pyros/pyros_algorithm_methods.py | 199 ++++++++---------- .../pyros/separation_problem_methods.py | 17 +- pyomo/contrib/pyros/util.py | 26 ++- 5 files changed, 133 insertions(+), 188 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index 1fc65a552a8..b71bbb9285a 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -550,9 +550,8 @@ def minimize_dr_vars(model_data, config): worst_blk_idx = max( model_data.master_model.scenarios.keys(), key=lambda idx: value( - model_data.master_model.scenarios[idx] - .second_stage_objective - ) + model_data.master_model.scenarios[idx].second_stage_objective + ), ) else: worst_blk_idx = (0, 0) @@ -560,16 +559,13 @@ def minimize_dr_vars(model_data, config): # debugging: summarize objective breakdown worst_master_blk = model_data.master_model.scenarios[worst_blk_idx] config.progress_logger.debug( - " First-stage objective " - f"{value(worst_master_blk.first_stage_objective)}" + " First-stage objective " f"{value(worst_master_blk.first_stage_objective)}" ) config.progress_logger.debug( - " Second-stage objective " - f"{value(worst_master_blk.second_stage_objective)}" + " Second-stage objective " f"{value(worst_master_blk.second_stage_objective)}" ) polished_master_obj = value( - worst_master_blk.first_stage_objective - + worst_master_blk.second_stage_objective + worst_master_blk.first_stage_objective + worst_master_blk.second_stage_objective ) config.progress_logger.debug(f" Objective {polished_master_obj}") @@ -785,8 +781,7 @@ def solver_call_master(model_data, config, solver, solve_data): f" Second-stage objective {master_soln.second_stage_objective}" ) master_obj = ( - master_soln.first_stage_objective - + master_soln.second_stage_objective + master_soln.first_stage_objective + master_soln.second_stage_objective ) config.progress_logger.debug(f" Objective {master_obj}") @@ -831,8 +826,7 @@ def solver_call_master(model_data, config, solver, solve_data): ), ) nlp_model.write( - output_problem_path, - io_options={'symbolic_solver_labels': True}, + output_problem_path, io_options={'symbolic_solver_labels': True} ) serialization_msg = ( f" Problem has been serialized to path {output_problem_path!r}." diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index c5efbb8dadb..89f0c7a39d6 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -62,18 +62,12 @@ def _get_pyomo_git_info(): git_info_dict = {} commands_dict = { - "branch": [ - "git", "-C", f"{pyros_dir}", "rev-parse", "--abbrev-ref", "HEAD" - ], - "commit hash": [ - "git", "-C", f"{pyros_dir}", "rev-parse", "--short", "HEAD" - ], + "branch": ["git", "-C", f"{pyros_dir}", "rev-parse", "--abbrev-ref", "HEAD"], + "commit hash": ["git", "-C", f"{pyros_dir}", "rev-parse", "--short", "HEAD"], } for field, command in commands_dict.items(): try: - field_val = ( - subprocess.check_output(command).decode("ascii").strip() - ) + field_val = subprocess.check_output(command).decode("ascii").strip() except subprocess.CalledProcessError: field_val = "unknown" @@ -711,10 +705,7 @@ def _log_intro(self, logger, **log_kwargs): Should not include `msg`. """ logger.log(msg="=" * self._LOG_LINE_LENGTH, **log_kwargs) - logger.log( - msg="PyROS: The Pyomo Robust Optimization Solver.", - **log_kwargs, - ) + logger.log(msg="PyROS: The Pyomo Robust Optimization Solver.", **log_kwargs) git_info_str = ", ".join( f"{field}: {val}" for field, val in _get_pyomo_git_info().items() @@ -747,8 +738,7 @@ def _log_intro(self, logger, **log_kwargs): ) logger.log( msg=( - "(1) Carnegie Mellon University, " - "Department of Chemical Engineering" + "(1) Carnegie Mellon University, " "Department of Chemical Engineering" ), **log_kwargs, ) @@ -788,10 +778,7 @@ def _log_disclaimer(self, logger, **log_kwargs): disclaimer_header = " DISCLAIMER ".center(self._LOG_LINE_LENGTH, "=") logger.log(msg=disclaimer_header, **log_kwargs) - logger.log( - msg="PyROS is still under development. ", - **log_kwargs, - ) + logger.log(msg="PyROS is still under development. ", **log_kwargs) logger.log( msg=( "Please provide feedback and/or report any issues by creating " @@ -799,10 +786,7 @@ def _log_disclaimer(self, logger, **log_kwargs): ), **log_kwargs, ) - logger.log( - msg="https://github.com/Pyomo/pyomo/issues/new/choose", - **log_kwargs, - ) + logger.log(msg="https://github.com/Pyomo/pyomo/issues/new/choose", **log_kwargs) logger.log(msg="=" * self._LOG_LINE_LENGTH, **log_kwargs) def solve( @@ -887,20 +871,14 @@ def solve( # === Start timer, run the algorithm model_data.timing = Bunch() with time_code( - timing_data_obj=model_data.timing, - code_block_name='total', - is_main_timer=True, - ): + timing_data_obj=model_data.timing, + code_block_name='total', + is_main_timer=True, + ): tt_timer = model_data.timing.tic_toc_timer # output intro and disclaimer - self._log_intro( - config.progress_logger, - level=logging.INFO, - ) - self._log_disclaimer( - config.progress_logger, - level=logging.INFO, - ) + self._log_intro(config.progress_logger, level=logging.INFO) + self._log_disclaimer(config.progress_logger, level=logging.INFO) # log solver options excl_from_config_display = [ @@ -1070,9 +1048,7 @@ def solve( pyrosTerminationCondition.robust_infeasible: ( "Problem is robust infeasible." ), - pyrosTerminationCondition.time_out: ( - "Maximum allowable time exceeded." - ), + pyrosTerminationCondition.time_out: ("Maximum allowable time exceeded."), pyrosTerminationCondition.max_iter: ( "Maximum number of iterations reached." ), @@ -1086,15 +1062,12 @@ def solve( ) config.progress_logger.info("-" * self._LOG_LINE_LENGTH) config.progress_logger.info("Termination stats:") - config.progress_logger.info( - f" {'Iterations':<22s}: {return_soln.iterations}" - ) + config.progress_logger.info(f" {'Iterations':<22s}: {return_soln.iterations}") config.progress_logger.info( f" {'Solve time (wall s)':<22s}: {return_soln.time:.4f}" ) config.progress_logger.info( - f" {'Final objective value':<22s}: " - f"{return_soln.final_objective_value}" + f" {'Final objective value':<22s}: " f"{return_soln.final_objective_value}" ) config.progress_logger.info( f" {'Termination condition':<22s}: " diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index d9a05425135..bba81222aa4 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -13,10 +13,7 @@ pyrosTerminationCondition, IterationLogRecord, ) -from pyomo.contrib.pyros.util import ( - get_main_elapsed_time, - coefficient_matching, -) +from pyomo.contrib.pyros.util import get_main_elapsed_time, coefficient_matching from pyomo.core.base import value from pyomo.common.collections import ComponentSet, ComponentMap from pyomo.core.base.var import _VarData as VarData @@ -51,20 +48,14 @@ def update_grcs_solve_data( def get_dr_var_to_scaled_expr_map( - decision_rule_eqns, - second_stage_vars, - uncertain_params, - decision_rule_vars, - ): + decision_rule_eqns, second_stage_vars, uncertain_params, decision_rule_vars +): """ Generate mapping from decision rule variables to their terms in a model's DR expression. """ var_to_scaled_expr_map = ComponentMap() - ssv_dr_eq_zip = zip( - second_stage_vars, - decision_rule_eqns, - ) + ssv_dr_eq_zip = zip(second_stage_vars, decision_rule_eqns) for ssv_idx, (ssv, dr_eq) in enumerate(ssv_dr_eq_zip): for term in dr_eq.body.args: is_ssv_term = ( @@ -83,16 +74,19 @@ def evaluate_and_log_component_stats(model_data, separation_model, config): """ Evaluate and log model component statistics. """ - config.progress_logger.info( - "Model statistics:" - ) + config.progress_logger.info("Model statistics:") # print model statistics - dr_var_set = ComponentSet(chain(*tuple( - indexed_dr_var.values() - for indexed_dr_var in model_data.working_model.util.decision_rule_vars - ))) + dr_var_set = ComponentSet( + chain( + *tuple( + indexed_dr_var.values() + for indexed_dr_var in model_data.working_model.util.decision_rule_vars + ) + ) + ) first_stage_vars = [ - var for var in model_data.working_model.util.first_stage_variables + var + for var in model_data.working_model.util.first_stage_variables if var not in dr_var_set ] @@ -107,24 +101,25 @@ def evaluate_and_log_component_stats(model_data, separation_model, config): num_vars = int(has_epigraph_con) + num_fsv + num_ssv + num_sv + num_dr_vars eq_cons = [ - con for con in - model_data.working_model.component_data_objects( - Constraint, - active=True, + con + for con in model_data.working_model.component_data_objects( + Constraint, active=True ) if con.equality ] - dr_eq_set = ComponentSet(chain(*tuple( - indexed_dr_eq.values() - for indexed_dr_eq in model_data.working_model.util.decision_rule_eqns - ))) + dr_eq_set = ComponentSet( + chain( + *tuple( + indexed_dr_eq.values() + for indexed_dr_eq in model_data.working_model.util.decision_rule_eqns + ) + ) + ) num_eq_cons = len(eq_cons) num_dr_cons = len(dr_eq_set) - num_coefficient_matching_cons = len(getattr( - model_data.working_model, - "coefficient_matching_constraints", - [], - )) + num_coefficient_matching_cons = len( + getattr(model_data.working_model, "coefficient_matching_constraints", []) + ) num_other_eq_cons = num_eq_cons - num_dr_cons - num_coefficient_matching_cons # get performance constraints as referenced in the separation @@ -135,8 +130,7 @@ def evaluate_and_log_component_stats(model_data, separation_model, config): for con in separation_model.util.performance_constraints ) is_epigraph_con_first_stage = ( - has_epigraph_con - and sep_model_epigraph_con not in perf_con_set + has_epigraph_con and sep_model_epigraph_con not in perf_con_set ) working_model_perf_con_set = ComponentSet( model_data.working_model.find_component(new_sep_con_map.get(con, con)) @@ -150,53 +144,38 @@ def evaluate_and_log_component_stats(model_data, separation_model, config): for var in first_stage_vars ) ineq_con_set = [ - con for con in - model_data.working_model.component_data_objects( - Constraint, - active=True, + con + for con in model_data.working_model.component_data_objects( + Constraint, active=True ) if not con.equality ] - num_fsv_ineqs = num_fsv_bounds + len( - [con for con in ineq_con_set if con not in working_model_perf_con_set] - ) + is_epigraph_con_first_stage - num_ineq_cons = ( - len(ineq_con_set) - + has_epigraph_con - + num_fsv_bounds + num_fsv_ineqs = ( + num_fsv_bounds + + len([con for con in ineq_con_set if con not in working_model_perf_con_set]) + + is_epigraph_con_first_stage ) + num_ineq_cons = len(ineq_con_set) + has_epigraph_con + num_fsv_bounds - config.progress_logger.info( - f"{' Number of variables'} : {num_vars}" - ) - config.progress_logger.info( - f"{' Epigraph variable'} : {int(has_epigraph_con)}" - ) + config.progress_logger.info(f"{' Number of variables'} : {num_vars}") + config.progress_logger.info(f"{' Epigraph variable'} : {int(has_epigraph_con)}") config.progress_logger.info(f"{' First-stage variables'} : {num_fsv}") config.progress_logger.info(f"{' Second-stage variables'} : {num_ssv}") config.progress_logger.info(f"{' State variables'} : {num_sv}") config.progress_logger.info(f"{' Decision rule variables'} : {num_dr_vars}") config.progress_logger.info( - f"{' Number of constraints'} : " - f"{num_ineq_cons + num_eq_cons}" - ) - config.progress_logger.info( - f"{' Equality constraints'} : {num_eq_cons}" + f"{' Number of constraints'} : " f"{num_ineq_cons + num_eq_cons}" ) + config.progress_logger.info(f"{' Equality constraints'} : {num_eq_cons}") config.progress_logger.info( f"{' Coefficient matching constraints'} : " f"{num_coefficient_matching_cons}" ) + config.progress_logger.info(f"{' Decision rule equations'} : {num_dr_cons}") config.progress_logger.info( - f"{' Decision rule equations'} : {num_dr_cons}" - ) - config.progress_logger.info( - f"{' All other equality constraints'} : " - f"{num_other_eq_cons}" - ) - config.progress_logger.info( - f"{' Inequality constraints'} : {num_ineq_cons}" + f"{' All other equality constraints'} : " f"{num_other_eq_cons}" ) + config.progress_logger.info(f"{' Inequality constraints'} : {num_ineq_cons}") config.progress_logger.info( f"{' First-stage inequalities (incl. certain var bounds)'} : " f"{num_fsv_ineqs}" @@ -319,9 +298,7 @@ def ROSolver_iterative_solve(model_data, config): ) evaluate_and_log_component_stats( - model_data=model_data, - separation_model=separation_model, - config=config, + model_data=model_data, separation_model=separation_model, config=config ) # === Create separation problem data container object and add information to catalog during solve @@ -373,22 +350,23 @@ def ROSolver_iterative_solve(model_data, config): dr_var_lists_polished = [] # set up first-stage variable and DR variable sets - master_dr_var_set = ComponentSet(chain(*tuple( - indexed_var.values() - for indexed_var - in master_data.master_model.scenarios[0, 0].util.decision_rule_vars - ))) + master_dr_var_set = ComponentSet( + chain( + *tuple( + indexed_var.values() + for indexed_var in master_data.master_model.scenarios[ + 0, 0 + ].util.decision_rule_vars + ) + ) + ) master_fsv_set = ComponentSet( - var for var in - master_data.master_model.scenarios[0, 0].util.first_stage_variables + var + for var in master_data.master_model.scenarios[0, 0].util.first_stage_variables if var not in master_dr_var_set ) - previous_master_fsv_vals = ComponentMap( - (var, None) for var in master_fsv_set - ) - previous_master_dr_var_vals = ComponentMap( - (var, None) for var in master_dr_var_set - ) + previous_master_fsv_vals = ComponentMap((var, None) for var in master_fsv_set) + previous_master_dr_var_vals = ComponentMap((var, None) for var in master_dr_var_set) nom_master_util_blk = master_data.master_model.scenarios[0, 0].util dr_var_scaled_expr_map = get_dr_var_to_scaled_expr_map( @@ -501,11 +479,11 @@ def ROSolver_iterative_solve(model_data, config): vals.append(dvar.value) dr_var_lists_original.append(vals) - polishing_results, polishing_successful = ( - master_problem_methods.minimize_dr_vars( - model_data=master_data, - config=config, - ) + ( + polishing_results, + polishing_successful, + ) = master_problem_methods.minimize_dr_vars( + model_data=master_data, config=config ) timing_data.total_dr_polish_time += get_time_from_solver(polishing_results) @@ -522,22 +500,31 @@ def ROSolver_iterative_solve(model_data, config): # and compare with previous first-stage and DR variable # values current_master_fsv_vals = ComponentMap( - (var, value(var)) - for var in master_fsv_set + (var, value(var)) for var in master_fsv_set ) current_master_dr_var_vals = ComponentMap( - (var, value(var)) - for var, expr in dr_var_scaled_expr_map.items() + (var, value(var)) for var, expr in dr_var_scaled_expr_map.items() ) if k > 0: - first_stage_var_shift = max( - abs(current_master_fsv_vals[var] - previous_master_fsv_vals[var]) - for var in previous_master_fsv_vals - ) if current_master_fsv_vals else None - dr_var_shift = max( - abs(current_master_dr_var_vals[var] - previous_master_dr_var_vals[var]) - for var in previous_master_dr_var_vals - ) if current_master_dr_var_vals else None + first_stage_var_shift = ( + max( + abs(current_master_fsv_vals[var] - previous_master_fsv_vals[var]) + for var in previous_master_fsv_vals + ) + if current_master_fsv_vals + else None + ) + dr_var_shift = ( + max( + abs( + current_master_dr_var_vals[var] + - previous_master_dr_var_vals[var] + ) + for var in previous_master_dr_var_vals + ) + if current_master_dr_var_vals + else None + ) else: first_stage_var_shift = None dr_var_shift = None @@ -623,20 +610,16 @@ def ROSolver_iterative_solve(model_data, config): scaled_violations = [ solve_call_res.scaled_violations[con] - for con, solve_call_res - in separation_results.main_loop_results.solver_call_results.items() + for con, solve_call_res in separation_results.main_loop_results.solver_call_results.items() if solve_call_res.scaled_violations is not None ] if scaled_violations: max_sep_con_violation = max(scaled_violations) else: max_sep_con_violation = None - num_violated_cons = len( - separation_results.violated_performance_constraints - ) - all_sep_problems_solved = ( - len(scaled_violations) - == len(separation_model.util.performance_constraints) + num_violated_cons = len(separation_results.violated_performance_constraints) + all_sep_problems_solved = len(scaled_violations) == len( + separation_model.util.performance_constraints ) iter_log_record = IterationLogRecord( @@ -723,7 +706,7 @@ def ROSolver_iterative_solve(model_data, config): config.progress_logger.debug("Points added to master:") config.progress_logger.debug( - np.array([pt for pt in separation_data.points_added_to_master]), + np.array([pt for pt in separation_data.points_added_to_master]) ) k += 1 diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index 6efa0f66071..7d8669286f5 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -537,11 +537,8 @@ def get_worst_discrete_separation_solution( def get_con_name_repr( - separation_model, - perf_con, - with_orig_name=True, - with_obj_name=True, - ): + separation_model, perf_con, with_orig_name=True, with_obj_name=True +): """ Get string representation of performance constraint and any other modeling components to which it has @@ -574,10 +571,8 @@ def get_con_name_repr( if with_orig_name: # check performance constraint was not added # at construction of separation problem - orig_con = ( - separation_model - .util - .map_new_constraint_list_to_original_con.get(perf_con, perf_con) + orig_con = separation_model.util.map_new_constraint_list_to_original_con.get( + perf_con, perf_con ) if orig_con is not perf_con: qual_strs.append(f"originally {orig_con.name!r}") @@ -1136,7 +1131,9 @@ def solver_call_separation( + ".bar" ), ) - nlp_model.write(output_problem_path, io_options={'symbolic_solver_labels': True}) + nlp_model.write( + output_problem_path, io_options={'symbolic_solver_labels': True} + ) serialization_msg = ( f"Problem has been serialized to path {output_problem_path!r}." ) diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index 9a7eb4e2f76..690595dd88b 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -1467,17 +1467,17 @@ class IterationLogRecord: } def __init__( - self, - iteration, - objective, - first_stage_var_shift, - dr_var_shift, - dr_polishing_failed, - num_violated_cons, - all_sep_problems_solved, - max_violation, - elapsed_time, - ): + self, + iteration, + objective, + first_stage_var_shift, + dr_var_shift, + dr_polishing_failed, + num_violated_cons, + all_sep_problems_solved, + max_violation, + elapsed_time, + ): """Initialize self (see class docstring).""" self.iteration = iteration self.objective = objective @@ -1529,9 +1529,7 @@ def _format_record_attr(self, attr_name): attr_val_str = f"{eval(attr_val_fstrs[attr_name])}{qual}" - return ( - f"{attr_val_str:{f'<{self._ATTR_FORMAT_LENGTHS[attr_name]}'}}" - ) + return f"{attr_val_str:{f'<{self._ATTR_FORMAT_LENGTHS[attr_name]}'}}" def log(self, log_func, **log_func_kwargs): """Log self.""" From 01b93b185c6b1d9baf6726f5bbf9bff0bb652972 Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 7 Sep 2023 19:40:20 -0400 Subject: [PATCH 0123/1204] Apply black formatting to tests --- pyomo/contrib/pyros/tests/test_grcs.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 0c9351835ec..3526b70b846 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -3590,7 +3590,9 @@ def test_solve_master(self): ) config.declare("subproblem_file_directory", ConfigValue(default=None)) config.declare("time_limit", ConfigValue(default=None)) - config.declare("progress_logger", ConfigValue(default=logging.getLogger(__name__))) + config.declare( + "progress_logger", ConfigValue(default=logging.getLogger(__name__)) + ) with time_code(master_data.timing, "total", is_main_timer=True): master_soln = solve_master(master_data, config) @@ -3904,10 +3906,7 @@ def test_minimize_dr_norm(self): TerminationCondition.optimal, msg="Minimize dr norm did not solve to optimality.", ) - self.assertTrue( - success, - msg=f"DR polishing {success=}, expected True." - ) + self.assertTrue(success, msg=f"DR polishing {success=}, expected True.") @unittest.skipUnless( baron_license_is_valid, "Global NLP solver is not available and licensed." From 2ca6ea1314263714dab642464904df593664af02 Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 7 Sep 2023 20:38:38 -0400 Subject: [PATCH 0124/1204] Refactor termination condition messages --- pyomo/contrib/pyros/pyros.py | 23 +---------------------- pyomo/contrib/pyros/util.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 89f0c7a39d6..7b0b78f6729 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -1038,28 +1038,7 @@ def solve( return_soln.time = get_main_elapsed_time(model_data.timing) return_soln.iterations = 0 - termination_msg_dict = { - pyrosTerminationCondition.robust_optimal: ( - "Robust optimal solution identified." - ), - pyrosTerminationCondition.robust_feasible: ( - "Robust feasible solution identified." - ), - pyrosTerminationCondition.robust_infeasible: ( - "Problem is robust infeasible." - ), - pyrosTerminationCondition.time_out: ("Maximum allowable time exceeded."), - pyrosTerminationCondition.max_iter: ( - "Maximum number of iterations reached." - ), - pyrosTerminationCondition.subsolver_error: ( - "Subordinate optimizer(s) could not solve a subproblem " - "to an acceptable status." - ), - } - config.progress_logger.info( - termination_msg_dict[return_soln.pyros_termination_condition] - ) + config.progress_logger.info(return_soln.pyros_termination_condition.message) config.progress_logger.info("-" * self._LOG_LINE_LENGTH) config.progress_logger.info("Termination stats:") config.progress_logger.info(f" {'Iterations':<22s}: {return_soln.iterations}") diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index 690595dd88b..d40670580f3 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -308,6 +308,25 @@ class pyrosTerminationCondition(Enum): time_out = 5 """Maximum allowable time exceeded.""" + @property + def message(self): + """ + str : Message associated with a given PyROS + termination condition. + """ + message_dict = { + self.robust_optimal: "Robust optimal solution identified.", + self.robust_feasible: "Robust feasible solution identified.", + self.robust_infeasible: "Problem is robust infeasible.", + self.time_out: "Maximum allowable time exceeded.", + self.max_iter: "Maximum number of iterations reached.", + self.subsolver_error: ( + "Subordinate optimizer(s) could not solve a subproblem " + "to an acceptable status." + ), + } + return message_dict[self] + class SeparationStrategy(Enum): all_violations = auto() From 51c8a71ea74de86abf6f031a3a678d82a654e8e4 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Thu, 7 Sep 2023 18:44:29 -0600 Subject: [PATCH 0125/1204] adding variable bound capability --- pyomo/util/latex_printer.py | 957 +++++++++++++++++++++--------------- 1 file changed, 570 insertions(+), 387 deletions(-) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index dc73930cbeb..a23ae3fdfba 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -10,6 +10,8 @@ # ___________________________________________________________________________ import math +import copy +import re import pyomo.environ as pyo from pyomo.core.expr.visitor import StreamBasedExpressionVisitor from pyomo.core.expr import ( @@ -44,7 +46,10 @@ templatize_rule, ) from pyomo.core.base.var import ScalarVar, _GeneralVarData, IndexedVar +from pyomo.core.base.param import _ParamData +from pyomo.core.base.set import _SetData from pyomo.core.base.constraint import ScalarConstraint, IndexedConstraint +from pyomo.common.collections.component_map import ComponentMap from pyomo.core.base.external import _PythonCallbackFunctionID @@ -218,66 +223,68 @@ def handle_inequality_node(visitor, node, arg1, arg2): def handle_var_node(visitor, node): - overwrite_dict = visitor.overwrite_dict - # varList = visitor.variableList - - name = node.name - - declaredIndex = None - if '[' in name: - openBracketIndex = name.index('[') - closeBracketIndex = name.index(']') - if closeBracketIndex != len(name) - 1: - # I dont think this can happen, but possibly through a raw string and a user - # who is really hacking the variable name setter - raise ValueError( - 'Variable %s has a close brace not at the end of the string' % (name) - ) - declaredIndex = name[openBracketIndex + 1 : closeBracketIndex] - name = name[0:openBracketIndex] - - if name in overwrite_dict.keys(): - name = overwrite_dict[name] - - if visitor.use_smart_variables: - splitName = name.split('_') - if declaredIndex is not None: - splitName.append(declaredIndex) - - filteredName = [] - - prfx = '' - psfx = '' - for i in range(0, len(splitName)): - se = splitName[i] - if se != 0: - if se == 'dot': - prfx = '\\dot{' - psfx = '}' - elif se == 'hat': - prfx = '\\hat{' - psfx = '}' - elif se == 'bar': - prfx = '\\bar{' - psfx = '}' - else: - filteredName.append(se) - else: - filteredName.append(se) - - joinedName = prfx + filteredName[0] + psfx - for i in range(1, len(filteredName)): - joinedName += '_{' + filteredName[i] - - joinedName += '}' * (len(filteredName) - 1) - - else: - if declaredIndex is not None: - joinedName = name + '[' + declaredIndex + ']' - else: - joinedName = name - - return joinedName + return visitor.variableMap[node] + # return node.name + # overwrite_dict = visitor.overwrite_dict + # # varList = visitor.variableList + + # name = node.name + + # declaredIndex = None + # if '[' in name: + # openBracketIndex = name.index('[') + # closeBracketIndex = name.index(']') + # if closeBracketIndex != len(name) - 1: + # # I dont think this can happen, but possibly through a raw string and a user + # # who is really hacking the variable name setter + # raise ValueError( + # 'Variable %s has a close brace not at the end of the string' % (name) + # ) + # declaredIndex = name[openBracketIndex + 1 : closeBracketIndex] + # name = name[0:openBracketIndex] + + # if name in overwrite_dict.keys(): + # name = overwrite_dict[name] + + # if visitor.use_smart_variables: + # splitName = name.split('_') + # if declaredIndex is not None: + # splitName.append(declaredIndex) + + # filteredName = [] + + # prfx = '' + # psfx = '' + # for i in range(0, len(splitName)): + # se = splitName[i] + # if se != 0: + # if se == 'dot': + # prfx = '\\dot{' + # psfx = '}' + # elif se == 'hat': + # prfx = '\\hat{' + # psfx = '}' + # elif se == 'bar': + # prfx = '\\bar{' + # psfx = '}' + # else: + # filteredName.append(se) + # else: + # filteredName.append(se) + + # joinedName = prfx + filteredName[0] + psfx + # for i in range(1, len(filteredName)): + # joinedName += '_{' + filteredName[i] + + # joinedName += '}' * (len(filteredName) - 1) + + # else: + # if declaredIndex is not None: + # joinedName = name + '[' + declaredIndex + ']' + # else: + # joinedName = name + + # return joinedName def handle_num_node(visitor, node): @@ -355,16 +362,16 @@ def handle_indexTemplate_node(visitor, node, *args): def handle_numericGIE_node(visitor, node, *args): - addFinalBrace = False - if '_' in args[0]: - splitName = args[0].split('_') - joinedName = splitName[0] - for i in range(1, len(splitName)): - joinedName += '_{' + splitName[i] - joinedName += '}' * (len(splitName) - 2) - addFinalBrace = True - else: - joinedName = args[0] + # addFinalBrace = False + # if '_' in args[0]: + # splitName = args[0].split('_') + # joinedName = splitName[0] + # for i in range(1, len(splitName)): + # joinedName += '_{' + splitName[i] + # joinedName += '}' * (len(splitName) - 2) + # addFinalBrace = True + # else: + joinedName = args[0] pstr = '' pstr += joinedName + '_{' @@ -374,8 +381,8 @@ def handle_numericGIE_node(visitor, node, *args): pstr += ',' else: pstr += '}' - if addFinalBrace: - pstr += '}' + # if addFinalBrace: + # pstr += '}' return pstr @@ -392,9 +399,6 @@ def handle_templateSumExpression_node(visitor, node, *args): class _LatexVisitor(StreamBasedExpressionVisitor): def __init__(self): super().__init__() - self.use_smart_variables = False - self.x_only_mode = False - self.overwrite_dict = {} self._operator_handles = { ScalarVar: handle_var_node, @@ -433,47 +437,237 @@ def exitNode(self, node, data): return self._operator_handles[node.__class__](self, node, *data) -def number_to_letterStack(num): - alphabet = [ - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i', - 'j', - 'k', - 'l', - 'm', - 'n', - 'o', - 'p', - 'q', - 'r', - 's', - 't', - 'u', - 'v', - 'w', - 'x', - 'y', - 'z', - ] +def applySmartVariables(name): + splitName = name.split('_') + # print(splitName) + + filteredName = [] + + prfx = '' + psfx = '' + for i in range(0, len(splitName)): + se = splitName[i] + if se != 0: + if se == 'dot': + prfx = '\\dot{' + psfx = '}' + elif se == 'hat': + prfx = '\\hat{' + psfx = '}' + elif se == 'bar': + prfx = '\\bar{' + psfx = '}' + else: + filteredName.append(se) + else: + filteredName.append(se) + + joinedName = prfx + filteredName[0] + psfx + # print(joinedName) + # print(filteredName) + for i in range(1, len(filteredName)): + joinedName += '_{' + filteredName[i] + + joinedName += '}' * (len(filteredName) - 1) + # print(joinedName) + + return joinedName + +def analyze_variable(vr, visitor): + domainMap = { + 'Reals': '\\mathds{R}', + 'PositiveReals': '\\mathds{R}_{> 0}', + 'NonPositiveReals': '\\mathds{R}_{\\leq 0}', + 'NegativeReals': '\\mathds{R}_{< 0}', + 'NonNegativeReals': '\\mathds{R}_{\\geq 0}', + 'Integers': '\\mathds{Z}', + 'PositiveIntegers': '\\mathds{Z}_{> 0}', + 'NonPositiveIntegers': '\\mathds{Z}_{\\leq 0}', + 'NegativeIntegers': '\\mathds{Z}_{< 0}', + 'NonNegativeIntegers': '\\mathds{Z}_{\\geq 0}', + 'Boolean': '\\left\\{ 0 , 1 \\right \\}', + 'Binary': '\\left\\{ 0 , 1 \\right \\}', + # 'Any': None, + # 'AnyWithNone': None, + 'EmptySet': '\\varnothing', + 'UnitInterval': '\\mathds{R}', + 'PercentFraction': '\\mathds{R}', + # 'RealInterval' : None , + # 'IntegerInterval' : None , + } + + domainName = vr.domain.name + varBounds = vr.bounds + lowerBoundValue = varBounds[0] + upperBoundValue = varBounds[1] + + if domainName in ['Reals', 'Integers']: + if lowerBoundValue is not None: + lowerBound = str(lowerBoundValue) + ' \\leq ' + else: + lowerBound = '' + + if upperBoundValue is not None: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = '' + + elif domainName in ['PositiveReals', 'PositiveIntegers']: + if lowerBoundValue is not None: + if lowerBoundValue > 0: + lowerBound = str(lowerBoundValue) + ' \\leq ' + else: + lowerBound = ' 0 < ' + else: + lowerBound = ' 0 < ' + + if upperBoundValue is not None: + if upperBoundValue <= 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' + % (vr.name) + ) + else: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = '' + + elif domainName in ['NonPositiveReals', 'NonPositiveIntegers']: + if lowerBoundValue is not None: + if lowerBoundValue > 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' + % (vr.name) + ) + elif lowerBoundValue == 0: + lowerBound = ' 0 = ' + else: + lowerBound = str(upperBoundValue) + ' \\leq ' + else: + lowerBound = '' + + if upperBoundValue is not None: + if upperBoundValue >= 0: + upperBound = ' \\leq 0 ' + else: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = ' \\leq 0 ' + + elif domainName in ['NegativeReals', 'NegativeIntegers']: + if lowerBoundValue is not None: + if lowerBoundValue >= 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' + % (vr.name) + ) + else: + lowerBound = str(upperBoundValue) + ' \\leq ' + else: + lowerBound = '' + + if upperBoundValue is not None: + if upperBoundValue >= 0: + upperBound = ' < 0 ' + else: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = ' < 0 ' + + elif domainName in ['NonNegativeReals', 'NonNegativeIntegers']: + if lowerBoundValue is not None: + if lowerBoundValue > 0: + lowerBound = str(lowerBoundValue) + ' \\leq ' + else: + lowerBound = ' 0 \\leq ' + else: + lowerBound = ' 0 \\leq ' + + if upperBoundValue is not None: + if upperBoundValue < 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' + % (vr.name) + ) + elif upperBoundValue == 0: + upperBound = ' = 0 ' + else: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = '' + + elif domainName in [ + 'Boolean', + 'Binary', + 'Any', + 'AnyWithNone', + 'EmptySet', + ]: + lowerBound = '' + upperBound = '' + + elif domainName in ['UnitInterval', 'PercentFraction']: + if lowerBoundValue is not None: + if lowerBoundValue > 0: + lowerBound = str(lowerBoundValue) + ' \\leq ' + elif lowerBoundValue > 1: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' + % (vr.name) + ) + elif lowerBoundValue == 1: + lowerBound = ' = 1 ' + else: + lowerBound = ' 0 \\leq ' + else: + lowerBound = ' 0 \\leq ' + + if upperBoundValue is not None: + if upperBoundValue < 1: + upperBound = ' \\leq ' + str(upperBoundValue) + elif upperBoundValue < 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' + % (vr.name) + ) + elif upperBoundValue == 0: + upperBound = ' = 0 ' + else: + upperBound = ' \\leq 1 ' + else: + upperBound = ' \\leq 1 ' + + else: + raise ValueError( + 'Domain %s not supported by the latex printer' % (domainName) + ) + + varBoundData = { + 'variable' : vr, + 'lowerBound' : lowerBound, + 'upperBound' : upperBound, + 'domainName' : domainName, + 'domainLatex' : domainMap[domainName], + } + + return varBoundData + + +def multiple_replace(pstr, rep_dict): + pattern = re.compile("|".join(rep_dict.keys()), flags=re.DOTALL) + return pattern.sub(lambda x: rep_dict[x.group(0)], pstr) def latex_printer( pyomo_component, filename=None, - use_align_environment=False, + use_equation_environment=False, split_continuous_sets=False, use_smart_variables=False, x_only_mode=0, use_short_descriptors=False, use_forall=False, - overwrite_dict={}, + overwrite_dict=None, ): """This function produces a string that can be rendered as LaTeX @@ -487,15 +681,15 @@ def latex_printer( filename: str An optional file to write the LaTeX to. Default of None produces no file - use_align_environment: bool + use_equation_environment: bool Default behavior uses equation/aligned and produces a single LaTeX Equation (ie, ==False). Setting this input to True will instead use the align environment, and produce equation numbers for each objective and constraint. Each objective and constraint will be labeled with its name in the pyomo model. This flag is only relevant for Models and Blocks. splitContinuous: bool - Default behavior has all sum indices be over "i \in I" or similar. Setting this flag to - True makes the sums go from: \sum_{i=1}^{5} if the set I is continuous and has 5 elements + Default behavior has all sum indices be over "i \\in I" or similar. Setting this flag to + True makes the sums go from: \\sum_{i=1}^{5} if the set I is continuous and has 5 elements Returns ------- @@ -509,6 +703,10 @@ def latex_printer( # is Single implies Objective, constraint, or expression # these objects require a slight modification of behavior # isSingle==False means a model or block + + if overwrite_dict is None: + overwrite_dict = ComponentMap() + isSingle = False if isinstance(pyomo_component, pyo.Objective): @@ -516,7 +714,7 @@ def latex_printer( constraints = [] expressions = [] templatize_fcn = templatize_constraint - use_align_environment = False + use_equation_environment = True isSingle = True elif isinstance(pyomo_component, pyo.Constraint): @@ -524,7 +722,7 @@ def latex_printer( constraints = [pyomo_component] expressions = [] templatize_fcn = templatize_constraint - use_align_environment = False + use_equation_environment = True isSingle = True elif isinstance(pyomo_component, pyo.Expression): @@ -532,7 +730,7 @@ def latex_printer( constraints = [] expressions = [pyomo_component] templatize_fcn = templatize_expression - use_align_environment = False + use_equation_environment = True isSingle = True elif isinstance(pyomo_component, (ExpressionBase, pyo.Var)): @@ -540,7 +738,7 @@ def latex_printer( constraints = [] expressions = [pyomo_component] templatize_fcn = templatize_passthrough - use_align_environment = False + use_equation_environment = True isSingle = True elif isinstance(pyomo_component, _BlockData): @@ -566,94 +764,56 @@ def latex_printer( ) if use_forall: - forallTag = ' \\forall' + forallTag = ' \\qquad \\forall' else: - forallTag = ', \\quad' + forallTag = ' \\quad' descriptorDict = {} if use_short_descriptors: descriptorDict['minimize'] = '\\min' descriptorDict['maximize'] = '\\max' descriptorDict['subject to'] = '\\text{s.t.}' + descriptorDict['with bounds'] = '\\text{w.b.}' else: descriptorDict['minimize'] = '\\text{minimize}' descriptorDict['maximize'] = '\\text{maximize}' descriptorDict['subject to'] = '\\text{subject to}' + descriptorDict['with bounds'] = '\\text{with bounds}' # In the case where just a single expression is passed, add this to the constraint list for printing constraints = constraints + expressions # Declare a visitor/walker visitor = _LatexVisitor() - visitor.use_smart_variables = use_smart_variables - # visitor.x_only_mode = x_only_mode - # # Only x modes - # # Mode 0 : dont use - # # Mode 1 : indexed variables become x_{_{ix}} - # # Mode 2 : uses standard alphabet [a,...,z,aa,...,az,...,aaa,...] with subscripts for indices, ex: abcd_{ix} - # # Mode 3 : unwrap everything into an x_{} list, WILL NOT WORK ON TEMPLATED CONSTRAINTS - nameReplacementDict = {} - if not isSingle: - # only works if you can get the variables from a block - variableList = [ - vr - for vr in pyomo_component.component_objects( - pyo.Var, descend_into=True, active=True - ) - ] - if x_only_mode == 1: - newVariableList = ['x' for i in range(0, len(variableList))] - for i in range(0, len(variableList)): - newVariableList[i] += '_' + str(i + 1) - overwrite_dict = dict(zip([v.name for v in variableList], newVariableList)) - elif x_only_mode == 2: - newVariableList = [ - alphabetStringGenerator(i) for i in range(0, len(variableList)) - ] - overwrite_dict = dict(zip([v.name for v in variableList], newVariableList)) - elif x_only_mode == 3: - newVariableList = ['x' for i in range(0, len(variableList))] - for i in range(0, len(variableList)): - newVariableList[i] += '_' + str(i + 1) - overwrite_dict = dict(zip([v.name for v in variableList], newVariableList)) - - unwrappedVarCounter = 0 - wrappedVarCounter = 0 - for v in variableList: - setData = v.index_set().data() - if setData[0] is None: - unwrappedVarCounter += 1 - wrappedVarCounter += 1 - nameReplacementDict['x_{' + str(wrappedVarCounter) + '}'] = ( - 'x_{' + str(unwrappedVarCounter) + '}' - ) - else: - wrappedVarCounter += 1 - for dta in setData: - dta_str = str(dta) - if '(' not in dta_str: - dta_str = '(' + dta_str + ')' - subsetString = dta_str.replace('(', '{') - subsetString = subsetString.replace(')', '}') - subsetString = subsetString.replace(' ', '') - unwrappedVarCounter += 1 - nameReplacementDict[ - 'x_{' + str(wrappedVarCounter) + '_' + subsetString + '}' - ] = ('x_{' + str(unwrappedVarCounter) + '}') - # for ky, vl in nameReplacementDict.items(): - # print(ky,vl) - # print(nameReplacementDict) + variableList = [ + vr + for vr in pyomo_component.component_objects( + pyo.Var, descend_into=True, active=True + ) + ] + + variableMap = ComponentMap() + vrIdx = 0 + for i in range(0,len(variableList)): + vr = variableList[i] + vrIdx += 1 + if isinstance(vr ,ScalarVar): + variableMap[vr] = 'x_' + str(vrIdx) + elif isinstance(vr, IndexedVar): + variableMap[vr] = 'x_' + str(vrIdx) + for sd in vr.index_set().data(): + vrIdx += 1 + variableMap[vr[sd]] = 'x_' + str(vrIdx) else: - # default to the standard mode where pyomo names are used - overwrite_dict = {} + raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') - visitor.overwrite_dict = overwrite_dict + visitor.variableMap = variableMap # starts building the output string pstr = '' - if use_align_environment: + if not use_equation_environment: pstr += '\\begin{align} \n' tbSpc = 4 trailingAligner = '& ' @@ -664,7 +824,7 @@ def latex_printer( tbSpc = 8 else: tbSpc = 4 - trailingAligner = '' + trailingAligner = '&' # Iterate over the objectives and print for obj in objectives: @@ -678,7 +838,7 @@ def latex_printer( visitor.walk_expression(obj_template), trailingAligner, ) - if use_align_environment: + if not use_equation_environment: pstr += '\\label{obj:' + pyomo_component.name + '_' + obj.name + '} ' if not isSingle: pstr += '\\\\ \n' @@ -735,54 +895,26 @@ def latex_printer( if use_forall: conLine += '%s %s \\in %s ' % (forallTag, idxTag, setTag) else: - if trailingAligner == '': - conLine = ( - conLine - + '%s %s \\in %s ' % (forallTag, idxTag, setTag) - + trailingAligner - ) - else: - conLine = ( - conLine[0:-2] - + '%s %s \\in %s ' % (forallTag, idxTag, setTag) - + trailingAligner - ) + conLine = ( + conLine[0:-2] + + ' ' + trailingAligner + + '%s %s \\in %s ' % (forallTag, idxTag, setTag) + ) pstr += conLine # Add labels as needed - if use_align_environment: + if not use_equation_environment: pstr += '\\label{con:' + pyomo_component.name + '_' + con.name + '} ' # prevents an emptly blank line from being at the end of the latex output if i <= len(constraints) - 2: pstr += tail else: - pstr += '\n' + pstr += tail + # pstr += '\n' # Print bounds and sets if not isSingle: - domainMap = { - 'Reals': '\\mathcal{R}', - 'PositiveReals': '\\mathcal{R}_{> 0}', - 'NonPositiveReals': '\\mathcal{R}_{\\leq 0}', - 'NegativeReals': '\\mathcal{R}_{< 0}', - 'NonNegativeReals': '\\mathcal{R}_{\\geq 0}', - 'Integers': '\\mathcal{Z}', - 'PositiveIntegers': '\\mathcal{Z}_{> 0}', - 'NonPositiveIntegers': '\\mathcal{Z}_{\\leq 0}', - 'NegativeIntegers': '\\mathcal{Z}_{< 0}', - 'NonNegativeIntegers': '\\mathcal{Z}_{\\geq 0}', - 'Boolean': '\\left{ 0 , 1 \\right }', - 'Binary': '\\left{ 0 , 1 \\right }', - 'Any': None, - 'AnyWithNone': None, - 'EmptySet': '\\varnothing', - 'UnitInterval': '\\left[ 0 , 1 \\right ]', - 'PercentFraction': '\\left[ 0 , 1 \\right ]', - # 'RealInterval' : None , - # 'IntegerInterval' : None , - } - variableList = [ vr for vr in pyomo_component.component_objects( @@ -790,184 +922,81 @@ def latex_printer( ) ] - varBoundData = [[] for v in variableList] + varBoundData = [] for i in range(0, len(variableList)): vr = variableList[i] if isinstance(vr, ScalarVar): - domainName = vr.domain.name - varBounds = vr.bounds - lowerBoundValue = varBounds[0] - upperBoundValue = varBounds[1] - - varLatexName = visitor.walk_expression(vr) - if varLatexName in overwrite_dict.keys(): - varReplaceName = overwrite_dict[varLatexName] - else: - varReplaceName = varLatexName - - if domainName in ['Reals', 'Integers']: - if lowerBoundValue is not None: - lowerBound = str(lowerBoundValue) + ' \\leq ' - else: - lowerBound = '' - - if upperBoundValue is not None: - upperBound = ' \\leq ' + str(upperBoundValue) - else: - upperBound = '' - - elif domainName in ['PositiveReals', 'PositiveIntegers']: - if lowerBoundValue is not None: - if lowerBoundValue > 0: - lowerBound = str(lowerBoundValue) + ' \\leq ' - else: - lowerBound = ' 0 < ' - else: - lowerBound = ' 0 < ' - - if upperBoundValue is not None: - if upperBoundValue <= 0: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) - ) - else: - upperBound = ' \\leq ' + str(upperBoundValue) - else: - upperBound = '' - - elif domainName in ['NonPositiveReals', 'NonPositiveIntegers']: - if lowerBoundValue is not None: - if lowerBoundValue > 0: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) - ) - elif lowerBoundValue == 0: - lowerBound = ' 0 = ' - else: - lowerBound = str(upperBoundValue) + ' \\leq ' - else: - lowerBound = '' - - if upperBoundValue is not None: - if upperBoundValue >= 0: - upperBound = ' \\leq 0 ' - else: - upperBound = ' \\leq ' + str(upperBoundValue) - else: - upperBound = ' \\leq 0 ' - - elif domainName in ['NegativeReals', 'NegativeIntegers']: - if lowerBoundValue is not None: - if lowerBoundValue >= 0: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) - ) - else: - lowerBound = str(upperBoundValue) + ' \\leq ' - else: - lowerBound = '' - - if upperBoundValue is not None: - if upperBoundValue >= 0: - upperBound = ' < 0 ' - else: - upperBound = ' \\leq ' + str(upperBoundValue) - else: - upperBound = ' < 0 ' - - elif domainName in ['NonNegativeReals', 'NonNegativeIntegers']: - if lowerBoundValue is not None: - if lowerBoundValue > 0: - lowerBound = str(lowerBoundValue) + ' \\leq ' - else: - lowerBound = ' 0 \\leq ' - else: - lowerBound = ' 0 \\leq ' - - if upperBoundValue is not None: - if upperBoundValue < 0: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) - ) - elif upperBoundValue == 0: - upperBound = ' = 0 ' - else: - upperBound = ' \\leq ' + str(upperBoundValue) - else: - upperBound = '' - - elif domainName in [ - 'Boolean', - 'Binary', - 'Any', - 'AnyWithNone', - 'EmptySet', - ]: - lowerBound = '' - upperBound = '' - - elif domainName in ['UnitInterval', 'PercentFraction']: - if lowerBoundValue is not None: - if lowerBoundValue > 0: - upperBound = str(lowerBoundValue) + ' \\leq ' - elif lowerBoundValue > 1: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) - ) - elif lowerBoundValue == 1: - lowerBound = ' = 1 ' - else: - lowerBoundValue = ' 0 \\leq ' - else: - lowerBound = ' 0 \\leq ' - - if upperBoundValue is not None: - if upperBoundValue < 1: - upperBound = ' \\leq ' + str(upperBoundValue) - elif upperBoundValue < 0: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) - ) - elif upperBoundValue == 0: - upperBound = ' = 0 ' - else: - upperBound = ' \\leq 1 ' - else: - upperBound = ' \\leq 1 ' - - else: - raise ValueError( - 'Domain %s not supported by the latex printer' % (domainName) - ) - - varBoundData[i] = [ - vr, - varLatexName, - varReplaceName, - lowerBound, - upperBound, - domainName, - domainMap[domainName], - ] + varBoundDataEntry = analyze_variable(vr, visitor) + varBoundData.append( varBoundDataEntry ) elif isinstance(vr, IndexedVar): + varBoundData_indexedVar = [] # need to wrap in function and do individually # Check on the final variable after all the indices are processed - pass + setData = vr.index_set().data() + for sd in setData: + varBoundDataEntry = analyze_variable(vr[sd], visitor) + varBoundData_indexedVar.append(varBoundDataEntry) + globIndexedVariables = True + for j in range(0,len(varBoundData_indexedVar)-1): + chks = [] + chks.append(varBoundData_indexedVar[j]['lowerBound']==varBoundData_indexedVar[j+1]['lowerBound']) + chks.append(varBoundData_indexedVar[j]['upperBound']==varBoundData_indexedVar[j+1]['upperBound']) + chks.append(varBoundData_indexedVar[j]['domainName']==varBoundData_indexedVar[j+1]['domainName']) + if not all(chks): + globIndexedVariables = False + break + if globIndexedVariables: + varBoundData.append({'variable': vr, + 'lowerBound': varBoundData_indexedVar[0]['lowerBound'], + 'upperBound': varBoundData_indexedVar[0]['upperBound'], + 'domainName': varBoundData_indexedVar[0]['domainName'], + 'domainLatex': varBoundData_indexedVar[0]['domainLatex'], + }) + else: + varBoundData += varBoundData_indexedVar else: raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers' ) # print the accumulated data to the string + bstr = '' + appendBoundString = False + useThreeAlgn = False + for i in range(0,len(varBoundData)): + vbd = varBoundData[i] + if vbd['lowerBound'] == '' and vbd['upperBound'] == '' and vbd['domainName']=='Reals': + # unbounded all real, do not print + if i <= len(varBoundData)-2: + bstr = bstr[0:-2] + else: + if not useThreeAlgn: + algn = '& &' + useThreeAlgn = True + else: + algn = '&&&' + + if use_equation_environment: + conLabel = '' + else: + conLabel = ' \\label{con:' + pyomo_component.name + '_' + variableMap[vbd['variable']] + '_bound' + '} ' + + appendBoundString = True + coreString = vbd['lowerBound'] + variableMap[vbd['variable']] + vbd['upperBound'] + ' ' + trailingAligner + '\\qquad \\in ' + vbd['domainLatex'] + conLabel + bstr += ' ' * tbSpc + algn + ' %s' % (coreString) + if i <= len(varBoundData)-2: + bstr += '\\\\ \n' + else: + bstr += '\n' + + if appendBoundString: + pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['with bounds']) + pstr += bstr + else: + pstr = pstr[0:-4] + '\n' # close off the print string - if use_align_environment: + if not use_equation_environment: pstr += '\\end{align} \n' else: if not isSingle: @@ -977,6 +1006,13 @@ def latex_printer( # Handling the iterator indices + + # ==================== + # ==================== + # ==================== + # ==================== + # ==================== + # ==================== # preferential order for indices setPreferenceOrder = [ 'I', @@ -1002,10 +1038,6 @@ def latex_printer( ln = latexLines[jj] # only modify if there is a placeholder in the line if "PLACEHOLDER_8675309_GROUP_" in ln: - if x_only_mode == 2: - raise RuntimeError( - 'Unwrapping indexed variables when an indexed constraint is present yields incorrect results' - ) splitLatex = ln.split('__') # Find the unique combinations of group numbers and set names for word in splitLatex: @@ -1096,14 +1128,169 @@ def latex_printer( # Assign the newly modified line latexLines[jj] = ln + # ==================== + # ==================== + # ==================== + # ==================== + # ==================== + # ==================== + + # rejoin the corrected lines pstr = '\n'.join(latexLines) + if x_only_mode in [1,2,3]: + # Need to preserve only the non-variable elments in the overwrite_dict + new_overwrite_dict = {} + for ky, vl in overwrite_dict.items(): + if isinstance(ky,_GeneralVarData): + pass + elif isinstance(ky,_ParamData): + raise ValueEror('not implemented yet') + elif isinstance(ky,_SetData): + new_overwrite_dict[ky] = overwrite_dict[ky] + else: + raise ValueError('The overwrite_dict object has a key of invalid type: %s'%(str(ky))) + overwrite_dict = new_overwrite_dict + + # # Only x modes + # # Mode 0 : dont use + # # Mode 1 : indexed variables become x_{_{ix}} + # # Mode 2 : uses standard alphabet [a,...,z,aa,...,az,...,aaa,...] with subscripts for indices, ex: abcd_{ix} + # # Mode 3 : unwrap everything into an x_{} list, including the indexed vars themselves + + if x_only_mode == 1: + vrIdx = 0 + new_variableMap = ComponentMap() + for i in range(0,len(variableList)): + vr = variableList[i] + vrIdx += 1 + if isinstance(vr ,ScalarVar): + new_variableMap[vr] = 'x_{' + str(vrIdx) + '}' + elif isinstance(vr, IndexedVar): + new_variableMap[vr] = 'x_{' + str(vrIdx) + '}' + for sd in vr.index_set().data(): + # vrIdx += 1 + sdString = str(sd) + if sdString[0]=='(': + sdString = sdString[1:] + if sdString[-1]==')': + sdString = sdString[0:-1] + new_variableMap[vr[sd]] = 'x_{' + str(vrIdx) + '_{' + sdString + '}' + '}' + else: + raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + + new_overwrite_dict = ComponentMap() + for ky, vl in new_variableMap.items(): + new_overwrite_dict[ky] = vl + for ky, vl in overwrite_dict.items(): + new_overwrite_dict[ky] = vl + overwrite_dict = new_overwrite_dict + + elif x_only_mode == 2: + vrIdx = 0 + new_variableMap = ComponentMap() + for i in range(0,len(variableList)): + vr = variableList[i] + vrIdx += 1 + if isinstance(vr ,ScalarVar): + new_variableMap[vr] = alphabetStringGenerator(i) + elif isinstance(vr, IndexedVar): + new_variableMap[vr] = alphabetStringGenerator(i) + for sd in vr.index_set().data(): + # vrIdx += 1 + sdString = str(sd) + if sdString[0]=='(': + sdString = sdString[1:] + if sdString[-1]==')': + sdString = sdString[0:-1] + new_variableMap[vr[sd]] = alphabetStringGenerator(i) + '_{' + sdString + '}' + else: + raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + + new_overwrite_dict = ComponentMap() + for ky, vl in new_variableMap.items(): + new_overwrite_dict[ky] = vl + for ky, vl in overwrite_dict.items(): + new_overwrite_dict[ky] = vl + overwrite_dict = new_overwrite_dict + + elif x_only_mode == 3: + new_overwrite_dict = ComponentMap() + for ky, vl in variableMap.items(): + new_overwrite_dict[ky] = vl + for ky, vl in overwrite_dict.items(): + new_overwrite_dict[ky] = vl + overwrite_dict = new_overwrite_dict + + else: + vrIdx = 0 + new_variableMap = ComponentMap() + for i in range(0,len(variableList)): + vr = variableList[i] + vrIdx += 1 + if isinstance(vr ,ScalarVar): + new_variableMap[vr] = vr.name + elif isinstance(vr, IndexedVar): + new_variableMap[vr] = vr.name + for sd in vr.index_set().data(): + # vrIdx += 1 + sdString = str(sd) + if sdString[0]=='(': + sdString = sdString[1:] + if sdString[-1]==')': + sdString = sdString[0:-1] + if use_smart_variables: + new_variableMap[vr[sd]] = applySmartVariables(vr.name + '_{' + sdString + '}') + else: + new_variableMap[vr[sd]] = vr[sd].name + else: + raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + + for ky, vl in new_variableMap.items(): + if ky not in overwrite_dict.keys(): + overwrite_dict[ky] = vl + + rep_dict = {} + for ky in list(reversed(list(overwrite_dict.keys()))): + if isinstance(ky,(pyo.Var,_GeneralVarData)): + if use_smart_variables and x_only_mode not in [1]: + overwrite_value = applySmartVariables(overwrite_dict[ky]) + else: + overwrite_value = overwrite_dict[ky] + rep_dict[variableMap[ky]] = overwrite_value + elif isinstance(ky,_ParamData): + raise ValueEror('not implemented yet') + elif isinstance(ky,_SetData): + # already handled + pass + else: + raise ValueError('The overwrite_dict object has a key of invalid type: %s'%(str(ky))) + + pstr = multiple_replace(pstr,rep_dict) + + pattern = r'_([^{]*)_{([^{]*)}_bound' + replacement = r'_\1_\2_bound' + pstr = re.sub(pattern, replacement, pstr) + + pattern = r'_{([^{]*)}_{([^{]*)}' + replacement = r'_{\1_{\2}}' + pstr = re.sub(pattern, replacement, pstr) + + pattern = r'_(.)_{([^}]*)}' + replacement = r'_{\1_{\2}}' + pstr = re.sub(pattern, replacement, pstr) + + + # optional write to output file if filename is not None: fstr = '' fstr += '\\documentclass{article} \n' fstr += '\\usepackage{amsmath} \n' + fstr += '\\usepackage{amssymb} \n' + fstr += '\\usepackage{dsfont} \n' + fstr += '\\allowdisplaybreaks \n' fstr += '\\begin{document} \n' fstr += pstr fstr += '\\end{document} \n' @@ -1111,9 +1298,5 @@ def latex_printer( f.write(fstr) f.close() - # Catch up on only x mode 3 and replace - for ky, vl in nameReplacementDict.items(): - pstr = pstr.replace(ky, vl) - # return the latex string return pstr From ca54afc4c17ad23d3c417732a1570571dae957a1 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 7 Sep 2023 23:47:53 -0400 Subject: [PATCH 0126/1204] add the support of greybox in mindtpy --- pyomo/contrib/mindtpy/algorithm_base_class.py | 80 ++++++++++++++----- pyomo/contrib/mindtpy/config_options.py | 8 ++ pyomo/contrib/mindtpy/cut_generation.py | 47 +++++++++++ pyomo/contrib/mindtpy/feasibility_pump.py | 7 +- .../mindtpy/global_outer_approximation.py | 1 + pyomo/contrib/mindtpy/outer_approximation.py | 13 ++- 6 files changed, 134 insertions(+), 22 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 7def1dcaab3..e1d6d3e98ba 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -34,6 +34,7 @@ SolutionStatus, SolverStatus, ) +from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock from pyomo.core import ( minimize, maximize, @@ -289,7 +290,7 @@ def model_is_valid(self): results = self.mip_opt.solve( self.original_model, tee=config.mip_solver_tee, - load_solutions=False, + load_solutions=config.load_solutions, **config.mip_solver_args, ) if len(results.solution) > 0: @@ -323,6 +324,11 @@ def build_ordered_component_lists(self, model): ctype=Constraint, active=True, descend_into=(Block) ) ) + util_block.grey_box_list = list( + model.component_data_objects( + ctype=ExternalGreyBoxBlock, active=True, descend_into=(Block) + ) + ) util_block.linear_constraint_list = list( c for c in util_block.constraint_list @@ -352,7 +358,9 @@ def build_ordered_component_lists(self, model): # preserve a deterministic ordering. util_block.variable_list = list( v - for v in model.component_data_objects(ctype=Var, descend_into=(Block)) + for v in model.component_data_objects( + ctype=Var, descend_into=(Block, ExternalGreyBoxBlock) + ) if v in var_set ) util_block.discrete_variable_list = list( @@ -802,18 +810,22 @@ def init_rNLP(self, add_oa_cuts=True): MindtPy unable to handle the termination condition of the relaxed NLP. """ config = self.config - m = self.working_model.clone() + self.rnlp = self.working_model.clone() config.logger.debug('Relaxed NLP: Solve relaxed integrality') - MindtPy = m.MindtPy_utils - TransformationFactory('core.relax_integer_vars').apply_to(m) + MindtPy = self.rnlp.MindtPy_utils + TransformationFactory('core.relax_integer_vars').apply_to(self.rnlp) nlp_args = dict(config.nlp_solver_args) update_solver_timelimit(self.nlp_opt, config.nlp_solver, self.timing, config) with SuppressInfeasibleWarning(): + print('solving rnlp') results = self.nlp_opt.solve( - m, tee=config.nlp_solver_tee, load_solutions=False, **nlp_args + self.rnlp, + tee=config.nlp_solver_tee, + load_solutions=config.load_solutions, + **nlp_args, ) if len(results.solution) > 0: - m.solutions.load_from(results) + self.rnlp.solutions.load_from(results) subprob_terminate_cond = results.solver.termination_condition if subprob_terminate_cond in {tc.optimal, tc.feasible, tc.locallyOptimal}: main_objective = MindtPy.objective_list[-1] @@ -841,24 +853,24 @@ def init_rNLP(self, add_oa_cuts=True): ): # TODO: recover the opposite dual when cyipopt issue #2831 is solved. dual_values = ( - list(-1 * m.dual[c] for c in MindtPy.constraint_list) + list(-1 * self.rnlp.dual[c] for c in MindtPy.constraint_list) if config.calculate_dual_at_solution else None ) else: dual_values = ( - list(m.dual[c] for c in MindtPy.constraint_list) + list(self.rnlp.dual[c] for c in MindtPy.constraint_list) if config.calculate_dual_at_solution else None ) copy_var_list_values( - m.MindtPy_utils.variable_list, + self.rnlp.MindtPy_utils.variable_list, self.mip.MindtPy_utils.variable_list, config, ) if config.init_strategy == 'FP': copy_var_list_values( - m.MindtPy_utils.variable_list, + self.rnlp.MindtPy_utils.variable_list, self.working_model.MindtPy_utils.variable_list, config, ) @@ -867,6 +879,7 @@ def init_rNLP(self, add_oa_cuts=True): linearize_active=True, linearize_violated=True, cb_opt=None, + nlp=self.rnlp, ) for var in self.mip.MindtPy_utils.discrete_variable_list: # We don't want to trigger the reset of the global stale @@ -936,7 +949,10 @@ def init_max_binaries(self): mip_args = dict(config.mip_solver_args) update_solver_timelimit(self.mip_opt, config.mip_solver, self.timing, config) results = self.mip_opt.solve( - m, tee=config.mip_solver_tee, load_solutions=False, **mip_args + m, + tee=config.mip_solver_tee, + load_solutions=config.load_solutions, + **mip_args, ) if len(results.solution) > 0: m.solutions.load_from(results) @@ -1055,7 +1071,7 @@ def solve_subproblem(self): results = self.nlp_opt.solve( self.fixed_nlp, tee=config.nlp_solver_tee, - load_solutions=False, + load_solutions=config.load_solutions, **nlp_args, ) if len(results.solution) > 0: @@ -1153,6 +1169,7 @@ def handle_subproblem_optimal(self, fixed_nlp, cb_opt=None, fp=False): linearize_active=True, linearize_violated=True, cb_opt=cb_opt, + nlp=self.fixed_nlp, ) var_values = list(v.value for v in fixed_nlp.MindtPy_utils.variable_list) @@ -1229,6 +1246,7 @@ def handle_subproblem_infeasible(self, fixed_nlp, cb_opt=None): linearize_active=True, linearize_violated=True, cb_opt=cb_opt, + nlp=feas_subproblem, ) # Add a no-good cut to exclude this discrete option var_values = list(v.value for v in fixed_nlp.MindtPy_utils.variable_list) @@ -1311,6 +1329,12 @@ def solve_feasibility_subproblem(self): update_solver_timelimit( self.feasibility_nlp_opt, config.nlp_solver, self.timing, config ) + TransformationFactory('contrib.deactivate_trivial_constraints').apply_to( + feas_subproblem, + tmp=True, + ignore_infeasible=False, + tolerance=config.constraint_tolerance, + ) with SuppressInfeasibleWarning(): try: with time_code(self.timing, 'feasibility subproblem'): @@ -1346,6 +1370,9 @@ def solve_feasibility_subproblem(self): constr.activate() active_obj.activate() MindtPy.feas_obj.deactivate() + TransformationFactory('contrib.deactivate_trivial_constraints').revert( + feas_subproblem + ) return feas_subproblem, feas_soln def handle_feasibility_subproblem_tc(self, subprob_terminate_cond, MindtPy): @@ -1480,7 +1507,10 @@ def fix_dual_bound(self, last_iter_cuts): self.mip_opt, config.mip_solver, self.timing, config ) main_mip_results = self.mip_opt.solve( - self.mip, tee=config.mip_solver_tee, load_solutions=False, **mip_args + self.mip, + tee=config.mip_solver_tee, + load_solutions=config.load_solutions, + **mip_args, ) if len(main_mip_results.solution) > 0: self.mip.solutions.load_from(main_mip_results) @@ -1564,7 +1594,10 @@ def solve_main(self): try: main_mip_results = self.mip_opt.solve( - self.mip, tee=config.mip_solver_tee, load_solutions=False, **mip_args + self.mip, + tee=config.mip_solver_tee, + load_solutions=config.load_solutions, + **mip_args, ) # update_attributes should be before load_from(main_mip_results), since load_from(main_mip_results) may fail. if len(main_mip_results.solution) > 0: @@ -1617,7 +1650,10 @@ def solve_fp_main(self): mip_args = self.set_up_mip_solver() main_mip_results = self.mip_opt.solve( - self.mip, tee=config.mip_solver_tee, load_solutions=False, **mip_args + self.mip, + tee=config.mip_solver_tee, + load_solutions=config.load_solutions, + **mip_args, ) # update_attributes should be before load_from(main_mip_results), since load_from(main_mip_results) may fail. # if config.single_tree or config.use_tabu_list: @@ -1659,7 +1695,7 @@ def solve_regularization_main(self): main_mip_results = self.regularization_mip_opt.solve( self.mip, tee=config.mip_solver_tee, - load_solutions=False, + load_solutions=config.load_solutions, **dict(config.mip_solver_args), ) if len(main_mip_results.solution) > 0: @@ -1871,7 +1907,7 @@ def handle_main_unbounded(self, main_mip): main_mip_results = self.mip_opt.solve( main_mip, tee=config.mip_solver_tee, - load_solutions=False, + load_solutions=config.load_solutions, **config.mip_solver_args, ) if len(main_mip_results.solution) > 0: @@ -2263,7 +2299,10 @@ def solve_fp_subproblem(self): with SuppressInfeasibleWarning(): with time_code(self.timing, 'fp subproblem'): results = self.nlp_opt.solve( - fp_nlp, tee=config.nlp_solver_tee, load_solutions=False, **nlp_args + fp_nlp, + tee=config.nlp_solver_tee, + load_solutions=config.load_solutions, + **nlp_args, ) if len(results.solution) > 0: fp_nlp.solutions.load_from(results) @@ -2482,6 +2521,9 @@ def initialize_mip_problem(self): getattr(self.mip, 'ipopt_zU_out', _DoNothing()).deactivate() MindtPy = self.mip.MindtPy_utils + if len(MindtPy.grey_box_list) > 0: + for grey_box in MindtPy.grey_box_list: + grey_box.deactivate() if config.init_strategy == 'FP': MindtPy.cuts.fp_orthogonality_cuts = ConstraintList( diff --git a/pyomo/contrib/mindtpy/config_options.py b/pyomo/contrib/mindtpy/config_options.py index ed0c86baae9..507cbd995f8 100644 --- a/pyomo/contrib/mindtpy/config_options.py +++ b/pyomo/contrib/mindtpy/config_options.py @@ -494,6 +494,14 @@ def _add_common_configs(CONFIG): domain=bool, ), ) + CONFIG.declare( + 'load_solutions', + ConfigValue( + default=True, + description='Whether to load solutions in solve() function', + domain=bool, + ), + ) def _add_subsolver_configs(CONFIG): diff --git a/pyomo/contrib/mindtpy/cut_generation.py b/pyomo/contrib/mindtpy/cut_generation.py index c0449054baa..5613155ee7d 100644 --- a/pyomo/contrib/mindtpy/cut_generation.py +++ b/pyomo/contrib/mindtpy/cut_generation.py @@ -181,6 +181,53 @@ def add_oa_cuts( ) +def add_oa_cuts_for_grey_box( + target_model, jacobians_model, config, objective_sense, mip_iter, cb_opt=None +): + sign_adjust = -1 if objective_sense == minimize else 1 + if config.add_slack: + slack_var = target_model.MindtPy_utils.cuts.slack_vars.add() + for target_model_grey_box, jacobian_model_grey_box in zip( + target_model.MindtPy_utils.grey_box_list, + jacobians_model.MindtPy_utils.grey_box_list, + ): + jacobian_matrix = ( + jacobian_model_grey_box.get_external_model() + .evaluate_jacobian_outputs() + .toarray() + ) + for index, output in enumerate(target_model_grey_box.outputs.values()): + dual_value = jacobians_model.dual[jacobian_model_grey_box][ + output.name.replace("outputs", "output_constraints") + ] + target_model.MindtPy_utils.cuts.oa_cuts.add( + expr=copysign(1, sign_adjust * dual_value) + * ( + sum( + jacobian_matrix[index][var_index] * (var - value(var)) + for var_index, var in enumerate( + target_model_grey_box.inputs.values() + ) + ) + ) + - (output - value(output)) + - (slack_var if config.add_slack else 0) + <= 0 + ) + # TODO: gurobi_persistent currently does not support greybox model. + if ( + config.single_tree + and config.mip_solver == 'gurobi_persistent' + and mip_iter > 0 + and cb_opt is not None + ): + cb_opt.cbLazy( + target_model.MindtPy_utils.cuts.oa_cuts[ + len(target_model.MindtPy_utils.cuts.oa_cuts) + ] + ) + + def add_ecp_cuts( target_model, jacobians, diff --git a/pyomo/contrib/mindtpy/feasibility_pump.py b/pyomo/contrib/mindtpy/feasibility_pump.py index 5716400598a..990f56b8f93 100644 --- a/pyomo/contrib/mindtpy/feasibility_pump.py +++ b/pyomo/contrib/mindtpy/feasibility_pump.py @@ -52,7 +52,12 @@ def initialize_mip_problem(self): ) def add_cuts( - self, dual_values, linearize_active=True, linearize_violated=True, cb_opt=None + self, + dual_values, + linearize_active=True, + linearize_violated=True, + cb_opt=None, + nlp=None, ): add_oa_cuts( self.mip, diff --git a/pyomo/contrib/mindtpy/global_outer_approximation.py b/pyomo/contrib/mindtpy/global_outer_approximation.py index ee3ffb62f55..dfb7ef54630 100644 --- a/pyomo/contrib/mindtpy/global_outer_approximation.py +++ b/pyomo/contrib/mindtpy/global_outer_approximation.py @@ -95,6 +95,7 @@ def add_cuts( linearize_active=True, linearize_violated=True, cb_opt=None, + nlp=None, ): add_affine_cuts(self.mip, self.config, self.timing) diff --git a/pyomo/contrib/mindtpy/outer_approximation.py b/pyomo/contrib/mindtpy/outer_approximation.py index 99d9cea1bd4..6cf0b26cb37 100644 --- a/pyomo/contrib/mindtpy/outer_approximation.py +++ b/pyomo/contrib/mindtpy/outer_approximation.py @@ -16,7 +16,7 @@ from pyomo.opt import SolverFactory from pyomo.contrib.mindtpy.config_options import _get_MindtPy_OA_config from pyomo.contrib.mindtpy.algorithm_base_class import _MindtPyAlgorithm -from pyomo.contrib.mindtpy.cut_generation import add_oa_cuts +from pyomo.contrib.mindtpy.cut_generation import add_oa_cuts, add_oa_cuts_for_grey_box @SolverFactory.register( @@ -102,7 +102,12 @@ def initialize_mip_problem(self): ) def add_cuts( - self, dual_values, linearize_active=True, linearize_violated=True, cb_opt=None + self, + dual_values, + linearize_active=True, + linearize_violated=True, + cb_opt=None, + nlp=None, ): add_oa_cuts( self.mip, @@ -117,6 +122,10 @@ def add_cuts( linearize_active, linearize_violated, ) + if len(self.mip.MindtPy_utils.grey_box_list) > 0: + add_oa_cuts_for_grey_box( + self.mip, nlp, self.config, self.objective_sense, self.mip_iter, cb_opt + ) def deactivate_no_good_cuts_when_fixing_bound(self, no_good_cuts): # Only deactivate the last OA cuts may not be correct. From ec05a3d49f10b13c27ee73a87d392f3b6fd6e722 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Fri, 8 Sep 2023 00:05:51 -0400 Subject: [PATCH 0127/1204] add grey box test in mindtpy --- pyomo/contrib/mindtpy/tests/MINLP_simple.py | 42 +++++- .../mindtpy/tests/MINLP_simple_grey_box.py | 140 ++++++++++++++++++ .../mindtpy/tests/test_mindtpy_grey_box.py | 75 ++++++++++ 3 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py create mode 100644 pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple.py b/pyomo/contrib/mindtpy/tests/MINLP_simple.py index 5663c93af8b..6e1518e1b63 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple.py @@ -35,14 +35,23 @@ RangeSet, Var, minimize, + Block, ) from pyomo.common.collections import ComponentMap +from pyomo.contrib.mindtpy.tests.MINLP_simple_grey_box import GreyBoxModel +from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock + + +def build_model_external(m): + ex_model = GreyBoxModel(initial={"X1": 0, "X2": 0, "Y1": 0, "Y2": 1, "Y3": 1}) + m.egb = ExternalGreyBoxBlock() + m.egb.set_external_model(ex_model) class SimpleMINLP(ConcreteModel): """Convex MINLP problem Assignment 6 APSE.""" - def __init__(self, *args, **kwargs): + def __init__(self, grey_box=False, *args, **kwargs): """Create the problem.""" kwargs.setdefault('name', 'SimpleMINLP') super(SimpleMINLP, self).__init__(*args, **kwargs) @@ -83,6 +92,37 @@ def __init__(self, *args, **kwargs): m.objective = Objective( expr=Y[1] + 1.5 * Y[2] + 0.5 * Y[3] + X[1] ** 2 + X[2] ** 2, sense=minimize ) + + if not grey_box: + m.objective = Objective( + expr=Y[1] + 1.5 * Y[2] + 0.5 * Y[3] + X[1] ** 2 + X[2] ** 2, + sense=minimize, + ) + else: + def _model_i(b): + build_model_external(b) + + m.my_block = Block(rule=_model_i) + + for i in m.I: + + def eq_inputX(m): + return m.X[i] == m.my_block.egb.inputs["X" + str(i)] + + con_name = "con_X_" + str(i) + m.add_component(con_name, Constraint(expr=eq_inputX)) + + for j in m.J: + + def eq_inputY(m): + return m.Y[j] == m.my_block.egb.inputs["Y" + str(j)] + + con_name = "con_Y_" + str(j) + m.add_component(con_name, Constraint(expr=eq_inputY)) + + # add objective + m.objective = Objective(expr=m.my_block.egb.outputs['z'], sense=minimize) + """Bound definitions""" # x (continuous) upper bounds x_ubs = {1: 4, 2: 4} diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py new file mode 100644 index 00000000000..069d2d894f4 --- /dev/null +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -0,0 +1,140 @@ +import numpy as np +import pyomo.environ as pyo +from scipy.sparse import coo_matrix +from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxModel +from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock + + +class GreyBoxModel(ExternalGreyBoxModel): + """Greybox model to compute the example OF.""" + + def __init__(self, initial, use_exact_derivatives=True, verbose=True): + """ + Parameters + + use_exact_derivatives: bool + If True, the exact derivatives are used. If False, the finite difference + approximation is used. + verbose: bool + If True, print information about the model. + """ + self._use_exact_derivatives = use_exact_derivatives + self.verbose = verbose + self.initial = initial + + # For use with exact Hessian + self._output_con_mult_values = np.zeros(1) + + if not use_exact_derivatives: + raise NotImplementedError("use_exact_derivatives == False not supported") + + def input_names(self): + """Return the names of the inputs.""" + self.input_name_list = ["X1", "X2", "Y1", "Y2", "Y3"] + + return self.input_name_list + + def equality_constraint_names(self): + """Return the names of the equality constraints.""" + # no equality constraints + return [] + + def output_names(self): + """Return the names of the outputs.""" + return ['z'] + + def set_output_constraint_multipliers(self, output_con_multiplier_values): + """Set the values of the output constraint multipliers.""" + # because we only have one output constraint + assert len(output_con_multiplier_values) == 1 + np.copyto(self._output_con_mult_values, output_con_multiplier_values) + + def finalize_block_construction(self, pyomo_block): + """Finalize the construction of the ExternalGreyBoxBlock.""" + if self.initial is not None: + print("initialized") + pyomo_block.inputs["X1"].value = self.initial["X1"] + pyomo_block.inputs["X2"].value = self.initial["X2"] + pyomo_block.inputs["Y1"].value = self.initial["Y1"] + pyomo_block.inputs["Y2"].value = self.initial["Y2"] + pyomo_block.inputs["Y3"].value = self.initial["Y3"] + + else: + print("uninitialized") + for n in self.input_name_list: + pyomo_block.inputs[n].value = 1 + + pyomo_block.inputs["X1"].setub(4) + pyomo_block.inputs["X1"].setlb(0) + + pyomo_block.inputs["X2"].setub(4) + pyomo_block.inputs["X2"].setlb(0) + + pyomo_block.inputs["Y1"].setub(1) + pyomo_block.inputs["Y1"].setlb(0) + + pyomo_block.inputs["Y2"].setub(1) + pyomo_block.inputs["Y2"].setlb(0) + + pyomo_block.inputs["Y3"].setub(1) + pyomo_block.inputs["Y3"].setlb(0) + + def set_input_values(self, input_values): + """Set the values of the inputs.""" + self._input_values = list(input_values) + + def evaluate_equality_constraints(self): + """Evaluate the equality constraints.""" + # Not sure what this function should return with no equality constraints + return None + + def evaluate_outputs(self): + """Evaluate the output of the model.""" + # form matrix as a list of lists + # M = self._extract_and_assemble_fim() + x1 = self._input_values[0] + x2 = self._input_values[1] + y1 = self._input_values[2] + y2 = self._input_values[3] + y3 = self._input_values[4] + # z + z = x1**2 + x2**2 + y1 + 1.5 * y2 + 0.5 * y3 + + if self.verbose: + pass + # print("\n Consider inputs [x1,x2,y1,y2,y3] =\n",x1, x2, y1, y2, y3) + # print(" z = ",z,"\n") + + return np.asarray([z], dtype=np.float64) + + def evaluate_jacobian_equality_constraints(self): + """Evaluate the Jacobian of the equality constraints.""" + return None + + ''' + def _extract_and_assemble_fim(self): + M = np.zeros((self.n_parameters, self.n_parameters)) + for i in range(self.n_parameters): + for k in range(self.n_parameters): + M[i,k] = self._input_values[self.ele_to_order[(i,k)]] + + return M + ''' + + def evaluate_jacobian_outputs(self): + """Evaluate the Jacobian of the outputs.""" + if self._use_exact_derivatives: + + # compute gradient of log determinant + row = np.zeros(5) # to store row index + col = np.zeros(5) # to store column index + data = np.zeros(5) # to store data + + row[0], col[0], data[0] = (0, 0, 2 * self._input_values[0]) # x1 + row[0], col[1], data[1] = (0, 1, 2 * self._input_values[1]) # x2 + row[0], col[2], data[2] = (0, 2, 1) # y1 + row[0], col[3], data[3] = (0, 3, 1.5) # y2 + row[0], col[4], data[4] = (0, 4, 0.5) # y3 + + # sparse matrix + return coo_matrix((data, (row, col)), shape=(1, 5)) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py new file mode 100644 index 00000000000..d9ba683d198 --- /dev/null +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py @@ -0,0 +1,75 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + +"""Tests for the MindtPy solver.""" +from pyomo.core.expr.calculus.diff_with_sympy import differentiate_available +import pyomo.common.unittest as unittest +from pyomo.contrib.mindtpy.tests.MINLP_simple import SimpleMINLP as SimpleMINLP +from pyomo.environ import SolverFactory, value, maximize +from pyomo.opt import TerminationCondition + + +model_list = [SimpleMINLP(grey_box=True)] +required_solvers = ('cyipopt', 'glpk') +if all(SolverFactory(s).available(exception_flag=False) for s in required_solvers): + subsolvers_available = True +else: + subsolvers_available = False + + +@unittest.skipIf( + not subsolvers_available, + 'Required subsolvers %s are not available' % (required_solvers,), +) +@unittest.skipIf( + not differentiate_available, 'Symbolic differentiation is not available' +) +class TestMindtPy(unittest.TestCase): + """Tests for the MindtPy solver plugin.""" + + def check_optimal_solution(self, model, places=1): + for var in model.optimal_solution: + self.assertAlmostEqual( + var.value, model.optimal_solution[var], places=places + ) + + def test_OA_rNLP(self): + """Test the outer approximation decomposition algorithm.""" + with SolverFactory('mindtpy') as opt: + for model in model_list: + model = model.clone() + results = opt.solve( + model, + strategy='OA', + init_strategy='rNLP', + mip_solver=required_solvers[1], + nlp_solver=required_solvers[0], + calculate_dual_at_solution=True, + nlp_solver_args={ + 'options': { + 'hessian_approximation': 'limited-memory', + 'linear_solver': 'mumps', + } + }, + ) + + self.assertIn( + results.solver.termination_condition, + [TerminationCondition.optimal, TerminationCondition.feasible], + ) + self.assertAlmostEqual( + value(model.objective.expr), model.optimal_value, places=1 + ) + self.check_optimal_solution(model) + + +if __name__ == '__main__': + unittest.main() From b7a446c70dcbadd43a4da5e330a8e39e785f0d71 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 8 Sep 2023 11:08:59 -0600 Subject: [PATCH 0128/1204] Update documentation on solver interfaces results --- .../developer_reference/solvers.rst | 36 +++++- pyomo/solver/IPOPT.py | 6 +- pyomo/solver/results.py | 116 ++++++++++++++---- 3 files changed, 130 insertions(+), 28 deletions(-) diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index d48e270cc7c..1dcd2f66da7 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -3,17 +3,47 @@ Solver Interfaces Pyomo offers interfaces into multiple solvers, both commercial and open source. +.. currentmodule:: pyomo.solver + + +Results +------- + +Every solver, at the end of a ``solve`` call, will return a ``Results`` object. +This object is a :py:class:`pyomo.common.config.ConfigDict`, which can be manipulated similar +to a standard ``dict`` in Python. + +.. autoclass:: pyomo.solver.results.Results + :show-inheritance: + :members: + :undoc-members: + Termination Conditions ---------------------- Pyomo offers a standard set of termination conditions to map to solver -returns. +returns. The intent of ``TerminationCondition`` is to notify the user of why +the solver exited. The user is expected to inspect the ``Results`` object or any +returned solver messages or logs for more information. -.. currentmodule:: pyomo.contrib.appsi -.. autoclass:: pyomo.contrib.appsi.base.TerminationCondition + +.. autoclass:: pyomo.solver.results.TerminationCondition + :show-inheritance: :noindex: +Solution Status +--------------- + +Pyomo offers a standard set of solution statuses to map to solver output. The +intent of ``SolutionStatus`` is to notify the user of what the solver returned +at a high level. The user is expected to inspect the ``Results`` object or any +returned solver messages or logs for more information. + +.. autoclass:: pyomo.solver.results.SolutionStatus + :show-inheritance: + :noindex: + diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index 384b2173840..cce0017c5be 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -133,9 +133,9 @@ def solve(self, model, **kwds): config.solver_options['max_cpu_time'] = config.time_limit for key, val in config.solver_options.items(): cmd.append(key + '=' + val) - process = subprocess.run(cmd, timeout=config.time_limit, - env=env, - universal_newlines=True) + process = subprocess.run( + cmd, timeout=config.time_limit, env=env, universal_newlines=True + ) if process.returncode != 0: if self.config.load_solution: diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index 02a898f2df5..6a940860661 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -30,68 +30,104 @@ class TerminationCondition(enum.Enum): """ - An enumeration for checking the termination condition of solvers - """ + An Enum that enumerates all possible exit statuses for a solver call. - """unknown serves as both a default value, and it is used when no other enum member makes sense""" - unknown = 42 + Attributes + ---------- + convergenceCriteriaSatisfied: 0 + The solver exited because convergence criteria of the problem were + satisfied. + maxTimeLimit: 1 + The solver exited due to reaching a specified time limit. + iterationLimit: 2 + The solver exited due to reaching a specified iteration limit. + objectiveLimit: 3 + The solver exited due to reaching an objective limit. For example, + in Gurobi, the exit message "Optimal objective for model was proven to + be worse than the value specified in the Cutoff parameter" would map + to objectiveLimit. + minStepLength: 4 + The solver exited due to a minimum step length. + Minimum step length reached may mean that the problem is infeasible or + that the problem is feasible but the solver could not converge. + unbounded: 5 + The solver exited because the problem has been found to be unbounded. + provenInfeasible: 6 + The solver exited because the problem has been proven infeasible. + locallyInfeasible: 7 + The solver exited because no feasible solution was found to the + submitted problem, but it could not be proven that no such solution exists. + infeasibleOrUnbounded: 8 + Some solvers do not specify between infeasibility or unboundedness and + instead return that one or the other has occurred. For example, in + Gurobi, this may occur because there are some steps in presolve that + prevent Gurobi from distinguishing between infeasibility and unboundedness. + error: 9 + The solver exited with some error. The error message will also be + captured and returned. + interrupted: 10 + The solver was interrupted while running. + licensingProblems: 11 + The solver experienced issues with licensing. This could be that no + license was found, the license is of the wrong type for the problem (e.g., + problem is too big for type of license), or there was an issue contacting + a licensing server. + unknown: 42 + All other unrecognized exit statuses fall in this category. + """ - """The solver exited because the convergence criteria were satisfied""" convergenceCriteriaSatisfied = 0 - """The solver exited due to a time limit""" maxTimeLimit = 1 - """The solver exited due to an iteration limit""" iterationLimit = 2 - """The solver exited due to an objective limit""" objectiveLimit = 3 - """The solver exited due to a minimum step length""" minStepLength = 4 - """The solver exited because the problem is unbounded""" unbounded = 5 - """The solver exited because the problem is proven infeasible""" provenInfeasible = 6 - """The solver exited because the problem was found to be locally infeasible""" locallyInfeasible = 7 - """The solver exited because the problem is either infeasible or unbounded""" infeasibleOrUnbounded = 8 - """The solver exited due to an error""" error = 9 - """The solver exited because it was interrupted""" interrupted = 10 - """The solver exited due to licensing problems""" licensingProblems = 11 + unknown = 42 + class SolutionStatus(enum.IntEnum): """ An enumeration for interpreting the result of a termination. This describes the designated status by the solver to be loaded back into the model. - For now, we are choosing to use IntEnum such that return values are numerically - assigned in increasing order. + Attributes + ---------- + noSolution: 0 + No (single) solution was found; possible that a population of solutions + was returned. + infeasible: 10 + Solution point does not satisfy some domains and/or constraints. + feasible: 20 + A solution for which all of the constraints in the model are satisfied. + optimal: 30 + A feasible solution where the objective function reaches its specified + sense (e.g., maximum, minimum) """ - """No (single) solution found; possible that a population of solutions was returned""" noSolution = 0 - """Solution point does not satisfy some domains and/or constraints""" infeasible = 10 - """Feasible solution identified""" feasible = 20 - """Optimal solution identified""" optimal = 30 @@ -99,9 +135,14 @@ class Results(ConfigDict): """ Attributes ---------- + solution_loader: SolutionLoaderBase + Object for loading the solution back into the model. termination_condition: TerminationCondition The reason the solver exited. This is a member of the TerminationCondition enum. + solution_status: SolutionStatus + The result of the solve call. This is a member of the SolutionStatus + enum. incumbent_objective: float If a feasible solution was found, this is the objective value of the best solution found. If no feasible solution was found, this is @@ -111,6 +152,19 @@ class Results(ConfigDict): the lower bound. For maximization problems, this is the upper bound. For solvers that do not provide an objective bound, this should be -inf (minimization) or inf (maximization) + solver_name: str + The name of the solver in use. + solver_version: tuple + A tuple representing the version of the solver in use. + iteration_count: int + The total number of iterations. + timing_info: ConfigDict + A ConfigDict containing three pieces of information: + start_time: UTC timestamp of when run was initiated + wall_time: elapsed wall clock time for entire process + solver_wall_time: elapsed wall clock time for solve call + extra_info: ConfigDict + A ConfigDict to store extra information such as solver messages. """ def __init__( @@ -181,6 +235,24 @@ def __str__(self): return s +class ResultsReader: + pass + + +def parse_sol_file(filename, results): + if results is None: + results = Results() + pass + + +def parse_yaml(): + pass + + +def parse_json(): + pass + + # Everything below here preserves backwards compatibility legacy_termination_condition_map = { From b3af7ac380aa58d9d40393890b0436438e7305ff Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 8 Sep 2023 11:14:10 -0600 Subject: [PATCH 0129/1204] Add in TODOs for documentation --- doc/OnlineDocs/developer_reference/solvers.rst | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index 1dcd2f66da7..75d95fc36db 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -6,6 +6,12 @@ Pyomo offers interfaces into multiple solvers, both commercial and open source. .. currentmodule:: pyomo.solver +Interface Implementation +------------------------ + +TBD: How to add a new interface; the pieces. + + Results ------- @@ -20,7 +26,7 @@ to a standard ``dict`` in Python. Termination Conditions ----------------------- +^^^^^^^^^^^^^^^^^^^^^^ Pyomo offers a standard set of termination conditions to map to solver returns. The intent of ``TerminationCondition`` is to notify the user of why @@ -35,7 +41,7 @@ returned solver messages or logs for more information. Solution Status ---------------- +^^^^^^^^^^^^^^^ Pyomo offers a standard set of solution statuses to map to solver output. The intent of ``SolutionStatus`` is to notify the user of what the solver returned @@ -47,3 +53,7 @@ returned solver messages or logs for more information. :noindex: +Solution +-------- + +TBD: How to load/parse a solution. From 6cdff50a4626a3c101b2264ff1e8b44bed179ae3 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Fri, 8 Sep 2023 11:44:13 -0600 Subject: [PATCH 0130/1204] adding param support --- pyomo/util/latex_printer.py | 138 +++++++++++++++++++++++++++++++++--- 1 file changed, 128 insertions(+), 10 deletions(-) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index a23ae3fdfba..5bbb4309c53 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -46,7 +46,7 @@ templatize_rule, ) from pyomo.core.base.var import ScalarVar, _GeneralVarData, IndexedVar -from pyomo.core.base.param import _ParamData +from pyomo.core.base.param import _ParamData, ScalarParam, IndexedParam from pyomo.core.base.set import _SetData from pyomo.core.base.constraint import ScalarConstraint, IndexedConstraint from pyomo.common.collections.component_map import ComponentMap @@ -395,6 +395,11 @@ def handle_templateSumExpression_node(visitor, node, *args): ) return pstr +def handle_param_node(visitor,node): + # return node.name + return visitor.parameterMap[node] + + class _LatexVisitor(StreamBasedExpressionVisitor): def __init__(self): @@ -431,6 +436,8 @@ def __init__(self): IndexTemplate: handle_indexTemplate_node, Numeric_GetItemExpression: handle_numericGIE_node, TemplateSumExpression: handle_templateSumExpression_node, + ScalarParam: handle_param_node, + _ParamData: handle_param_node, } def exitNode(self, node, data): @@ -811,6 +818,31 @@ def latex_printer( visitor.variableMap = variableMap + parameterList = [ + pm + for pm in pyomo_component.component_objects( + pyo.Param, descend_into=True, active=True + ) + ] + + parameterMap = ComponentMap() + pmIdx = 0 + for i in range(0,len(parameterList)): + vr = parameterList[i] + pmIdx += 1 + if isinstance(vr ,ScalarParam): + parameterMap[vr] = 'p_' + str(pmIdx) + elif isinstance(vr, IndexedParam): + parameterMap[vr] = 'p_' + str(pmIdx) + for sd in vr.index_set().data(): + pmIdx += 1 + parameterMap[vr[sd]] = 'p_' + str(pmIdx) + else: + raise DeveloperError( 'Parameter is not a parameter. Should not happen. Contact developers') + + visitor.parameterMap = parameterMap + + # starts building the output string pstr = '' if not use_equation_environment: @@ -991,7 +1023,7 @@ def latex_printer( if appendBoundString: pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['with bounds']) - pstr += bstr + pstr += bstr + '\n' else: pstr = pstr[0:-4] + '\n' @@ -1140,13 +1172,13 @@ def latex_printer( pstr = '\n'.join(latexLines) if x_only_mode in [1,2,3]: - # Need to preserve only the non-variable elments in the overwrite_dict + # Need to preserve only the set elments in the overwrite_dict new_overwrite_dict = {} for ky, vl in overwrite_dict.items(): if isinstance(ky,_GeneralVarData): pass elif isinstance(ky,_ParamData): - raise ValueEror('not implemented yet') + pass elif isinstance(ky,_SetData): new_overwrite_dict[ky] = overwrite_dict[ky] else: @@ -1180,9 +1212,30 @@ def latex_printer( else: raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + pmIdx = 0 + new_parameterMap = ComponentMap() + for i in range(0,len(parameterList)): + pm = parameterList[i] + pmIdx += 1 + if isinstance(pm ,ScalarParam): + new_parameterMap[pm] = 'p_{' + str(pmIdx) + '}' + elif isinstance(pm, IndexedParam): + new_parameterMap[pm] = 'p_{' + str(pmIdx) + '}' + for sd in pm.index_set().data(): + sdString = str(sd) + if sdString[0]=='(': + sdString = sdString[1:] + if sdString[-1]==')': + sdString = sdString[0:-1] + new_parameterMap[pm[sd]] = 'p_{' + str(pmIdx) + '_{' + sdString + '}' + '}' + else: + raise DeveloperError( 'Parameter is not a parameter. Should not happen. Contact developers') + new_overwrite_dict = ComponentMap() for ky, vl in new_variableMap.items(): new_overwrite_dict[ky] = vl + for ky, vl in new_parameterMap.items(): + new_overwrite_dict[ky] = vl for ky, vl in overwrite_dict.items(): new_overwrite_dict[ky] = vl overwrite_dict = new_overwrite_dict @@ -1208,9 +1261,30 @@ def latex_printer( else: raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + pmIdx = vrIdx-1 + new_parameterMap = ComponentMap() + for i in range(0,len(parameterList)): + pm = parameterList[i] + pmIdx += 1 + if isinstance(pm ,ScalarParam): + new_parameterMap[pm] = alphabetStringGenerator(pmIdx) + elif isinstance(pm, IndexedParam): + new_parameterMap[pm] = alphabetStringGenerator(pmIdx) + for sd in pm.index_set().data(): + sdString = str(sd) + if sdString[0]=='(': + sdString = sdString[1:] + if sdString[-1]==')': + sdString = sdString[0:-1] + new_parameterMap[pm[sd]] = alphabetStringGenerator(pmIdx) + '_{' + sdString + '}' + else: + raise DeveloperError( 'Parameter is not a parameter. Should not happen. Contact developers') + new_overwrite_dict = ComponentMap() for ky, vl in new_variableMap.items(): new_overwrite_dict[ky] = vl + for ky, vl in new_parameterMap.items(): + new_overwrite_dict[ky] = vl for ky, vl in overwrite_dict.items(): new_overwrite_dict[ky] = vl overwrite_dict = new_overwrite_dict @@ -1219,6 +1293,8 @@ def latex_printer( new_overwrite_dict = ComponentMap() for ky, vl in variableMap.items(): new_overwrite_dict[ky] = vl + for ky, vl in parameterMap.items(): + new_overwrite_dict[ky] = vl for ky, vl in overwrite_dict.items(): new_overwrite_dict[ky] = vl overwrite_dict = new_overwrite_dict @@ -1247,9 +1323,35 @@ def latex_printer( else: raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + pmIdx = 0 + new_parameterMap = ComponentMap() + for i in range(0,len(parameterList)): + pm = parameterList[i] + pmIdx += 1 + if isinstance(pm ,ScalarParam): + new_parameterMap[pm] = pm.name + elif isinstance(pm, IndexedParam): + new_parameterMap[pm] = pm.name + for sd in pm.index_set().data(): + # pmIdx += 1 + sdString = str(sd) + if sdString[0]=='(': + sdString = sdString[1:] + if sdString[-1]==')': + sdString = sdString[0:-1] + if use_smart_variables: + new_parameterMap[pm[sd]] = applySmartVariables(pm.name + '_{' + sdString + '}') + else: + new_parameterMap[pm[sd]] = pm[sd].name + else: + raise DeveloperError( 'Parameter is not a parameter. Should not happen. Contact developers') + for ky, vl in new_variableMap.items(): if ky not in overwrite_dict.keys(): overwrite_dict[ky] = vl + for ky, vl in new_parameterMap.items(): + if ky not in overwrite_dict.keys(): + overwrite_dict[ky] = vl rep_dict = {} for ky in list(reversed(list(overwrite_dict.keys()))): @@ -1259,19 +1361,35 @@ def latex_printer( else: overwrite_value = overwrite_dict[ky] rep_dict[variableMap[ky]] = overwrite_value - elif isinstance(ky,_ParamData): - raise ValueEror('not implemented yet') + elif isinstance(ky,(pyo.Param,_ParamData)): + if use_smart_variables and x_only_mode not in [1]: + overwrite_value = applySmartVariables(overwrite_dict[ky]) + else: + overwrite_value = overwrite_dict[ky] + rep_dict[parameterMap[ky]] = overwrite_value elif isinstance(ky,_SetData): # already handled pass else: raise ValueError('The overwrite_dict object has a key of invalid type: %s'%(str(ky))) - pstr = multiple_replace(pstr,rep_dict) + if not use_smart_variables: + for ky, vl in rep_dict.items(): + rep_dict[ky] = vl.replace('_','\\_') - pattern = r'_([^{]*)_{([^{]*)}_bound' - replacement = r'_\1_\2_bound' - pstr = re.sub(pattern, replacement, pstr) + label_rep_dict = copy.deepcopy(rep_dict) + for ky, vl in label_rep_dict.items(): + label_rep_dict[ky] = vl.replace('{','').replace('}','').replace('\\','') + + splitLines = pstr.split('\n') + for i in range(0,len(splitLines)): + if '\\label{' in splitLines[i]: + epr, lbl = splitLines[i].split('\\label{') + epr = multiple_replace(epr,rep_dict) + lbl = multiple_replace(lbl,label_rep_dict) + splitLines[i] = epr + '\\label{' + lbl + + pstr = '\n'.join(splitLines) pattern = r'_{([^{]*)}_{([^{]*)}' replacement = r'_{\1_{\2}}' From c2877a3fa0589d379aeb622ae889cac2672ef9be Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Fri, 8 Sep 2023 14:49:15 -0400 Subject: [PATCH 0131/1204] black format --- pyomo/contrib/mindtpy/tests/MINLP_simple.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple.py b/pyomo/contrib/mindtpy/tests/MINLP_simple.py index 6e1518e1b63..91976997c34 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple.py @@ -99,6 +99,7 @@ def __init__(self, grey_box=False, *args, **kwargs): sense=minimize, ) else: + def _model_i(b): build_model_external(b) From ccff4167e61363a84d2899016dd390eeb5c596c1 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Fri, 8 Sep 2023 14:52:42 -0400 Subject: [PATCH 0132/1204] black format --- pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py | 1 - pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index 069d2d894f4..562e88ea667 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -124,7 +124,6 @@ def _extract_and_assemble_fim(self): def evaluate_jacobian_outputs(self): """Evaluate the Jacobian of the outputs.""" if self._use_exact_derivatives: - # compute gradient of log determinant row = np.zeros(5) # to store row index col = np.zeros(5) # to store column index diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py index a37507dee6b..0b6774a7d1a 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py @@ -426,7 +426,11 @@ def load_state_into_pyomo(self, bound_multipliers=None): # not we are minimizing or maximizing - this is done in the ASL interface # for ipopt, but does not appear to be done in cyipopt. obj_sign = 1.0 - objs = list(m.component_data_objects(ctype=pyo.Objective, active=True, descend_into=True)) + objs = list( + m.component_data_objects( + ctype=pyo.Objective, active=True, descend_into=True + ) + ) assert len(objs) == 1 if objs[0].sense == pyo.maximize: obj_sign = -1.0 From 3054a4431bfdde777b87d3c2d2ea75c51632ed97 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Fri, 8 Sep 2023 16:05:08 -0600 Subject: [PATCH 0133/1204] functionality working I think --- pyomo/util/latex_printer.py | 400 ++++++++++++++++-------------------- 1 file changed, 177 insertions(+), 223 deletions(-) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index 5bbb4309c53..7a853bd0308 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -102,39 +102,71 @@ def indexCorrector(ixs, base): return ixs -def alphabetStringGenerator(num): - alphabet = [ - '.', - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i', - 'j', - 'k', - 'l', - 'm', - 'n', - 'o', - 'p', - 'q', - 'r', - 's', - 't', - 'u', - 'v', - 'w', - 'x', - 'y', - 'z', - ] - ixs = decoder(num + 1, 26) +def alphabetStringGenerator(num,indexMode = False): + if indexMode: + alphabet = [ + '.', + 'i', + 'j', + 'k', + 'm', + 'n', + 'p', + 'q', + 'r', + # 'a', + # 'b', + # 'c', + # 'd', + # 'e', + # 'f', + # 'g', + # 'h', + # 'l', + # 'o', + # 's', + # 't', + # 'u', + # 'v', + # 'w', + # 'x', + # 'y', + # 'z', + ] + + else: + alphabet = [ + '.', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + ] + ixs = decoder(num + 1, len(alphabet)-1) pstr = '' - ixs = indexCorrector(ixs, 26) + ixs = indexCorrector(ixs, len(alphabet)-1) for i in range(0, len(ixs)): ix = ixs[i] pstr += alphabet[ix] @@ -224,68 +256,6 @@ def handle_inequality_node(visitor, node, arg1, arg2): def handle_var_node(visitor, node): return visitor.variableMap[node] - # return node.name - # overwrite_dict = visitor.overwrite_dict - # # varList = visitor.variableList - - # name = node.name - - # declaredIndex = None - # if '[' in name: - # openBracketIndex = name.index('[') - # closeBracketIndex = name.index(']') - # if closeBracketIndex != len(name) - 1: - # # I dont think this can happen, but possibly through a raw string and a user - # # who is really hacking the variable name setter - # raise ValueError( - # 'Variable %s has a close brace not at the end of the string' % (name) - # ) - # declaredIndex = name[openBracketIndex + 1 : closeBracketIndex] - # name = name[0:openBracketIndex] - - # if name in overwrite_dict.keys(): - # name = overwrite_dict[name] - - # if visitor.use_smart_variables: - # splitName = name.split('_') - # if declaredIndex is not None: - # splitName.append(declaredIndex) - - # filteredName = [] - - # prfx = '' - # psfx = '' - # for i in range(0, len(splitName)): - # se = splitName[i] - # if se != 0: - # if se == 'dot': - # prfx = '\\dot{' - # psfx = '}' - # elif se == 'hat': - # prfx = '\\hat{' - # psfx = '}' - # elif se == 'bar': - # prfx = '\\bar{' - # psfx = '}' - # else: - # filteredName.append(se) - # else: - # filteredName.append(se) - - # joinedName = prfx + filteredName[0] + psfx - # for i in range(1, len(filteredName)): - # joinedName += '_{' + filteredName[i] - - # joinedName += '}' * (len(filteredName) - 1) - - # else: - # if declaredIndex is not None: - # joinedName = name + '[' + declaredIndex + ']' - # else: - # joinedName = name - - # return joinedName - def handle_num_node(visitor, node): if isinstance(node, float): @@ -358,19 +328,10 @@ def handle_functionID_node(visitor, node, *args): def handle_indexTemplate_node(visitor, node, *args): - return '__INDEX_PLACEHOLDER_8675309_GROUP_%s_%s__' % (node._group, node._set) + return '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % (node._group, visitor.setMap[node._set]) def handle_numericGIE_node(visitor, node, *args): - # addFinalBrace = False - # if '_' in args[0]: - # splitName = args[0].split('_') - # joinedName = splitName[0] - # for i in range(1, len(splitName)): - # joinedName += '_{' + splitName[i] - # joinedName += '}' * (len(splitName) - 2) - # addFinalBrace = True - # else: joinedName = args[0] pstr = '' @@ -381,22 +342,19 @@ def handle_numericGIE_node(visitor, node, *args): pstr += ',' else: pstr += '}' - # if addFinalBrace: - # pstr += '}' return pstr def handle_templateSumExpression_node(visitor, node, *args): pstr = '' - pstr += '\\sum_{%s} %s' % ( - '__SET_PLACEHOLDER_8675309_GROUP_%s_%s__' - % (node._iters[0][0]._group, str(node._iters[0][0]._set)), - args[0], - ) + for i in range(0,len(node._iters)): + pstr += '\\sum_{__S_PLACEHOLDER_8675309_GROUP_%s_%s__} ' %(node._iters[i][0]._group, visitor.setMap[node._iters[i][0]._set]) + + pstr += args[0] + return pstr def handle_param_node(visitor,node): - # return node.name return visitor.parameterMap[node] @@ -464,6 +422,9 @@ def applySmartVariables(name): elif se == 'bar': prfx = '\\bar{' psfx = '}' + elif se == 'mathcal': + prfx = '\\mathcal{' + psfx = '}' else: filteredName.append(se) else: @@ -673,7 +634,6 @@ def latex_printer( use_smart_variables=False, x_only_mode=0, use_short_descriptors=False, - use_forall=False, overwrite_dict=None, ): """This function produces a string that can be rendered as LaTeX @@ -770,10 +730,7 @@ def latex_printer( % (str(type(pyomo_component))) ) - if use_forall: - forallTag = ' \\qquad \\forall' - else: - forallTag = ' \\quad' + forallTag = ' \\qquad \\forall' descriptorDict = {} if use_short_descriptors: @@ -800,7 +757,6 @@ def latex_printer( pyo.Var, descend_into=True, active=True ) ] - variableMap = ComponentMap() vrIdx = 0 for i in range(0,len(variableList)): @@ -815,7 +771,6 @@ def latex_printer( variableMap[vr[sd]] = 'x_' + str(vrIdx) else: raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') - visitor.variableMap = variableMap parameterList = [ @@ -824,7 +779,7 @@ def latex_printer( pyo.Param, descend_into=True, active=True ) ] - + parameterMap = ComponentMap() pmIdx = 0 for i in range(0,len(parameterList)): @@ -839,9 +794,19 @@ def latex_printer( parameterMap[vr[sd]] = 'p_' + str(pmIdx) else: raise DeveloperError( 'Parameter is not a parameter. Should not happen. Contact developers') - visitor.parameterMap = parameterMap + setList = [ + st + for st in pyomo_component.component_objects( + pyo.Set, descend_into=True, active=True + ) + ] + setMap = ComponentMap() + for i in range(0,len(setList)): + st = setList[i] + setMap[st] = 'SET' + str(i+1) + visitor.setMap = setMap # starts building the output string pstr = '' @@ -915,23 +880,16 @@ def latex_printer( # Multiple constraints are generated using a set if len(indices) > 0: - idxTag = '__INDEX_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( + idxTag = '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( indices[0]._group, - indices[0]._set, + setMap[indices[0]._set], ) - setTag = '__SET_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( + setTag = '__S_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( indices[0]._group, - indices[0]._set, + setMap[indices[0]._set], ) - if use_forall: - conLine += '%s %s \\in %s ' % (forallTag, idxTag, setTag) - else: - conLine = ( - conLine[0:-2] - + ' ' + trailingAligner - + '%s %s \\in %s ' % (forallTag, idxTag, setTag) - ) + conLine += '%s %s \\in %s ' % (forallTag, idxTag, setTag) pstr += conLine # Add labels as needed @@ -1037,32 +995,28 @@ def latex_printer( pstr += '\\end{equation} \n' # Handling the iterator indices + defaultSetLatexNames = ComponentMap() + for i in range(0,len(setList)): + st = setList[i] + if use_smart_variables: + chkName = setList[i].name + if len(chkName)==1 and chkName.upper() == chkName: + chkName += '_mathcal' + defaultSetLatexNames[st] = applySmartVariables( chkName ) + else: + defaultSetLatexNames[st] = setList[i].name.replace('_','\\_') + ## Could be used in the future if someone has a lot of sets + # defaultSetLatexNames[st] = 'mathcal{' + alphabetStringGenerator(i).upper() + '}' - # ==================== - # ==================== - # ==================== - # ==================== - # ==================== - # ==================== - # preferential order for indices - setPreferenceOrder = [ - 'I', - 'J', - 'K', - 'M', - 'N', - 'P', - 'Q', - 'R', - 'S', - 'T', - 'U', - 'V', - 'W', - ] + if st in overwrite_dict.keys(): + if use_smart_variables: + defaultSetLatexNames[st] = applySmartVariables( overwrite_dict[st][0] ) + else: + defaultSetLatexNames[st] = overwrite_dict[st][0].replace('_','\\_') + + defaultSetLatexNames[st] = defaultSetLatexNames[st].replace('\\mathcal',r'\\mathcal') - # Go line by line and replace the placeholders with correct set names and index names latexLines = pstr.split('\n') for jj in range(0, len(latexLines)): groupMap = {} @@ -1082,94 +1036,84 @@ def latex_printer( uniqueSets.append(stName) # Determine if the set is continuous - continuousSets = dict( - zip(uniqueSets, [False for i in range(0, len(uniqueSets))]) + setInfo = dict( + zip(uniqueSets, [{'continuous':False} for i in range(0, len(uniqueSets))]) ) + + for ky, vl in setInfo.items(): + ix = int(ky[3:])-1 + setInfo[ky]['setObject'] = setList[ix] + setInfo[ky]['setRegEx'] = r'__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__'%(ky) + setInfo[ky]['sumSetRegEx'] = r'sum_{__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__}'%(ky) + # setInfo[ky]['idxRegEx'] = r'__I_PLACEHOLDER_8675309_GROUP_[0-9*]_%s__'%(ky) + + if split_continuous_sets: - for i in range(0, len(uniqueSets)): - st = getattr(pyomo_component, uniqueSets[i]) + for ky, vl in setInfo.items(): + st = vl['setObject'] stData = st.data() stCont = True for ii in range(0, len(stData)): if ii + stData[0] != stData[ii]: stCont = False break - continuousSets[uniqueSets[i]] = stCont - - # Add the continuous set data to the groupMap - for ky, vl in groupMap.items(): - groupMap[ky].append(continuousSets[vl[0]]) - - # Set up new names for duplicate sets - assignedSetNames = [] - gmk_list = list(groupMap.keys()) - for i in range(0, len(groupMap.keys())): - ix = gmk_list[i] - # set not already used - if groupMap[str(ix)][0] not in assignedSetNames: - assignedSetNames.append(groupMap[str(ix)][0]) - groupMap[str(ix)].append(groupMap[str(ix)][0]) - else: - # Pick a new set from the preference order - for j in range(0, len(setPreferenceOrder)): - stprf = setPreferenceOrder[j] - # must not be already used - if stprf not in assignedSetNames: - assignedSetNames.append(stprf) - groupMap[str(ix)].append(stprf) - break + setInfo[ky]['continuous'] = stCont - # set up the substitutions - setStrings = {} - indexStrings = {} - for ky, vl in groupMap.items(): - setStrings['__SET_PLACEHOLDER_8675309_GROUP_%s_%s__' % (ky, vl[0])] = [ - vl[2], - vl[1], - vl[0], - ] - indexStrings[ - '__INDEX_PLACEHOLDER_8675309_GROUP_%s_%s__' % (ky, vl[0]) - ] = vl[2].lower() - - # replace the indices - for ky, vl in indexStrings.items(): - ln = ln.replace(ky, vl) # replace the sets - for ky, vl in setStrings.items(): + for ky, vl in setInfo.items(): # if the set is continuous and the flag has been set - if split_continuous_sets and vl[1]: - st = getattr(pyomo_component, vl[2]) + if split_continuous_sets and setInfo[ky]['continuous']: + st = setInfo[ky]['setObject'] stData = st.data() bgn = stData[0] ed = stData[-1] - ln = ln.replace( - '\\sum_{%s}' % (ky), - '\\sum_{%s = %d}^{%d}' % (vl[0].lower(), bgn, ed), - ) - ln = ln.replace(ky, vl[2]) + + replacement = r'sum_{ __I_PLACEHOLDER_8675309_GROUP_\1_%s__ = %d }^{%d}'%(ky,bgn,ed) + ln = re.sub(setInfo[ky]['sumSetRegEx'], replacement, ln) else: # if the set is not continuous or the flag has not been set - ln = ln.replace( - '\\sum_{%s}' % (ky), - '\\sum_{%s \\in %s}' % (vl[0].lower(), vl[0]), - ) - ln = ln.replace(ky, vl[2]) + replacement = r'sum_{ __I_PLACEHOLDER_8675309_GROUP_\1_%s__ \\in __S_PLACEHOLDER_8675309_GROUP_\1_%s__ }'%(ky,ky) + ln = re.sub(setInfo[ky]['sumSetRegEx'], replacement, ln) + + replacement = defaultSetLatexNames[setInfo[ky]['setObject']] + ln = re.sub(setInfo[ky]['setRegEx'],replacement,ln) + + # groupNumbers = re.findall(r'__I_PLACEHOLDER_8675309_GROUP_([0-9*])_SET[0-9]*__',ln) + setNumbers = re.findall(r'__I_PLACEHOLDER_8675309_GROUP_[0-9*]_SET([0-9]*)__',ln) + groupSetPairs = re.findall(r'__I_PLACEHOLDER_8675309_GROUP_([0-9*])_SET([0-9]*)__',ln) + + groupInfo = {} + for vl in setNumbers: + groupInfo['SET'+vl] = {'setObject':setInfo['SET'+vl]['setObject'], 'indices':[]} + + for gp in groupSetPairs: + if gp[0] not in groupInfo['SET'+gp[1]]['indices']: + groupInfo['SET'+gp[1]]['indices'].append(gp[0]) + + + indexCounter = 0 + for ky, vl in groupInfo.items(): + if vl['setObject'] in overwrite_dict.keys(): + indexNames = overwrite_dict[vl['setObject']][1] + if len(indexNames) < len(vl['indices']): + raise ValueError('Insufficient number of indices provided to the overwite dictionary for set %s'%(vl['setObject'].name)) + for i in range(0,len(indexNames)): + ln = ln.replace('__I_PLACEHOLDER_8675309_GROUP_%s_%s__'%(vl['indices'][i],ky),indexNames[i]) + else: + for i in range(0,len(vl['indices'])): + ln = ln.replace('__I_PLACEHOLDER_8675309_GROUP_%s_%s__'%(vl['indices'][i],ky),alphabetStringGenerator(indexCounter,True)) + indexCounter += 1 - # Assign the newly modified line - latexLines[jj] = ln - # ==================== - # ==================== - # ==================== - # ==================== - # ==================== - # ==================== + # print('gn',groupInfo) + + latexLines[jj] = ln - # rejoin the corrected lines pstr = '\n'.join(latexLines) + # pstr = pstr.replace('\\mathcal{', 'mathcal{') + # pstr = pstr.replace('mathcal{', '\\mathcal{') if x_only_mode in [1,2,3]: # Need to preserve only the set elments in the overwrite_dict @@ -1342,7 +1286,7 @@ def latex_printer( if use_smart_variables: new_parameterMap[pm[sd]] = applySmartVariables(pm.name + '_{' + sdString + '}') else: - new_parameterMap[pm[sd]] = pm[sd].name + new_parameterMap[pm[sd]] = str(pm[sd])#.name else: raise DeveloperError( 'Parameter is not a parameter. Should not happen. Contact developers') @@ -1370,6 +1314,9 @@ def latex_printer( elif isinstance(ky,_SetData): # already handled pass + elif isinstance(ky,(float,int)): + # happens when immutable parameters are used, do nothing + pass else: raise ValueError('The overwrite_dict object has a key of invalid type: %s'%(str(ky))) @@ -1399,6 +1346,13 @@ def latex_printer( replacement = r'_{\1_{\2}}' pstr = re.sub(pattern, replacement, pstr) + splitLines = pstr.split('\n') + finalLines = [] + for sl in splitLines: + if sl != '': + finalLines.append(sl) + + pstr = '\n'.join(finalLines) # optional write to output file From e58669149fccfac3ff3c53d3bf0e1c4b0910a840 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 8 Sep 2023 21:26:45 -0400 Subject: [PATCH 0134/1204] Use `HierarchicalTimer` for detailed PyROS timing --- pyomo/contrib/pyros/master_problem_methods.py | 6 + pyomo/contrib/pyros/pyros.py | 20 ++- .../contrib/pyros/pyros_algorithm_methods.py | 1 + .../pyros/separation_problem_methods.py | 2 + pyomo/contrib/pyros/tests/test_grcs.py | 9 +- pyomo/contrib/pyros/util.py | 133 ++++++++++++++++-- 6 files changed, 147 insertions(+), 24 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index b71bbb9285a..3a85c7f70a2 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -245,6 +245,7 @@ def solve_master_feasibility_problem(model_data, config): orig_setting, custom_setting_present = adjust_solver_time_settings( model_data.timing, solver, config ) + model_data.timing.start_timer("main.master_feasibility") timer.tic(msg=None) try: results = solver.solve(model, tee=config.tee, load_solutions=False) @@ -260,6 +261,7 @@ def solve_master_feasibility_problem(model_data, config): raise else: setattr(results.solver, TIC_TOC_SOLVE_TIME_ATTR, timer.toc(msg=None)) + model_data.timing.stop_timer("main.master_feasibility") finally: revert_solver_max_time_adjustment( solver, orig_setting, custom_setting_present, config @@ -484,6 +486,7 @@ def minimize_dr_vars(model_data, config): orig_setting, custom_setting_present = adjust_solver_time_settings( model_data.timing, solver, config ) + model_data.timing.start_timer("main.dr_polishing") timer.tic(msg=None) try: results = solver.solve(polishing_model, tee=config.tee, load_solutions=False) @@ -496,6 +499,7 @@ def minimize_dr_vars(model_data, config): raise else: setattr(results.solver, TIC_TOC_SOLVE_TIME_ATTR, timer.toc(msg=None)) + model_data.timing.stop_timer("main.dr_polishing") finally: revert_solver_max_time_adjustment( solver, orig_setting, custom_setting_present, config @@ -696,6 +700,7 @@ def solver_call_master(model_data, config, solver, solve_data): orig_setting, custom_setting_present = adjust_solver_time_settings( model_data.timing, opt, config ) + model_data.timing.start_timer("main.master") timer.tic(msg=None) try: results = opt.solve( @@ -716,6 +721,7 @@ def solver_call_master(model_data, config, solver, solve_data): raise else: setattr(results.solver, TIC_TOC_SOLVE_TIME_ATTR, timer.toc(msg=None)) + model_data.timing.stop_timer("main.master") finally: revert_solver_max_time_adjustment( solver, orig_setting, custom_setting_present, config diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 7b0b78f6729..0e9dd6fa051 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -39,6 +39,7 @@ replace_uncertain_bounds_with_constraints, IterationLogRecord, DEFAULT_LOGGER_NAME, + TimingData, ) from pyomo.contrib.pyros.solve_data import ROSolveResults from pyomo.contrib.pyros.pyros_algorithm_methods import ROSolver_iterative_solve @@ -869,13 +870,12 @@ def solve( model_data.timing = Bunch() # === Start timer, run the algorithm - model_data.timing = Bunch() + model_data.timing = TimingData() with time_code( timing_data_obj=model_data.timing, - code_block_name='total', + code_block_name="main", is_main_timer=True, ): - tt_timer = model_data.timing.tic_toc_timer # output intro and disclaimer self._log_intro(config.progress_logger, level=logging.INFO) self._log_disclaimer(config.progress_logger, level=logging.INFO) @@ -897,7 +897,7 @@ def solve( # begin preprocessing config.progress_logger.info("Preprocessing...") - tt_timer.toc(msg=None) + model_data.timing.start_timer("main.preprocessing") # === A block to hold list-type data to make cloning easy util = Block(concrete=True) @@ -978,9 +978,13 @@ def solve( if "bound_con" in c.name: wm_util.ssv_bounds.append(c) + model_data.timing.stop_timer("main.preprocessing") + preprocessing_time = model_data.timing.get_total_time( + "main.preprocessing", + ) config.progress_logger.info( f"Done preprocessing; required wall time of " - f"{tt_timer.toc(msg=None, delta=True):.2f}s." + f"{preprocessing_time:.2f}s." ) # === Solve and load solution into model @@ -1052,6 +1056,12 @@ def solve( f" {'Termination condition':<22s}: " f"{return_soln.pyros_termination_condition}" ) + config.progress_logger.info("-" * self._LOG_LINE_LENGTH) + config.progress_logger.info( + f"Timing breakdown:\n\n{model_data.timing}", + ) + config.progress_logger.info("-" * self._LOG_LINE_LENGTH) + config.progress_logger.info("All done. Exiting PyROS.") config.progress_logger.info("=" * self._LOG_LINE_LENGTH) return return_soln diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index bba81222aa4..456089c2882 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -74,6 +74,7 @@ def evaluate_and_log_component_stats(model_data, separation_model, config): """ Evaluate and log model component statistics. """ + IterationLogRecord.log_header_rule(config.progress_logger.info) config.progress_logger.info("Model statistics:") # print model statistics dr_var_set = ComponentSet( diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index 7d8669286f5..5a3bf5469e5 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -1030,6 +1030,7 @@ def solver_call_separation( orig_setting, custom_setting_present = adjust_solver_time_settings( model_data.timing, opt, config ) + model_data.timing.start_timer(f"main.{solve_mode}_separation") timer.tic(msg=None) try: results = opt.solve( @@ -1052,6 +1053,7 @@ def solver_call_separation( raise else: setattr(results.solver, TIC_TOC_SOLVE_TIME_ATTR, timer.toc(msg=None)) + model_data.timing.stop_timer(f"main.{solve_mode}_separation") finally: revert_solver_max_time_adjustment( opt, orig_setting, custom_setting_present, config diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 3526b70b846..50be1e218dd 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -19,6 +19,7 @@ ObjectiveType, pyrosTerminationCondition, coefficient_matching, + TimingData, ) from pyomo.contrib.pyros.util import replace_uncertain_bounds_with_constraints from pyomo.contrib.pyros.util import get_vars_from_component @@ -3568,7 +3569,7 @@ def test_solve_master(self): expr=master_data.master_model.scenarios[0, 0].x ) master_data.iteration = 0 - master_data.timing = Bunch() + master_data.timing = TimingData() box_set = BoxSet(bounds=[(0, 2)]) solver = SolverFactory(global_solver) @@ -3594,7 +3595,7 @@ def test_solve_master(self): "progress_logger", ConfigValue(default=logging.getLogger(__name__)) ) - with time_code(master_data.timing, "total", is_main_timer=True): + with time_code(master_data.timing, "main", is_main_timer=True): master_soln = solve_master(master_data, config) self.assertEqual( master_soln.termination_condition, @@ -3898,8 +3899,8 @@ def test_minimize_dr_norm(self): master_data.master_model.const_efficiency_applied = False master_data.master_model.linear_efficiency_applied = False - master_data.timing = Bunch() - with time_code(master_data.timing, "total", is_main_timer=True): + master_data.timing = TimingData() + with time_code(master_data.timing, "main", is_main_timer=True): results, success = minimize_dr_vars(model_data=master_data, config=config) self.assertEqual( results.solver.termination_condition, diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index d40670580f3..6c46ffaf1bb 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -41,7 +41,7 @@ import logging from pprint import pprint import math -from pyomo.common.timing import TicTocTimer +from pyomo.common.timing import HierarchicalTimer # Tolerances used in the code @@ -54,40 +54,143 @@ DEFAULT_LOGGER_NAME = "pyomo.contrib.pyros" +class TimingData: + """ + PyROS solver timing data object. + + A wrapper around `common.timing.HierarchicalTimer`, + with added functionality for enforcing a standardized + hierarchy of identifiers. + + Attributes + ---------- + hierarchical_timer_full_ids : set of str + (Class attribute.) Valid identifiers for use with + the encapsulated hierarchical timer. + """ + + hierarchical_timer_full_ids = { + "main", + "main.preprocessing", + "main.master_feasibility", + "main.master", + "main.dr_polishing", + "main.local_separation", + "main.global_separation", + } + + def __init__(self): + """Initialize self (see class docstring). + + """ + self._hierarchical_timer = HierarchicalTimer() + + def __str__(self): + """ + String representation of `self`. Currently + returns the string representation of `self.hierarchical_timer`. + + Returns + ------- + str + String representation. + """ + return self._hierarchical_timer.__str__() + + def _validate_full_identifier(self, full_identifier): + """ + Validate identifier for hierarchical timer. + + Raises + ------ + ValueError + If identifier not in `self.hierarchical_timer_full_ids`. + """ + if full_identifier not in self.hierarchical_timer_full_ids: + raise ValueError( + "PyROS timing data object does not support timing ID: " + f"{full_identifier}." + ) + + def start_timer(self, full_identifier): + """Start timer for `self.hierarchical_timer`. + + """ + self._validate_full_identifier(full_identifier) + identifier = full_identifier.split(".")[-1] + return self._hierarchical_timer.start(identifier=identifier) + + def stop_timer(self, full_identifier): + """Stop timer for `self.hierarchical_timer`. + + """ + self._validate_full_identifier(full_identifier) + identifier = full_identifier.split(".")[-1] + return self._hierarchical_timer.stop(identifier=identifier) + + def get_total_time(self, full_identifier): + """ + Get total time spent with identifier active. + """ + self._validate_full_identifier(full_identifier) + return self._hierarchical_timer.get_total_time( + identifier=full_identifier, + ) + + def get_main_elapsed_time(self): + """ + Get total time elapsed for main timer of + the HierarchicalTimer contained in self. + + Returns + ------- + float + Total elapsed time. + + Note + ---- + This method is meant for use while the main timer is active. + Otherwise, use ``self.get_total_time("main")``. + """ + # clean? + return self._hierarchical_timer.timers["main"].tic_toc.toc( + msg=None, + delta=False, + ) + + '''Code borrowed from gdpopt: time_code, get_main_elapsed_time, a_logger.''' @contextmanager def time_code(timing_data_obj, code_block_name, is_main_timer=False): - """Starts timer at entry, stores elapsed time at exit + """ + Starts timer at entry, stores elapsed time at exit. + + Parameters + ---------- + timing_data_obj : TimingData + Timing data object. + code_block_name : str + Name of code block being timed. If `is_main_timer=True`, the start time is stored in the timing_data_obj, allowing calculation of total elapsed time 'on the fly' (e.g. to enforce a time limit) using `get_main_elapsed_time(timing_data_obj)`. """ # initialize tic toc timer - timing_data_obj.tic_toc_timer = TicTocTimer() - timing_data_obj.tic_toc_timer.tic(msg=None) + timing_data_obj.start_timer(code_block_name) start_time = timeit.default_timer() if is_main_timer: timing_data_obj.main_timer_start_time = start_time yield - elapsed_time = timeit.default_timer() - start_time - prev_time = timing_data_obj.get(code_block_name, 0) - timing_data_obj[code_block_name] = prev_time + elapsed_time + timing_data_obj.stop_timer(code_block_name) def get_main_elapsed_time(timing_data_obj): """Returns the time since entering the main `time_code` context""" - current_time = timeit.default_timer() - try: - return current_time - timing_data_obj.main_timer_start_time - except AttributeError as e: - if 'main_timer_start_time' in str(e): - raise AttributeError( - "You need to be in a 'time_code' context to use `get_main_elapsed_time()`." - ) + return timing_data_obj.get_main_elapsed_time() def adjust_solver_time_settings(timing_data_obj, solver, config): From 89ede577c86624bc452a6438762ebff98f181b0f Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 8 Sep 2023 21:29:03 -0400 Subject: [PATCH 0135/1204] Fix debug statement typo --- pyomo/contrib/pyros/master_problem_methods.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index 3a85c7f70a2..2f7076ff336 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -546,7 +546,7 @@ def minimize_dr_vars(model_data, config): for mvar, pvar in zip(master_dr.values(), polish_dr.values()): mvar.set_value(value(pvar), skip_validation=True) - config.progress_logger.debug(f" Optimized DDR norm: {value(polishing_obj)}") + config.progress_logger.debug(f" Optimized DR norm: {value(polishing_obj)}") config.progress_logger.debug("Polished Master objective:") # print master solution From 3895c35bac9e4445327ed67dcc03442bdd30f2a7 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 8 Sep 2023 21:39:21 -0400 Subject: [PATCH 0136/1204] Delete `pyros.util.output_logger` --- pyomo/contrib/pyros/master_problem_methods.py | 6 - .../pyros/separation_problem_methods.py | 2 +- pyomo/contrib/pyros/util.py | 110 ------------------ 3 files changed, 1 insertion(+), 117 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index 2f7076ff336..1707bba288e 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -22,7 +22,6 @@ adjust_solver_time_settings, revert_solver_max_time_adjustment, get_main_elapsed_time, - output_logger, ) from pyomo.contrib.pyros.solve_data import MasterProblemData, MasterResult from pyomo.opt.results import check_optimal_termination @@ -807,7 +806,6 @@ def solver_call_master(model_data, config, solver, solve_data): master_soln.pyros_termination_condition = ( pyrosTerminationCondition.time_out ) - output_logger(config=config, time_out=True, elapsed=elapsed) if not try_backup: return master_soln @@ -883,10 +881,6 @@ def solve_master(model_data, config): None, pyrosTerminationCondition.time_out, ) - - # log time out message - output_logger(config=config, time_out=True, elapsed=elapsed) - return master_soln solver = ( diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index 5a3bf5469e5..43d8108924a 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -6,7 +6,7 @@ from pyomo.core.base import Var, Param from pyomo.common.collections import ComponentSet, ComponentMap from pyomo.common.dependencies import numpy as np -from pyomo.contrib.pyros.util import ObjectiveType, get_time_from_solver, output_logger +from pyomo.contrib.pyros.util import ObjectiveType, get_time_from_solver from pyomo.contrib.pyros.solve_data import ( DiscreteSeparationSolveCallResults, SeparationSolveCallResults, diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index 6c46ffaf1bb..7ab49a818a8 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -1681,113 +1681,3 @@ def log_header(log_func, with_rules=True, **log_func_kwargs): def log_header_rule(log_func, fillchar="-", **log_func_kwargs): """Log header rule.""" log_func(fillchar * IterationLogRecord._LINE_LENGTH, **log_func_kwargs) - - -def output_logger(config, **kwargs): - ''' - All user returned messages (termination conditions, runtime errors) are here - Includes when - "sub-solver %s returned status infeasible..." - :return: - ''' - - # === PREAMBLE + LICENSING - # Version printing - if "preamble" in kwargs: - if kwargs["preamble"]: - version = str(kwargs["version"]) - preamble = ( - "===========================================================================================\n" - "PyROS: Pyomo Robust Optimization Solver v.%s \n" - "Developed by: Natalie M. Isenberg (1), Jason A. F. Sherman (1), \n" - " John D. Siirola (2), Chrysanthos E. Gounaris (1) \n" - "(1) Carnegie Mellon University, Department of Chemical Engineering \n" - "(2) Sandia National Laboratories, Center for Computing Research\n\n" - "The developers gratefully acknowledge support from the U.S. Department of Energy's \n" - "Institute for the Design of Advanced Energy Systems (IDAES) \n" - "===========================================================================================" - % version - ) - print(preamble) - # === DISCLAIMER - if "disclaimer" in kwargs: - if kwargs["disclaimer"]: - print( - "======================================== DISCLAIMER =======================================\n" - "PyROS is still under development. \n" - "Please provide feedback and/or report any issues by opening a Pyomo ticket.\n" - "===========================================================================================\n" - ) - # === ALL LOGGER RETURN MESSAGES - if "bypass_global_separation" in kwargs: - if kwargs["bypass_global_separation"]: - config.progress_logger.info( - "NOTE: Option to bypass global separation was chosen. " - "Robust feasibility and optimality of the reported " - "solution are not guaranteed." - ) - if "robust_optimal" in kwargs: - if kwargs["robust_optimal"]: - config.progress_logger.info( - 'Robust optimal solution identified. Exiting PyROS.' - ) - - if "robust_feasible" in kwargs: - if kwargs["robust_feasible"]: - config.progress_logger.info( - 'Robust feasible solution identified. Exiting PyROS.' - ) - - if "robust_infeasible" in kwargs: - if kwargs["robust_infeasible"]: - config.progress_logger.info('Robust infeasible problem. Exiting PyROS.') - - if "time_out" in kwargs: - if kwargs["time_out"]: - config.progress_logger.info( - 'PyROS was unable to identify robust solution ' - 'before exceeding time limit of %s seconds. ' - 'Consider increasing the time limit via option time_limit.' - % config.time_limit - ) - - if "max_iter" in kwargs: - if kwargs["max_iter"]: - config.progress_logger.info( - 'PyROS was unable to identify robust solution ' - 'within %s iterations of the GRCS algorithm. ' - 'Consider increasing the iteration limit via option max_iter.' - % config.max_iter - ) - - if "master_error" in kwargs: - if kwargs["master_error"]: - status_dict = kwargs["status_dict"] - filename = kwargs["filename"] # solver name to solver termination condition - if kwargs["iteration"] == 0: - raise AttributeError( - "User-supplied solver(s) could not solve the deterministic model. " - "Returned termination conditions were: %s" - "Please ensure deterministic model is solvable by at least one of the supplied solvers. " - "Exiting PyROS." % pprint(status_dict, width=1) - ) - config.progress_logger.info( - "User-supplied solver(s) could not solve the master model at iteration %s.\n" - "Returned termination conditions were: %s\n" - "For debugging, this problem has been written to a GAMS file titled %s. Exiting PyROS." - % (kwargs["iteration"], pprint(status_dict), filename) - ) - if "separation_error" in kwargs: - if kwargs["separation_error"]: - status_dict = kwargs["status_dict"] - filename = kwargs["filename"] - iteration = kwargs["iteration"] - obj = kwargs["objective"] - config.progress_logger.info( - "User-supplied solver(s) could not solve the separation problem at iteration %s under separation objective %s.\n" - "Returned termination conditions were: %s\n" - "For debugging, this problem has been written to a GAMS file titled %s. Exiting PyROS." - % (iteration, obj, pprint(status_dict, width=1), filename) - ) - - return From f0e26ba8d6749716a0fce4f0cf001bc69a586b99 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 8 Sep 2023 21:55:21 -0400 Subject: [PATCH 0137/1204] Add more detail to master failure message --- pyomo/contrib/pyros/master_problem_methods.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index 1707bba288e..b18201888ad 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -686,6 +686,7 @@ def solver_call_master(model_data, config, solver, solve_data): higher_order_decision_rule_efficiency(config, model_data) + solve_mode = "global" if config.solve_master_globally else "local" config.progress_logger.debug("Solving master problem") timer = TicTocTimer() @@ -715,7 +716,7 @@ def solver_call_master(model_data, config, solver, solve_data): config.progress_logger.error( f"Optimizer {repr(opt)} ({idx + 1} of {len(solvers)}) " "encountered exception attempting to " - f"solve master problem in iteration {model_data.iteration}." + f"solve master problem in iteration {model_data.iteration}" ) raise else: @@ -836,16 +837,26 @@ def solver_call_master(model_data, config, solver, solve_data): f" Problem has been serialized to path {output_problem_path!r}." ) + deterministic_model_qual = ( + " (i.e., the deterministic model)" + if model_data.iteration == 0 else "" + ) + deterministic_msg = ( + " Please ensure your deterministic model " + f"is solvable by at least one of the subordinate {solve_mode} " + "optimizers provided." + ) if model_data.iteration == 0 else "" master_soln.pyros_termination_condition = pyrosTerminationCondition.subsolver_error - solve_mode = "global" if config.solve_master_globally else "local" config.progress_logger.warning( f"Could not successfully solve master problem of iteration " - f"{model_data.iteration} with any of the " + f"{model_data.iteration}{deterministic_model_qual} with any of the " f"provided subordinate {solve_mode} optimizers. " f"(Termination statuses: " - f"{[term_cond for term_cond in solver_term_cond_dict.values()]}.) " + f"{[term_cond for term_cond in solver_term_cond_dict.values()]}.)" + f"{deterministic_msg}" f"{serialization_msg}" ) + return master_soln From 27cac3da25dd553afc5bfbfb55099696565b675a Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 8 Sep 2023 22:00:15 -0400 Subject: [PATCH 0138/1204] Tweak master and separation serialization msgs --- pyomo/contrib/pyros/master_problem_methods.py | 3 ++- pyomo/contrib/pyros/separation_problem_methods.py | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index b18201888ad..28f7945205e 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -834,7 +834,8 @@ def solver_call_master(model_data, config, solver, solve_data): output_problem_path, io_options={'symbolic_solver_labels': True} ) serialization_msg = ( - f" Problem has been serialized to path {output_problem_path!r}." + " For debugging, problem has been serialized to the file " + f"{output_problem_path!r}." ) deterministic_model_qual = ( diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index 43d8108924a..9e3e0b72f07 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -1137,7 +1137,8 @@ def solver_call_separation( output_problem_path, io_options={'symbolic_solver_labels': True} ) serialization_msg = ( - f"Problem has been serialized to path {output_problem_path!r}." + " For debugging, problem has been serialized to the file " + f"{output_problem_path!r}." ) solve_call_results.message = ( "Could not successfully solve separation problem of iteration " @@ -1145,7 +1146,7 @@ def solver_call_separation( f"for performance constraint {con_name_repr} with any of the " f"provided subordinate {solve_mode} optimizers. " f"(Termination statuses: " - f"{[str(term_cond) for term_cond in solver_status_dict.values()]}.) " + f"{[str(term_cond) for term_cond in solver_status_dict.values()]}.)" f"{serialization_msg}" ) config.progress_logger.warning(solve_call_results.message) From b56c0343c3243da34d22bc08f8030abc531d12cd Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 8 Sep 2023 23:15:43 -0400 Subject: [PATCH 0139/1204] Tweak format of wall time and final objective --- pyomo/contrib/pyros/pyros.py | 4 ++-- pyomo/contrib/pyros/util.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 0e9dd6fa051..34850e33872 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -1047,10 +1047,10 @@ def solve( config.progress_logger.info("Termination stats:") config.progress_logger.info(f" {'Iterations':<22s}: {return_soln.iterations}") config.progress_logger.info( - f" {'Solve time (wall s)':<22s}: {return_soln.time:.4f}" + f" {'Solve time (wall s)':<22s}: {return_soln.time:.3f}" ) config.progress_logger.info( - f" {'Final objective value':<22s}: " f"{return_soln.final_objective_value}" + f" {'Final objective value':<22s}: " f"{return_soln.final_objective_value:.4e}" ) config.progress_logger.info( f" {'Termination condition':<22s}: " diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index 7ab49a818a8..578f1d59e20 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -1638,7 +1638,7 @@ def _format_record_attr(self, attr_name): "dr_var_shift": "f'{attr_val:.4e}'", "num_violated_cons": "f'{attr_val:d}'", "max_violation": "f'{attr_val:.4e}'", - "elapsed_time": "f'{attr_val:.2f}'", + "elapsed_time": "f'{attr_val:.3f}'", } # qualifier for DR polishing and separation columns From 91ed9eb112166fa6bb893c9d763b59a1dc8662f3 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 8 Sep 2023 23:42:57 -0400 Subject: [PATCH 0140/1204] Make `ROSolveResults` more structured --- pyomo/contrib/pyros/pyros.py | 16 ++----- pyomo/contrib/pyros/solve_data.py | 72 +++++++++++++++++++++++++++---- 2 files changed, 68 insertions(+), 20 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 34850e33872..5c76736ff37 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -1042,21 +1042,13 @@ def solve( return_soln.time = get_main_elapsed_time(model_data.timing) return_soln.iterations = 0 - config.progress_logger.info(return_soln.pyros_termination_condition.message) - config.progress_logger.info("-" * self._LOG_LINE_LENGTH) - config.progress_logger.info("Termination stats:") - config.progress_logger.info(f" {'Iterations':<22s}: {return_soln.iterations}") - config.progress_logger.info( - f" {'Solve time (wall s)':<22s}: {return_soln.time:.3f}" - ) + # log termination-related messages config.progress_logger.info( - f" {'Final objective value':<22s}: " f"{return_soln.final_objective_value:.4e}" - ) - config.progress_logger.info( - f" {'Termination condition':<22s}: " - f"{return_soln.pyros_termination_condition}" + return_soln.pyros_termination_condition.message ) config.progress_logger.info("-" * self._LOG_LINE_LENGTH) + config.progress_logger.info(return_soln) + config.progress_logger.info("-" * self._LOG_LINE_LENGTH) config.progress_logger.info( f"Timing breakdown:\n\n{model_data.timing}", ) diff --git a/pyomo/contrib/pyros/solve_data.py b/pyomo/contrib/pyros/solve_data.py index 511c042e48e..232a761ffc6 100644 --- a/pyomo/contrib/pyros/solve_data.py +++ b/pyomo/contrib/pyros/solve_data.py @@ -5,17 +5,73 @@ class ROSolveResults(object): """ - Container for solve-instance data returned to the user after solving with PyROS. + PyROS solver results object. - Attributes: - :pyros_termination_condition: termination condition of the PyROS algorithm - :config: the config block for this solve instance - :time: Total solver CPU time - :iterations: total iterations done by PyROS solver - :final_objective_value: objective function value at termination + Parameters + ---------- + config : ConfigDict, optional + User-specified solver settings. + iterations : int, optional + Number of iterations required. + time : float, optional + Total elapsed time (or wall time), in seconds. + final_objective_value : float, optional + Final objective function value to report. + pyros_termination_condition : pyrosTerminationCondition, optional + PyROS-specific termination condition. + + Attributes + ---------- + config : ConfigDict, optional + User-specified solver settings. + iterations : int, optional + Number of iterations required by PyROS. + time : float, optional + Total elapsed time (or wall time), in seconds. + final_objective_value : float, optional + Final objective function value to report. + pyros_termination_condition : pyros.util.pyrosTerminationStatus + Indicator of the manner of termination. """ - pass + def __init__( + self, + config=None, + iterations=None, + time=None, + final_objective_value=None, + pyros_termination_condition=None, + ): + """Initialize self (see class docstring). + + """ + self.config = config + self.iterations = iterations + self.time = time + self.final_objective_value = final_objective_value + self.pyros_termination_condition = pyros_termination_condition + + def __str__(self): + """ + Generate string representation of self. + Does not include any information about `self.config`. + """ + lines = ["Termination_stats:"] + attr_name_format_dict = { + "iterations": ("Iterations", "f'{val}'"), + "time": ("Solve time (wall s)", "f'{val:.3f}'"), + "final_objective_value": ("Final objective value", "f'{val:.4e}'"), + "pyros_termination_condition": ("Termination condition", "f'{val}'"), + } + attr_desc_pad_length = 1 + max( + len(desc) for desc, _ in attr_name_format_dict.values() + ) + for attr_name, (attr_desc, fmt_str) in attr_name_format_dict.items(): + val = getattr(self, attr_name) + val_str = eval(fmt_str) + lines.append(f" {attr_desc:<{attr_desc_pad_length}s} : {val_str}") + + return "\n".join(lines) class MasterProblemData(object): From a36865266c0d92ad3312d53ea0121cd40c52c6d1 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 8 Sep 2023 23:52:47 -0400 Subject: [PATCH 0141/1204] Log timing breakdown before results object --- pyomo/contrib/pyros/pyros.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 5c76736ff37..ba9cc677802 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -1047,12 +1047,12 @@ def solve( return_soln.pyros_termination_condition.message ) config.progress_logger.info("-" * self._LOG_LINE_LENGTH) - config.progress_logger.info(return_soln) - config.progress_logger.info("-" * self._LOG_LINE_LENGTH) config.progress_logger.info( f"Timing breakdown:\n\n{model_data.timing}", ) config.progress_logger.info("-" * self._LOG_LINE_LENGTH) + config.progress_logger.info(return_soln) + config.progress_logger.info("-" * self._LOG_LINE_LENGTH) config.progress_logger.info("All done. Exiting PyROS.") config.progress_logger.info("=" * self._LOG_LINE_LENGTH) From f456e294877cea15953edf45979016e5579ad09b Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 8 Sep 2023 23:54:15 -0400 Subject: [PATCH 0142/1204] Tweak solve results string rep --- pyomo/contrib/pyros/solve_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/solve_data.py b/pyomo/contrib/pyros/solve_data.py index 232a761ffc6..41ff15f7939 100644 --- a/pyomo/contrib/pyros/solve_data.py +++ b/pyomo/contrib/pyros/solve_data.py @@ -56,7 +56,7 @@ def __str__(self): Generate string representation of self. Does not include any information about `self.config`. """ - lines = ["Termination_stats:"] + lines = ["Termination stats:"] attr_name_format_dict = { "iterations": ("Iterations", "f'{val}'"), "time": ("Solve time (wall s)", "f'{val:.3f}'"), From 020e2aaf3f2e7ddb7e51fe82810bb44a24d1fdbd Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 9 Sep 2023 16:21:32 -0400 Subject: [PATCH 0143/1204] Make bypass global sep message warning level --- pyomo/contrib/pyros/pyros_algorithm_methods.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index 456089c2882..aa8cbbd0718 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -671,8 +671,8 @@ def ROSolver_iterative_solve(model_data, config): robustness_certified = separation_results.robustness_certified if robustness_certified: if config.bypass_global_separation: - config.progress_logger.info( - "NOTE: Option to bypass global separation was chosen. " + config.progress_logger.warning( + "Option to bypass global separation was chosen. " "Robust feasibility and optimality of the reported " "solution are not guaranteed." ) From d88df0666cc7bfdcee24339adc3b65feffa32411 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 9 Sep 2023 16:29:53 -0400 Subject: [PATCH 0144/1204] Add documentation of PyROS solver logs --- doc/OnlineDocs/contributed_packages/pyros.rst | 279 +++++++++++++++++- 1 file changed, 276 insertions(+), 3 deletions(-) diff --git a/doc/OnlineDocs/contributed_packages/pyros.rst b/doc/OnlineDocs/contributed_packages/pyros.rst index 4ef57fbf26c..f7b88020a82 100644 --- a/doc/OnlineDocs/contributed_packages/pyros.rst +++ b/doc/OnlineDocs/contributed_packages/pyros.rst @@ -604,6 +604,8 @@ optional keyword argument ``decision_rule_order`` to the PyROS In this example, we select affine decision rules by setting ``decision_rule_order=1``: +.. _example-two-stg: + .. doctest:: :skipif: not (baron.available() and baron.license_is_valid()) @@ -733,7 +735,278 @@ set size on the robust optimal objective function value and demonstrates the ease of implementing a price of robustness study for a given optimization problem under uncertainty. -.. note:: +PyROS Solver Log Output +------------------------------- + +The PyROS solver log output is controlled through the optional +``progress_logger`` argument, itself cast to +a standard Python logger (:py:class:`logging.Logger`) object +at the outset of a :meth:`~pyomo.contrib.pyros.PyROS.solve` call. +The level of detail of the solver log output +can be adjusted by adjusting the level of the +logger object; see :ref:`the following table `. +Note that by default, ``progress_logger`` is cast to a logger of level +:py:obj:`logging.INFO`. + +We refer the reader to the +:doc:`official Python logging library documentation ` +for customization of Python logger objects; +for a basic tutorial, see the :doc:`logging HOWTO `. + +.. _table-logging-levels: + +.. list-table:: PyROS solver log output at the various standard Python :py:mod:`logging` levels. + :widths: 10 50 + :header-rows: 1 + + * - Logging Level + - Output Messages + * - :py:obj:`logging.ERROR` + - * Information on the subproblem for which an exception was raised + by a subordinate solver + * Details about failure of the PyROS coefficient matching routine + * - :py:obj:`logging.WARNING` + - * Information about a subproblem not solved to an acceptable status + by the user-provided subordinate optimizers + * Invocation of a backup solver for a particular subproblem + * Caution about solution robustness guarantees in event that + user passes ``bypass_global_separation=True`` + * - :py:obj:`logging.INFO` + - * PyROS version, author, and disclaimer information + * Summary of user options + * Breakdown of model component statistics + * Iteration log table + * Termination details: message, timing breakdown, summary of statistics + * - :py:obj:`logging.DEBUG` + - * Termination outcomes (and/or summary of statistics) + for every subproblem + * Summary of separation loop outcomes: performance constraints + violated, uncertain parameter value added to master + +An example of an output log produced through the default PyROS +progress logger is shown in +:ref:`the snippet that follows `. +Observe that the log contains the following information: + + +* **Introductory information** (lines 1--18). + Includes the version number, author + information, (UTC) time at which the solver was invoked, + and, if available, information on the local Git branch and + commit hash. +* **Summary of solver options** (lines 19--38). +* **Preprocessing information** (lines 39--41). + Wall time required for preprocessing + the deterministic model and associated components, + i.e. standardizing model components and adding the decision rule + variables and equations. +* **Model component statistics** (lines 42--57). + Breakdown of model component statistics. + Includes components added by PyROS, such as the decision rule variables + and equations. +* **Iteration log table** (lines 58--68). + Summary information on the problem iterates and subproblem outcomes. + The constituent columns are defined in detail in + :ref:`the table following the snippet `. +* **Termination message** (lines 69--70). Very brief summary of the termination outcome. +* **Timing statistics** (lines 71--87). + Tabulated breakdown of the solver timing statistics, based on a + :class:`pyomo.common.timing.HierarchicalTimer` printout. + The identifiers are as follows: + + * ``main``: Total time elapsed by the solver. + * ``main.dr_polishing``: Total time elapsed by the subordinate solvers + on polishing of the decision rules. + * ``main.global_separation``: Total time elapsed by the subordinate solvers + on global separation subproblems. + * ``main.local_separation``: Total time elapsed by the subordinate solvers + on local separation subproblems. + * ``main.master``: Total time elapsed by the subordinate solvers on + the master problems. + * ``main.master_feasibility``: Total time elapsed by the subordinate solvers + on the master feasibility problems. + * ``main.preprocessing``: Total preprocessing time. + * ``main.other``: Total overhead time. + +* **Termination statistics** (lines 88--93). Summary of statistics related to the + iterate at which PyROS terminates. +* **Exit message** (lines 94--95). + +.. _solver-log-snippet: + +.. code-block:: text + :caption: PyROS solver output log for the :ref:`two-stage problem example `. + :linenos: + + ============================================================================== + PyROS: The Pyomo Robust Optimization Solver. + Version 1.2.7 | Git branch: unknown, commit hash: unknown + Invoked at UTC 2023-09-09T18:13:21.893626 + + Developed by: Natalie M. Isenberg (1), Jason A. F. Sherman (1), + John D. Siirola (2), Chrysanthos E. Gounaris (1) + (1) Carnegie Mellon University, Department of Chemical Engineering + (2) Sandia National Laboratories, Center for Computing Research + + The developers gratefully acknowledge support from the U.S. Department + of Energy's Institute for the Design of Advanced Energy Systems (IDAES). + ============================================================================== + ================================= DISCLAIMER ================================= + PyROS is still under development. + Please provide feedback and/or report any issues by creating a ticket at + https://github.com/Pyomo/pyomo/issues/new/choose + ============================================================================== + Solver options: + time_limit=None + keepfiles=False + tee=False + load_solution=True + objective_focus= + nominal_uncertain_param_vals=[0.13248000000000001, 4.97, 4.97, 1800] + decision_rule_order=1 + solve_master_globally=True + max_iter=-1 + robust_feasibility_tolerance=0.0001 + separation_priority_order={} + progress_logger= + backup_local_solvers=[] + backup_global_solvers=[] + subproblem_file_directory=None + bypass_local_separation=False + bypass_global_separation=False + p_robustness={} + ------------------------------------------------------------------------------ + Preprocessing... + Done preprocessing; required wall time of 0.23s. + ------------------------------------------------------------------------------ + Model statistics: + Number of variables : 62 + Epigraph variable : 1 + First-stage variables : 7 + Second-stage variables : 6 + State variables : 18 + Decision rule variables : 30 + Number of constraints : 81 + Equality constraints : 24 + Coefficient matching constraints : 0 + Decision rule equations : 6 + All other equality constraints : 18 + Inequality constraints : 57 + First-stage inequalities (incl. certain var bounds) : 10 + Performance constraints (incl. var bounds) : 47 + ------------------------------------------------------------------------------ + Itn Objective 1-Stg Shift DR Shift #CViol Max Viol Wall Time (s) + ------------------------------------------------------------------------------ + 0 3.5838e+07 - - 5 1.8832e+04 1.212 + 1 3.5838e+07 7.4506e-09 1.6105e+03 7 3.7766e+04 2.712 + 2 3.6116e+07 2.7803e+05 1.2918e+03 8 1.3466e+06 4.548 + 3 3.6285e+07 1.6957e+05 5.8386e+03 6 4.8734e+03 6.542 + 4 3.6285e+07 1.4901e-08 3.3097e+03 1 3.5036e+01 8.916 + 5 3.6285e+07 2.9786e-10 3.3597e+03 6 2.9103e+00 11.204 + 6 3.6285e+07 7.4506e-07 8.7228e+02 5 4.1726e-01 13.546 + 7 3.6285e+07 7.4506e-07 8.1995e+02 0 9.3279e-10 20.666 + ------------------------------------------------------------------------------ + Robust optimal solution identified. + ------------------------------------------------------------------------------ + Timing breakdown: + + Identifier ncalls cumtime percall % + ----------------------------------------------------------- + main 1 20.668 20.668 100.0 + ------------------------------------------------------ + dr_polishing 7 1.459 0.208 7.1 + global_separation 47 1.281 0.027 6.2 + local_separation 376 9.105 0.024 44.1 + master 8 5.356 0.669 25.9 + master_feasibility 7 0.456 0.065 2.2 + preprocessing 1 0.232 0.232 1.1 + other n/a 2.779 n/a 13.4 + ====================================================== + =========================================================== + + ------------------------------------------------------------------------------ + Termination stats: + Iterations : 8 + Solve time (wall s) : 20.668 + Final objective value : 3.6285e+07 + Termination condition : pyrosTerminationCondition.robust_optimal + ------------------------------------------------------------------------------ + All done. Exiting PyROS. + ============================================================================== + + +The iteration log table is designed to provide, in a concise manner, +important information about the progress of the iterative algorithm for +the problem of interest. +The constituent columns are defined in the +:ref:`table that follows `. + +.. _table-iteration-log-columns: + +.. list-table:: PyROS iteration log table columns. + :widths: 10 50 + :header-rows: 1 - Please provide feedback and/or report any problems by opening an issue on - the `Pyomo GitHub page `_. + * - Column Name + - Definition + * - Itn + - Iteration number. + * - Objective + - Master solution objective function value. + If the objective of the deterministic model provided + has a maximization sense, + then the negative of the objective function value is displayed. + Expect this value to trend upward as the iteration number + increases. + If the master problems are solved globally + (by passing ``solve_master_globally=True``), + then this value should be monotonically nondecreasing + as the iteration number is increased. + * - 1-Stg Shift + - Infinity norm of the difference between the first-stage + variable vectors of the master solutions of the current + and previous iterations. Expect this value to trend + downward as the iteration number increases. + A dash ("-") is produced in lieu of a value in the first iteration, + if there are no first-stage variables, or if the master problem + of the current iteration is not solved successfully. + * - DR Shift + - Infinity norm of the difference between the decision rule + variable vectors of the master solutions of the current + and previous iterations. + Expect this value to trend downward as the iteration number increases. + An asterisk ("*") is appended to this value if the decision rules are + not successfully polished. + A dash ("-") is produced in lieu of a value in the first iteration, + if there are no decision rule variables, or if the master problem + of the current iteration is not solved successfully. + * - #CViol + - Number of performance constraints found to be violated during + the separation step of the current iteration. + Unless a custom prioritization of the model's performance constraints + is specified (through the ``separation_priority_order`` argument), + expect this number to trend downward as the iteration number increases. + A "+" is appended if not all of the separation problems + were solved, either due to custom prioritization, a time out, or + an issue encountered by the subordinate optimizers. + A dash ("-") is produced in lieu of a value if the separation + routine is not invoked during the current iteration. + * - Max Viol + - Maximum scaled performance constraint violation. + Expect this value to trend downward as the iteration number increases. + If this value is smaller than the robust feasibility tolerance, + then the master solution of the current iteration is certified to be + robust feasible or robust optimal (up to the robust feasibility tolerance), + and PyROS should terminate at the end of the iteration. + A dash ("-") is produced in lieu of a value if the separation + routine is not invoked during the current iteration, or if there are + no performance constraints. + * - Wall time (s) + - Total time elapsed by the solver, in seconds, up to the end of the + current iteration. + + +Feedback and Reporting Issues +------------------------------- +Please provide feedback and/or report any problems by opening an issue on +the `Pyomo GitHub page `_. From 7e2b6d98840941e1cfd6139b4cc1b70dd029ea33 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 9 Sep 2023 16:40:40 -0400 Subject: [PATCH 0145/1204] Update `progress_logger` argument documentation --- pyomo/contrib/pyros/pyros.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index ba9cc677802..a7ac59118cc 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -519,8 +519,13 @@ def pyros_config(): doc=( """ Logger (or name thereof) used for reporting PyROS solver - progress. If a `str` is specified, then - ``logging.getLogger(progress_logger)`` is used. + progress. If a `str` is specified, then the string + is cast to a ``logging.Logger`` object + with ``name=progress_logger`` and ``level=logging.INFO``. + All handlers are cleared, + and a single ``StreamHandler`` (with default settings) + is added. + In the default case, we also set ``propagate=False``. """ ), is_optional=True, From 5fa861da9e59bd0ea283c1df5a5828c1464a4601 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 9 Sep 2023 17:12:51 -0400 Subject: [PATCH 0146/1204] Add global sep indicator to solver logs --- doc/OnlineDocs/contributed_packages/pyros.rst | 25 +++++++++---------- .../contrib/pyros/pyros_algorithm_methods.py | 3 +++ pyomo/contrib/pyros/util.py | 8 ++++-- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/doc/OnlineDocs/contributed_packages/pyros.rst b/doc/OnlineDocs/contributed_packages/pyros.rst index f7b88020a82..73dbc29eaac 100644 --- a/doc/OnlineDocs/contributed_packages/pyros.rst +++ b/doc/OnlineDocs/contributed_packages/pyros.rst @@ -832,6 +832,7 @@ Observe that the log contains the following information: iterate at which PyROS terminates. * **Exit message** (lines 94--95). + .. _solver-log-snippet: .. code-block:: text @@ -895,16 +896,16 @@ Observe that the log contains the following information: First-stage inequalities (incl. certain var bounds) : 10 Performance constraints (incl. var bounds) : 47 ------------------------------------------------------------------------------ - Itn Objective 1-Stg Shift DR Shift #CViol Max Viol Wall Time (s) + Itn Objective 1-Stg Shift DR Shift #CViol Max Viol Wall Time (s) ------------------------------------------------------------------------------ - 0 3.5838e+07 - - 5 1.8832e+04 1.212 - 1 3.5838e+07 7.4506e-09 1.6105e+03 7 3.7766e+04 2.712 - 2 3.6116e+07 2.7803e+05 1.2918e+03 8 1.3466e+06 4.548 - 3 3.6285e+07 1.6957e+05 5.8386e+03 6 4.8734e+03 6.542 - 4 3.6285e+07 1.4901e-08 3.3097e+03 1 3.5036e+01 8.916 - 5 3.6285e+07 2.9786e-10 3.3597e+03 6 2.9103e+00 11.204 - 6 3.6285e+07 7.4506e-07 8.7228e+02 5 4.1726e-01 13.546 - 7 3.6285e+07 7.4506e-07 8.1995e+02 0 9.3279e-10 20.666 + 0 3.5838e+07 - - 5 1.8832e+04 1.212 + 1 3.5838e+07 7.4506e-09 1.6105e+03 7 3.7766e+04 2.712 + 2 3.6116e+07 2.7803e+05 1.2918e+03 8 1.3466e+06 4.548 + 3 3.6285e+07 1.6957e+05 5.8386e+03 6 4.8734e+03 6.542 + 4 3.6285e+07 1.4901e-08 3.3097e+03 1 3.5036e+01 8.916 + 5 3.6285e+07 2.9786e-10 3.3597e+03 6 2.9103e+00 11.204 + 6 3.6285e+07 7.4506e-07 8.7228e+02 5 4.1726e-01 13.546 + 7 3.6285e+07 7.4506e-07 8.1995e+02 0 9.3279e-10g 20.666 ------------------------------------------------------------------------------ Robust optimal solution identified. ------------------------------------------------------------------------------ @@ -994,10 +995,8 @@ The constituent columns are defined in the * - Max Viol - Maximum scaled performance constraint violation. Expect this value to trend downward as the iteration number increases. - If this value is smaller than the robust feasibility tolerance, - then the master solution of the current iteration is certified to be - robust feasible or robust optimal (up to the robust feasibility tolerance), - and PyROS should terminate at the end of the iteration. + A 'g' is appended to the value if the separation problems were solved + globally during the current iteration. A dash ("-") is produced in lieu of a value if the separation routine is not invoked during the current iteration, or if there are no performance constraints. diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index aa8cbbd0718..00e6af29053 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -439,6 +439,7 @@ def ROSolver_iterative_solve(model_data, config): max_violation=None, dr_polishing_failed=None, all_sep_problems_solved=None, + global_separation=None, elapsed_time=get_main_elapsed_time(model_data.timing), ) log_record.log(config.progress_logger.info) @@ -543,6 +544,7 @@ def ROSolver_iterative_solve(model_data, config): max_violation=None, dr_polishing_failed=not polishing_successful, all_sep_problems_solved=None, + global_separation=None, elapsed_time=elapsed, ) update_grcs_solve_data( @@ -632,6 +634,7 @@ def ROSolver_iterative_solve(model_data, config): max_violation=max_sep_con_violation, dr_polishing_failed=not polishing_successful, all_sep_problems_solved=all_sep_problems_solved, + global_separation=separation_results.solved_globally, elapsed_time=get_main_elapsed_time(model_data.timing), ) diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index 578f1d59e20..ff0ba89c264 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -1575,8 +1575,8 @@ class IterationLogRecord: "first_stage_var_shift": 13, "dr_var_shift": 13, "num_violated_cons": 8, - "max_violation": 12, - "elapsed_time": 14, + "max_violation": 13, + "elapsed_time": 13, } _ATTR_HEADER_NAMES = { "iteration": "Itn", @@ -1597,6 +1597,7 @@ def __init__( dr_polishing_failed, num_violated_cons, all_sep_problems_solved, + global_separation, max_violation, elapsed_time, ): @@ -1608,6 +1609,7 @@ def __init__( self.dr_polishing_failed = dr_polishing_failed self.num_violated_cons = num_violated_cons self.all_sep_problems_solved = all_sep_problems_solved + self.global_separation = global_separation self.max_violation = max_violation self.elapsed_time = elapsed_time @@ -1646,6 +1648,8 @@ def _format_record_attr(self, attr_name): qual = "*" if self.dr_polishing_failed else "" elif attr_name == "num_violated_cons": qual = "+" if not self.all_sep_problems_solved else "" + elif attr_name == "max_violation": + qual = "g" if self.global_separation else "" else: qual = "" From 17a50fffc3ec697965f250ba1ff2fa036ebd682f Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 9 Sep 2023 18:00:27 -0400 Subject: [PATCH 0147/1204] Apply black --- pyomo/contrib/pyros/master_problem_methods.py | 15 +++++++++------ pyomo/contrib/pyros/pyros.py | 12 +++--------- pyomo/contrib/pyros/solve_data.py | 18 ++++++++---------- pyomo/contrib/pyros/util.py | 19 +++++-------------- 4 files changed, 25 insertions(+), 39 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index 28f7945205e..2db9410ca95 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -839,14 +839,17 @@ def solver_call_master(model_data, config, solver, solve_data): ) deterministic_model_qual = ( - " (i.e., the deterministic model)" - if model_data.iteration == 0 else "" + " (i.e., the deterministic model)" if model_data.iteration == 0 else "" ) deterministic_msg = ( - " Please ensure your deterministic model " - f"is solvable by at least one of the subordinate {solve_mode} " - "optimizers provided." - ) if model_data.iteration == 0 else "" + ( + " Please ensure your deterministic model " + f"is solvable by at least one of the subordinate {solve_mode} " + "optimizers provided." + ) + if model_data.iteration == 0 + else "" + ) master_soln.pyros_termination_condition = pyrosTerminationCondition.subsolver_error config.progress_logger.warning( f"Could not successfully solve master problem of iteration " diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index a7ac59118cc..d3bd37406ff 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -984,9 +984,7 @@ def solve( wm_util.ssv_bounds.append(c) model_data.timing.stop_timer("main.preprocessing") - preprocessing_time = model_data.timing.get_total_time( - "main.preprocessing", - ) + preprocessing_time = model_data.timing.get_total_time("main.preprocessing") config.progress_logger.info( f"Done preprocessing; required wall time of " f"{preprocessing_time:.2f}s." @@ -1048,13 +1046,9 @@ def solve( return_soln.iterations = 0 # log termination-related messages - config.progress_logger.info( - return_soln.pyros_termination_condition.message - ) + config.progress_logger.info(return_soln.pyros_termination_condition.message) config.progress_logger.info("-" * self._LOG_LINE_LENGTH) - config.progress_logger.info( - f"Timing breakdown:\n\n{model_data.timing}", - ) + config.progress_logger.info(f"Timing breakdown:\n\n{model_data.timing}") config.progress_logger.info("-" * self._LOG_LINE_LENGTH) config.progress_logger.info(return_soln) config.progress_logger.info("-" * self._LOG_LINE_LENGTH) diff --git a/pyomo/contrib/pyros/solve_data.py b/pyomo/contrib/pyros/solve_data.py index 41ff15f7939..c19b8000dca 100644 --- a/pyomo/contrib/pyros/solve_data.py +++ b/pyomo/contrib/pyros/solve_data.py @@ -35,16 +35,14 @@ class ROSolveResults(object): """ def __init__( - self, - config=None, - iterations=None, - time=None, - final_objective_value=None, - pyros_termination_condition=None, - ): - """Initialize self (see class docstring). - - """ + self, + config=None, + iterations=None, + time=None, + final_objective_value=None, + pyros_termination_condition=None, + ): + """Initialize self (see class docstring).""" self.config = config self.iterations = iterations self.time = time diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index ff0ba89c264..e20e198f833 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -80,9 +80,7 @@ class TimingData: } def __init__(self): - """Initialize self (see class docstring). - - """ + """Initialize self (see class docstring).""" self._hierarchical_timer = HierarchicalTimer() def __str__(self): @@ -113,17 +111,13 @@ def _validate_full_identifier(self, full_identifier): ) def start_timer(self, full_identifier): - """Start timer for `self.hierarchical_timer`. - - """ + """Start timer for `self.hierarchical_timer`.""" self._validate_full_identifier(full_identifier) identifier = full_identifier.split(".")[-1] return self._hierarchical_timer.start(identifier=identifier) def stop_timer(self, full_identifier): - """Stop timer for `self.hierarchical_timer`. - - """ + """Stop timer for `self.hierarchical_timer`.""" self._validate_full_identifier(full_identifier) identifier = full_identifier.split(".")[-1] return self._hierarchical_timer.stop(identifier=identifier) @@ -133,9 +127,7 @@ def get_total_time(self, full_identifier): Get total time spent with identifier active. """ self._validate_full_identifier(full_identifier) - return self._hierarchical_timer.get_total_time( - identifier=full_identifier, - ) + return self._hierarchical_timer.get_total_time(identifier=full_identifier) def get_main_elapsed_time(self): """ @@ -154,8 +146,7 @@ def get_main_elapsed_time(self): """ # clean? return self._hierarchical_timer.timers["main"].tic_toc.toc( - msg=None, - delta=False, + msg=None, delta=False ) From bcf99ef2b9006a5c91aaf7c2208ff326c179265e Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 9 Sep 2023 18:14:03 -0400 Subject: [PATCH 0148/1204] Fix 3.7 test syntax error --- pyomo/contrib/pyros/tests/test_grcs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 50be1e218dd..997ca24920f 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -3907,7 +3907,7 @@ def test_minimize_dr_norm(self): TerminationCondition.optimal, msg="Minimize dr norm did not solve to optimality.", ) - self.assertTrue(success, msg=f"DR polishing {success=}, expected True.") + self.assertTrue(success, msg=f"DR polishing success {success}, expected True.") @unittest.skipUnless( baron_license_is_valid, "Global NLP solver is not available and licensed." From a6e1b7b000b569bc9a30ff05c02af7c643e97a34 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 9 Sep 2023 18:23:02 -0400 Subject: [PATCH 0149/1204] Apply black to test module --- pyomo/contrib/pyros/tests/test_grcs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 997ca24920f..0b67251e7bd 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -3907,7 +3907,9 @@ def test_minimize_dr_norm(self): TerminationCondition.optimal, msg="Minimize dr norm did not solve to optimality.", ) - self.assertTrue(success, msg=f"DR polishing success {success}, expected True.") + self.assertTrue( + success, msg=f"DR polishing success {success}, expected True." + ) @unittest.skipUnless( baron_license_is_valid, "Global NLP solver is not available and licensed." From 69c04e3f6bd9a2547d7751bafbdcddcd040bb2e4 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 9 Sep 2023 18:30:49 -0400 Subject: [PATCH 0150/1204] Modify casting of `progress_logger` argument --- pyomo/contrib/pyros/pyros.py | 15 ++++++------ pyomo/contrib/pyros/util.py | 47 +++++++++++++++++++++++++----------- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index d3bd37406ff..6343780f86f 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -519,13 +519,14 @@ def pyros_config(): doc=( """ Logger (or name thereof) used for reporting PyROS solver - progress. If a `str` is specified, then the string - is cast to a ``logging.Logger`` object - with ``name=progress_logger`` and ``level=logging.INFO``. - All handlers are cleared, - and a single ``StreamHandler`` (with default settings) - is added. - In the default case, we also set ``propagate=False``. + progress. If a `str` is specified, then ``progress_logger`` + is cast to ``logging.getLogger(progress_logger)``. + In the default case, we also configure the logger + as follows: + set ``propagate=False``, + set ``level=logging.INFO``, + clear all handlers, + and add a single ``StreamHandler`` with default options. """ ), is_optional=True, diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index e20e198f833..d5c8891159e 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -323,6 +323,36 @@ def revert_solver_max_time_adjustment( del solver.options[options_key] +def setup_default_pyros_logger(): + """ + Setup default PyROS logger. + + Returns + ------- + logging.Logger + Default PyROS logger. Settings: + + - ``name=DEFAULT_LOGGER_NAME`` + - ``propagate=False`` + - All handlers cleared, and a single ``StreamHandler`` + (with default settings) added. + """ + logger = logging.getLogger(DEFAULT_LOGGER_NAME) + + # avoid possible influence of Pyomo logger customizations + logger.propagate = False + + # clear handlers, want just a single stream handler + logger.handlers.clear() + ch = logging.StreamHandler() + logger.addHandler(ch) + + # info level logger + logger.setLevel(logging.INFO) + + return logger + + def a_logger(str_or_logger): """ Standardize a string or logger object to a logger object. @@ -344,21 +374,10 @@ def a_logger(str_or_logger): if isinstance(str_or_logger, logging.Logger): return str_or_logger else: - logger = logging.getLogger(str_or_logger) - if str_or_logger == DEFAULT_LOGGER_NAME: - # turn off propagate to remove possible influence - # of overarching Pyomo logger settings - logger.propagate = False - - # clear handlers, want just a single stream handler - logger.handlers.clear() - ch = logging.StreamHandler() - logger.addHandler(ch) - - # info level logger - logger.setLevel(logging.INFO) - + logger = setup_default_pyros_logger() + else: + logger = logging.getLogger(str_or_logger) return logger From 41ac3721180c8e229d160cdc4aa0304cc0da7b98 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 9 Sep 2023 22:55:12 -0400 Subject: [PATCH 0151/1204] Tweak preprocessing time format --- pyomo/contrib/pyros/pyros.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 6343780f86f..e8decbea451 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -988,7 +988,7 @@ def solve( preprocessing_time = model_data.timing.get_total_time("main.preprocessing") config.progress_logger.info( f"Done preprocessing; required wall time of " - f"{preprocessing_time:.2f}s." + f"{preprocessing_time:.3f}s." ) # === Solve and load solution into model From 83c9e4cb7c19900ccb3fc07a0e421f12c63bf41d Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 9 Sep 2023 23:10:48 -0400 Subject: [PATCH 0152/1204] Update logging doc time format --- doc/OnlineDocs/contributed_packages/pyros.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/OnlineDocs/contributed_packages/pyros.rst b/doc/OnlineDocs/contributed_packages/pyros.rst index 73dbc29eaac..400e5ecbea0 100644 --- a/doc/OnlineDocs/contributed_packages/pyros.rst +++ b/doc/OnlineDocs/contributed_packages/pyros.rst @@ -878,7 +878,7 @@ Observe that the log contains the following information: p_robustness={} ------------------------------------------------------------------------------ Preprocessing... - Done preprocessing; required wall time of 0.23s. + Done preprocessing; required wall time of 0.232s. ------------------------------------------------------------------------------ Model statistics: Number of variables : 62 From 9c1e99fd25e87b718de96fb1a43ca4d020cbc1ef Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 10 Sep 2023 17:57:27 -0400 Subject: [PATCH 0153/1204] Log number of uncertain parameters --- doc/OnlineDocs/contributed_packages/pyros.rst | 13 +++++++------ pyomo/contrib/pyros/pyros_algorithm_methods.py | 5 +++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/doc/OnlineDocs/contributed_packages/pyros.rst b/doc/OnlineDocs/contributed_packages/pyros.rst index 400e5ecbea0..e4128c77b94 100644 --- a/doc/OnlineDocs/contributed_packages/pyros.rst +++ b/doc/OnlineDocs/contributed_packages/pyros.rst @@ -800,16 +800,16 @@ Observe that the log contains the following information: the deterministic model and associated components, i.e. standardizing model components and adding the decision rule variables and equations. -* **Model component statistics** (lines 42--57). +* **Model component statistics** (lines 42--58). Breakdown of model component statistics. Includes components added by PyROS, such as the decision rule variables and equations. -* **Iteration log table** (lines 58--68). +* **Iteration log table** (lines 59--69). Summary information on the problem iterates and subproblem outcomes. The constituent columns are defined in detail in :ref:`the table following the snippet `. -* **Termination message** (lines 69--70). Very brief summary of the termination outcome. -* **Timing statistics** (lines 71--87). +* **Termination message** (lines 70--71). Very brief summary of the termination outcome. +* **Timing statistics** (lines 72--88). Tabulated breakdown of the solver timing statistics, based on a :class:`pyomo.common.timing.HierarchicalTimer` printout. The identifiers are as follows: @@ -828,9 +828,9 @@ Observe that the log contains the following information: * ``main.preprocessing``: Total preprocessing time. * ``main.other``: Total overhead time. -* **Termination statistics** (lines 88--93). Summary of statistics related to the +* **Termination statistics** (lines 89--94). Summary of statistics related to the iterate at which PyROS terminates. -* **Exit message** (lines 94--95). +* **Exit message** (lines 95--96). .. _solver-log-snippet: @@ -887,6 +887,7 @@ Observe that the log contains the following information: Second-stage variables : 6 State variables : 18 Decision rule variables : 30 + Number of uncertain parameters : 4 Number of constraints : 81 Equality constraints : 24 Coefficient matching constraints : 0 diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index 00e6af29053..3458f2caa3d 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -101,6 +101,8 @@ def evaluate_and_log_component_stats(model_data, separation_model, config): num_dr_vars = len(dr_var_set) num_vars = int(has_epigraph_con) + num_fsv + num_ssv + num_sv + num_dr_vars + num_uncertain_params = len(model_data.working_model.util.uncertain_params) + eq_cons = [ con for con in model_data.working_model.component_data_objects( @@ -164,6 +166,9 @@ def evaluate_and_log_component_stats(model_data, separation_model, config): config.progress_logger.info(f"{' Second-stage variables'} : {num_ssv}") config.progress_logger.info(f"{' State variables'} : {num_sv}") config.progress_logger.info(f"{' Decision rule variables'} : {num_dr_vars}") + config.progress_logger.info( + f"{' Number of uncertain parameters'} : {num_uncertain_params}" + ) config.progress_logger.info( f"{' Number of constraints'} : " f"{num_ineq_cons + num_eq_cons}" ) From ff9fb5897f1c70357702ec6ffb09707b3b6af7e0 Mon Sep 17 00:00:00 2001 From: gomesraphael7d Date: Sun, 10 Sep 2023 19:41:31 -0300 Subject: [PATCH 0154/1204] Creating tests to cover the adjustments --- .../integer_variable_declaration.mps.baseline | 27 +++++++++ ..._integer_variable_declaration.mps.baseline | 39 ++++++++++++ pyomo/repn/tests/mps/test_mps.py | 59 ++++++++++++++++++- 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 pyomo/repn/tests/mps/integer_variable_declaration.mps.baseline create mode 100644 pyomo/repn/tests/mps/knapsack_problem_integer_variable_declaration.mps.baseline diff --git a/pyomo/repn/tests/mps/integer_variable_declaration.mps.baseline b/pyomo/repn/tests/mps/integer_variable_declaration.mps.baseline new file mode 100644 index 00000000000..91901aad77e --- /dev/null +++ b/pyomo/repn/tests/mps/integer_variable_declaration.mps.baseline @@ -0,0 +1,27 @@ +* Source: Pyomo MPS Writer +* Format: Free MPS +* +NAME Example-mix-integer-linear-problem +OBJSENSE + MIN +ROWS + N obj + G c_l_const1_ + L c_u_const2_ +COLUMNS + INT 'MARKER' 'INTORG' + x1 obj 3 + x1 c_l_const1_ 4 + x1 c_u_const2_ 1 + INTEND 'MARKER' 'INTEND' + x2 obj 2 + x2 c_l_const1_ 3 + x2 c_u_const2_ 2 +RHS + RHS c_l_const1_ 10 + RHS c_u_const2_ 7 +BOUNDS + LI BOUND x1 0 + UI BOUND x1 10E20 + LO BOUND x2 0 +ENDATA diff --git a/pyomo/repn/tests/mps/knapsack_problem_integer_variable_declaration.mps.baseline b/pyomo/repn/tests/mps/knapsack_problem_integer_variable_declaration.mps.baseline new file mode 100644 index 00000000000..cfaac8e7595 --- /dev/null +++ b/pyomo/repn/tests/mps/knapsack_problem_integer_variable_declaration.mps.baseline @@ -0,0 +1,39 @@ +* Source: Pyomo MPS Writer +* Format: Free MPS +* +NAME knapsack problem +OBJSENSE + MIN +ROWS + N obj + G c_l_const1_ +COLUMNS + INT 'MARKER' 'INTORG' + x(_1_) obj 3 + x(_1_) c_l_const1_ 30 + x(_2_) obj 2 + x(_2_) c_l_const1_ 24 + x(_3_) obj 2 + x(_3_) c_l_const1_ 11 + x(_4_) obj 4 + x(_4_) c_l_const1_ 35 + x(_5_) obj 5 + x(_5_) c_l_const1_ 29 + x(_6_) obj 4 + x(_6_) c_l_const1_ 8 + x(_7_) obj 3 + x(_7_) c_l_const1_ 31 + x(_8_) obj 1 + x(_8_) c_l_const1_ 18 +RHS + RHS c_l_const1_ 60 +BOUNDS + BV BOUND x(_1_) + BV BOUND x(_2_) + BV BOUND x(_3_) + BV BOUND x(_4_) + BV BOUND x(_5_) + BV BOUND x(_6_) + BV BOUND x(_7_) + BV BOUND x(_8_) +ENDATA diff --git a/pyomo/repn/tests/mps/test_mps.py b/pyomo/repn/tests/mps/test_mps.py index 44f3d93b75e..e2e5f7aa6ef 100644 --- a/pyomo/repn/tests/mps/test_mps.py +++ b/pyomo/repn/tests/mps/test_mps.py @@ -18,7 +18,17 @@ from filecmp import cmp import pyomo.common.unittest as unittest -from pyomo.environ import ConcreteModel, Var, Objective, Constraint, ComponentMap +from pyomo.environ import ( + ConcreteModel, + Var, + Objective, + Constraint, + ComponentMap, + minimize, + Binary, + NonNegativeReals, + NonNegativeIntegers, +) thisdir = os.path.dirname(os.path.abspath(__file__)) @@ -41,6 +51,7 @@ def _check_baseline(self, model, **kwds): io_options = {"symbolic_solver_labels": True} io_options.update(kwds) model.write(test_fname, format="mps", io_options=io_options) + self.assertTrue( cmp(test_fname, baseline_fname), msg="Files %s and %s differ" % (test_fname, baseline_fname), @@ -185,6 +196,52 @@ def test_row_ordering(self): row_order[model.con4[2]] = -1 self._check_baseline(model, row_order=row_order) + def test_knapsack_problem_binary_variable_declaration(self): + elements_size = [30, 24, 11, 35, 29, 8, 31, 18] + elements_weight = [3, 2, 2, 4, 5, 4, 3, 1] + capacity = 60 + + model = ConcreteModel("knapsack problem") + var_names = [f"{i + 1}" for i in range(len(elements_size))] + + model.x = Var(var_names, within=Binary) + + model.obj = Objective( + expr=sum( + model.x[var_names[i]] * elements_weight[i] + for i in range(len(elements_size)) + ), + sense=minimize, + name="obj", + ) + + model.const1 = Constraint( + expr=sum( + model.x[var_names[i]] * elements_size[i] + for i in range(len(elements_size)) + ) + >= capacity, + name="const", + ) + + self._check_baseline(model) + + def test_integer_variable_declaration(self): + model = ConcreteModel("Example-mix-integer-linear-problem") + + # Define the decision variables + model.x1 = Var(within=NonNegativeIntegers) # Integer variable + model.x2 = Var(within=NonNegativeReals) # Continuous variable + + # Define the objective function + model.obj = Objective(expr=3 * model.x1 + 2 * model.x2, sense=minimize) + + # Define the constraints + model.const1 = Constraint(expr=4 * model.x1 + 3 * model.x2 >= 10) + model.const2 = Constraint(expr=model.x1 + 2 * model.x2 <= 7) + + self._check_baseline(model) + if __name__ == "__main__": unittest.main() From df8931e6546158de13cf1708a8aafad6b5801dd7 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 10 Sep 2023 19:28:30 -0400 Subject: [PATCH 0155/1204] Tweak iteration log column definitions --- doc/OnlineDocs/contributed_packages/pyros.rst | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/doc/OnlineDocs/contributed_packages/pyros.rst b/doc/OnlineDocs/contributed_packages/pyros.rst index e4128c77b94..c00ce27f38b 100644 --- a/doc/OnlineDocs/contributed_packages/pyros.rst +++ b/doc/OnlineDocs/contributed_packages/pyros.rst @@ -962,16 +962,20 @@ The constituent columns are defined in the increases. If the master problems are solved globally (by passing ``solve_master_globally=True``), - then this value should be monotonically nondecreasing + then after the iteration number exceeds the number of uncertain parameters, + this value should be monotonically nondecreasing as the iteration number is increased. + A dash ("-") is produced in lieu of a value if the master + problem of the current iteration is not solved successfully. * - 1-Stg Shift - Infinity norm of the difference between the first-stage variable vectors of the master solutions of the current and previous iterations. Expect this value to trend downward as the iteration number increases. - A dash ("-") is produced in lieu of a value in the first iteration, - if there are no first-stage variables, or if the master problem - of the current iteration is not solved successfully. + A dash ("-") is produced in lieu of a value + if the current iteration number is 0, + there are no first-stage variables, + or the master problem of the current iteration is not solved successfully. * - DR Shift - Infinity norm of the difference between the decision rule variable vectors of the master solutions of the current @@ -979,9 +983,10 @@ The constituent columns are defined in the Expect this value to trend downward as the iteration number increases. An asterisk ("*") is appended to this value if the decision rules are not successfully polished. - A dash ("-") is produced in lieu of a value in the first iteration, - if there are no decision rule variables, or if the master problem - of the current iteration is not solved successfully. + A dash ("-") is produced in lieu of a value + if the current iteration number is 0, + there are no decision rule variables, + or the master problem of the current iteration is not solved successfully. * - #CViol - Number of performance constraints found to be violated during the separation step of the current iteration. @@ -989,8 +994,8 @@ The constituent columns are defined in the is specified (through the ``separation_priority_order`` argument), expect this number to trend downward as the iteration number increases. A "+" is appended if not all of the separation problems - were solved, either due to custom prioritization, a time out, or - an issue encountered by the subordinate optimizers. + were solved successfully, either due to custom prioritization, a time out, + or an issue encountered by the subordinate optimizers. A dash ("-") is produced in lieu of a value if the separation routine is not invoked during the current iteration. * - Max Viol From b380486fec1143437880819409608ad684744f6b Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 10 Sep 2023 19:47:52 -0400 Subject: [PATCH 0156/1204] Update `IterationLogRecord` docs and attributes --- .../contrib/pyros/pyros_algorithm_methods.py | 15 +++-- pyomo/contrib/pyros/util.py | 55 +++++++++++++++++-- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index 3458f2caa3d..a5747017016 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -442,7 +442,7 @@ def ROSolver_iterative_solve(model_data, config): dr_var_shift=None, num_violated_cons=None, max_violation=None, - dr_polishing_failed=None, + dr_polishing_success=None, all_sep_problems_solved=None, global_separation=None, elapsed_time=get_main_elapsed_time(model_data.timing), @@ -547,7 +547,7 @@ def ROSolver_iterative_solve(model_data, config): dr_var_shift=dr_var_shift, num_violated_cons=None, max_violation=None, - dr_polishing_failed=not polishing_successful, + dr_polishing_success=polishing_successful, all_sep_problems_solved=None, global_separation=None, elapsed_time=elapsed, @@ -626,8 +626,13 @@ def ROSolver_iterative_solve(model_data, config): else: max_sep_con_violation = None num_violated_cons = len(separation_results.violated_performance_constraints) - all_sep_problems_solved = len(scaled_violations) == len( - separation_model.util.performance_constraints + + all_sep_problems_solved = ( + len(scaled_violations) == len( + separation_model.util.performance_constraints + ) + and not separation_results.subsolver_error + and not separation_results.time_out ) iter_log_record = IterationLogRecord( @@ -637,7 +642,7 @@ def ROSolver_iterative_solve(model_data, config): dr_var_shift=dr_var_shift, num_violated_cons=num_violated_cons, max_violation=max_sep_con_violation, - dr_polishing_failed=not polishing_successful, + dr_polishing_success=polishing_successful, all_sep_problems_solved=all_sep_problems_solved, global_separation=separation_results.solved_globally, elapsed_time=get_main_elapsed_time(model_data.timing), diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index d5c8891159e..fd50df6a92d 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -1556,6 +1556,40 @@ class IterationLogRecord: """ PyROS solver iteration log record. + Parameters + ---------- + iteration : int or None, optional + Iteration number. + objective : int or None, optional + Master problem objective value. + Note: if the sense of the original model is maximization, + then this is the negative of the objective value + of the original model. + first_stage_var_shift : float or None, optional + Infinity norm of the difference between first-stage + variable vectors for the current and previous iterations. + dr_var_shift : float or None, optional + Infinity norm of the difference between decision rule + variable vectors for the current and previous iterations. + dr_polishing_success : bool or None, optional + True if DR polishing solved successfully, False otherwise. + num_violated_cons : int or None, optional + Number of performance constraints found to be violated + during separation step. + all_sep_problems_solved : int or None, optional + True if all separation problems were solved successfully, + False otherwise (such as if there was a time out, subsolver + error, or only a subset of the problems were solved due to + custom constraint prioritization). + global_separation : bool, optional + True if separation problems were solved with the subordinate + global optimizer(s), False otherwise. + max_violation : int or None + Maximum scaled violation of any performance constraint + found during separation step. + elapsed_time : float, optional + Total time elapsed up to the current iteration, in seconds. + Attributes ---------- iteration : int or None @@ -1563,19 +1597,32 @@ class IterationLogRecord: objective : int or None Master problem objective value. Note: if the sense of the original model is maximization, - then this is the negative of the objective value. + then this is the negative of the objective value + of the original model. first_stage_var_shift : float or None Infinity norm of the difference between first-stage variable vectors for the current and previous iterations. dr_var_shift : float or None Infinity norm of the difference between decision rule variable vectors for the current and previous iterations. + dr_polishing_success : bool or None + True if DR polishing solved successfully, False otherwise. num_violated_cons : int or None Number of performance constraints found to be violated during separation step. + all_sep_problems_solved : int or None + True if all separation problems were solved successfully, + False otherwise (such as if there was a time out, subsolver + error, or only a subset of the problems were solved due to + custom constraint prioritization). + global_separation : bool + True if separation problems were solved with the subordinate + global optimizer(s), False otherwise. max_violation : int or None Maximum scaled violation of any performance constraint found during separation step. + elapsed_time : float + Total time elapsed up to the current iteration, in seconds. """ _LINE_LENGTH = 78 @@ -1604,7 +1651,7 @@ def __init__( objective, first_stage_var_shift, dr_var_shift, - dr_polishing_failed, + dr_polishing_success, num_violated_cons, all_sep_problems_solved, global_separation, @@ -1616,7 +1663,7 @@ def __init__( self.objective = objective self.first_stage_var_shift = first_stage_var_shift self.dr_var_shift = dr_var_shift - self.dr_polishing_failed = dr_polishing_failed + self.dr_polishing_success = dr_polishing_success self.num_violated_cons = num_violated_cons self.all_sep_problems_solved = all_sep_problems_solved self.global_separation = global_separation @@ -1655,7 +1702,7 @@ def _format_record_attr(self, attr_name): # qualifier for DR polishing and separation columns if attr_name == "dr_var_shift": - qual = "*" if self.dr_polishing_failed else "" + qual = "*" if not self.dr_polishing_success else "" elif attr_name == "num_violated_cons": qual = "+" if not self.all_sep_problems_solved else "" elif attr_name == "max_violation": From ef3334ffb101dd1681435995ec2eeb9d7c465c35 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 10 Sep 2023 20:40:42 -0400 Subject: [PATCH 0157/1204] Fix `SeparationLoopResults` attribute retrieval logic --- pyomo/contrib/pyros/solve_data.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/pyomo/contrib/pyros/solve_data.py b/pyomo/contrib/pyros/solve_data.py index c19b8000dca..10ca1bc6db8 100644 --- a/pyomo/contrib/pyros/solve_data.py +++ b/pyomo/contrib/pyros/solve_data.py @@ -580,16 +580,11 @@ def get_violating_attr(self, attr_name): object Attribute value. """ - if self.solved_locally: - local_loop_val = getattr(self.local_separation_loop_results, attr_name) - else: - local_loop_val = None - - if local_loop_val is not None: - attr_val = local_loop_val + if self.solved_globally: + attr_val = getattr(self.global_separation_loop_results, attr_name) else: - if self.solved_globally: - attr_val = getattr(self.global_separation_loop_results, attr_name) + if self.solved_locally: + attr_val = getattr(self.local_separation_loop_results, attr_name) else: attr_val = None @@ -598,7 +593,8 @@ def get_violating_attr(self, attr_name): @property def worst_case_perf_con(self): """ - ... + Performance constraint corresponding to the separation + solution chosen for the next master problem. """ return self.get_violating_attr("worst_case_perf_con") From fd94521d6234e512fa5bf1d15ab531384bb07583 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 10 Sep 2023 20:41:43 -0400 Subject: [PATCH 0158/1204] Apply black formatter --- pyomo/contrib/pyros/pyros_algorithm_methods.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index a5747017016..1b94d61a710 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -628,9 +628,7 @@ def ROSolver_iterative_solve(model_data, config): num_violated_cons = len(separation_results.violated_performance_constraints) all_sep_problems_solved = ( - len(scaled_violations) == len( - separation_model.util.performance_constraints - ) + len(scaled_violations) == len(separation_model.util.performance_constraints) and not separation_results.subsolver_error and not separation_results.time_out ) From 7a73f9bc9f6746ddffbf4f147cad7ca312b36a56 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 10 Sep 2023 20:55:12 -0400 Subject: [PATCH 0159/1204] Update usage example log outputs --- doc/OnlineDocs/contributed_packages/pyros.rst | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/doc/OnlineDocs/contributed_packages/pyros.rst b/doc/OnlineDocs/contributed_packages/pyros.rst index c00ce27f38b..f99fa3a94ab 100644 --- a/doc/OnlineDocs/contributed_packages/pyros.rst +++ b/doc/OnlineDocs/contributed_packages/pyros.rst @@ -538,12 +538,16 @@ correspond to first-stage degrees of freedom. ... solve_master_globally=True, ... load_solution=False, ... ) - =========================================================================================== - PyROS: Pyomo Robust Optimization Solver ... - =========================================================================================== + ============================================================================== + PyROS: The Pyomo Robust Optimization Solver. ... - INFO: Robust optimal solution identified. Exiting PyROS. - + ------------------------------------------------------------------------------ + Robust optimal solution identified. + ------------------------------------------------------------------------------ + ... + ------------------------------------------------------------------------------ + All done. Exiting PyROS. + ============================================================================== >>> # === Query results === >>> time = results_1.time >>> iterations = results_1.iterations @@ -627,11 +631,16 @@ In this example, we select affine decision rules by setting ... solve_master_globally=True, ... decision_rule_order=1, ... ) - =========================================================================================== - PyROS: Pyomo Robust Optimization Solver ... + ============================================================================== + PyROS: The Pyomo Robust Optimization Solver. ... - INFO: Robust optimal solution identified. Exiting PyROS. - + ------------------------------------------------------------------------------ + Robust optimal solution identified. + ------------------------------------------------------------------------------ + ... + ------------------------------------------------------------------------------ + All done. Exiting PyROS. + ============================================================================== >>> # === Compare final objective to the single-stage solution >>> two_stage_final_objective = round( ... pyo.value(results_2.final_objective_value), From e461e93b8253f4d00ae50b1dd9056ed8f09437b2 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Sun, 10 Sep 2023 21:49:08 -0400 Subject: [PATCH 0160/1204] add comment --- pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py index 0b6774a7d1a..d2a55c67f48 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py @@ -426,6 +426,8 @@ def load_state_into_pyomo(self, bound_multipliers=None): # not we are minimizing or maximizing - this is done in the ASL interface # for ipopt, but does not appear to be done in cyipopt. obj_sign = 1.0 + # since we will assert the number of objective functions, + # we only focus on active objective function. objs = list( m.component_data_objects( ctype=pyo.Objective, active=True, descend_into=True From f818de7351d1785c587c6ff8c340dedf04ac84eb Mon Sep 17 00:00:00 2001 From: gomesraphael7d Date: Mon, 11 Sep 2023 12:20:39 -0300 Subject: [PATCH 0161/1204] Change mps.baseline name --- ... => knapsack_problem_binary_variable_declaration.mps.baseline} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pyomo/repn/tests/mps/{knapsack_problem_integer_variable_declaration.mps.baseline => knapsack_problem_binary_variable_declaration.mps.baseline} (100%) diff --git a/pyomo/repn/tests/mps/knapsack_problem_integer_variable_declaration.mps.baseline b/pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration.mps.baseline similarity index 100% rename from pyomo/repn/tests/mps/knapsack_problem_integer_variable_declaration.mps.baseline rename to pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration.mps.baseline From 873c4f3bcbe4e121d9288fefa8dd1be3e1ba4f52 Mon Sep 17 00:00:00 2001 From: jasherma Date: Mon, 11 Sep 2023 13:10:31 -0400 Subject: [PATCH 0162/1204] Simplify separation results code + docs --- pyomo/contrib/pyros/solve_data.py | 44 +++++++++++++++++-------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/pyomo/contrib/pyros/solve_data.py b/pyomo/contrib/pyros/solve_data.py index 10ca1bc6db8..308a0ac3ac4 100644 --- a/pyomo/contrib/pyros/solve_data.py +++ b/pyomo/contrib/pyros/solve_data.py @@ -497,6 +497,7 @@ class SeparationResults: ---------- local_separation_loop_results global_separation_loop_results + main_loop_results subsolver_error time_out solved_locally @@ -516,7 +517,7 @@ def __init__(self, local_separation_loop_results, global_separation_loop_results @property def time_out(self): """ - Return True if time out found for local or global + bool : True if time out found for local or global separation loop, False otherwise. """ local_time_out = ( @@ -530,7 +531,7 @@ def time_out(self): @property def subsolver_error(self): """ - Return True if subsolver error found for local or global + bool : True if subsolver error found for local or global separation loop, False otherwise. """ local_subsolver_error = ( @@ -544,7 +545,7 @@ def subsolver_error(self): @property def solved_locally(self): """ - Return true if local separation loop was invoked, + bool : true if local separation loop was invoked, False otherwise. """ return self.local_separation_loop_results is not None @@ -552,13 +553,18 @@ def solved_locally(self): @property def solved_globally(self): """ - Return True if global separation loop was invoked, + bool : True if global separation loop was invoked, False otherwise. """ return self.global_separation_loop_results is not None def get_violating_attr(self, attr_name): """ + If separation problems solved globally, returns + value of attribute of global separation loop results. + + Otherwise, if separation problems solved locally, + returns value of attribute of local separation loop results. If local separation loop results specified, return value of attribute of local separation loop results. @@ -580,39 +586,37 @@ def get_violating_attr(self, attr_name): object Attribute value. """ - if self.solved_globally: - attr_val = getattr(self.global_separation_loop_results, attr_name) - else: - if self.solved_locally: - attr_val = getattr(self.local_separation_loop_results, attr_name) - else: - attr_val = None - - return attr_val + return getattr( + self.main_loop_results, + attr_name, + None, + ) @property def worst_case_perf_con(self): """ - Performance constraint corresponding to the separation - solution chosen for the next master problem. + ConstraintData : Performance constraint corresponding to the + separation solution chosen for the next master problem. """ return self.get_violating_attr("worst_case_perf_con") @property def main_loop_results(self): """ - Get main separation loop results. + SeparationLoopResults : Main separation loop results. + In particular, this is considered to be the global + loop result if solved globally, and the local loop + results otherwise. """ - if self.global_separation_loop_results is not None: + if self.solved_globally: return self.global_separation_loop_results return self.local_separation_loop_results @property def found_violation(self): """ - bool: True if ``found_violation`` attribute for - local or global separation loop results found - to be True, False otherwise. + bool : True if ``found_violation`` attribute for + main separation loop results is True, False otherwise. """ found_viol = self.get_violating_attr("found_violation") if found_viol is None: From 6c6053f4d312dca69a3352b3a3a84230b372baa9 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Mon, 11 Sep 2023 11:52:25 -0600 Subject: [PATCH 0163/1204] fixing a bracket bug --- pyomo/util/latex_printer.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index 7a853bd0308..48fd2c164e9 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -1261,7 +1261,7 @@ def latex_printer( if sdString[-1]==')': sdString = sdString[0:-1] if use_smart_variables: - new_variableMap[vr[sd]] = applySmartVariables(vr.name + '_{' + sdString + '}') + new_variableMap[vr[sd]] = applySmartVariables(vr.name + '_' + sdString ) else: new_variableMap[vr[sd]] = vr[sd].name else: @@ -1297,16 +1297,17 @@ def latex_printer( if ky not in overwrite_dict.keys(): overwrite_dict[ky] = vl + rep_dict = {} for ky in list(reversed(list(overwrite_dict.keys()))): if isinstance(ky,(pyo.Var,_GeneralVarData)): - if use_smart_variables and x_only_mode not in [1]: + if use_smart_variables and x_only_mode in [3]: overwrite_value = applySmartVariables(overwrite_dict[ky]) else: overwrite_value = overwrite_dict[ky] rep_dict[variableMap[ky]] = overwrite_value elif isinstance(ky,(pyo.Param,_ParamData)): - if use_smart_variables and x_only_mode not in [1]: + if use_smart_variables and x_only_mode in [3]: overwrite_value = applySmartVariables(overwrite_dict[ky]) else: overwrite_value = overwrite_dict[ky] @@ -1330,11 +1331,14 @@ def latex_printer( splitLines = pstr.split('\n') for i in range(0,len(splitLines)): - if '\\label{' in splitLines[i]: - epr, lbl = splitLines[i].split('\\label{') - epr = multiple_replace(epr,rep_dict) - lbl = multiple_replace(lbl,label_rep_dict) - splitLines[i] = epr + '\\label{' + lbl + if use_equation_environment: + splitLines[i] = multiple_replace(splitLines[i],rep_dict) + else: + if '\\label{' in splitLines[i]: + epr, lbl = splitLines[i].split('\\label{') + epr = multiple_replace(epr,rep_dict) + lbl = multiple_replace(lbl,label_rep_dict) + splitLines[i] = epr + '\\label{' + lbl pstr = '\n'.join(splitLines) From 8b5f51c8cf14fa8311aae40c59fdba35f23772bc Mon Sep 17 00:00:00 2001 From: jasherma Date: Mon, 11 Sep 2023 14:52:40 -0400 Subject: [PATCH 0164/1204] Apply black --- pyomo/contrib/pyros/solve_data.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pyomo/contrib/pyros/solve_data.py b/pyomo/contrib/pyros/solve_data.py index 308a0ac3ac4..91645921a10 100644 --- a/pyomo/contrib/pyros/solve_data.py +++ b/pyomo/contrib/pyros/solve_data.py @@ -586,11 +586,7 @@ def get_violating_attr(self, attr_name): object Attribute value. """ - return getattr( - self.main_loop_results, - attr_name, - None, - ) + return getattr(self.main_loop_results, attr_name, None) @property def worst_case_perf_con(self): From 69880ae77ce871eddb179340171c7bed567b2943 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Mon, 11 Sep 2023 13:03:28 -0600 Subject: [PATCH 0165/1204] fixing a bracket issue and a minor bound print bug --- pyomo/util/latex_printer.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index 48fd2c164e9..aca6040e58f 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -510,7 +510,7 @@ def analyze_variable(vr, visitor): elif lowerBoundValue == 0: lowerBound = ' 0 = ' else: - lowerBound = str(upperBoundValue) + ' \\leq ' + lowerBound = str(lowerBoundValue) + ' \\leq ' else: lowerBound = '' @@ -530,7 +530,7 @@ def analyze_variable(vr, visitor): % (vr.name) ) else: - lowerBound = str(upperBoundValue) + ' \\leq ' + lowerBound = str(lowerBoundValue) + ' \\leq ' else: lowerBound = '' @@ -1284,7 +1284,7 @@ def latex_printer( if sdString[-1]==')': sdString = sdString[0:-1] if use_smart_variables: - new_parameterMap[pm[sd]] = applySmartVariables(pm.name + '_{' + sdString + '}') + new_parameterMap[pm[sd]] = applySmartVariables(pm.name + '_' + sdString ) else: new_parameterMap[pm[sd]] = str(pm[sd])#.name else: @@ -1297,21 +1297,20 @@ def latex_printer( if ky not in overwrite_dict.keys(): overwrite_dict[ky] = vl - rep_dict = {} for ky in list(reversed(list(overwrite_dict.keys()))): - if isinstance(ky,(pyo.Var,_GeneralVarData)): - if use_smart_variables and x_only_mode in [3]: + if isinstance(ky,(pyo.Var,pyo.Param)): + if use_smart_variables and x_only_mode in [0,3]: overwrite_value = applySmartVariables(overwrite_dict[ky]) else: overwrite_value = overwrite_dict[ky] rep_dict[variableMap[ky]] = overwrite_value - elif isinstance(ky,(pyo.Param,_ParamData)): + elif isinstance(ky,(_GeneralVarData,_ParamData)): if use_smart_variables and x_only_mode in [3]: overwrite_value = applySmartVariables(overwrite_dict[ky]) else: overwrite_value = overwrite_dict[ky] - rep_dict[parameterMap[ky]] = overwrite_value + rep_dict[variableMap[ky]] = overwrite_value elif isinstance(ky,_SetData): # already handled pass From 5c05c7880f8281e52cf8cc8cd8407374e5313998 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 12 Sep 2023 12:44:11 -0600 Subject: [PATCH 0166/1204] SAVE POINT: Start the termination conditions/etc. --- pyomo/solver/results.py | 68 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index 6a940860661..c8c92109040 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -26,6 +26,7 @@ SolverStatus as LegacySolverStatus, ) from pyomo.solver.solution import SolutionLoaderBase +from pyomo.solver.util import SolverSystemError class TerminationCondition(enum.Enum): @@ -239,10 +240,73 @@ class ResultsReader: pass -def parse_sol_file(filename, results): +def parse_sol_file(file, results): + # The original reader for sol files is in pyomo.opt.plugins.sol. + # Per my original complaint, it has "magic numbers" that I just don't + # know how to test. It's apparently less fragile than that in APPSI. + # NOTE: The Results object now also holds the solution loader, so we do + # not need pass in a solution like we did previously. if results is None: results = Results() - pass + + # For backwards compatibility and general safety, we will parse all + # lines until "Options" appears. Anything before "Options" we will + # consider to be the solver message. + message = [] + for line in file: + if not line: + break + line = line.strip() + if "Options" in line: + break + message.append(line) + message = '\n'.join(message) + # Once "Options" appears, we must now read the content under it. + model_objects = [] + if "Options" in line: + line = file.readline() + number_of_options = int(line) + need_tolerance = False + if number_of_options > 4: # MRM: Entirely unclear why this is necessary, or if it even is + number_of_options -= 2 + need_tolerance = True + for i in range(number_of_options + 4): + line = file.readline() + model_objects.append(int(line)) + if need_tolerance: # MRM: Entirely unclear why this is necessary, or if it even is + line = file.readline() + model_objects.append(float(line)) + else: + raise SolverSystemError("ERROR READING `sol` FILE. No 'Options' line found.") + # Identify the total number of variables and constraints + number_of_cons = model_objects[number_of_options + 1] + number_of_vars = model_objects[number_of_options + 3] + constraints = [] + variables = [] + # Parse through the constraint lines and capture the constraints + i = 0 + while i < number_of_cons: + line = file.readline() + constraints.append(float(line)) + # Parse through the variable lines and capture the variables + i = 0 + while i < number_of_vars: + line = file.readline() + variables.append(float(line)) + exit_code = [0, 0] + line = file.readline() + if line and ('objno' in line): + exit_code_line = line.split() + if (len(exit_code_line) != 3): + raise SolverSystemError(f"ERROR READING `sol` FILE. Expected two numbers in `objno` line; received {line}.") + exit_code = [int(exit_code_line[1]), int(exit_code_line[2])] + else: + raise SolverSystemError(f"ERROR READING `sol` FILE. Expected `objno`; received {line}.") + results.extra_info.solver_message = message.strip().replace('\n', '; ') + # Not sure if next two lines are needed + # if isinstance(res.solver.message, str): + # res.solver.message = res.solver.message.replace(':', '\\x3a') + def parse_yaml(): From 549f375278fe120a34786a2773e7c6d1457c979f Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Fri, 15 Sep 2023 17:40:41 -0600 Subject: [PATCH 0167/1204] black line length stuff --- .../pynumero/algorithms/solvers/tests/test_cyipopt_solver.py | 5 +---- pyomo/contrib/pynumero/interfaces/cyipopt_interface.py | 4 +--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py index e3596993082..ead6331ed1b 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py @@ -167,10 +167,7 @@ def make_hs071_model(): m.x[3] = 1.0 m.obj = pyo.Objective(expr=m.x[0] * m.x[3] * (m.x[0] + m.x[1] + m.x[2]) + m.x[2]) # This expression evaluates to zero, but is not well defined when x[0] > 1.1 - trivial_expr_with_eval_error = ( - # 0.0 - (pyo.sqrt(1.1 - m.x[0])) ** 2 + m.x[0] - 1.1 - ) + trivial_expr_with_eval_error = ((pyo.sqrt(1.1 - m.x[0])) ** 2 + m.x[0] - 1.1) m.ineq1 = pyo.Constraint(expr=m.x[0] * m.x[1] * m.x[2] * m.x[3] >= 25.0) m.eq1 = pyo.Constraint( expr=( diff --git a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py index 89ce6683f4d..cddc2ce000f 100644 --- a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py +++ b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py @@ -353,9 +353,7 @@ def constraints(self, x): self._set_primals_if_necessary(x) return self._nlp.evaluate_constraints() except PyNumeroEvaluationError: - raise cyipopt.CyIpoptEvaluationError( - "Error in constraint evaluation" - ) + raise cyipopt.CyIpoptEvaluationError("Error in constraint evaluation") def jacobianstructure(self): return self._jac_g.row, self._jac_g.col From 167d11650a3f7c21f9fdb4e7b9086f7257866d78 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Fri, 15 Sep 2023 17:52:17 -0600 Subject: [PATCH 0168/1204] black no paren --- .../pynumero/algorithms/solvers/tests/test_cyipopt_solver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py index ead6331ed1b..7a670a2e41c 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py @@ -167,7 +167,7 @@ def make_hs071_model(): m.x[3] = 1.0 m.obj = pyo.Objective(expr=m.x[0] * m.x[3] * (m.x[0] + m.x[1] + m.x[2]) + m.x[2]) # This expression evaluates to zero, but is not well defined when x[0] > 1.1 - trivial_expr_with_eval_error = ((pyo.sqrt(1.1 - m.x[0])) ** 2 + m.x[0] - 1.1) + trivial_expr_with_eval_error = (pyo.sqrt(1.1 - m.x[0])) ** 2 + m.x[0] - 1.1 m.ineq1 = pyo.Constraint(expr=m.x[0] * m.x[1] * m.x[2] * m.x[3] >= 25.0) m.eq1 = pyo.Constraint( expr=( From ceaa9b63c65f18495a005643db51ee69975daaac Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Sep 2023 09:03:56 -0600 Subject: [PATCH 0169/1204] Fix return value to match documentation --- pyomo/repn/plugins/nl_writer.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 38a94a17495..34d8cbeeb39 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -338,13 +338,13 @@ def _generate_symbol_map(self, info): # Now that the row/column ordering is resolved, create the labels symbol_map = SymbolMap() symbol_map.addSymbols( - (info[0], f"v{idx}") for idx, info in enumerate(info.variables) + (info, f"v{idx}") for idx, info in enumerate(info.variables) ) symbol_map.addSymbols( - (info[0], f"c{idx}") for idx, info in enumerate(info.constraints) + (info, f"c{idx}") for idx, info in enumerate(info.constraints) ) symbol_map.addSymbols( - (info[0], f"o{idx}") for idx, info in enumerate(info.objectives) + (info, f"o{idx}") for idx, info in enumerate(info.objectives) ) return symbol_map @@ -1412,9 +1412,9 @@ def write(self, model): # Generate the return information info = NLWriterInfo( - variables, - constraints, - objectives, + [info[0] for info in variables], + [info[0] for info in constraints], + [info[0] for info in objectives], sorted(amplfunc_libraries), row_labels, col_labels, From 6f76dbde885296ba56b53de63ed13b92bb21c26b Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Sep 2023 09:05:11 -0600 Subject: [PATCH 0170/1204] Reduce repeated code --- pyomo/repn/plugins/nl_writer.py | 53 +++++++++++---------------------- 1 file changed, 18 insertions(+), 35 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 34d8cbeeb39..eb531596998 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -951,26 +951,10 @@ def write(self, model): row_comments = [f'\t#{lbl}' for lbl in row_labels] col_labels = [labeler(info[0]) for info in variables] col_comments = [f'\t#{lbl}' for lbl in col_labels] - if scaling_factor.scale: - self.var_id_to_nl = _map = {} - for var_idx, info in enumerate(variables): - _id = info[1] - scale = scaling_factor.suffix_cache[_id] - if scale == 1: - _map[_id] = f'v{var_idx}{col_comments[var_idx]}' - else: - _map[_id] = ( - template.division - + f'v{var_idx}' - + col_comments[var_idx] - + '\n' - + template.const % scale - ).rstrip() - else: - self.var_id_to_nl = { - info[1]: f'v{var_idx}{col_comments[var_idx]}' - for var_idx, info in enumerate(variables) - } + self.var_id_to_nl = { + info[1]: f'v{var_idx}{col_comments[var_idx]}' + for var_idx, info in enumerate(variables) + } # Write out the .row and .col data if self.rowstream is not None: self.rowstream.write('\n'.join(row_labels)) @@ -981,21 +965,20 @@ def write(self, model): else: row_labels = row_comments = [''] * (n_cons + n_objs) col_labels = col_comments = [''] * len(variables) - if scaling_factor.scale: - self.var_id_to_nl = _map = {} - for var_idx, info in enumerate(variables): - _id = info[1] - scale = scaling_factor.suffix_cache[_id] - if scale == 1: - _map[_id] = f"v{var_idx}" - else: - _map[_id] = ( - template.division + f'v{var_idx}\n' + template.const % scale - ).rstrip() - else: - self.var_id_to_nl = { - info[1]: f"v{var_idx}" for var_idx, info in enumerate(variables) - } + self.var_id_to_nl = { + info[1]: f"v{var_idx}" for var_idx, info in enumerate(variables) + } + + if scaling_factor.scale: + _vmap = self.var_id_to_nl + for var_idx, info in enumerate(variables): + _id = info[1] + scale = _scaling[_id] + if scale != 1: + _vmap[_id] = ( + template.division + _vmap[_id] + '\n' + template.const % scale + ).rstrip() + timer.toc("Generated row/col labels & comments", level=logging.DEBUG) # From 377bfd64754ac3c0c8df8b595e76df36d0464e47 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Sep 2023 09:08:13 -0600 Subject: [PATCH 0171/1204] Implement scaling for linear terms, var values, and dual values --- pyomo/repn/plugins/nl_writer.py | 43 ++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index eb531596998..0122159ac6d 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -564,6 +564,7 @@ def write(self, model): if self.config.scale_model and 'scaling_factor' in suffix_data: scaling_factor = CachingNumericSuffixFinder('scaling_factor', 1) + scaling_cache = scaling_factor.scaling_cache del suffix_data['scaling_factor'] else: scaling_factor = _NoScalingFactor() @@ -973,7 +974,7 @@ def write(self, model): _vmap = self.var_id_to_nl for var_idx, info in enumerate(variables): _id = info[1] - scale = _scaling[_id] + scale = scaling_cache[_id] if scale != 1: _vmap[_id] = ( template.division + _vmap[_id] + '\n' + template.const % scale @@ -1257,6 +1258,19 @@ def write(self, model): if 'dual' in suffix_data: data = suffix_data['dual'] data.compile(column_order, row_order, obj_order, model_id) + if scaling_factor.scale: + if objectives: + if len(objectives) > 1: + logger.warning( + "Scaling model with dual suffixes and multiple " + "objectives. Assuming that the duals are computed " + "against the first objective." + ) + _obj_scale = scaling_cache[objectives[0][1]] + else: + _obj_scale = 1 + for _id in _data.con: + _data.con[_id] *= _obj_scale / scaling_cache[constraints[_id][1]] if data.var: logger.warning("ignoring 'dual' suffix for Var types") if data.obj: @@ -1278,6 +1292,9 @@ def write(self, model): for var_idx, val in enumerate(v[0].value for v in variables) if val is not None ] + if scaling_factor.scale: + for i, (var_idx, val) in _init_lines: + _init_lines[i] = (var_idx, val * scaling_cache[variables[var_idx][1]]) ostream.write( 'x%d%s\n' % (len(_init_lines), "\t# initial guess" if symbolic_solver_labels else '') @@ -1370,12 +1387,14 @@ def write(self, model): # (i.e., a constant objective), then skip this entry if not linear: continue + if scaling_factor.scale: + for _id, val in linear.items(): + linear[_id] /= scaling_cache[_id] ostream.write(f'J{row_idx} {len(linear)}{row_comments[row_idx]}\n') - for _id in sorted(linear.keys(), key=column_order.__getitem__): - val = linear[_id] - if val.__class__ not in int_float: - val = float(val) - ostream.write(f'{column_order[_id]} {val!r}\n') + ostream.write(''.join( + f'{column_order[_id]} {linear[_id]!r}\n' + for _id in sorted(linear.keys(), key=column_order.__getitem__) + )) # # "G" lines (non-empty terms in the Objective) @@ -1386,12 +1405,14 @@ def write(self, model): # (i.e., a constant objective), then skip this entry if not linear: continue + if scaling_factor.scale: + for _id, val in linear.items(): + linear[_id] /= scaling_cache[_id] ostream.write(f'G{obj_idx} {len(linear)}{row_comments[obj_idx + n_cons]}\n') - for _id in sorted(linear.keys(), key=column_order.__getitem__): - val = linear[_id] - if val.__class__ not in int_float: - val = float(val) - ostream.write(f'{column_order[_id]} {val!r}\n') + ostream.write(''.join( + f'{column_order[_id]} {linear[_id]!r}\n' + for _id in sorted(linear.keys(), key=column_order.__getitem__) + )) # Generate the return information info = NLWriterInfo( From 0b608752da96d10ba023cdf9e8623067e908a64d Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Sep 2023 13:04:22 -0600 Subject: [PATCH 0172/1204] Remove unused import --- pyomo/util/calc_var_value.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/util/calc_var_value.py b/pyomo/util/calc_var_value.py index 85e8d4abd22..714477b9d32 100644 --- a/pyomo/util/calc_var_value.py +++ b/pyomo/util/calc_var_value.py @@ -14,7 +14,6 @@ native_numeric_types, native_complex_types, value, - is_fixed, ) from pyomo.core.expr.calculus.derivatives import differentiate from pyomo.core.base.constraint import Constraint, _ConstraintData From 2640630197f13a76029d78a2f47e2eb71938b6bc Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Sep 2023 13:05:16 -0600 Subject: [PATCH 0173/1204] Apply black --- pyomo/repn/tests/test_linear.py | 16 +++++----------- pyomo/util/calc_var_value.py | 6 +----- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/pyomo/repn/tests/test_linear.py b/pyomo/repn/tests/test_linear.py index 7334703f1d5..faf12a7da09 100644 --- a/pyomo/repn/tests/test_linear.py +++ b/pyomo/repn/tests/test_linear.py @@ -1497,31 +1497,27 @@ def test_type_registrations(self): try: # native type self.assertEqual( - bcd.register_dispatcher(visitor, 5), - (False, (linear._CONSTANT, 5)), + bcd.register_dispatcher(visitor, 5), (False, (linear._CONSTANT, 5)) ) self.assertEqual(len(bcd), 1) self.assertIs(bcd[int], bcd._before_native) # complex type self.assertEqual( - bcd.register_dispatcher(visitor, 5j), - (False, (linear._CONSTANT, 5j)), + bcd.register_dispatcher(visitor, 5j), (False, (linear._CONSTANT, 5j)) ) self.assertEqual(len(bcd), 2) self.assertIs(bcd[complex], bcd._before_complex) # ScalarParam m.p = Param(initialize=5) self.assertEqual( - bcd.register_dispatcher(visitor, m.p), - (False, (linear._CONSTANT, 5)), + bcd.register_dispatcher(visitor, m.p), (False, (linear._CONSTANT, 5)) ) self.assertEqual(len(bcd), 3) self.assertIs(bcd[m.p.__class__], bcd._before_param) # ParamData m.q = Param([0], initialize=6, mutable=True) self.assertEqual( - bcd.register_dispatcher(visitor, m.q[0]), - (False, (linear._CONSTANT, 6)), + bcd.register_dispatcher(visitor, m.q[0]), (False, (linear._CONSTANT, 6)) ) self.assertEqual(len(bcd), 4) self.assertIs(bcd[m.q[0].__class__], bcd._before_param) @@ -1535,9 +1531,7 @@ def test_type_registrations(self): self.assertIs(bcd[LinearExpression], bcd._before_general_expression) # Named expression m.e = Expression(expr=m.p + m.q[0]) - self.assertEqual( - bcd.register_dispatcher(visitor, m.e), (True, None) - ) + self.assertEqual(bcd.register_dispatcher(visitor, m.e), (True, None)) self.assertEqual(len(bcd), 7) self.assertIs(bcd[m.e.__class__], bcd._before_named_expression) diff --git a/pyomo/util/calc_var_value.py b/pyomo/util/calc_var_value.py index 714477b9d32..42d38f2f874 100644 --- a/pyomo/util/calc_var_value.py +++ b/pyomo/util/calc_var_value.py @@ -10,11 +10,7 @@ # ___________________________________________________________________________ from pyomo.common.errors import IterationLimitError -from pyomo.common.numeric_types import ( - native_numeric_types, - native_complex_types, - value, -) +from pyomo.common.numeric_types import native_numeric_types, native_complex_types, value from pyomo.core.expr.calculus.derivatives import differentiate from pyomo.core.base.constraint import Constraint, _ConstraintData From 1c65805103f1d0ffa568a7342c346ac5d4bb751d Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Sep 2023 13:47:33 -0600 Subject: [PATCH 0174/1204] Check for platform-specific float/complex types --- pyomo/common/dependencies.py | 14 ++++++++++++-- .../tests/unit/test_kernel_register_numpy_types.py | 12 +++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 5d0da11765a..9f29d211232 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -800,13 +800,23 @@ def _finalize_numpy(np, available): # registration here (to bypass the deprecation warning) until we # finally remove all support for it numeric_types._native_boolean_types.add(t) - for t in (np.float_, np.float16, np.float32, np.float64, np.float128): + _floats = [np.float_, np.float16, np.float32, np.float64] + if hasattr(np, 'float96'): + _floats.append(np.float96) + if hasattr(np, 'float128'): + _floats.append(np.float128) + for t in _floats: numeric_types.RegisterNumericType(t) # We have deprecated RegisterBooleanType, so we will mock up the # registration here (to bypass the deprecation warning) until we # finally remove all support for it numeric_types._native_boolean_types.add(t) - for t in (np.complex_, np.complex64, np.complex128, np.complex256): + _complex = [np.complex_, np.complex64, np.complex128] + if hasattr(np, 'complex192'): + _complex.append(np.complex192) + if hasattr(np, 'complex256'): + _complex.append(np.complex256) + for t in _complex: numeric_types.RegisterComplexType(t) diff --git a/pyomo/core/tests/unit/test_kernel_register_numpy_types.py b/pyomo/core/tests/unit/test_kernel_register_numpy_types.py index 1aef71bf632..9ec8722884e 100644 --- a/pyomo/core/tests/unit/test_kernel_register_numpy_types.py +++ b/pyomo/core/tests/unit/test_kernel_register_numpy_types.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ import pyomo.common.unittest as unittest -from pyomo.common.dependencies import numpy_available +from pyomo.common.dependencies import numpy, numpy_available from pyomo.common.log import LoggingIntercept # Boolean @@ -38,14 +38,20 @@ numpy_float_names.append('float16') numpy_float_names.append('float32') numpy_float_names.append('float64') - numpy_float_names.append('float128') + if hasattr(numpy, 'float96'): + numpy_float_names.append('float96') + if hasattr(numpy, 'float128'): + numpy_float_names.append('float128') # Complex numpy_complex_names = [] if numpy_available: numpy_complex_names.append('complex_') numpy_complex_names.append('complex64') numpy_complex_names.append('complex128') - numpy_complex_names.append('complex256') + if hasattr(numpy, 'complex192'): + numpy_complex_names.append('complex192') + if hasattr(numpy, 'complex256'): + numpy_complex_names.append('complex256') class TestNumpyRegistration(unittest.TestCase): From d1224904e8c59848fc7b89c0fc04521e640796aa Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Sep 2023 15:55:24 -0600 Subject: [PATCH 0175/1204] Leverage new native_complex_types set --- pyomo/core/kernel/register_numpy_types.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pyomo/core/kernel/register_numpy_types.py b/pyomo/core/kernel/register_numpy_types.py index 0570684e7e3..5f7812354d9 100644 --- a/pyomo/core/kernel/register_numpy_types.py +++ b/pyomo/core/kernel/register_numpy_types.py @@ -17,10 +17,11 @@ version='6.1', ) -from pyomo.core.expr.numvalue import ( +from pyomo.common.numeric_types import ( RegisterNumericType, RegisterIntegerType, RegisterBooleanType, + native_complex_types, native_numeric_types, native_integer_types, native_boolean_types, @@ -37,6 +38,8 @@ numpy_float = [] numpy_bool_names = [] numpy_bool = [] +numpy_complex_names = [] +numpy_complex = [] if _has_numpy: # Historically, the lists included several numpy aliases @@ -44,6 +47,8 @@ numpy_int.extend((numpy.int_, numpy.intc, numpy.intp)) numpy_float_names.append('float_') numpy_float.append(numpy.float_) + numpy_complex_names.append('complex_') + numpy_complex.append(numpy.complex_) # Re-build the old numpy_* lists for t in native_boolean_types: @@ -63,13 +68,8 @@ # Complex -numpy_complex_names = [] -numpy_complex = [] -if _has_numpy: - numpy_complex_names.extend(('complex_', 'complex64', 'complex128', 'complex256')) - for _type_name in numpy_complex_names: - try: - _type = getattr(numpy, _type_name) - numpy_complex.append(_type) - except: # pragma:nocover - pass +for t in native_complex_types: + if t.__module__ == 'numpy': + if t.__name__ not in numpy_complex_names: + numpy_complex.append(t) + numpy_complex_names.append(t.__name__) From 431e8bbe5272724a357026d3b6797cbc8b533fe4 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Sep 2023 16:13:31 -0600 Subject: [PATCH 0176/1204] Fix base class for detecting named subexpressiosn --- pyomo/repn/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index 4161796d2fb..9f9af6b97b2 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -38,7 +38,7 @@ SortComponents, ) from pyomo.core.base.component import ActiveComponent -from pyomo.core.base.expression import _GeneralExpressionData +from pyomo.core.base.expression import _ExpressionData from pyomo.core.expr.numvalue import is_fixed, value import pyomo.core.expr as EXPR import pyomo.core.kernel as kernel @@ -53,7 +53,7 @@ EXPR.NPV_SumExpression, } _named_subexpression_types = ( - _GeneralExpressionData, + _ExpressionData, kernel.expression.expression, kernel.objective.objective, ) From 9a077d901f27c4c8db9c9e95bddda34b95996616 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Mon, 18 Sep 2023 18:19:02 -0600 Subject: [PATCH 0177/1204] making updates to the single constraint printer --- pyomo/util/latex_printer.py | 474 +++++++++++++++++++++++------------- 1 file changed, 302 insertions(+), 172 deletions(-) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index aca6040e58f..4c1a4c31e07 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -31,6 +31,7 @@ ExternalFunctionExpression, ) +from pyomo.core.expr.visitor import identify_components from pyomo.core.expr.base import ExpressionBase from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData from pyomo.core.base.objective import ScalarObjective, _GeneralObjectiveData @@ -50,6 +51,7 @@ from pyomo.core.base.set import _SetData from pyomo.core.base.constraint import ScalarConstraint, IndexedConstraint from pyomo.common.collections.component_map import ComponentMap +from pyomo.common.collections.component_set import ComponentSet from pyomo.core.base.external import _PythonCallbackFunctionID @@ -102,7 +104,7 @@ def indexCorrector(ixs, base): return ixs -def alphabetStringGenerator(num,indexMode = False): +def alphabetStringGenerator(num, indexMode=False): if indexMode: alphabet = [ '.', @@ -164,9 +166,9 @@ def alphabetStringGenerator(num,indexMode = False): 'y', 'z', ] - ixs = decoder(num + 1, len(alphabet)-1) + ixs = decoder(num + 1, len(alphabet) - 1) pstr = '' - ixs = indexCorrector(ixs, len(alphabet)-1) + ixs = indexCorrector(ixs, len(alphabet) - 1) for i in range(0, len(ixs)): ix = ixs[i] pstr += alphabet[ix] @@ -257,6 +259,7 @@ def handle_inequality_node(visitor, node, arg1, arg2): def handle_var_node(visitor, node): return visitor.variableMap[node] + def handle_num_node(visitor, node): if isinstance(node, float): if node.is_integer(): @@ -328,7 +331,10 @@ def handle_functionID_node(visitor, node, *args): def handle_indexTemplate_node(visitor, node, *args): - return '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % (node._group, visitor.setMap[node._set]) + return '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( + node._group, + visitor.setMap[node._set], + ) def handle_numericGIE_node(visitor, node, *args): @@ -347,16 +353,19 @@ def handle_numericGIE_node(visitor, node, *args): def handle_templateSumExpression_node(visitor, node, *args): pstr = '' - for i in range(0,len(node._iters)): - pstr += '\\sum_{__S_PLACEHOLDER_8675309_GROUP_%s_%s__} ' %(node._iters[i][0]._group, visitor.setMap[node._iters[i][0]._set]) + for i in range(0, len(node._iters)): + pstr += '\\sum_{__S_PLACEHOLDER_8675309_GROUP_%s_%s__} ' % ( + node._iters[i][0]._group, + visitor.setMap[node._iters[i][0]._set], + ) pstr += args[0] return pstr -def handle_param_node(visitor,node): - return visitor.parameterMap[node] +def handle_param_node(visitor, node): + return visitor.parameterMap[node] class _LatexVisitor(StreamBasedExpressionVisitor): @@ -441,6 +450,7 @@ def applySmartVariables(name): return joinedName + def analyze_variable(vr, visitor): domainMap = { 'Reals': '\\mathds{R}', @@ -492,8 +502,7 @@ def analyze_variable(vr, visitor): if upperBoundValue is not None: if upperBoundValue <= 0: raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) ) else: upperBound = ' \\leq ' + str(upperBoundValue) @@ -504,8 +513,7 @@ def analyze_variable(vr, visitor): if lowerBoundValue is not None: if lowerBoundValue > 0: raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) ) elif lowerBoundValue == 0: lowerBound = ' 0 = ' @@ -526,8 +534,7 @@ def analyze_variable(vr, visitor): if lowerBoundValue is not None: if lowerBoundValue >= 0: raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) ) else: lowerBound = str(lowerBoundValue) + ' \\leq ' @@ -554,8 +561,7 @@ def analyze_variable(vr, visitor): if upperBoundValue is not None: if upperBoundValue < 0: raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) ) elif upperBoundValue == 0: upperBound = ' = 0 ' @@ -564,13 +570,7 @@ def analyze_variable(vr, visitor): else: upperBound = '' - elif domainName in [ - 'Boolean', - 'Binary', - 'Any', - 'AnyWithNone', - 'EmptySet', - ]: + elif domainName in ['Boolean', 'Binary', 'Any', 'AnyWithNone', 'EmptySet']: lowerBound = '' upperBound = '' @@ -580,8 +580,7 @@ def analyze_variable(vr, visitor): lowerBound = str(lowerBoundValue) + ' \\leq ' elif lowerBoundValue > 1: raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) ) elif lowerBoundValue == 1: lowerBound = ' = 1 ' @@ -595,8 +594,7 @@ def analyze_variable(vr, visitor): upperBound = ' \\leq ' + str(upperBoundValue) elif upperBoundValue < 0: raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) ) elif upperBoundValue == 0: upperBound = ' = 0 ' @@ -606,16 +604,14 @@ def analyze_variable(vr, visitor): upperBound = ' \\leq 1 ' else: - raise ValueError( - 'Domain %s not supported by the latex printer' % (domainName) - ) + raise ValueError('Domain %s not supported by the latex printer' % (domainName)) varBoundData = { - 'variable' : vr, - 'lowerBound' : lowerBound, - 'upperBound' : upperBound, - 'domainName' : domainName, - 'domainLatex' : domainMap[domainName], + 'variable': vr, + 'lowerBound': lowerBound, + 'upperBound': upperBound, + 'domainName': domainName, + 'domainLatex': domainMap[domainName], } return varBoundData @@ -730,6 +726,57 @@ def latex_printer( % (str(type(pyomo_component))) ) + if isSingle: + temp_comp, temp_indexes = templatize_fcn(pyomo_component) + variableList = [] + for v in identify_components( + temp_comp, [ScalarVar, _GeneralVarData, IndexedVar] + ): + if isinstance(v, _GeneralVarData): + v_write = v.parent_component() + if v_write not in ComponentSet(variableList): + variableList.append(v_write) + else: + if v not in ComponentSet(variableList): + variableList.append(v) + + parameterList = [] + for p in identify_components( + temp_comp, [ScalarParam, _ParamData, IndexedParam] + ): + if isinstance(p, _ParamData): + p_write = p.parent_component() + if p_write not in ComponentSet(parameterList): + parameterList.append(p_write) + else: + if p not in ComponentSet(parameterList): + parameterList.append(p) + + # TODO: cannot extract this information, waiting on resolution of an issue + # setList = identify_components(pyomo_component.expr, pyo.Set) + + else: + variableList = [ + vr + for vr in pyomo_component.component_objects( + pyo.Var, descend_into=True, active=True + ) + ] + + parameterList = [ + pm + for pm in pyomo_component.component_objects( + pyo.Param, descend_into=True, active=True + ) + ] + + setList = [ + st + for st in pyomo_component.component_objects( + pyo.Set, descend_into=True, active=True + ) + ] + forallTag = ' \\qquad \\forall' descriptorDict = {} @@ -750,19 +797,12 @@ def latex_printer( # Declare a visitor/walker visitor = _LatexVisitor() - - variableList = [ - vr - for vr in pyomo_component.component_objects( - pyo.Var, descend_into=True, active=True - ) - ] variableMap = ComponentMap() vrIdx = 0 - for i in range(0,len(variableList)): + for i in range(0, len(variableList)): vr = variableList[i] vrIdx += 1 - if isinstance(vr ,ScalarVar): + if isinstance(vr, ScalarVar): variableMap[vr] = 'x_' + str(vrIdx) elif isinstance(vr, IndexedVar): variableMap[vr] = 'x_' + str(vrIdx) @@ -770,22 +810,17 @@ def latex_printer( vrIdx += 1 variableMap[vr[sd]] = 'x_' + str(vrIdx) else: - raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + raise DeveloperError( + 'Variable is not a variable. Should not happen. Contact developers' + ) visitor.variableMap = variableMap - parameterList = [ - pm - for pm in pyomo_component.component_objects( - pyo.Param, descend_into=True, active=True - ) - ] - parameterMap = ComponentMap() pmIdx = 0 - for i in range(0,len(parameterList)): + for i in range(0, len(parameterList)): vr = parameterList[i] pmIdx += 1 - if isinstance(vr ,ScalarParam): + if isinstance(vr, ScalarParam): parameterMap[vr] = 'p_' + str(pmIdx) elif isinstance(vr, IndexedParam): parameterMap[vr] = 'p_' + str(pmIdx) @@ -793,19 +828,15 @@ def latex_printer( pmIdx += 1 parameterMap[vr[sd]] = 'p_' + str(pmIdx) else: - raise DeveloperError( 'Parameter is not a parameter. Should not happen. Contact developers') + raise DeveloperError( + 'Parameter is not a parameter. Should not happen. Contact developers' + ) visitor.parameterMap = parameterMap - setList = [ - st - for st in pyomo_component.component_objects( - pyo.Set, descend_into=True, active=True - ) - ] setMap = ComponentMap() - for i in range(0,len(setList)): + for i in range(0, len(setList)): st = setList[i] - setMap[st] = 'SET' + str(i+1) + setMap[st] = 'SET' + str(i + 1) visitor.setMap = setMap # starts building the output string @@ -825,7 +856,13 @@ def latex_printer( # Iterate over the objectives and print for obj in objectives: - obj_template, obj_indices = templatize_fcn(obj) + try: + obj_template, obj_indices = templatize_fcn(obj) + except: + raise RuntimeError( + "An objective has been constructed that cannot be templatized" + ) + if obj.sense == 1: pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['minimize']) else: @@ -869,7 +906,12 @@ def latex_printer( # grab the constraint and templatize con = constraints[i] - con_template, indices = templatize_fcn(con) + try: + con_template, indices = templatize_fcn(con) + except: + raise RuntimeError( + "A constraint has been constructed that cannot be templatized" + ) # Walk the constraint conLine = ( @@ -917,7 +959,7 @@ def latex_printer( vr = variableList[i] if isinstance(vr, ScalarVar): varBoundDataEntry = analyze_variable(vr, visitor) - varBoundData.append( varBoundDataEntry ) + varBoundData.append(varBoundDataEntry) elif isinstance(vr, IndexedVar): varBoundData_indexedVar = [] # need to wrap in function and do individually @@ -927,21 +969,33 @@ def latex_printer( varBoundDataEntry = analyze_variable(vr[sd], visitor) varBoundData_indexedVar.append(varBoundDataEntry) globIndexedVariables = True - for j in range(0,len(varBoundData_indexedVar)-1): + for j in range(0, len(varBoundData_indexedVar) - 1): chks = [] - chks.append(varBoundData_indexedVar[j]['lowerBound']==varBoundData_indexedVar[j+1]['lowerBound']) - chks.append(varBoundData_indexedVar[j]['upperBound']==varBoundData_indexedVar[j+1]['upperBound']) - chks.append(varBoundData_indexedVar[j]['domainName']==varBoundData_indexedVar[j+1]['domainName']) + chks.append( + varBoundData_indexedVar[j]['lowerBound'] + == varBoundData_indexedVar[j + 1]['lowerBound'] + ) + chks.append( + varBoundData_indexedVar[j]['upperBound'] + == varBoundData_indexedVar[j + 1]['upperBound'] + ) + chks.append( + varBoundData_indexedVar[j]['domainName'] + == varBoundData_indexedVar[j + 1]['domainName'] + ) if not all(chks): globIndexedVariables = False break if globIndexedVariables: - varBoundData.append({'variable': vr, - 'lowerBound': varBoundData_indexedVar[0]['lowerBound'], - 'upperBound': varBoundData_indexedVar[0]['upperBound'], - 'domainName': varBoundData_indexedVar[0]['domainName'], - 'domainLatex': varBoundData_indexedVar[0]['domainLatex'], - }) + varBoundData.append( + { + 'variable': vr, + 'lowerBound': varBoundData_indexedVar[0]['lowerBound'], + 'upperBound': varBoundData_indexedVar[0]['upperBound'], + 'domainName': varBoundData_indexedVar[0]['domainName'], + 'domainLatex': varBoundData_indexedVar[0]['domainLatex'], + } + ) else: varBoundData += varBoundData_indexedVar else: @@ -953,11 +1007,15 @@ def latex_printer( bstr = '' appendBoundString = False useThreeAlgn = False - for i in range(0,len(varBoundData)): - vbd = varBoundData[i] - if vbd['lowerBound'] == '' and vbd['upperBound'] == '' and vbd['domainName']=='Reals': + for i in range(0, len(varBoundData)): + vbd = varBoundData[i] + if ( + vbd['lowerBound'] == '' + and vbd['upperBound'] == '' + and vbd['domainName'] == 'Reals' + ): # unbounded all real, do not print - if i <= len(varBoundData)-2: + if i <= len(varBoundData) - 2: bstr = bstr[0:-2] else: if not useThreeAlgn: @@ -969,12 +1027,28 @@ def latex_printer( if use_equation_environment: conLabel = '' else: - conLabel = ' \\label{con:' + pyomo_component.name + '_' + variableMap[vbd['variable']] + '_bound' + '} ' + conLabel = ( + ' \\label{con:' + + pyomo_component.name + + '_' + + variableMap[vbd['variable']] + + '_bound' + + '} ' + ) appendBoundString = True - coreString = vbd['lowerBound'] + variableMap[vbd['variable']] + vbd['upperBound'] + ' ' + trailingAligner + '\\qquad \\in ' + vbd['domainLatex'] + conLabel + coreString = ( + vbd['lowerBound'] + + variableMap[vbd['variable']] + + vbd['upperBound'] + + ' ' + + trailingAligner + + '\\qquad \\in ' + + vbd['domainLatex'] + + conLabel + ) bstr += ' ' * tbSpc + algn + ' %s' % (coreString) - if i <= len(varBoundData)-2: + if i <= len(varBoundData) - 2: bstr += '\\\\ \n' else: bstr += '\n' @@ -996,26 +1070,28 @@ def latex_printer( # Handling the iterator indices defaultSetLatexNames = ComponentMap() - for i in range(0,len(setList)): + for i in range(0, len(setList)): st = setList[i] if use_smart_variables: - chkName = setList[i].name - if len(chkName)==1 and chkName.upper() == chkName: + chkName = setList[i].name + if len(chkName) == 1 and chkName.upper() == chkName: chkName += '_mathcal' - defaultSetLatexNames[st] = applySmartVariables( chkName ) + defaultSetLatexNames[st] = applySmartVariables(chkName) else: - defaultSetLatexNames[st] = setList[i].name.replace('_','\\_') + defaultSetLatexNames[st] = setList[i].name.replace('_', '\\_') ## Could be used in the future if someone has a lot of sets # defaultSetLatexNames[st] = 'mathcal{' + alphabetStringGenerator(i).upper() + '}' if st in overwrite_dict.keys(): if use_smart_variables: - defaultSetLatexNames[st] = applySmartVariables( overwrite_dict[st][0] ) + defaultSetLatexNames[st] = applySmartVariables(overwrite_dict[st][0]) else: - defaultSetLatexNames[st] = overwrite_dict[st][0].replace('_','\\_') + defaultSetLatexNames[st] = overwrite_dict[st][0].replace('_', '\\_') - defaultSetLatexNames[st] = defaultSetLatexNames[st].replace('\\mathcal',r'\\mathcal') + defaultSetLatexNames[st] = defaultSetLatexNames[st].replace( + '\\mathcal', r'\\mathcal' + ) latexLines = pstr.split('\n') for jj in range(0, len(latexLines)): @@ -1037,17 +1113,23 @@ def latex_printer( # Determine if the set is continuous setInfo = dict( - zip(uniqueSets, [{'continuous':False} for i in range(0, len(uniqueSets))]) + zip( + uniqueSets, + [{'continuous': False} for i in range(0, len(uniqueSets))], + ) ) for ky, vl in setInfo.items(): - ix = int(ky[3:])-1 + ix = int(ky[3:]) - 1 setInfo[ky]['setObject'] = setList[ix] - setInfo[ky]['setRegEx'] = r'__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__'%(ky) - setInfo[ky]['sumSetRegEx'] = r'sum_{__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__}'%(ky) + setInfo[ky][ + 'setRegEx' + ] = r'__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__' % (ky) + setInfo[ky][ + 'sumSetRegEx' + ] = r'sum_{__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__}' % (ky) # setInfo[ky]['idxRegEx'] = r'__I_PLACEHOLDER_8675309_GROUP_[0-9*]_%s__'%(ky) - if split_continuous_sets: for ky, vl in setInfo.items(): st = vl['setObject'] @@ -1059,7 +1141,6 @@ def latex_printer( break setInfo[ky]['continuous'] = stCont - # replace the sets for ky, vl in setInfo.items(): # if the set is continuous and the flag has been set @@ -1069,45 +1150,66 @@ def latex_printer( bgn = stData[0] ed = stData[-1] - replacement = r'sum_{ __I_PLACEHOLDER_8675309_GROUP_\1_%s__ = %d }^{%d}'%(ky,bgn,ed) + replacement = ( + r'sum_{ __I_PLACEHOLDER_8675309_GROUP_\1_%s__ = %d }^{%d}' + % (ky, bgn, ed) + ) ln = re.sub(setInfo[ky]['sumSetRegEx'], replacement, ln) else: # if the set is not continuous or the flag has not been set - replacement = r'sum_{ __I_PLACEHOLDER_8675309_GROUP_\1_%s__ \\in __S_PLACEHOLDER_8675309_GROUP_\1_%s__ }'%(ky,ky) - ln = re.sub(setInfo[ky]['sumSetRegEx'], replacement, ln) + replacement = ( + r'sum_{ __I_PLACEHOLDER_8675309_GROUP_\1_%s__ \\in __S_PLACEHOLDER_8675309_GROUP_\1_%s__ }' + % (ky, ky) + ) + ln = re.sub(setInfo[ky]['sumSetRegEx'], replacement, ln) replacement = defaultSetLatexNames[setInfo[ky]['setObject']] - ln = re.sub(setInfo[ky]['setRegEx'],replacement,ln) + ln = re.sub(setInfo[ky]['setRegEx'], replacement, ln) # groupNumbers = re.findall(r'__I_PLACEHOLDER_8675309_GROUP_([0-9*])_SET[0-9]*__',ln) - setNumbers = re.findall(r'__I_PLACEHOLDER_8675309_GROUP_[0-9*]_SET([0-9]*)__',ln) - groupSetPairs = re.findall(r'__I_PLACEHOLDER_8675309_GROUP_([0-9*])_SET([0-9]*)__',ln) + setNumbers = re.findall( + r'__I_PLACEHOLDER_8675309_GROUP_[0-9*]_SET([0-9]*)__', ln + ) + groupSetPairs = re.findall( + r'__I_PLACEHOLDER_8675309_GROUP_([0-9*])_SET([0-9]*)__', ln + ) groupInfo = {} for vl in setNumbers: - groupInfo['SET'+vl] = {'setObject':setInfo['SET'+vl]['setObject'], 'indices':[]} + groupInfo['SET' + vl] = { + 'setObject': setInfo['SET' + vl]['setObject'], + 'indices': [], + } for gp in groupSetPairs: - if gp[0] not in groupInfo['SET'+gp[1]]['indices']: - groupInfo['SET'+gp[1]]['indices'].append(gp[0]) + if gp[0] not in groupInfo['SET' + gp[1]]['indices']: + groupInfo['SET' + gp[1]]['indices'].append(gp[0]) - indexCounter = 0 for ky, vl in groupInfo.items(): if vl['setObject'] in overwrite_dict.keys(): indexNames = overwrite_dict[vl['setObject']][1] if len(indexNames) < len(vl['indices']): - raise ValueError('Insufficient number of indices provided to the overwite dictionary for set %s'%(vl['setObject'].name)) - for i in range(0,len(indexNames)): - ln = ln.replace('__I_PLACEHOLDER_8675309_GROUP_%s_%s__'%(vl['indices'][i],ky),indexNames[i]) + raise ValueError( + 'Insufficient number of indices provided to the overwrite dictionary for set %s' + % (vl['setObject'].name) + ) + for i in range(0, len(indexNames)): + ln = ln.replace( + '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' + % (vl['indices'][i], ky), + indexNames[i], + ) else: - for i in range(0,len(vl['indices'])): - ln = ln.replace('__I_PLACEHOLDER_8675309_GROUP_%s_%s__'%(vl['indices'][i],ky),alphabetStringGenerator(indexCounter,True)) + for i in range(0, len(vl['indices'])): + ln = ln.replace( + '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' + % (vl['indices'][i], ky), + alphabetStringGenerator(indexCounter, True), + ) indexCounter += 1 - # print('gn',groupInfo) - latexLines[jj] = ln @@ -1115,18 +1217,21 @@ def latex_printer( # pstr = pstr.replace('\\mathcal{', 'mathcal{') # pstr = pstr.replace('mathcal{', '\\mathcal{') - if x_only_mode in [1,2,3]: - # Need to preserve only the set elments in the overwrite_dict + if x_only_mode in [1, 2, 3]: + # Need to preserve only the set elements in the overwrite_dict new_overwrite_dict = {} for ky, vl in overwrite_dict.items(): - if isinstance(ky,_GeneralVarData): + if isinstance(ky, _GeneralVarData): pass - elif isinstance(ky,_ParamData): + elif isinstance(ky, _ParamData): pass - elif isinstance(ky,_SetData): + elif isinstance(ky, _SetData): new_overwrite_dict[ky] = overwrite_dict[ky] else: - raise ValueError('The overwrite_dict object has a key of invalid type: %s'%(str(ky))) + raise ValueError( + 'The overwrite_dict object has a key of invalid type: %s' + % (str(ky)) + ) overwrite_dict = new_overwrite_dict # # Only x modes @@ -1138,42 +1243,50 @@ def latex_printer( if x_only_mode == 1: vrIdx = 0 new_variableMap = ComponentMap() - for i in range(0,len(variableList)): + for i in range(0, len(variableList)): vr = variableList[i] vrIdx += 1 - if isinstance(vr ,ScalarVar): + if isinstance(vr, ScalarVar): new_variableMap[vr] = 'x_{' + str(vrIdx) + '}' elif isinstance(vr, IndexedVar): new_variableMap[vr] = 'x_{' + str(vrIdx) + '}' for sd in vr.index_set().data(): # vrIdx += 1 sdString = str(sd) - if sdString[0]=='(': + if sdString[0] == '(': sdString = sdString[1:] - if sdString[-1]==')': + if sdString[-1] == ')': sdString = sdString[0:-1] - new_variableMap[vr[sd]] = 'x_{' + str(vrIdx) + '_{' + sdString + '}' + '}' + new_variableMap[vr[sd]] = ( + 'x_{' + str(vrIdx) + '_{' + sdString + '}' + '}' + ) else: - raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + raise DeveloperError( + 'Variable is not a variable. Should not happen. Contact developers' + ) pmIdx = 0 new_parameterMap = ComponentMap() - for i in range(0,len(parameterList)): + for i in range(0, len(parameterList)): pm = parameterList[i] pmIdx += 1 - if isinstance(pm ,ScalarParam): + if isinstance(pm, ScalarParam): new_parameterMap[pm] = 'p_{' + str(pmIdx) + '}' elif isinstance(pm, IndexedParam): new_parameterMap[pm] = 'p_{' + str(pmIdx) + '}' for sd in pm.index_set().data(): sdString = str(sd) - if sdString[0]=='(': + if sdString[0] == '(': sdString = sdString[1:] - if sdString[-1]==')': + if sdString[-1] == ')': sdString = sdString[0:-1] - new_parameterMap[pm[sd]] = 'p_{' + str(pmIdx) + '_{' + sdString + '}' + '}' + new_parameterMap[pm[sd]] = ( + 'p_{' + str(pmIdx) + '_{' + sdString + '}' + '}' + ) else: - raise DeveloperError( 'Parameter is not a parameter. Should not happen. Contact developers') + raise DeveloperError( + 'Parameter is not a parameter. Should not happen. Contact developers' + ) new_overwrite_dict = ComponentMap() for ky, vl in new_variableMap.items(): @@ -1187,42 +1300,50 @@ def latex_printer( elif x_only_mode == 2: vrIdx = 0 new_variableMap = ComponentMap() - for i in range(0,len(variableList)): + for i in range(0, len(variableList)): vr = variableList[i] vrIdx += 1 - if isinstance(vr ,ScalarVar): + if isinstance(vr, ScalarVar): new_variableMap[vr] = alphabetStringGenerator(i) elif isinstance(vr, IndexedVar): new_variableMap[vr] = alphabetStringGenerator(i) for sd in vr.index_set().data(): # vrIdx += 1 sdString = str(sd) - if sdString[0]=='(': + if sdString[0] == '(': sdString = sdString[1:] - if sdString[-1]==')': + if sdString[-1] == ')': sdString = sdString[0:-1] - new_variableMap[vr[sd]] = alphabetStringGenerator(i) + '_{' + sdString + '}' + new_variableMap[vr[sd]] = ( + alphabetStringGenerator(i) + '_{' + sdString + '}' + ) else: - raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + raise DeveloperError( + 'Variable is not a variable. Should not happen. Contact developers' + ) - pmIdx = vrIdx-1 + pmIdx = vrIdx - 1 new_parameterMap = ComponentMap() - for i in range(0,len(parameterList)): + for i in range(0, len(parameterList)): pm = parameterList[i] pmIdx += 1 - if isinstance(pm ,ScalarParam): + if isinstance(pm, ScalarParam): new_parameterMap[pm] = alphabetStringGenerator(pmIdx) elif isinstance(pm, IndexedParam): new_parameterMap[pm] = alphabetStringGenerator(pmIdx) for sd in pm.index_set().data(): sdString = str(sd) - if sdString[0]=='(': + if sdString[0] == '(': sdString = sdString[1:] - if sdString[-1]==')': + if sdString[-1] == ')': sdString = sdString[0:-1] - new_parameterMap[pm[sd]] = alphabetStringGenerator(pmIdx) + '_{' + sdString + '}' + new_parameterMap[pm[sd]] = ( + alphabetStringGenerator(pmIdx) + '_{' + sdString + '}' + ) else: - raise DeveloperError( 'Parameter is not a parameter. Should not happen. Contact developers') + raise DeveloperError( + 'Parameter is not a parameter. Should not happen. Contact developers' + ) new_overwrite_dict = ComponentMap() for ky, vl in new_variableMap.items(): @@ -1243,52 +1364,60 @@ def latex_printer( new_overwrite_dict[ky] = vl overwrite_dict = new_overwrite_dict - else: + else: vrIdx = 0 new_variableMap = ComponentMap() - for i in range(0,len(variableList)): + for i in range(0, len(variableList)): vr = variableList[i] vrIdx += 1 - if isinstance(vr ,ScalarVar): + if isinstance(vr, ScalarVar): new_variableMap[vr] = vr.name elif isinstance(vr, IndexedVar): new_variableMap[vr] = vr.name for sd in vr.index_set().data(): # vrIdx += 1 sdString = str(sd) - if sdString[0]=='(': + if sdString[0] == '(': sdString = sdString[1:] - if sdString[-1]==')': + if sdString[-1] == ')': sdString = sdString[0:-1] if use_smart_variables: - new_variableMap[vr[sd]] = applySmartVariables(vr.name + '_' + sdString ) + new_variableMap[vr[sd]] = applySmartVariables( + vr.name + '_' + sdString + ) else: new_variableMap[vr[sd]] = vr[sd].name else: - raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + raise DeveloperError( + 'Variable is not a variable. Should not happen. Contact developers' + ) pmIdx = 0 new_parameterMap = ComponentMap() - for i in range(0,len(parameterList)): + for i in range(0, len(parameterList)): pm = parameterList[i] pmIdx += 1 - if isinstance(pm ,ScalarParam): + if isinstance(pm, ScalarParam): new_parameterMap[pm] = pm.name elif isinstance(pm, IndexedParam): new_parameterMap[pm] = pm.name for sd in pm.index_set().data(): # pmIdx += 1 sdString = str(sd) - if sdString[0]=='(': + if sdString[0] == '(': sdString = sdString[1:] - if sdString[-1]==')': + if sdString[-1] == ')': sdString = sdString[0:-1] if use_smart_variables: - new_parameterMap[pm[sd]] = applySmartVariables(pm.name + '_' + sdString ) + new_parameterMap[pm[sd]] = applySmartVariables( + pm.name + '_' + sdString + ) else: - new_parameterMap[pm[sd]] = str(pm[sd])#.name + new_parameterMap[pm[sd]] = str(pm[sd]) # .name else: - raise DeveloperError( 'Parameter is not a parameter. Should not happen. Contact developers') + raise DeveloperError( + 'Parameter is not a parameter. Should not happen. Contact developers' + ) for ky, vl in new_variableMap.items(): if ky not in overwrite_dict.keys(): @@ -1299,44 +1428,46 @@ def latex_printer( rep_dict = {} for ky in list(reversed(list(overwrite_dict.keys()))): - if isinstance(ky,(pyo.Var,pyo.Param)): - if use_smart_variables and x_only_mode in [0,3]: + if isinstance(ky, (pyo.Var, pyo.Param)): + if use_smart_variables and x_only_mode in [0, 3]: overwrite_value = applySmartVariables(overwrite_dict[ky]) else: overwrite_value = overwrite_dict[ky] rep_dict[variableMap[ky]] = overwrite_value - elif isinstance(ky,(_GeneralVarData,_ParamData)): + elif isinstance(ky, (_GeneralVarData, _ParamData)): if use_smart_variables and x_only_mode in [3]: overwrite_value = applySmartVariables(overwrite_dict[ky]) else: overwrite_value = overwrite_dict[ky] rep_dict[variableMap[ky]] = overwrite_value - elif isinstance(ky,_SetData): + elif isinstance(ky, _SetData): # already handled pass - elif isinstance(ky,(float,int)): + elif isinstance(ky, (float, int)): # happens when immutable parameters are used, do nothing pass else: - raise ValueError('The overwrite_dict object has a key of invalid type: %s'%(str(ky))) + raise ValueError( + 'The overwrite_dict object has a key of invalid type: %s' % (str(ky)) + ) if not use_smart_variables: for ky, vl in rep_dict.items(): - rep_dict[ky] = vl.replace('_','\\_') + rep_dict[ky] = vl.replace('_', '\\_') label_rep_dict = copy.deepcopy(rep_dict) for ky, vl in label_rep_dict.items(): - label_rep_dict[ky] = vl.replace('{','').replace('}','').replace('\\','') + label_rep_dict[ky] = vl.replace('{', '').replace('}', '').replace('\\', '') splitLines = pstr.split('\n') - for i in range(0,len(splitLines)): + for i in range(0, len(splitLines)): if use_equation_environment: - splitLines[i] = multiple_replace(splitLines[i],rep_dict) + splitLines[i] = multiple_replace(splitLines[i], rep_dict) else: if '\\label{' in splitLines[i]: epr, lbl = splitLines[i].split('\\label{') - epr = multiple_replace(epr,rep_dict) - lbl = multiple_replace(lbl,label_rep_dict) + epr = multiple_replace(epr, rep_dict) + lbl = multiple_replace(lbl, label_rep_dict) splitLines[i] = epr + '\\label{' + lbl pstr = '\n'.join(splitLines) @@ -1357,7 +1488,6 @@ def latex_printer( pstr = '\n'.join(finalLines) - # optional write to output file if filename is not None: fstr = '' From 13a72133f5be5b612a4585dd989d32f3fc7435f8 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 19 Sep 2023 19:33:25 -0400 Subject: [PATCH 0178/1204] remove the support of greybox in LP/NLP gurobi --- pyomo/contrib/mindtpy/cut_generation.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/mindtpy/cut_generation.py b/pyomo/contrib/mindtpy/cut_generation.py index 5613155ee7d..dd60b004830 100644 --- a/pyomo/contrib/mindtpy/cut_generation.py +++ b/pyomo/contrib/mindtpy/cut_generation.py @@ -215,17 +215,18 @@ def add_oa_cuts_for_grey_box( <= 0 ) # TODO: gurobi_persistent currently does not support greybox model. - if ( - config.single_tree - and config.mip_solver == 'gurobi_persistent' - and mip_iter > 0 - and cb_opt is not None - ): - cb_opt.cbLazy( - target_model.MindtPy_utils.cuts.oa_cuts[ - len(target_model.MindtPy_utils.cuts.oa_cuts) - ] - ) + # https://github.com/Pyomo/pyomo/issues/3000 + # if ( + # config.single_tree + # and config.mip_solver == 'gurobi_persistent' + # and mip_iter > 0 + # and cb_opt is not None + # ): + # cb_opt.cbLazy( + # target_model.MindtPy_utils.cuts.oa_cuts[ + # len(target_model.MindtPy_utils.cuts.oa_cuts) + # ] + # ) def add_ecp_cuts( From 20ccbdc5663fef2280408cda46d68bc0c21eebae Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 19 Sep 2023 19:41:48 -0400 Subject: [PATCH 0179/1204] black format --- pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py index d2a55c67f48..945e9a05f51 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py @@ -426,7 +426,7 @@ def load_state_into_pyomo(self, bound_multipliers=None): # not we are minimizing or maximizing - this is done in the ASL interface # for ipopt, but does not appear to be done in cyipopt. obj_sign = 1.0 - # since we will assert the number of objective functions, + # since we will assert the number of objective functions, # we only focus on active objective function. objs = list( m.component_data_objects( From 4288e338459a8d5b785403daf4a30de87ed1a9e9 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 20 Sep 2023 11:54:35 -0400 Subject: [PATCH 0180/1204] update import source --- pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index 562e88ea667..00b78e7b89f 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -1,6 +1,5 @@ -import numpy as np -import pyomo.environ as pyo -from scipy.sparse import coo_matrix +from pyomo.common.dependencies import numpy as np +from pyomo.common.dependencies.scipy.sparse import coo_matrix from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxModel from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock From 8d7f6c5915f881dbdf272cef2ccc83ead5772450 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 20 Sep 2023 12:06:49 -0400 Subject: [PATCH 0181/1204] remove redundant import --- pyomo/contrib/pynumero/interfaces/external_grey_box.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 8fd728a7c9b..3684a6c3234 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -11,8 +11,6 @@ import abc import logging -import numpy as np -from scipy.sparse import coo_matrix from pyomo.common.deprecation import RenamedClass from pyomo.common.log import is_debug_set From 14def43dbbddeb51873fb9b64f76e6230e13ae92 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Sep 2023 10:28:52 -0600 Subject: [PATCH 0182/1204] relax kernel test to track differences in numpy builds --- pyomo/core/tests/unit/test_kernel_register_numpy_types.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/core/tests/unit/test_kernel_register_numpy_types.py b/pyomo/core/tests/unit/test_kernel_register_numpy_types.py index 9ec8722884e..9841410c97d 100644 --- a/pyomo/core/tests/unit/test_kernel_register_numpy_types.py +++ b/pyomo/core/tests/unit/test_kernel_register_numpy_types.py @@ -41,7 +41,8 @@ if hasattr(numpy, 'float96'): numpy_float_names.append('float96') if hasattr(numpy, 'float128'): - numpy_float_names.append('float128') + # On some numpy builds, the name of float128 is longdouble + numpy_float_names.append(numpy.float128.__name__) # Complex numpy_complex_names = [] if numpy_available: From 807e4f5fea8f91b09680ec4cea2a84b1a5fa45fd Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 20 Sep 2023 14:29:27 -0400 Subject: [PATCH 0183/1204] add config check for load_solutions --- pyomo/contrib/mindtpy/algorithm_base_class.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index e1d6d3e98ba..f3877304adb 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -817,7 +817,6 @@ def init_rNLP(self, add_oa_cuts=True): nlp_args = dict(config.nlp_solver_args) update_solver_timelimit(self.nlp_opt, config.nlp_solver, self.timing, config) with SuppressInfeasibleWarning(): - print('solving rnlp') results = self.nlp_opt.solve( self.rnlp, tee=config.nlp_solver_tee, @@ -2236,6 +2235,17 @@ def check_config(self): config.logger.info("Solution pool does not support APPSI solver.") config.mip_solver = 'cplex_persistent' + # related to https://github.com/Pyomo/pyomo/issues/2363 + if ( + 'appsi' in config.mip_solver + or 'appsi' in config.nlp_solver + or ( + config.mip_regularization_solver is not None + and 'appsi' in config.mip_regularization_solver + ) + ): + config.load_solutions = False + ################################################################################################################################ # Feasibility Pump From 30771b332f866bd861abf339c042114a88e7e0c1 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 20 Sep 2023 14:35:29 -0400 Subject: [PATCH 0184/1204] recover numpy and scipy import --- pyomo/contrib/pynumero/interfaces/external_grey_box.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 3684a6c3234..8fd728a7c9b 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -11,6 +11,8 @@ import abc import logging +import numpy as np +from scipy.sparse import coo_matrix from pyomo.common.deprecation import RenamedClass from pyomo.common.log import is_debug_set From 72b142baa8053644b2eca30e21db9f01d71fce68 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 20 Sep 2023 14:43:05 -0400 Subject: [PATCH 0185/1204] change numpy and scipy import --- pyomo/contrib/pynumero/interfaces/external_grey_box.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 8fd728a7c9b..1594098069c 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -11,8 +11,8 @@ import abc import logging -import numpy as np -from scipy.sparse import coo_matrix +from pyomo.common.dependencies import numpy as np +from pyomo.common.dependencies.scipy.sparse import coo_matrix from pyomo.common.deprecation import RenamedClass from pyomo.common.log import is_debug_set From ff401df0a9e732f80635a1a7af5abc43c2176bff Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 20 Sep 2023 22:34:31 -0400 Subject: [PATCH 0186/1204] change scipy import --- pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py | 4 ++-- pyomo/contrib/pynumero/interfaces/external_grey_box.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index 00b78e7b89f..186db3bb5a2 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -1,5 +1,5 @@ from pyomo.common.dependencies import numpy as np -from pyomo.common.dependencies.scipy.sparse import coo_matrix +import pyomo.common.dependencies.scipy.sparse as scipy_sparse from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxModel from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock @@ -135,4 +135,4 @@ def evaluate_jacobian_outputs(self): row[0], col[4], data[4] = (0, 4, 0.5) # y3 # sparse matrix - return coo_matrix((data, (row, col)), shape=(1, 5)) + return scipy_sparse.coo_matrix((data, (row, col)), shape=(1, 5)) diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 1594098069c..8fd728a7c9b 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -11,8 +11,8 @@ import abc import logging -from pyomo.common.dependencies import numpy as np -from pyomo.common.dependencies.scipy.sparse import coo_matrix +import numpy as np +from scipy.sparse import coo_matrix from pyomo.common.deprecation import RenamedClass from pyomo.common.log import is_debug_set From 6d442b24542d524c0ff414a6009408d27deb39df Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 20 Sep 2023 22:38:14 -0400 Subject: [PATCH 0187/1204] disable ipopt warmstart for feasibility subproblem solver --- pyomo/contrib/mindtpy/algorithm_base_class.py | 2 +- pyomo/contrib/mindtpy/util.py | 21 ++++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index f3877304adb..c254c5d3f3d 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -2574,7 +2574,7 @@ def initialize_subsolvers(self): self.nlp_opt, config.nlp_solver, config ) set_solver_constraint_violation_tolerance( - self.feasibility_nlp_opt, config.nlp_solver, config + self.feasibility_nlp_opt, config.nlp_solver, config, warm_start=False ) self.set_appsi_solver_update_config() diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index e336715cc8f..068cd61aba1 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -566,7 +566,7 @@ def set_solver_mipgap(opt, solver_name, config): opt.options['add_options'].append('option optcr=%s;' % config.mip_solver_mipgap) -def set_solver_constraint_violation_tolerance(opt, solver_name, config): +def set_solver_constraint_violation_tolerance(opt, solver_name, config, warm_start=True): """Set constraint violation tolerance for solvers. Parameters @@ -600,15 +600,16 @@ def set_solver_constraint_violation_tolerance(opt, solver_name, config): opt.options['add_options'].append( 'constr_viol_tol ' + str(config.zero_tolerance) ) - # Ipopt warmstart options - opt.options['add_options'].append( - 'warm_start_init_point yes\n' - 'warm_start_bound_push 1e-9\n' - 'warm_start_bound_frac 1e-9\n' - 'warm_start_slack_bound_frac 1e-9\n' - 'warm_start_slack_bound_push 1e-9\n' - 'warm_start_mult_bound_push 1e-9\n' - ) + if warm_start: + # Ipopt warmstart options + opt.options['add_options'].append( + 'warm_start_init_point yes\n' + 'warm_start_bound_push 1e-9\n' + 'warm_start_bound_frac 1e-9\n' + 'warm_start_slack_bound_frac 1e-9\n' + 'warm_start_slack_bound_push 1e-9\n' + 'warm_start_mult_bound_push 1e-9\n' + ) elif config.nlp_solver_args['solver'] == 'conopt': opt.options['add_options'].append( 'RTNWMA ' + str(config.zero_tolerance) From a6e92c53e63b9febad79e8a19a5230c8c308328a Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 21 Sep 2023 01:44:41 -0400 Subject: [PATCH 0188/1204] create new copy_var_list_values function --- pyomo/contrib/mindtpy/algorithm_base_class.py | 4 ++- pyomo/contrib/mindtpy/single_tree.py | 3 +- pyomo/contrib/mindtpy/util.py | 29 +++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index c254c5d3f3d..836df9fff78 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -56,7 +56,6 @@ SuppressInfeasibleWarning, _DoNothing, lower_logger_level_to, - copy_var_list_values, get_main_elapsed_time, time_code, ) @@ -81,6 +80,7 @@ set_solver_mipgap, set_solver_constraint_violation_tolerance, update_solver_timelimit, + copy_var_list_values ) single_tree, single_tree_available = attempt_import('pyomo.contrib.mindtpy.single_tree') @@ -866,12 +866,14 @@ def init_rNLP(self, add_oa_cuts=True): self.rnlp.MindtPy_utils.variable_list, self.mip.MindtPy_utils.variable_list, config, + ignore_integrality=True ) if config.init_strategy == 'FP': copy_var_list_values( self.rnlp.MindtPy_utils.variable_list, self.working_model.MindtPy_utils.variable_list, config, + ignore_integrality=True ) self.add_cuts( dual_values=dual_values, diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index 9776920f434..f3be27cbc4c 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -16,9 +16,8 @@ from pyomo.repn import generate_standard_repn import pyomo.core.expr as EXPR from math import copysign -from pyomo.contrib.mindtpy.util import get_integer_solution +from pyomo.contrib.mindtpy.util import get_integer_solution, copy_var_list_values from pyomo.contrib.gdpopt.util import ( - copy_var_list_values, get_main_elapsed_time, time_code, ) diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index 068cd61aba1..59490248e49 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -23,6 +23,7 @@ RangeSet, ConstraintList, TransformationFactory, + value ) from pyomo.repn import generate_standard_repn from pyomo.contrib.mcpp.pyomo_mcpp import mcpp_available, McCormick @@ -964,3 +965,31 @@ def generate_norm_constraint(fp_nlp_model, mip_model, config): mip_model.MindtPy_utils.discrete_variable_list, ): fp_nlp_model.norm_constraint.add(nlp_var - mip_var.value <= rhs) + +def copy_var_list_values(from_list, to_list, config, + skip_stale=False, skip_fixed=True, + ignore_integrality=False): + """Copy variable values from one list to another. + Rounds to Binary/Integer if necessary + Sets to zero for NonNegativeReals if necessary + """ + for v_from, v_to in zip(from_list, to_list): + if skip_stale and v_from.stale: + continue # Skip stale variable values. + if skip_fixed and v_to.is_fixed(): + continue # Skip fixed variables. + var_val = value(v_from, exception=False) + rounded_val = int(round(var_val)) + if var_val in v_to.domain: + v_to.set_value(value(v_from, exception=False)) + elif ignore_integrality and v_to.is_integer(): + v_to.set_value(value(v_from, exception=False)) + elif abs(var_val) <= config.zero_tolerance and 0 in v_to.domain: + v_to.set_value(0) + elif v_to.is_integer() and (math.fabs(var_val - rounded_val) <= + config.integer_tolerance): + print('var_val', var_val) + v_to.pprint() + v_to.set_value(rounded_val) + else: + raise From f31e0050e14764a17f86fa5052837bd7032fe672 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 21 Sep 2023 15:14:12 -0600 Subject: [PATCH 0189/1204] relax kernel test to track differences in numpy builds --- pyomo/core/tests/unit/test_kernel_register_numpy_types.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/core/tests/unit/test_kernel_register_numpy_types.py b/pyomo/core/tests/unit/test_kernel_register_numpy_types.py index 9841410c97d..117de5c5f4c 100644 --- a/pyomo/core/tests/unit/test_kernel_register_numpy_types.py +++ b/pyomo/core/tests/unit/test_kernel_register_numpy_types.py @@ -52,7 +52,8 @@ if hasattr(numpy, 'complex192'): numpy_complex_names.append('complex192') if hasattr(numpy, 'complex256'): - numpy_complex_names.append('complex256') + # On some numpy builds, the name of complex256 is clongdouble + numpy_complex_names.append(numpy.complex256.__name__) class TestNumpyRegistration(unittest.TestCase): From d82dcde63ebcb402d626b85608f02ae4325cdc4d Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 21 Sep 2023 17:59:11 -0400 Subject: [PATCH 0190/1204] fix import error --- pyomo/contrib/mindtpy/algorithm_base_class.py | 7 +++---- pyomo/contrib/mindtpy/tests/MINLP_simple.py | 5 +++-- pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index f3877304adb..533b4daa88f 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -34,7 +34,6 @@ SolutionStatus, SolverStatus, ) -from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock from pyomo.core import ( minimize, maximize, @@ -85,7 +84,7 @@ single_tree, single_tree_available = attempt_import('pyomo.contrib.mindtpy.single_tree') tabu_list, tabu_list_available = attempt_import('pyomo.contrib.mindtpy.tabu_list') - +egb = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box')[0] class _MindtPyAlgorithm(object): def __init__(self, **kwds): @@ -326,7 +325,7 @@ def build_ordered_component_lists(self, model): ) util_block.grey_box_list = list( model.component_data_objects( - ctype=ExternalGreyBoxBlock, active=True, descend_into=(Block) + ctype=egb.ExternalGreyBoxBlock, active=True, descend_into=(Block) ) ) util_block.linear_constraint_list = list( @@ -359,7 +358,7 @@ def build_ordered_component_lists(self, model): util_block.variable_list = list( v for v in model.component_data_objects( - ctype=Var, descend_into=(Block, ExternalGreyBoxBlock) + ctype=Var, descend_into=(Block, egb.ExternalGreyBoxBlock) ) if v in var_set ) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple.py b/pyomo/contrib/mindtpy/tests/MINLP_simple.py index 91976997c34..7498b65adad 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple.py @@ -39,12 +39,13 @@ ) from pyomo.common.collections import ComponentMap from pyomo.contrib.mindtpy.tests.MINLP_simple_grey_box import GreyBoxModel -from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock +from pyomo.common.dependencies import attempt_import +egb = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box')[0] def build_model_external(m): ex_model = GreyBoxModel(initial={"X1": 0, "X2": 0, "Y1": 0, "Y2": 1, "Y3": 1}) - m.egb = ExternalGreyBoxBlock() + m.egb = egb.ExternalGreyBoxBlock() m.egb.set_external_model(ex_model) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index 186db3bb5a2..d6af495d504 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -1,10 +1,10 @@ from pyomo.common.dependencies import numpy as np import pyomo.common.dependencies.scipy.sparse as scipy_sparse -from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxModel -from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock +from pyomo.common.dependencies import attempt_import +egb = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box')[0] -class GreyBoxModel(ExternalGreyBoxModel): +class GreyBoxModel(egb.ExternalGreyBoxModel): """Greybox model to compute the example OF.""" def __init__(self, initial, use_exact_derivatives=True, verbose=True): From cb1c2a94f5d79a937cdfcc91512a34e244767923 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 21 Sep 2023 17:59:44 -0400 Subject: [PATCH 0191/1204] black format --- pyomo/contrib/mindtpy/algorithm_base_class.py | 1 + pyomo/contrib/mindtpy/tests/MINLP_simple.py | 1 + pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py | 1 + 3 files changed, 3 insertions(+) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 533b4daa88f..d562c924a7d 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -86,6 +86,7 @@ tabu_list, tabu_list_available = attempt_import('pyomo.contrib.mindtpy.tabu_list') egb = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box')[0] + class _MindtPyAlgorithm(object): def __init__(self, **kwds): """ diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple.py b/pyomo/contrib/mindtpy/tests/MINLP_simple.py index 7498b65adad..04315f59458 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple.py @@ -40,6 +40,7 @@ from pyomo.common.collections import ComponentMap from pyomo.contrib.mindtpy.tests.MINLP_simple_grey_box import GreyBoxModel from pyomo.common.dependencies import attempt_import + egb = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box')[0] diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index d6af495d504..9fccf1e7108 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -1,6 +1,7 @@ from pyomo.common.dependencies import numpy as np import pyomo.common.dependencies.scipy.sparse as scipy_sparse from pyomo.common.dependencies import attempt_import + egb = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box')[0] From e80c6dcf607ac82277a89f64ae42b826dd5d9319 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 21 Sep 2023 18:59:29 -0400 Subject: [PATCH 0192/1204] update mindtpy import in pynumero --- pyomo/contrib/pynumero/interfaces/external_grey_box.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 8fd728a7c9b..8b815b84335 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -11,7 +11,7 @@ import abc import logging -import numpy as np +from pyomo.common.dependencies import numpy as np from scipy.sparse import coo_matrix from pyomo.common.deprecation import RenamedClass From f766c0a9d6f66da8fea8b11f277bb719ef94f48f Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 21 Sep 2023 19:23:03 -0400 Subject: [PATCH 0193/1204] update log format --- pyomo/contrib/mindtpy/algorithm_base_class.py | 30 +++++++++++++++++-- pyomo/contrib/mindtpy/util.py | 2 -- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 836df9fff78..514c2edaedb 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -124,6 +124,9 @@ def __init__(self, **kwds): self.fixed_nlp_log_formatter = ( '{:1}{:>9} {:>15} {:>15g} {:>12g} {:>12g} {:>7.2%} {:>7.2f}' ) + self.infeasible_fixed_nlp_log_formatter = ( + '{:1}{:>9} {:>15} {:>15} {:>12g} {:>12g} {:>7.2%} {:>7.2f}' + ) self.log_note_formatter = ' {:>9} {:>15} {:>15}' # Flag indicating whether the solution improved in the past @@ -1210,7 +1213,18 @@ def handle_subproblem_infeasible(self, fixed_nlp, cb_opt=None): # TODO try something else? Reinitialize with different initial # value? config = self.config - config.logger.info('NLP subproblem was locally infeasible.') + config.logger.info( + self.infeasible_fixed_nlp_log_formatter.format( + ' ', + self.nlp_iter, + 'Fixed NLP', + 'Infeasible', + self.primal_bound, + self.dual_bound, + self.rel_gap, + get_main_elapsed_time(self.timing), + ) + ) self.nlp_infeasible_counter += 1 if config.calculate_dual_at_solution: for c in fixed_nlp.MindtPy_utils.constraint_list: @@ -1232,7 +1246,7 @@ def handle_subproblem_infeasible(self, fixed_nlp, cb_opt=None): # elif var.has_lb() and abs(value(var) - var.lb) < config.absolute_bound_tolerance: # fixed_nlp.ipopt_zU_out[var] = -1 - config.logger.info('Solving feasibility problem') + # config.logger.info('Solving feasibility problem') feas_subproblem, feas_subproblem_results = self.solve_feasibility_subproblem() # TODO: do we really need this? if self.should_terminate: @@ -1366,6 +1380,18 @@ def solve_feasibility_subproblem(self): self.handle_feasibility_subproblem_tc( feas_soln.solver.termination_condition, MindtPy ) + config.logger.info( + self.fixed_nlp_log_formatter.format( + ' ', + self.nlp_iter, + 'Feasibility NLP', + value(feas_subproblem.MindtPy_utils.feas_obj), + self.primal_bound, + self.dual_bound, + self.rel_gap, + get_main_elapsed_time(self.timing), + ) + ) MindtPy.feas_opt.deactivate() for constr in MindtPy.nonlinear_constraint_list: constr.activate() diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index 59490248e49..4a4b77767a9 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -988,8 +988,6 @@ def copy_var_list_values(from_list, to_list, config, v_to.set_value(0) elif v_to.is_integer() and (math.fabs(var_val - rounded_val) <= config.integer_tolerance): - print('var_val', var_val) - v_to.pprint() v_to.set_value(rounded_val) else: raise From 89596661fb641287e64263d95925cb1d065a3d85 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 21 Sep 2023 19:28:09 -0400 Subject: [PATCH 0194/1204] remove redundant scipy import --- pyomo/contrib/pynumero/interfaces/external_grey_box.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 8b815b84335..a1a01d751e7 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -12,7 +12,6 @@ import abc import logging from pyomo.common.dependencies import numpy as np -from scipy.sparse import coo_matrix from pyomo.common.deprecation import RenamedClass from pyomo.common.log import is_debug_set From 61f42d49f00f6e0dc0022ffaeab2c078c1fcd958 Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 21 Sep 2023 20:17:46 -0400 Subject: [PATCH 0195/1204] Standardize contents of the results object --- pyomo/contrib/pyros/pyros.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index e8decbea451..4800b346f70 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -1007,13 +1007,9 @@ def solve( ): load_final_solution(model_data, pyros_soln.master_soln, config) - # === Return time info - model_data.total_cpu_time = get_main_elapsed_time(model_data.timing) - iterations = pyros_soln.total_iters + 1 - - # === Return config to user - return_soln.config = config - # Report the negative of the objective value if it was originally maximize, since we use the minimize form in the algorithm + # Report the negative of the objective value if it was + # originally maximize, since we use the minimize form + # in the algorithm if next(model.component_data_objects(Objective)).sense == maximize: negation = -1 else: @@ -1029,9 +1025,7 @@ def solve( return_soln.pyros_termination_condition = ( pyros_soln.pyros_termination_condition ) - - return_soln.time = model_data.total_cpu_time - return_soln.iterations = iterations + return_soln.iterations = pyros_soln.total_iters + 1 # === Remove util block model.del_component(model_data.util_block) @@ -1039,13 +1033,15 @@ def solve( del pyros_soln.util_block del pyros_soln.working_model else: + return_soln.final_objective_value = None return_soln.pyros_termination_condition = ( pyrosTerminationCondition.robust_infeasible ) - return_soln.final_objective_value = None - return_soln.time = get_main_elapsed_time(model_data.timing) return_soln.iterations = 0 + return_soln.config = config + return_soln.time = model_data.timing.get_main_elapsed_time() + # log termination-related messages config.progress_logger.info(return_soln.pyros_termination_condition.message) config.progress_logger.info("-" * self._LOG_LINE_LENGTH) From 0ee1253542ac676106aeb9503d374536abe903a4 Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 21 Sep 2023 20:37:01 -0400 Subject: [PATCH 0196/1204] Modularize logging of config entries --- pyomo/contrib/pyros/pyros.py | 59 +++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 4800b346f70..c17d66485df 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -796,6 +796,40 @@ def _log_disclaimer(self, logger, **log_kwargs): logger.log(msg="https://github.com/Pyomo/pyomo/issues/new/choose", **log_kwargs) logger.log(msg="=" * self._LOG_LINE_LENGTH, **log_kwargs) + def _log_config(self, logger, config, exclude_options=None, **log_kwargs): + """ + Log PyROS solver options. + + Parameters + ---------- + logger : logging.Logger + Logger for the solver options. + config : ConfigDict + PyROS solver options. + exclude_options : None or iterable of str, optional + Options (keys of the ConfigDict) to exclude from + logging. If `None` passed, then the names of the + required arguments to ``self.solve()`` are skipped. + **log_kwargs : dict, optional + Keyword arguments to each statement of ``logger.log()``. + """ + # log solver options + if exclude_options is None: + exclude_options = [ + "first_stage_variables", + "second_stage_variables", + "uncertain_params", + "uncertainty_set", + "local_solver", + "global_solver", + ] + + logger.log(msg="Solver options:", **log_kwargs) + for key, val in config.items(): + if key not in exclude_options: + logger.log(msg=f" {key}={val!r}", **log_kwargs) + logger.log(msg="-" * self._LOG_LINE_LENGTH, **log_kwargs) + def solve( self, model, @@ -883,23 +917,14 @@ def solve( is_main_timer=True, ): # output intro and disclaimer - self._log_intro(config.progress_logger, level=logging.INFO) - self._log_disclaimer(config.progress_logger, level=logging.INFO) - - # log solver options - excl_from_config_display = [ - "first_stage_variables", - "second_stage_variables", - "uncertain_params", - "uncertainty_set", - "local_solver", - "global_solver", - ] - config.progress_logger.info("Solver options:") - for key, val in config.items(): - if key not in excl_from_config_display: - config.progress_logger.info(f" {key}={val!r}") - config.progress_logger.info("-" * self._LOG_LINE_LENGTH) + self._log_intro(logger=config.progress_logger, level=logging.INFO) + self._log_disclaimer(logger=config.progress_logger, level=logging.INFO) + self._log_config( + logger=config.progress_logger, + config=config, + exclude_options=None, + level=logging.INFO, + ) # begin preprocessing config.progress_logger.info("Preprocessing...") From f9018a164a20a432d1258697b1ac4431e0517464 Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 21 Sep 2023 20:51:05 -0400 Subject: [PATCH 0197/1204] Ensure objective sense accounted for in results reporting --- pyomo/contrib/pyros/pyros.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index c17d66485df..8b463d96169 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -1032,19 +1032,16 @@ def solve( ): load_final_solution(model_data, pyros_soln.master_soln, config) - # Report the negative of the objective value if it was - # originally maximize, since we use the minimize form - # in the algorithm - if next(model.component_data_objects(Objective)).sense == maximize: - negation = -1 - else: - negation = 1 + # account for sense of the original model objective + # when reporting the final PyROS (master) objective, + # since maximization objective is changed to + # minimization objective during preprocessing if config.objective_focus == ObjectiveType.nominal: - return_soln.final_objective_value = negation * value( + return_soln.final_objective_value = active_obj.sense * value( pyros_soln.master_soln.master_model.obj ) elif config.objective_focus == ObjectiveType.worst_case: - return_soln.final_objective_value = negation * value( + return_soln.final_objective_value = active_obj.sense * value( pyros_soln.master_soln.master_model.zeta ) return_soln.pyros_termination_condition = ( From 899faa48c8e58e7f7f4b559900fc82822492625f Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 21 Sep 2023 21:18:41 -0400 Subject: [PATCH 0198/1204] Add more detailed master feasibility problem logging --- pyomo/contrib/pyros/master_problem_methods.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index 2db9410ca95..9543f8e4df7 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -235,6 +235,11 @@ def solve_master_feasibility_problem(model_data, config): """ model = construct_master_feasibility_problem(model_data, config) + active_obj = next(model.component_data_objects(Objective, active=True)) + config.progress_logger.debug( + f"Initial master feasibility objective (total slack): {value(active_obj)}" + ) + if config.solve_master_globally: solver = config.global_solver else: @@ -274,6 +279,18 @@ def solve_master_feasibility_problem(model_data, config): } if results.solver.termination_condition in feasible_terminations: model.solutions.load_from(results) + config.progress_logger.debug( + f"Final master feasibility objective (total slack): {value(active_obj)}" + ) + else: + config.progress_logger.warning( + "Could not successfully solve master feasibility problem " + f" of iteration {model_data.iteration} with primary subordinate " + f"{'global' if config.solve_master_globally else 'local'} solver " + "to acceptable level. " + f"Termination stats:\n{results.solver}" + "Maintaining unoptimized point for master problem initialization." + ) # load master feasibility point to master model for master_var, feas_var in model_data.feasibility_problem_varmap: From ba67d71ef2d8299a542fe0614a82fab9a3922e2a Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 22 Sep 2023 00:00:11 -0400 Subject: [PATCH 0199/1204] Update separation initial infeas msgs --- .../pyros/separation_problem_methods.py | 63 +++++++++++++------ 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index 9e3e0b72f07..ce76afc8ae7 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -537,7 +537,7 @@ def get_worst_discrete_separation_solution( def get_con_name_repr( - separation_model, perf_con, with_orig_name=True, with_obj_name=True + separation_model, con, with_orig_name=True, with_obj_name=True ): """ Get string representation of performance constraint @@ -548,9 +548,8 @@ def get_con_name_repr( ---------- separation_model : ConcreteModel Separation model. - perf_con : ScalarConstraint or ConstraintData - Performance constraint for which to get the - representation + con : ScalarConstraint or ConstraintData + Constraint for which to get the representation. with_orig_name : bool, optional If constraint was added during construction of the separation problem (i.e. if the constraint is a member of @@ -559,7 +558,8 @@ def get_con_name_repr( `perf_con` was created. with_obj_name : bool, optional Include name of separation model objective to which - performance constraint is mapped. + constraint is mapped. Applicable only to performance + constraints of the separation problem. Returns ------- @@ -572,18 +572,18 @@ def get_con_name_repr( # check performance constraint was not added # at construction of separation problem orig_con = separation_model.util.map_new_constraint_list_to_original_con.get( - perf_con, perf_con + con, con ) - if orig_con is not perf_con: + if orig_con is not con: qual_strs.append(f"originally {orig_con.name!r}") if with_obj_name: objectives_map = separation_model.util.map_obj_to_constr - separation_obj = objectives_map[perf_con] + separation_obj = objectives_map[con] qual_strs.append(f"mapped to objective {separation_obj.name!r}") - final_qual_str = f"({', '.join(qual_strs)})" if qual_strs else "" + final_qual_str = f" ({', '.join(qual_strs)})" if qual_strs else "" - return f"{perf_con.name!r} {final_qual_str}" + return f"{con.name!r}{final_qual_str}" def perform_separation_loop(model_data, config, solve_globally): @@ -854,7 +854,7 @@ def evaluate_performance_constraint_violations( return (violating_param_realization, scaled_violations, constraint_violated) -def initialize_separation(model_data, config): +def initialize_separation(perf_con_to_maximize, model_data, config): """ Initialize separation problem variables, and fix all first-stage variables to their corresponding values from most recent @@ -862,6 +862,9 @@ def initialize_separation(model_data, config): Parameters ---------- + perf_con_to_maximize : ConstraintData + Performance constraint whose violation is to be maximized + for the separation problem of interest. model_data : SeparationProblemData Separation problem data. config : ConfigDict @@ -943,13 +946,35 @@ def get_parent_master_blk(var): "All h(x,q) type constraints must be deactivated in separation." ) - # check: initial point feasible? + # confirm the initial point is feasible for cases where + # we expect it to be (i.e. non-discrete uncertainty sets). + # otherwise, log the violated constraints + tol = ABS_CON_CHECK_FEAS_TOL + perf_con_name_repr = get_con_name_repr( + separation_model=model_data.separation_model, + con=perf_con_to_maximize, + with_orig_name=True, + with_obj_name=True, + ) + uncertainty_set_is_discrete = ( + config.uncertainty_set.geometry + is Geometry.DISCRETE_SCENARIOS + ) for con in sep_model.component_data_objects(Constraint, active=True): - lb, val, ub = value(con.lb), value(con.body), value(con.ub) - lb_viol = val < lb - ABS_CON_CHECK_FEAS_TOL if lb is not None else False - ub_viol = val > ub + ABS_CON_CHECK_FEAS_TOL if ub is not None else False - if lb_viol or ub_viol: - config.progress_logger.debug(con.name, lb, val, ub) + lslack, uslack = con.lslack(), con.uslack() + if (lslack < -tol or uslack < -tol) and not uncertainty_set_is_discrete: + con_name_repr = get_con_name_repr( + separation_model=model_data.separation_model, + con=con, + with_orig_name=True, + with_obj_name=False, + ) + config.progress_logger.debug( + f"Initial point for separation of performance constraint " + f"{perf_con_name_repr} violates the model constraint " + f"{con_name_repr} by more than {tol}. " + f"(lslack={con.lslack()}, uslack={con.uslack()})" + ) locally_acceptable = {tc.optimal, tc.locallyOptimal, tc.globallyOptimal} @@ -1000,14 +1025,14 @@ def solver_call_separation( # get name of constraint for loggers con_name_repr = get_con_name_repr( separation_model=nlp_model, - perf_con=perf_con_to_maximize, + con=perf_con_to_maximize, with_orig_name=True, with_obj_name=True, ) solve_mode = "global" if solve_globally else "local" # === Initialize separation problem; fix first-stage variables - initialize_separation(model_data, config) + initialize_separation(perf_con_to_maximize, model_data, config) separation_obj.activate() From 442c0a2249493fabb04a0a513f7b5882e443b5a0 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 22 Sep 2023 00:44:40 -0400 Subject: [PATCH 0200/1204] Update debug-level logging for master problems --- pyomo/contrib/pyros/master_problem_methods.py | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index 9543f8e4df7..572e0b790a8 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -236,8 +236,10 @@ def solve_master_feasibility_problem(model_data, config): model = construct_master_feasibility_problem(model_data, config) active_obj = next(model.component_data_objects(Objective, active=True)) + + config.progress_logger.debug("Solving master feasibility problem") config.progress_logger.debug( - f"Initial master feasibility objective (total slack): {value(active_obj)}" + f" Initial objective (total slack): {value(active_obj)}" ) if config.solve_master_globally: @@ -260,7 +262,7 @@ def solve_master_feasibility_problem(model_data, config): config.progress_logger.error( f"Optimizer {repr(solver)} encountered exception " "attempting to solve master feasibility problem in iteration " - f"{model_data.iteration}" + f"{model_data.iteration}." ) raise else: @@ -280,7 +282,13 @@ def solve_master_feasibility_problem(model_data, config): if results.solver.termination_condition in feasible_terminations: model.solutions.load_from(results) config.progress_logger.debug( - f"Final master feasibility objective (total slack): {value(active_obj)}" + f" Final objective (total slack): {value(active_obj)}" + ) + config.progress_logger.debug( + f" Termination condition: {results.solver.termination_condition}" + ) + config.progress_logger.debug( + f" Solve time: {getattr(results.solver, TIC_TOC_SOLVE_TIME_ATTR)}s" ) else: config.progress_logger.warning( @@ -563,7 +571,7 @@ def minimize_dr_vars(model_data, config): mvar.set_value(value(pvar), skip_validation=True) config.progress_logger.debug(f" Optimized DR norm: {value(polishing_obj)}") - config.progress_logger.debug("Polished Master objective:") + config.progress_logger.debug(" Polished Master objective:") # print master solution if config.objective_focus == ObjectiveType.worst_case: @@ -579,15 +587,15 @@ def minimize_dr_vars(model_data, config): # debugging: summarize objective breakdown worst_master_blk = model_data.master_model.scenarios[worst_blk_idx] config.progress_logger.debug( - " First-stage objective " f"{value(worst_master_blk.first_stage_objective)}" + " First-stage objective: " f"{value(worst_master_blk.first_stage_objective)}" ) config.progress_logger.debug( - " Second-stage objective " f"{value(worst_master_blk.second_stage_objective)}" + " Second-stage objective: " f"{value(worst_master_blk.second_stage_objective)}" ) polished_master_obj = value( worst_master_blk.first_stage_objective + worst_master_blk.second_stage_objective ) - config.progress_logger.debug(f" Objective {polished_master_obj}") + config.progress_logger.debug(f" Objective: {polished_master_obj}") return results, True @@ -796,17 +804,23 @@ def solver_call_master(model_data, config, solver, solve_data): ) # debugging: log breakdown of master objective - config.progress_logger.debug("Master objective") + config.progress_logger.debug(" Optimized master objective breakdown:") config.progress_logger.debug( - f" First-stage objective {master_soln.first_stage_objective}" + f" First-stage objective: {master_soln.first_stage_objective}" ) config.progress_logger.debug( - f" Second-stage objective {master_soln.second_stage_objective}" + f" Second-stage objective: {master_soln.second_stage_objective}" ) master_obj = ( master_soln.first_stage_objective + master_soln.second_stage_objective ) - config.progress_logger.debug(f" Objective {master_obj}") + config.progress_logger.debug(f" Objective: {master_obj}") + config.progress_logger.debug( + f" Termination condition: {results.solver.termination_condition}" + ) + config.progress_logger.debug( + f" Solve time: {getattr(results.solver, TIC_TOC_SOLVE_TIME_ATTR)}s" + ) master_soln.nominal_block = nlp_model.scenarios[0, 0] master_soln.results = results From c80d64c6edc6c3e376202033b4f58325ef720d9e Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 22 Sep 2023 00:56:54 -0400 Subject: [PATCH 0201/1204] Apply black --- pyomo/contrib/pyros/separation_problem_methods.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index ce76afc8ae7..94d9bb53553 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -536,9 +536,7 @@ def get_worst_discrete_separation_solution( ) -def get_con_name_repr( - separation_model, con, with_orig_name=True, with_obj_name=True -): +def get_con_name_repr(separation_model, con, with_orig_name=True, with_obj_name=True): """ Get string representation of performance constraint and any other modeling components to which it has @@ -957,8 +955,7 @@ def get_parent_master_blk(var): with_obj_name=True, ) uncertainty_set_is_discrete = ( - config.uncertainty_set.geometry - is Geometry.DISCRETE_SCENARIOS + config.uncertainty_set.geometry is Geometry.DISCRETE_SCENARIOS ) for con in sep_model.component_data_objects(Constraint, active=True): lslack, uslack = con.lslack(), con.uslack() From e2d642a3b6467157560961deca2de9528dcfb7a4 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 22 Sep 2023 01:15:10 -0400 Subject: [PATCH 0202/1204] Add additional separation debug message --- pyomo/contrib/pyros/separation_problem_methods.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index 94d9bb53553..81fe06ba6cb 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -770,6 +770,8 @@ def perform_separation_loop(model_data, config, solve_globally): # violating separation problem solution now chosen. # exit loop break + else: + config.progress_logger.debug("No violated performance constraints found.") return SeparationLoopResults( solver_call_results=all_solve_call_results, From bea5834bd97d6be29b80299328d597db76b6c6e4 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 22 Sep 2023 01:17:25 -0400 Subject: [PATCH 0203/1204] Update online doc log level table --- doc/OnlineDocs/contributed_packages/pyros.rst | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/doc/OnlineDocs/contributed_packages/pyros.rst b/doc/OnlineDocs/contributed_packages/pyros.rst index f99fa3a94ab..485c253e5ce 100644 --- a/doc/OnlineDocs/contributed_packages/pyros.rst +++ b/doc/OnlineDocs/contributed_packages/pyros.rst @@ -787,10 +787,15 @@ for a basic tutorial, see the :doc:`logging HOWTO `. * Iteration log table * Termination details: message, timing breakdown, summary of statistics * - :py:obj:`logging.DEBUG` - - * Termination outcomes (and/or summary of statistics) - for every subproblem + - * Termination outcomes and summary of statistics for + every master feasility, master, and DR polishing problem + * Progress updates for the separation procedure + * Separation subproblem initial point infeasibilities * Summary of separation loop outcomes: performance constraints - violated, uncertain parameter value added to master + violated, uncertain parameter scenario added to the + master problem + * Uncertain parameter scenarios added to the master problem + thus far An example of an output log produced through the default PyROS progress logger is shown in From e417da635959722de364047bd1c59dbee658e06a Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Fri, 22 Sep 2023 00:49:16 -0600 Subject: [PATCH 0204/1204] reworking the latex printer --- pyomo/util/latex_map_generator.py | 460 +++++++++ pyomo/util/latex_printer.py | 467 +++------ pyomo/util/latex_printer_1.py | 1506 +++++++++++++++++++++++++++++ 3 files changed, 2080 insertions(+), 353 deletions(-) create mode 100644 pyomo/util/latex_map_generator.py create mode 100644 pyomo/util/latex_printer_1.py diff --git a/pyomo/util/latex_map_generator.py b/pyomo/util/latex_map_generator.py new file mode 100644 index 00000000000..afa383d3217 --- /dev/null +++ b/pyomo/util/latex_map_generator.py @@ -0,0 +1,460 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2023 +# 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 math +import copy +import re +import pyomo.environ as pyo +from pyomo.core.expr.visitor import StreamBasedExpressionVisitor +from pyomo.core.expr import ( + NegationExpression, + ProductExpression, + DivisionExpression, + PowExpression, + AbsExpression, + UnaryFunctionExpression, + MonomialTermExpression, + LinearExpression, + SumExpression, + EqualityExpression, + InequalityExpression, + RangedExpression, + Expr_ifExpression, + ExternalFunctionExpression, +) + +from pyomo.core.expr.visitor import identify_components +from pyomo.core.expr.base import ExpressionBase +from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData +from pyomo.core.base.objective import ScalarObjective, _GeneralObjectiveData +import pyomo.core.kernel as kernel +from pyomo.core.expr.template_expr import ( + GetItemExpression, + GetAttrExpression, + TemplateSumExpression, + IndexTemplate, + Numeric_GetItemExpression, + templatize_constraint, + resolve_template, + templatize_rule, +) +from pyomo.core.base.var import ScalarVar, _GeneralVarData, IndexedVar +from pyomo.core.base.param import _ParamData, ScalarParam, IndexedParam +from pyomo.core.base.set import _SetData +from pyomo.core.base.constraint import ScalarConstraint, IndexedConstraint +from pyomo.common.collections.component_map import ComponentMap +from pyomo.common.collections.component_set import ComponentSet + +from pyomo.core.base.external import _PythonCallbackFunctionID + +from pyomo.core.base.block import _BlockData + +from pyomo.repn.util import ExprType + +from pyomo.common import DeveloperError + +_CONSTANT = ExprType.CONSTANT +_MONOMIAL = ExprType.MONOMIAL +_GENERAL = ExprType.GENERAL + +def applySmartVariables(name): + splitName = name.split('_') + # print(splitName) + + filteredName = [] + + prfx = '' + psfx = '' + for i in range(0, len(splitName)): + se = splitName[i] + if se != 0: + if se == 'dot': + prfx = '\\dot{' + psfx = '}' + elif se == 'hat': + prfx = '\\hat{' + psfx = '}' + elif se == 'bar': + prfx = '\\bar{' + psfx = '}' + elif se == 'mathcal': + prfx = '\\mathcal{' + psfx = '}' + else: + filteredName.append(se) + else: + filteredName.append(se) + + joinedName = prfx + filteredName[0] + psfx + # print(joinedName) + # print(filteredName) + for i in range(1, len(filteredName)): + joinedName += '_{' + filteredName[i] + + joinedName += '}' * (len(filteredName) - 1) + # print(joinedName) + + return joinedName + +# def multiple_replace(pstr, rep_dict): +# pattern = re.compile("|".join(rep_dict.keys()), flags=re.DOTALL) +# return pattern.sub(lambda x: rep_dict[x.group(0)], pstr) + + +def latex_component_map_generator( + pyomo_component, + use_smart_variables=False, + x_only_mode=0, + overwrite_dict=None, + # latex_component_map=None, +): + """This function produces a string that can be rendered as LaTeX + + As described, this function produces a string that can be rendered as LaTeX + + Parameters + ---------- + pyomo_component: _BlockData or Model or Constraint or Expression or Objective + The thing to be printed to LaTeX. Accepts Blocks (including models), Constraints, and Expressions + + filename: str + An optional file to write the LaTeX to. Default of None produces no file + + use_equation_environment: bool + Default behavior uses equation/aligned and produces a single LaTeX Equation (ie, ==False). + Setting this input to True will instead use the align environment, and produce equation numbers for each + objective and constraint. Each objective and constraint will be labeled with its name in the pyomo model. + This flag is only relevant for Models and Blocks. + + splitContinuous: bool + Default behavior has all sum indices be over "i \\in I" or similar. Setting this flag to + True makes the sums go from: \\sum_{i=1}^{5} if the set I is continuous and has 5 elements + + Returns + ------- + str + A LaTeX string of the pyomo_component + + """ + + # Various setup things + + # is Single implies Objective, constraint, or expression + # these objects require a slight modification of behavior + # isSingle==False means a model or block + + if overwrite_dict is None: + overwrite_dict = ComponentMap() + + isSingle = False + + if isinstance(pyomo_component, (pyo.Objective, pyo.Constraint, pyo.Expression, ExpressionBase, pyo.Var)): + isSingle = True + elif isinstance(pyomo_component, _BlockData): + # is not single, leave alone + pass + else: + raise ValueError( + "Invalid type %s passed into the latex printer" + % (str(type(pyomo_component))) + ) + + if isSingle: + temp_comp, temp_indexes = templatize_fcn(pyomo_component) + variableList = [] + for v in identify_components( + temp_comp, [ScalarVar, _GeneralVarData, IndexedVar] + ): + if isinstance(v, _GeneralVarData): + v_write = v.parent_component() + if v_write not in ComponentSet(variableList): + variableList.append(v_write) + else: + if v not in ComponentSet(variableList): + variableList.append(v) + + parameterList = [] + for p in identify_components( + temp_comp, [ScalarParam, _ParamData, IndexedParam] + ): + if isinstance(p, _ParamData): + p_write = p.parent_component() + if p_write not in ComponentSet(parameterList): + parameterList.append(p_write) + else: + if p not in ComponentSet(parameterList): + parameterList.append(p) + + # TODO: cannot extract this information, waiting on resolution of an issue + # For now, will raise an error + raise RuntimeError('Printing of non-models is not currently supported, but will be added soon') + # setList = identify_components(pyomo_component.expr, pyo.Set) + + else: + variableList = [ + vr + for vr in pyomo_component.component_objects( + pyo.Var, descend_into=True, active=True + ) + ] + + parameterList = [ + pm + for pm in pyomo_component.component_objects( + pyo.Param, descend_into=True, active=True + ) + ] + + setList = [ + st + for st in pyomo_component.component_objects( + pyo.Set, descend_into=True, active=True + ) + ] + + variableMap = ComponentMap() + vrIdx = 0 + for i in range(0, len(variableList)): + vr = variableList[i] + vrIdx += 1 + if isinstance(vr, ScalarVar): + variableMap[vr] = 'x_' + str(vrIdx) + elif isinstance(vr, IndexedVar): + variableMap[vr] = 'x_' + str(vrIdx) + for sd in vr.index_set().data(): + vrIdx += 1 + variableMap[vr[sd]] = 'x_' + str(vrIdx) + else: + raise DeveloperError( + 'Variable is not a variable. Should not happen. Contact developers' + ) + + parameterMap = ComponentMap() + pmIdx = 0 + for i in range(0, len(parameterList)): + vr = parameterList[i] + pmIdx += 1 + if isinstance(vr, ScalarParam): + parameterMap[vr] = 'p_' + str(pmIdx) + elif isinstance(vr, IndexedParam): + parameterMap[vr] = 'p_' + str(pmIdx) + for sd in vr.index_set().data(): + pmIdx += 1 + parameterMap[vr[sd]] = 'p_' + str(pmIdx) + else: + raise DeveloperError( + 'Parameter is not a parameter. Should not happen. Contact developers' + ) + + setMap = ComponentMap() + for i in range(0, len(setList)): + st = setList[i] + setMap[st] = 'SET' + str(i + 1) + + # # Only x modes + # # False : dont use + # # True : indexed variables become x_{ix_{subix}} + + if x_only_mode: + # Need to preserve only the set elements in the overwrite_dict + new_overwrite_dict = {} + for ky, vl in overwrite_dict.items(): + if isinstance(ky, _GeneralVarData): + pass + elif isinstance(ky, _ParamData): + pass + elif isinstance(ky, _SetData): + new_overwrite_dict[ky] = overwrite_dict[ky] + else: + raise ValueError( + 'The overwrite_dict object has a key of invalid type: %s' + % (str(ky)) + ) + overwrite_dict = new_overwrite_dict + + vrIdx = 0 + new_variableMap = ComponentMap() + for i in range(0, len(variableList)): + vr = variableList[i] + vrIdx += 1 + if isinstance(vr, ScalarVar): + new_variableMap[vr] = 'x_{' + str(vrIdx) + '}' + elif isinstance(vr, IndexedVar): + new_variableMap[vr] = 'x_{' + str(vrIdx) + '}' + for sd in vr.index_set().data(): + # vrIdx += 1 + sdString = str(sd) + if sdString[0] == '(': + sdString = sdString[1:] + if sdString[-1] == ')': + sdString = sdString[0:-1] + new_variableMap[vr[sd]] = ( + 'x_{' + str(vrIdx) + '_{' + sdString + '}' + '}' + ) + else: + raise DeveloperError( + 'Variable is not a variable. Should not happen. Contact developers' + ) + + pmIdx = 0 + new_parameterMap = ComponentMap() + for i in range(0, len(parameterList)): + pm = parameterList[i] + pmIdx += 1 + if isinstance(pm, ScalarParam): + new_parameterMap[pm] = 'p_{' + str(pmIdx) + '}' + elif isinstance(pm, IndexedParam): + new_parameterMap[pm] = 'p_{' + str(pmIdx) + '}' + for sd in pm.index_set().data(): + sdString = str(sd) + if sdString[0] == '(': + sdString = sdString[1:] + if sdString[-1] == ')': + sdString = sdString[0:-1] + new_parameterMap[pm[sd]] = ( + 'p_{' + str(pmIdx) + '_{' + sdString + '}' + '}' + ) + else: + raise DeveloperError( + 'Parameter is not a parameter. Should not happen. Contact developers' + ) + + new_overwrite_dict = ComponentMap() + for ky, vl in new_variableMap.items(): + new_overwrite_dict[ky] = vl + for ky, vl in new_parameterMap.items(): + new_overwrite_dict[ky] = vl + for ky, vl in overwrite_dict.items(): + new_overwrite_dict[ky] = vl + overwrite_dict = new_overwrite_dict + + else: + vrIdx = 0 + new_variableMap = ComponentMap() + for i in range(0, len(variableList)): + vr = variableList[i] + vrIdx += 1 + if isinstance(vr, ScalarVar): + new_variableMap[vr] = vr.name + elif isinstance(vr, IndexedVar): + new_variableMap[vr] = vr.name + for sd in vr.index_set().data(): + # vrIdx += 1 + sdString = str(sd) + if sdString[0] == '(': + sdString = sdString[1:] + if sdString[-1] == ')': + sdString = sdString[0:-1] + if use_smart_variables: + new_variableMap[vr[sd]] = applySmartVariables( + vr.name + '_' + sdString + ) + else: + new_variableMap[vr[sd]] = vr[sd].name + else: + raise DeveloperError( + 'Variable is not a variable. Should not happen. Contact developers' + ) + + pmIdx = 0 + new_parameterMap = ComponentMap() + for i in range(0, len(parameterList)): + pm = parameterList[i] + pmIdx += 1 + if isinstance(pm, ScalarParam): + new_parameterMap[pm] = pm.name + elif isinstance(pm, IndexedParam): + new_parameterMap[pm] = pm.name + for sd in pm.index_set().data(): + # pmIdx += 1 + sdString = str(sd) + if sdString[0] == '(': + sdString = sdString[1:] + if sdString[-1] == ')': + sdString = sdString[0:-1] + if use_smart_variables: + new_parameterMap[pm[sd]] = applySmartVariables( + pm.name + '_' + sdString + ) + else: + new_parameterMap[pm[sd]] = str(pm[sd]) # .name + else: + raise DeveloperError( + 'Parameter is not a parameter. Should not happen. Contact developers' + ) + + for ky, vl in new_variableMap.items(): + if ky not in overwrite_dict.keys(): + overwrite_dict[ky] = vl + for ky, vl in new_parameterMap.items(): + if ky not in overwrite_dict.keys(): + overwrite_dict[ky] = vl + + for ky in overwrite_dict.keys(): + if isinstance(ky, (pyo.Var, pyo.Param)): + if use_smart_variables and x_only_mode in [0, 3]: + overwrite_dict[ky] = applySmartVariables(overwrite_dict[ky]) + elif isinstance(ky, (_GeneralVarData, _ParamData)): + if use_smart_variables and x_only_mode in [3]: + overwrite_dict[ky] = applySmartVariables(overwrite_dict[ky]) + elif isinstance(ky, _SetData): + # already handled + pass + elif isinstance(ky, (float, int)): + # happens when immutable parameters are used, do nothing + pass + else: + raise ValueError( + 'The overwrite_dict object has a key of invalid type: %s' % (str(ky)) + ) + + for ky, vl in overwrite_dict.items(): + if use_smart_variables: + pattern = r'_{([^{]*)}_{([^{]*)}' + replacement = r'_{\1_{\2}}' + overwrite_dict[ky] = re.sub(pattern, replacement, overwrite_dict[ky]) + + pattern = r'_(.)_{([^}]*)}' + replacement = r'_{\1_{\2}}' + overwrite_dict[ky] = re.sub(pattern, replacement, overwrite_dict[ky]) + else: + overwrite_dict[ky] = vl.replace('_', '\\_') + + + + defaultSetLatexNames = ComponentMap() + for i in range(0, len(setList)): + st = setList[i] + if use_smart_variables: + chkName = setList[i].name + if len(chkName) == 1 and chkName.upper() == chkName: + chkName += '_mathcal' + defaultSetLatexNames[st] = applySmartVariables(chkName) + else: + defaultSetLatexNames[st] = setList[i].name.replace('_', '\\_') + + ## Could be used in the future if someone has a lot of sets + # defaultSetLatexNames[st] = 'mathcal{' + alphabetStringGenerator(i).upper() + '}' + + if st in overwrite_dict.keys(): + if use_smart_variables: + defaultSetLatexNames[st] = applySmartVariables(overwrite_dict[st][0]) + else: + defaultSetLatexNames[st] = overwrite_dict[st][0].replace('_', '\\_') + + defaultSetLatexNames[st] = defaultSetLatexNames[st].replace( + '\\mathcal', r'\\mathcal' + ) + + for ky, vl in defaultSetLatexNames.items(): + overwrite_dict[ky] = [ vl , [] ] + + return overwrite_dict diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index 4c1a4c31e07..68eb20785ef 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -12,6 +12,7 @@ import math import copy import re +import io import pyomo.environ as pyo from pyomo.core.expr.visitor import StreamBasedExpressionVisitor from pyomo.core.expr import ( @@ -116,24 +117,6 @@ def alphabetStringGenerator(num, indexMode=False): 'p', 'q', 'r', - # 'a', - # 'b', - # 'c', - # 'd', - # 'e', - # 'f', - # 'g', - # 'h', - # 'l', - # 'o', - # 's', - # 't', - # 'u', - # 'v', - # 'w', - # 'x', - # 'y', - # 'z', ] else: @@ -410,48 +393,7 @@ def __init__(self): def exitNode(self, node, data): return self._operator_handles[node.__class__](self, node, *data) - -def applySmartVariables(name): - splitName = name.split('_') - # print(splitName) - - filteredName = [] - - prfx = '' - psfx = '' - for i in range(0, len(splitName)): - se = splitName[i] - if se != 0: - if se == 'dot': - prfx = '\\dot{' - psfx = '}' - elif se == 'hat': - prfx = '\\hat{' - psfx = '}' - elif se == 'bar': - prfx = '\\bar{' - psfx = '}' - elif se == 'mathcal': - prfx = '\\mathcal{' - psfx = '}' - else: - filteredName.append(se) - else: - filteredName.append(se) - - joinedName = prfx + filteredName[0] + psfx - # print(joinedName) - # print(filteredName) - for i in range(1, len(filteredName)): - joinedName += '_{' + filteredName[i] - - joinedName += '}' * (len(filteredName) - 1) - # print(joinedName) - - return joinedName - - -def analyze_variable(vr, visitor): +def analyze_variable(vr): domainMap = { 'Reals': '\\mathds{R}', 'PositiveReals': '\\mathds{R}_{> 0}', @@ -624,14 +566,12 @@ def multiple_replace(pstr, rep_dict): def latex_printer( pyomo_component, - filename=None, + latex_component_map=None, + write_object=None, use_equation_environment=False, split_continuous_sets=False, - use_smart_variables=False, - x_only_mode=0, use_short_descriptors=False, - overwrite_dict=None, -): + ): """This function produces a string that can be rendered as LaTeX As described, this function produces a string that can be rendered as LaTeX @@ -641,7 +581,7 @@ def latex_printer( pyomo_component: _BlockData or Model or Constraint or Expression or Objective The thing to be printed to LaTeX. Accepts Blocks (including models), Constraints, and Expressions - filename: str + write_object: str An optional file to write the LaTeX to. Default of None produces no file use_equation_environment: bool @@ -667,8 +607,11 @@ def latex_printer( # these objects require a slight modification of behavior # isSingle==False means a model or block - if overwrite_dict is None: - overwrite_dict = ComponentMap() + if latex_component_map is None: + latex_component_map = ComponentMap() + existing_components = ComponentSet([]) + else: + existing_components = ComponentSet(list(latex_component_map.keys())) isSingle = False @@ -753,6 +696,8 @@ def latex_printer( parameterList.append(p) # TODO: cannot extract this information, waiting on resolution of an issue + # For now, will raise an error + raise RuntimeError('Printing of non-models is not currently supported, but will be added soon') # setList = identify_components(pyomo_component.expr, pyo.Set) else: @@ -777,8 +722,6 @@ def latex_printer( ) ] - forallTag = ' \\qquad \\forall' - descriptorDict = {} if use_short_descriptors: descriptorDict['minimize'] = '\\min' @@ -931,42 +874,29 @@ def latex_printer( setMap[indices[0]._set], ) - conLine += '%s %s \\in %s ' % (forallTag, idxTag, setTag) + conLine += ' \\qquad \\forall %s \\in %s ' % (idxTag, setTag) pstr += conLine # Add labels as needed if not use_equation_environment: pstr += '\\label{con:' + pyomo_component.name + '_' + con.name + '} ' - # prevents an emptly blank line from being at the end of the latex output - if i <= len(constraints) - 2: - pstr += tail - else: - pstr += tail - # pstr += '\n' + pstr += tail + # Print bounds and sets if not isSingle: - variableList = [ - vr - for vr in pyomo_component.component_objects( - pyo.Var, descend_into=True, active=True - ) - ] - varBoundData = [] for i in range(0, len(variableList)): vr = variableList[i] if isinstance(vr, ScalarVar): - varBoundDataEntry = analyze_variable(vr, visitor) + varBoundDataEntry = analyze_variable(vr) varBoundData.append(varBoundDataEntry) elif isinstance(vr, IndexedVar): varBoundData_indexedVar = [] - # need to wrap in function and do individually - # Check on the final variable after all the indices are processed setData = vr.index_set().data() for sd in setData: - varBoundDataEntry = analyze_variable(vr[sd], visitor) + varBoundDataEntry = analyze_variable(vr[sd]) varBoundData_indexedVar.append(varBoundDataEntry) globIndexedVariables = True for j in range(0, len(varBoundData_indexedVar) - 1): @@ -1072,26 +1002,9 @@ def latex_printer( defaultSetLatexNames = ComponentMap() for i in range(0, len(setList)): st = setList[i] - if use_smart_variables: - chkName = setList[i].name - if len(chkName) == 1 and chkName.upper() == chkName: - chkName += '_mathcal' - defaultSetLatexNames[st] = applySmartVariables(chkName) - else: - defaultSetLatexNames[st] = setList[i].name.replace('_', '\\_') - - ## Could be used in the future if someone has a lot of sets - # defaultSetLatexNames[st] = 'mathcal{' + alphabetStringGenerator(i).upper() + '}' - - if st in overwrite_dict.keys(): - if use_smart_variables: - defaultSetLatexNames[st] = applySmartVariables(overwrite_dict[st][0]) - else: - defaultSetLatexNames[st] = overwrite_dict[st][0].replace('_', '\\_') - - defaultSetLatexNames[st] = defaultSetLatexNames[st].replace( - '\\mathcal', r'\\mathcal' - ) + defaultSetLatexNames[st] = setList[i].name.replace('_', '\\_') + if st in ComponentSet(latex_component_map.keys()): + defaultSetLatexNames[st] = latex_component_map[st][0]#.replace('_', '\\_') latexLines = pstr.split('\n') for jj in range(0, len(latexLines)): @@ -1108,7 +1021,7 @@ def latex_printer( gpNum, stName = ifo.split('_') if gpNum not in groupMap.keys(): groupMap[gpNum] = [stName] - if stName not in uniqueSets: + if stName not in ComponentSet(uniqueSets): uniqueSets.append(stName) # Determine if the set is continuous @@ -1187,19 +1100,28 @@ def latex_printer( indexCounter = 0 for ky, vl in groupInfo.items(): - if vl['setObject'] in overwrite_dict.keys(): - indexNames = overwrite_dict[vl['setObject']][1] - if len(indexNames) < len(vl['indices']): - raise ValueError( - 'Insufficient number of indices provided to the overwrite dictionary for set %s' - % (vl['setObject'].name) - ) - for i in range(0, len(indexNames)): - ln = ln.replace( - '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' - % (vl['indices'][i], ky), - indexNames[i], - ) + if vl['setObject'] in ComponentSet(latex_component_map.keys()) : + indexNames = latex_component_map[vl['setObject']][1] + if len(indexNames) != 0: + if len(indexNames) < len(vl['indices']): + raise ValueError( + 'Insufficient number of indices provided to the overwrite dictionary for set %s' + % (vl['setObject'].name) + ) + for i in range(0, len(indexNames)): + ln = ln.replace( + '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' + % (vl['indices'][i], ky), + indexNames[i], + ) + else: + for i in range(0, len(vl['indices'])): + ln = ln.replace( + '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' + % (vl['indices'][i], ky), + alphabetStringGenerator(indexCounter, True), + ) + indexCounter += 1 else: for i in range(0, len(vl['indices'])): ln = ln.replace( @@ -1209,237 +1131,73 @@ def latex_printer( ) indexCounter += 1 - # print('gn',groupInfo) - latexLines[jj] = ln pstr = '\n'.join(latexLines) - # pstr = pstr.replace('\\mathcal{', 'mathcal{') - # pstr = pstr.replace('mathcal{', '\\mathcal{') - - if x_only_mode in [1, 2, 3]: - # Need to preserve only the set elements in the overwrite_dict - new_overwrite_dict = {} - for ky, vl in overwrite_dict.items(): - if isinstance(ky, _GeneralVarData): - pass - elif isinstance(ky, _ParamData): - pass - elif isinstance(ky, _SetData): - new_overwrite_dict[ky] = overwrite_dict[ky] - else: - raise ValueError( - 'The overwrite_dict object has a key of invalid type: %s' - % (str(ky)) - ) - overwrite_dict = new_overwrite_dict - # # Only x modes - # # Mode 0 : dont use - # # Mode 1 : indexed variables become x_{_{ix}} - # # Mode 2 : uses standard alphabet [a,...,z,aa,...,az,...,aaa,...] with subscripts for indices, ex: abcd_{ix} - # # Mode 3 : unwrap everything into an x_{} list, including the indexed vars themselves - - if x_only_mode == 1: - vrIdx = 0 - new_variableMap = ComponentMap() - for i in range(0, len(variableList)): - vr = variableList[i] - vrIdx += 1 - if isinstance(vr, ScalarVar): - new_variableMap[vr] = 'x_{' + str(vrIdx) + '}' - elif isinstance(vr, IndexedVar): - new_variableMap[vr] = 'x_{' + str(vrIdx) + '}' - for sd in vr.index_set().data(): - # vrIdx += 1 - sdString = str(sd) - if sdString[0] == '(': - sdString = sdString[1:] - if sdString[-1] == ')': - sdString = sdString[0:-1] - new_variableMap[vr[sd]] = ( - 'x_{' + str(vrIdx) + '_{' + sdString + '}' + '}' - ) - else: - raise DeveloperError( - 'Variable is not a variable. Should not happen. Contact developers' - ) - - pmIdx = 0 - new_parameterMap = ComponentMap() - for i in range(0, len(parameterList)): - pm = parameterList[i] - pmIdx += 1 - if isinstance(pm, ScalarParam): - new_parameterMap[pm] = 'p_{' + str(pmIdx) + '}' - elif isinstance(pm, IndexedParam): - new_parameterMap[pm] = 'p_{' + str(pmIdx) + '}' - for sd in pm.index_set().data(): - sdString = str(sd) - if sdString[0] == '(': - sdString = sdString[1:] - if sdString[-1] == ')': - sdString = sdString[0:-1] - new_parameterMap[pm[sd]] = ( - 'p_{' + str(pmIdx) + '_{' + sdString + '}' + '}' - ) - else: - raise DeveloperError( - 'Parameter is not a parameter. Should not happen. Contact developers' - ) - - new_overwrite_dict = ComponentMap() - for ky, vl in new_variableMap.items(): - new_overwrite_dict[ky] = vl - for ky, vl in new_parameterMap.items(): - new_overwrite_dict[ky] = vl - for ky, vl in overwrite_dict.items(): - new_overwrite_dict[ky] = vl - overwrite_dict = new_overwrite_dict - - elif x_only_mode == 2: - vrIdx = 0 - new_variableMap = ComponentMap() - for i in range(0, len(variableList)): - vr = variableList[i] - vrIdx += 1 - if isinstance(vr, ScalarVar): - new_variableMap[vr] = alphabetStringGenerator(i) - elif isinstance(vr, IndexedVar): - new_variableMap[vr] = alphabetStringGenerator(i) - for sd in vr.index_set().data(): - # vrIdx += 1 - sdString = str(sd) - if sdString[0] == '(': - sdString = sdString[1:] - if sdString[-1] == ')': - sdString = sdString[0:-1] - new_variableMap[vr[sd]] = ( - alphabetStringGenerator(i) + '_{' + sdString + '}' - ) - else: - raise DeveloperError( - 'Variable is not a variable. Should not happen. Contact developers' - ) - - pmIdx = vrIdx - 1 - new_parameterMap = ComponentMap() - for i in range(0, len(parameterList)): - pm = parameterList[i] - pmIdx += 1 - if isinstance(pm, ScalarParam): - new_parameterMap[pm] = alphabetStringGenerator(pmIdx) - elif isinstance(pm, IndexedParam): - new_parameterMap[pm] = alphabetStringGenerator(pmIdx) - for sd in pm.index_set().data(): - sdString = str(sd) - if sdString[0] == '(': - sdString = sdString[1:] - if sdString[-1] == ')': - sdString = sdString[0:-1] - new_parameterMap[pm[sd]] = ( - alphabetStringGenerator(pmIdx) + '_{' + sdString + '}' - ) - else: - raise DeveloperError( - 'Parameter is not a parameter. Should not happen. Contact developers' - ) - - new_overwrite_dict = ComponentMap() - for ky, vl in new_variableMap.items(): - new_overwrite_dict[ky] = vl - for ky, vl in new_parameterMap.items(): - new_overwrite_dict[ky] = vl - for ky, vl in overwrite_dict.items(): - new_overwrite_dict[ky] = vl - overwrite_dict = new_overwrite_dict - - elif x_only_mode == 3: - new_overwrite_dict = ComponentMap() - for ky, vl in variableMap.items(): - new_overwrite_dict[ky] = vl - for ky, vl in parameterMap.items(): - new_overwrite_dict[ky] = vl - for ky, vl in overwrite_dict.items(): - new_overwrite_dict[ky] = vl - overwrite_dict = new_overwrite_dict - - else: - vrIdx = 0 - new_variableMap = ComponentMap() - for i in range(0, len(variableList)): - vr = variableList[i] - vrIdx += 1 - if isinstance(vr, ScalarVar): - new_variableMap[vr] = vr.name - elif isinstance(vr, IndexedVar): - new_variableMap[vr] = vr.name - for sd in vr.index_set().data(): - # vrIdx += 1 - sdString = str(sd) - if sdString[0] == '(': - sdString = sdString[1:] - if sdString[-1] == ')': - sdString = sdString[0:-1] - if use_smart_variables: - new_variableMap[vr[sd]] = applySmartVariables( - vr.name + '_' + sdString - ) - else: - new_variableMap[vr[sd]] = vr[sd].name - else: - raise DeveloperError( - 'Variable is not a variable. Should not happen. Contact developers' - ) + vrIdx = 0 + new_variableMap = ComponentMap() + for i in range(0, len(variableList)): + vr = variableList[i] + vrIdx += 1 + if isinstance(vr, ScalarVar): + new_variableMap[vr] = vr.name + elif isinstance(vr, IndexedVar): + new_variableMap[vr] = vr.name + for sd in vr.index_set().data(): + # vrIdx += 1 + sdString = str(sd) + if sdString[0] == '(': + sdString = sdString[1:] + if sdString[-1] == ')': + sdString = sdString[0:-1] + new_variableMap[vr[sd]] = vr[sd].name + else: + raise DeveloperError( + 'Variable is not a variable. Should not happen. Contact developers' + ) - pmIdx = 0 - new_parameterMap = ComponentMap() - for i in range(0, len(parameterList)): - pm = parameterList[i] - pmIdx += 1 - if isinstance(pm, ScalarParam): - new_parameterMap[pm] = pm.name - elif isinstance(pm, IndexedParam): - new_parameterMap[pm] = pm.name - for sd in pm.index_set().data(): - # pmIdx += 1 - sdString = str(sd) - if sdString[0] == '(': - sdString = sdString[1:] - if sdString[-1] == ')': - sdString = sdString[0:-1] - if use_smart_variables: - new_parameterMap[pm[sd]] = applySmartVariables( - pm.name + '_' + sdString - ) - else: - new_parameterMap[pm[sd]] = str(pm[sd]) # .name - else: - raise DeveloperError( - 'Parameter is not a parameter. Should not happen. Contact developers' - ) + pmIdx = 0 + new_parameterMap = ComponentMap() + for i in range(0, len(parameterList)): + pm = parameterList[i] + pmIdx += 1 + if isinstance(pm, ScalarParam): + new_parameterMap[pm] = pm.name + elif isinstance(pm, IndexedParam): + new_parameterMap[pm] = pm.name + for sd in pm.index_set().data(): + # pmIdx += 1 + sdString = str(sd) + if sdString[0] == '(': + sdString = sdString[1:] + if sdString[-1] == ')': + sdString = sdString[0:-1] + new_parameterMap[pm[sd]] = str(pm[sd]) # .name + else: + raise DeveloperError( + 'Parameter is not a parameter. Should not happen. Contact developers' + ) - for ky, vl in new_variableMap.items(): - if ky not in overwrite_dict.keys(): - overwrite_dict[ky] = vl - for ky, vl in new_parameterMap.items(): - if ky not in overwrite_dict.keys(): - overwrite_dict[ky] = vl + for ky, vl in new_variableMap.items(): + if ky not in ComponentSet(latex_component_map.keys()): + latex_component_map[ky] = vl + for ky, vl in new_parameterMap.items(): + if ky not in ComponentSet(latex_component_map.keys()): + latex_component_map[ky] = vl rep_dict = {} - for ky in list(reversed(list(overwrite_dict.keys()))): - if isinstance(ky, (pyo.Var, pyo.Param)): - if use_smart_variables and x_only_mode in [0, 3]: - overwrite_value = applySmartVariables(overwrite_dict[ky]) - else: - overwrite_value = overwrite_dict[ky] - rep_dict[variableMap[ky]] = overwrite_value - elif isinstance(ky, (_GeneralVarData, _ParamData)): - if use_smart_variables and x_only_mode in [3]: - overwrite_value = applySmartVariables(overwrite_dict[ky]) - else: - overwrite_value = overwrite_dict[ky] + for ky in ComponentSet(list(reversed(list(latex_component_map.keys())))): + if isinstance(ky, (pyo.Var, _GeneralVarData)): + overwrite_value = latex_component_map[ky] + if ky not in existing_components: + overwrite_value = overwrite_value.replace('_', '\\_') rep_dict[variableMap[ky]] = overwrite_value + elif isinstance(ky, (pyo.Param, _ParamData)): + overwrite_value = latex_component_map[ky] + if ky not in existing_components: + overwrite_value = overwrite_value.replace('_', '\\_') + rep_dict[parameterMap[ky]] = overwrite_value elif isinstance(ky, _SetData): # already handled pass @@ -1448,13 +1206,9 @@ def latex_printer( pass else: raise ValueError( - 'The overwrite_dict object has a key of invalid type: %s' % (str(ky)) + 'The latex_component_map object has a key of invalid type: %s' % (str(ky)) ) - if not use_smart_variables: - for ky, vl in rep_dict.items(): - rep_dict[ky] = vl.replace('_', '\\_') - label_rep_dict = copy.deepcopy(rep_dict) for ky, vl in label_rep_dict.items(): label_rep_dict[ky] = vl.replace('{', '').replace('}', '').replace('\\', '') @@ -1467,6 +1221,7 @@ def latex_printer( if '\\label{' in splitLines[i]: epr, lbl = splitLines[i].split('\\label{') epr = multiple_replace(epr, rep_dict) + # rep_dict[ky] = vl.replace('_', '\\_') lbl = multiple_replace(lbl, label_rep_dict) splitLines[i] = epr + '\\label{' + lbl @@ -1488,8 +1243,7 @@ def latex_printer( pstr = '\n'.join(finalLines) - # optional write to output file - if filename is not None: + if write_object is not None: fstr = '' fstr += '\\documentclass{article} \n' fstr += '\\usepackage{amsmath} \n' @@ -1497,11 +1251,18 @@ def latex_printer( fstr += '\\usepackage{dsfont} \n' fstr += '\\allowdisplaybreaks \n' fstr += '\\begin{document} \n' - fstr += pstr + fstr += pstr + '\n' fstr += '\\end{document} \n' - f = open(filename, 'w') + + # optional write to output file + if isinstance(write_object, (io.TextIOWrapper, io.StringIO)): + write_object.write(fstr) + elif isinstance(write_object,str): + f = open(write_object, 'w') f.write(fstr) f.close() + else: + raise ValueError('Invalid type %s encountered when parsing the write_object. Must be a StringIO, FileIO, or valid filename string') # return the latex string return pstr diff --git a/pyomo/util/latex_printer_1.py b/pyomo/util/latex_printer_1.py new file mode 100644 index 00000000000..e251dda5927 --- /dev/null +++ b/pyomo/util/latex_printer_1.py @@ -0,0 +1,1506 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2023 +# 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 math +import copy +import re +import pyomo.environ as pyo +from pyomo.core.expr.visitor import StreamBasedExpressionVisitor +from pyomo.core.expr import ( + NegationExpression, + ProductExpression, + DivisionExpression, + PowExpression, + AbsExpression, + UnaryFunctionExpression, + MonomialTermExpression, + LinearExpression, + SumExpression, + EqualityExpression, + InequalityExpression, + RangedExpression, + Expr_ifExpression, + ExternalFunctionExpression, +) + +from pyomo.core.expr.visitor import identify_components +from pyomo.core.expr.base import ExpressionBase +from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData +from pyomo.core.base.objective import ScalarObjective, _GeneralObjectiveData +import pyomo.core.kernel as kernel +from pyomo.core.expr.template_expr import ( + GetItemExpression, + GetAttrExpression, + TemplateSumExpression, + IndexTemplate, + Numeric_GetItemExpression, + templatize_constraint, + resolve_template, + templatize_rule, +) +from pyomo.core.base.var import ScalarVar, _GeneralVarData, IndexedVar +from pyomo.core.base.param import _ParamData, ScalarParam, IndexedParam +from pyomo.core.base.set import _SetData +from pyomo.core.base.constraint import ScalarConstraint, IndexedConstraint +from pyomo.common.collections.component_map import ComponentMap +from pyomo.common.collections.component_set import ComponentSet + +from pyomo.core.base.external import _PythonCallbackFunctionID + +from pyomo.core.base.block import _BlockData + +from pyomo.repn.util import ExprType + +from pyomo.common import DeveloperError + +_CONSTANT = ExprType.CONSTANT +_MONOMIAL = ExprType.MONOMIAL +_GENERAL = ExprType.GENERAL + + +def decoder(num, base): + if isinstance(base, float): + if not base.is_integer(): + raise ValueError('Invalid base') + else: + base = int(base) + + if base <= 1: + raise ValueError('Invalid base') + + if num == 0: + numDigs = 1 + else: + numDigs = math.ceil(math.log(num, base)) + if math.log(num, base).is_integer(): + numDigs += 1 + digs = [0.0 for i in range(0, numDigs)] + rem = num + for i in range(0, numDigs): + ix = numDigs - i - 1 + dg = math.floor(rem / base**ix) + rem = rem % base**ix + digs[i] = dg + return digs + + +def indexCorrector(ixs, base): + for i in range(0, len(ixs)): + ix = ixs[i] + if i + 1 < len(ixs): + if ixs[i + 1] == 0: + ixs[i] -= 1 + ixs[i + 1] = base + if ixs[i] == 0: + ixs = indexCorrector(ixs, base) + return ixs + + +def alphabetStringGenerator(num, indexMode=False): + if indexMode: + alphabet = [ + '.', + 'i', + 'j', + 'k', + 'm', + 'n', + 'p', + 'q', + 'r', + # 'a', + # 'b', + # 'c', + # 'd', + # 'e', + # 'f', + # 'g', + # 'h', + # 'l', + # 'o', + # 's', + # 't', + # 'u', + # 'v', + # 'w', + # 'x', + # 'y', + # 'z', + ] + + else: + alphabet = [ + '.', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + ] + ixs = decoder(num + 1, len(alphabet) - 1) + pstr = '' + ixs = indexCorrector(ixs, len(alphabet) - 1) + for i in range(0, len(ixs)): + ix = ixs[i] + pstr += alphabet[ix] + pstr = pstr.replace('.', '') + return pstr + + +def templatize_expression(expr): + expr, indices = templatize_rule(expr.parent_block(), expr._rule, expr.index_set()) + return (expr, indices) + + +def templatize_passthrough(con): + return (con, []) + + +def precedenceChecker(node, arg1, arg2=None): + childPrecedence = [] + for a in node.args: + if hasattr(a, 'PRECEDENCE'): + if a.PRECEDENCE is None: + childPrecedence.append(-1) + else: + childPrecedence.append(a.PRECEDENCE) + else: + childPrecedence.append(-1) + + if hasattr(node, 'PRECEDENCE'): + precedence = node.PRECEDENCE + else: + # Should never hit this + raise DeveloperError( + 'This error should never be thrown, node does not have a precedence. Report to developers' + ) + + if childPrecedence[0] > precedence: + arg1 = ' \\left( ' + arg1 + ' \\right) ' + + if arg2 is not None: + if childPrecedence[1] > precedence: + arg2 = ' \\left( ' + arg2 + ' \\right) ' + + return arg1, arg2 + + +def handle_negation_node(visitor, node, arg1): + arg1, tsh = precedenceChecker(node, arg1) + return '-' + arg1 + + +def handle_product_node(visitor, node, arg1, arg2): + arg1, arg2 = precedenceChecker(node, arg1, arg2) + return ' '.join([arg1, arg2]) + + +def handle_pow_node(visitor, node, arg1, arg2): + arg1, arg2 = precedenceChecker(node, arg1, arg2) + return "%s^{%s}" % (arg1, arg2) + + +def handle_division_node(visitor, node, arg1, arg2): + return '\\frac{%s}{%s}' % (arg1, arg2) + + +def handle_abs_node(visitor, node, arg1): + return ' \\left| ' + arg1 + ' \\right| ' + + +def handle_unary_node(visitor, node, arg1): + fcn_handle = node.getname() + if fcn_handle == 'log10': + fcn_handle = 'log_{10}' + + if fcn_handle == 'sqrt': + return '\\sqrt { ' + arg1 + ' }' + else: + return '\\' + fcn_handle + ' \\left( ' + arg1 + ' \\right) ' + + +def handle_equality_node(visitor, node, arg1, arg2): + return arg1 + ' = ' + arg2 + + +def handle_inequality_node(visitor, node, arg1, arg2): + return arg1 + ' \\leq ' + arg2 + + +def handle_var_node(visitor, node): + return visitor.variableMap[node] + + +def handle_num_node(visitor, node): + if isinstance(node, float): + if node.is_integer(): + node = int(node) + return str(node) + + +def handle_sumExpression_node(visitor, node, *args): + rstr = args[0] + for i in range(1, len(args)): + if args[i][0] == '-': + rstr += ' - ' + args[i][1:] + else: + rstr += ' + ' + args[i] + return rstr + + +def handle_monomialTermExpression_node(visitor, node, arg1, arg2): + if arg1 == '1': + return arg2 + elif arg1 == '-1': + return '-' + arg2 + else: + return arg1 + ' ' + arg2 + + +def handle_named_expression_node(visitor, node, arg1): + # needed to preserve consistencency with the exitNode function call + # prevents the need to type check in the exitNode function + return arg1 + + +def handle_ranged_inequality_node(visitor, node, arg1, arg2, arg3): + return arg1 + ' \\leq ' + arg2 + ' \\leq ' + arg3 + + +def handle_exprif_node(visitor, node, arg1, arg2, arg3): + return 'f_{\\text{exprIf}}(' + arg1 + ',' + arg2 + ',' + arg3 + ')' + + ## Could be handled in the future using cases or similar + + ## Raises not implemented error + # raise NotImplementedError('Expr_if objects not supported by the Latex Printer') + + ## Puts cases in a bracketed matrix + # pstr = '' + # pstr += '\\begin{Bmatrix} ' + # pstr += arg2 + ' , & ' + arg1 + '\\\\ ' + # pstr += arg3 + ' , & \\text{otherwise}' + '\\\\ ' + # pstr += '\\end{Bmatrix}' + # return pstr + + +def handle_external_function_node(visitor, node, *args): + pstr = '' + pstr += 'f(' + for i in range(0, len(args) - 1): + pstr += args[i] + if i <= len(args) - 3: + pstr += ',' + else: + pstr += ')' + return pstr + + +def handle_functionID_node(visitor, node, *args): + # seems to just be a placeholder empty wrapper object + return '' + + +def handle_indexTemplate_node(visitor, node, *args): + return '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( + node._group, + visitor.setMap[node._set], + ) + + +def handle_numericGIE_node(visitor, node, *args): + joinedName = args[0] + + pstr = '' + pstr += joinedName + '_{' + for i in range(1, len(args)): + pstr += args[i] + if i <= len(args) - 2: + pstr += ',' + else: + pstr += '}' + return pstr + + +def handle_templateSumExpression_node(visitor, node, *args): + pstr = '' + for i in range(0, len(node._iters)): + pstr += '\\sum_{__S_PLACEHOLDER_8675309_GROUP_%s_%s__} ' % ( + node._iters[i][0]._group, + visitor.setMap[node._iters[i][0]._set], + ) + + pstr += args[0] + + return pstr + + +def handle_param_node(visitor, node): + return visitor.parameterMap[node] + + +class _LatexVisitor(StreamBasedExpressionVisitor): + def __init__(self): + super().__init__() + + self._operator_handles = { + ScalarVar: handle_var_node, + int: handle_num_node, + float: handle_num_node, + NegationExpression: handle_negation_node, + ProductExpression: handle_product_node, + DivisionExpression: handle_division_node, + PowExpression: handle_pow_node, + AbsExpression: handle_abs_node, + UnaryFunctionExpression: handle_unary_node, + Expr_ifExpression: handle_exprif_node, + EqualityExpression: handle_equality_node, + InequalityExpression: handle_inequality_node, + RangedExpression: handle_ranged_inequality_node, + _GeneralExpressionData: handle_named_expression_node, + ScalarExpression: handle_named_expression_node, + kernel.expression.expression: handle_named_expression_node, + kernel.expression.noclone: handle_named_expression_node, + _GeneralObjectiveData: handle_named_expression_node, + _GeneralVarData: handle_var_node, + ScalarObjective: handle_named_expression_node, + kernel.objective.objective: handle_named_expression_node, + ExternalFunctionExpression: handle_external_function_node, + _PythonCallbackFunctionID: handle_functionID_node, + LinearExpression: handle_sumExpression_node, + SumExpression: handle_sumExpression_node, + MonomialTermExpression: handle_monomialTermExpression_node, + IndexedVar: handle_var_node, + IndexTemplate: handle_indexTemplate_node, + Numeric_GetItemExpression: handle_numericGIE_node, + TemplateSumExpression: handle_templateSumExpression_node, + ScalarParam: handle_param_node, + _ParamData: handle_param_node, + } + + def exitNode(self, node, data): + return self._operator_handles[node.__class__](self, node, *data) + + +def applySmartVariables(name): + splitName = name.split('_') + # print(splitName) + + filteredName = [] + + prfx = '' + psfx = '' + for i in range(0, len(splitName)): + se = splitName[i] + if se != 0: + if se == 'dot': + prfx = '\\dot{' + psfx = '}' + elif se == 'hat': + prfx = '\\hat{' + psfx = '}' + elif se == 'bar': + prfx = '\\bar{' + psfx = '}' + elif se == 'mathcal': + prfx = '\\mathcal{' + psfx = '}' + else: + filteredName.append(se) + else: + filteredName.append(se) + + joinedName = prfx + filteredName[0] + psfx + # print(joinedName) + # print(filteredName) + for i in range(1, len(filteredName)): + joinedName += '_{' + filteredName[i] + + joinedName += '}' * (len(filteredName) - 1) + # print(joinedName) + + return joinedName + + +def analyze_variable(vr, visitor): + domainMap = { + 'Reals': '\\mathds{R}', + 'PositiveReals': '\\mathds{R}_{> 0}', + 'NonPositiveReals': '\\mathds{R}_{\\leq 0}', + 'NegativeReals': '\\mathds{R}_{< 0}', + 'NonNegativeReals': '\\mathds{R}_{\\geq 0}', + 'Integers': '\\mathds{Z}', + 'PositiveIntegers': '\\mathds{Z}_{> 0}', + 'NonPositiveIntegers': '\\mathds{Z}_{\\leq 0}', + 'NegativeIntegers': '\\mathds{Z}_{< 0}', + 'NonNegativeIntegers': '\\mathds{Z}_{\\geq 0}', + 'Boolean': '\\left\\{ 0 , 1 \\right \\}', + 'Binary': '\\left\\{ 0 , 1 \\right \\}', + # 'Any': None, + # 'AnyWithNone': None, + 'EmptySet': '\\varnothing', + 'UnitInterval': '\\mathds{R}', + 'PercentFraction': '\\mathds{R}', + # 'RealInterval' : None , + # 'IntegerInterval' : None , + } + + domainName = vr.domain.name + varBounds = vr.bounds + lowerBoundValue = varBounds[0] + upperBoundValue = varBounds[1] + + if domainName in ['Reals', 'Integers']: + if lowerBoundValue is not None: + lowerBound = str(lowerBoundValue) + ' \\leq ' + else: + lowerBound = '' + + if upperBoundValue is not None: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = '' + + elif domainName in ['PositiveReals', 'PositiveIntegers']: + if lowerBoundValue is not None: + if lowerBoundValue > 0: + lowerBound = str(lowerBoundValue) + ' \\leq ' + else: + lowerBound = ' 0 < ' + else: + lowerBound = ' 0 < ' + + if upperBoundValue is not None: + if upperBoundValue <= 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) + ) + else: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = '' + + elif domainName in ['NonPositiveReals', 'NonPositiveIntegers']: + if lowerBoundValue is not None: + if lowerBoundValue > 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) + ) + elif lowerBoundValue == 0: + lowerBound = ' 0 = ' + else: + lowerBound = str(lowerBoundValue) + ' \\leq ' + else: + lowerBound = '' + + if upperBoundValue is not None: + if upperBoundValue >= 0: + upperBound = ' \\leq 0 ' + else: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = ' \\leq 0 ' + + elif domainName in ['NegativeReals', 'NegativeIntegers']: + if lowerBoundValue is not None: + if lowerBoundValue >= 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) + ) + else: + lowerBound = str(lowerBoundValue) + ' \\leq ' + else: + lowerBound = '' + + if upperBoundValue is not None: + if upperBoundValue >= 0: + upperBound = ' < 0 ' + else: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = ' < 0 ' + + elif domainName in ['NonNegativeReals', 'NonNegativeIntegers']: + if lowerBoundValue is not None: + if lowerBoundValue > 0: + lowerBound = str(lowerBoundValue) + ' \\leq ' + else: + lowerBound = ' 0 \\leq ' + else: + lowerBound = ' 0 \\leq ' + + if upperBoundValue is not None: + if upperBoundValue < 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) + ) + elif upperBoundValue == 0: + upperBound = ' = 0 ' + else: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = '' + + elif domainName in ['Boolean', 'Binary', 'Any', 'AnyWithNone', 'EmptySet']: + lowerBound = '' + upperBound = '' + + elif domainName in ['UnitInterval', 'PercentFraction']: + if lowerBoundValue is not None: + if lowerBoundValue > 0: + lowerBound = str(lowerBoundValue) + ' \\leq ' + elif lowerBoundValue > 1: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) + ) + elif lowerBoundValue == 1: + lowerBound = ' = 1 ' + else: + lowerBound = ' 0 \\leq ' + else: + lowerBound = ' 0 \\leq ' + + if upperBoundValue is not None: + if upperBoundValue < 1: + upperBound = ' \\leq ' + str(upperBoundValue) + elif upperBoundValue < 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) + ) + elif upperBoundValue == 0: + upperBound = ' = 0 ' + else: + upperBound = ' \\leq 1 ' + else: + upperBound = ' \\leq 1 ' + + else: + raise ValueError('Domain %s not supported by the latex printer' % (domainName)) + + varBoundData = { + 'variable': vr, + 'lowerBound': lowerBound, + 'upperBound': upperBound, + 'domainName': domainName, + 'domainLatex': domainMap[domainName], + } + + return varBoundData + + +def multiple_replace(pstr, rep_dict): + pattern = re.compile("|".join(rep_dict.keys()), flags=re.DOTALL) + return pattern.sub(lambda x: rep_dict[x.group(0)], pstr) + + +def latex_printer( + pyomo_component, + filename=None, + use_equation_environment=False, + split_continuous_sets=False, + use_smart_variables=False, + x_only_mode=0, + use_short_descriptors=False, + overwrite_dict=None, +): + """This function produces a string that can be rendered as LaTeX + + As described, this function produces a string that can be rendered as LaTeX + + Parameters + ---------- + pyomo_component: _BlockData or Model or Constraint or Expression or Objective + The thing to be printed to LaTeX. Accepts Blocks (including models), Constraints, and Expressions + + filename: str + An optional file to write the LaTeX to. Default of None produces no file + + use_equation_environment: bool + Default behavior uses equation/aligned and produces a single LaTeX Equation (ie, ==False). + Setting this input to True will instead use the align environment, and produce equation numbers for each + objective and constraint. Each objective and constraint will be labeled with its name in the pyomo model. + This flag is only relevant for Models and Blocks. + + splitContinuous: bool + Default behavior has all sum indices be over "i \\in I" or similar. Setting this flag to + True makes the sums go from: \\sum_{i=1}^{5} if the set I is continuous and has 5 elements + + Returns + ------- + str + A LaTeX string of the pyomo_component + + """ + + # Various setup things + + # is Single implies Objective, constraint, or expression + # these objects require a slight modification of behavior + # isSingle==False means a model or block + + if overwrite_dict is None: + overwrite_dict = ComponentMap() + + isSingle = False + + if isinstance(pyomo_component, pyo.Objective): + objectives = [pyomo_component] + constraints = [] + expressions = [] + templatize_fcn = templatize_constraint + use_equation_environment = True + isSingle = True + + elif isinstance(pyomo_component, pyo.Constraint): + objectives = [] + constraints = [pyomo_component] + expressions = [] + templatize_fcn = templatize_constraint + use_equation_environment = True + isSingle = True + + elif isinstance(pyomo_component, pyo.Expression): + objectives = [] + constraints = [] + expressions = [pyomo_component] + templatize_fcn = templatize_expression + use_equation_environment = True + isSingle = True + + elif isinstance(pyomo_component, (ExpressionBase, pyo.Var)): + objectives = [] + constraints = [] + expressions = [pyomo_component] + templatize_fcn = templatize_passthrough + use_equation_environment = True + isSingle = True + + elif isinstance(pyomo_component, _BlockData): + objectives = [ + obj + for obj in pyomo_component.component_data_objects( + pyo.Objective, descend_into=True, active=True + ) + ] + constraints = [ + con + for con in pyomo_component.component_objects( + pyo.Constraint, descend_into=True, active=True + ) + ] + expressions = [] + templatize_fcn = templatize_constraint + + else: + raise ValueError( + "Invalid type %s passed into the latex printer" + % (str(type(pyomo_component))) + ) + + if isSingle: + temp_comp, temp_indexes = templatize_fcn(pyomo_component) + variableList = [] + for v in identify_components( + temp_comp, [ScalarVar, _GeneralVarData, IndexedVar] + ): + if isinstance(v, _GeneralVarData): + v_write = v.parent_component() + if v_write not in ComponentSet(variableList): + variableList.append(v_write) + else: + if v not in ComponentSet(variableList): + variableList.append(v) + + parameterList = [] + for p in identify_components( + temp_comp, [ScalarParam, _ParamData, IndexedParam] + ): + if isinstance(p, _ParamData): + p_write = p.parent_component() + if p_write not in ComponentSet(parameterList): + parameterList.append(p_write) + else: + if p not in ComponentSet(parameterList): + parameterList.append(p) + + # TODO: cannot extract this information, waiting on resolution of an issue + # setList = identify_components(pyomo_component.expr, pyo.Set) + + else: + variableList = [ + vr + for vr in pyomo_component.component_objects( + pyo.Var, descend_into=True, active=True + ) + ] + + parameterList = [ + pm + for pm in pyomo_component.component_objects( + pyo.Param, descend_into=True, active=True + ) + ] + + setList = [ + st + for st in pyomo_component.component_objects( + pyo.Set, descend_into=True, active=True + ) + ] + + forallTag = ' \\qquad \\forall' + + descriptorDict = {} + if use_short_descriptors: + descriptorDict['minimize'] = '\\min' + descriptorDict['maximize'] = '\\max' + descriptorDict['subject to'] = '\\text{s.t.}' + descriptorDict['with bounds'] = '\\text{w.b.}' + else: + descriptorDict['minimize'] = '\\text{minimize}' + descriptorDict['maximize'] = '\\text{maximize}' + descriptorDict['subject to'] = '\\text{subject to}' + descriptorDict['with bounds'] = '\\text{with bounds}' + + # In the case where just a single expression is passed, add this to the constraint list for printing + constraints = constraints + expressions + + # Declare a visitor/walker + visitor = _LatexVisitor() + + variableMap = ComponentMap() + vrIdx = 0 + for i in range(0, len(variableList)): + vr = variableList[i] + vrIdx += 1 + if isinstance(vr, ScalarVar): + variableMap[vr] = 'x_' + str(vrIdx) + elif isinstance(vr, IndexedVar): + variableMap[vr] = 'x_' + str(vrIdx) + for sd in vr.index_set().data(): + vrIdx += 1 + variableMap[vr[sd]] = 'x_' + str(vrIdx) + else: + raise DeveloperError( + 'Variable is not a variable. Should not happen. Contact developers' + ) + visitor.variableMap = variableMap + + parameterMap = ComponentMap() + pmIdx = 0 + for i in range(0, len(parameterList)): + vr = parameterList[i] + pmIdx += 1 + if isinstance(vr, ScalarParam): + parameterMap[vr] = 'p_' + str(pmIdx) + elif isinstance(vr, IndexedParam): + parameterMap[vr] = 'p_' + str(pmIdx) + for sd in vr.index_set().data(): + pmIdx += 1 + parameterMap[vr[sd]] = 'p_' + str(pmIdx) + else: + raise DeveloperError( + 'Parameter is not a parameter. Should not happen. Contact developers' + ) + visitor.parameterMap = parameterMap + + setMap = ComponentMap() + for i in range(0, len(setList)): + st = setList[i] + setMap[st] = 'SET' + str(i + 1) + visitor.setMap = setMap + + # starts building the output string + pstr = '' + if not use_equation_environment: + pstr += '\\begin{align} \n' + tbSpc = 4 + trailingAligner = '& ' + else: + pstr += '\\begin{equation} \n' + if not isSingle: + pstr += ' \\begin{aligned} \n' + tbSpc = 8 + else: + tbSpc = 4 + trailingAligner = '&' + + # Iterate over the objectives and print + for obj in objectives: + try: + obj_template, obj_indices = templatize_fcn(obj) + except: + raise RuntimeError( + "An objective has been constructed that cannot be templatized" + ) + + if obj.sense == 1: + pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['minimize']) + else: + pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['maximize']) + + pstr += ' ' * tbSpc + '& & %s %s' % ( + visitor.walk_expression(obj_template), + trailingAligner, + ) + if not use_equation_environment: + pstr += '\\label{obj:' + pyomo_component.name + '_' + obj.name + '} ' + if not isSingle: + pstr += '\\\\ \n' + else: + pstr += '\n' + + # Iterate over the constraints + if len(constraints) > 0: + # only print this if printing a full formulation + if not isSingle: + pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['subject to']) + + # first constraint needs different alignment because of the 'subject to': + # & minimize & & [Objective] + # & subject to & & [Constraint 1] + # & & & [Constraint 2] + # & & & [Constraint N] + + # The double '& &' renders better for some reason + + for i in range(0, len(constraints)): + if not isSingle: + if i == 0: + algn = '& &' + else: + algn = '&&&' + else: + algn = '' + + tail = '\\\\ \n' + + # grab the constraint and templatize + con = constraints[i] + try: + con_template, indices = templatize_fcn(con) + except: + raise RuntimeError( + "A constraint has been constructed that cannot be templatized" + ) + + # Walk the constraint + conLine = ( + ' ' * tbSpc + + algn + + ' %s %s' % (visitor.walk_expression(con_template), trailingAligner) + ) + + # Multiple constraints are generated using a set + if len(indices) > 0: + idxTag = '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( + indices[0]._group, + setMap[indices[0]._set], + ) + setTag = '__S_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( + indices[0]._group, + setMap[indices[0]._set], + ) + + conLine += '%s %s \\in %s ' % (forallTag, idxTag, setTag) + pstr += conLine + + # Add labels as needed + if not use_equation_environment: + pstr += '\\label{con:' + pyomo_component.name + '_' + con.name + '} ' + + # prevents an emptly blank line from being at the end of the latex output + if i <= len(constraints) - 2: + pstr += tail + else: + pstr += tail + # pstr += '\n' + + # Print bounds and sets + if not isSingle: + variableList = [ + vr + for vr in pyomo_component.component_objects( + pyo.Var, descend_into=True, active=True + ) + ] + + varBoundData = [] + for i in range(0, len(variableList)): + vr = variableList[i] + if isinstance(vr, ScalarVar): + varBoundDataEntry = analyze_variable(vr, visitor) + varBoundData.append(varBoundDataEntry) + elif isinstance(vr, IndexedVar): + varBoundData_indexedVar = [] + setData = vr.index_set().data() + for sd in setData: + varBoundDataEntry = analyze_variable(vr[sd], visitor) + varBoundData_indexedVar.append(varBoundDataEntry) + globIndexedVariables = True + for j in range(0, len(varBoundData_indexedVar) - 1): + chks = [] + chks.append( + varBoundData_indexedVar[j]['lowerBound'] + == varBoundData_indexedVar[j + 1]['lowerBound'] + ) + chks.append( + varBoundData_indexedVar[j]['upperBound'] + == varBoundData_indexedVar[j + 1]['upperBound'] + ) + chks.append( + varBoundData_indexedVar[j]['domainName'] + == varBoundData_indexedVar[j + 1]['domainName'] + ) + if not all(chks): + globIndexedVariables = False + break + if globIndexedVariables: + varBoundData.append( + { + 'variable': vr, + 'lowerBound': varBoundData_indexedVar[0]['lowerBound'], + 'upperBound': varBoundData_indexedVar[0]['upperBound'], + 'domainName': varBoundData_indexedVar[0]['domainName'], + 'domainLatex': varBoundData_indexedVar[0]['domainLatex'], + } + ) + else: + varBoundData += varBoundData_indexedVar + else: + raise DeveloperError( + 'Variable is not a variable. Should not happen. Contact developers' + ) + + # print the accumulated data to the string + bstr = '' + appendBoundString = False + useThreeAlgn = False + for i in range(0, len(varBoundData)): + vbd = varBoundData[i] + if ( + vbd['lowerBound'] == '' + and vbd['upperBound'] == '' + and vbd['domainName'] == 'Reals' + ): + # unbounded all real, do not print + if i <= len(varBoundData) - 2: + bstr = bstr[0:-2] + else: + if not useThreeAlgn: + algn = '& &' + useThreeAlgn = True + else: + algn = '&&&' + + if use_equation_environment: + conLabel = '' + else: + conLabel = ( + ' \\label{con:' + + pyomo_component.name + + '_' + + variableMap[vbd['variable']] + + '_bound' + + '} ' + ) + + appendBoundString = True + coreString = ( + vbd['lowerBound'] + + variableMap[vbd['variable']] + + vbd['upperBound'] + + ' ' + + trailingAligner + + '\\qquad \\in ' + + vbd['domainLatex'] + + conLabel + ) + bstr += ' ' * tbSpc + algn + ' %s' % (coreString) + if i <= len(varBoundData) - 2: + bstr += '\\\\ \n' + else: + bstr += '\n' + + if appendBoundString: + pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['with bounds']) + pstr += bstr + '\n' + else: + pstr = pstr[0:-4] + '\n' + + # close off the print string + if not use_equation_environment: + pstr += '\\end{align} \n' + else: + if not isSingle: + pstr += ' \\end{aligned} \n' + pstr += ' \\label{%s} \n' % (pyomo_component.name) + pstr += '\\end{equation} \n' + + # Handling the iterator indices + defaultSetLatexNames = ComponentMap() + for i in range(0, len(setList)): + st = setList[i] + if use_smart_variables: + chkName = setList[i].name + if len(chkName) == 1 and chkName.upper() == chkName: + chkName += '_mathcal' + defaultSetLatexNames[st] = applySmartVariables(chkName) + else: + defaultSetLatexNames[st] = setList[i].name.replace('_', '\\_') + + ## Could be used in the future if someone has a lot of sets + # defaultSetLatexNames[st] = 'mathcal{' + alphabetStringGenerator(i).upper() + '}' + + if st in overwrite_dict.keys(): + if use_smart_variables: + defaultSetLatexNames[st] = applySmartVariables(overwrite_dict[st][0]) + else: + defaultSetLatexNames[st] = overwrite_dict[st][0].replace('_', '\\_') + + defaultSetLatexNames[st] = defaultSetLatexNames[st].replace( + '\\mathcal', r'\\mathcal' + ) + + latexLines = pstr.split('\n') + for jj in range(0, len(latexLines)): + groupMap = {} + uniqueSets = [] + ln = latexLines[jj] + # only modify if there is a placeholder in the line + if "PLACEHOLDER_8675309_GROUP_" in ln: + splitLatex = ln.split('__') + # Find the unique combinations of group numbers and set names + for word in splitLatex: + if "PLACEHOLDER_8675309_GROUP_" in word: + ifo = word.split("PLACEHOLDER_8675309_GROUP_")[1] + gpNum, stName = ifo.split('_') + if gpNum not in groupMap.keys(): + groupMap[gpNum] = [stName] + if stName not in uniqueSets: + uniqueSets.append(stName) + + # Determine if the set is continuous + setInfo = dict( + zip( + uniqueSets, + [{'continuous': False} for i in range(0, len(uniqueSets))], + ) + ) + + for ky, vl in setInfo.items(): + ix = int(ky[3:]) - 1 + setInfo[ky]['setObject'] = setList[ix] + setInfo[ky][ + 'setRegEx' + ] = r'__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__' % (ky) + setInfo[ky][ + 'sumSetRegEx' + ] = r'sum_{__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__}' % (ky) + # setInfo[ky]['idxRegEx'] = r'__I_PLACEHOLDER_8675309_GROUP_[0-9*]_%s__'%(ky) + + if split_continuous_sets: + for ky, vl in setInfo.items(): + st = vl['setObject'] + stData = st.data() + stCont = True + for ii in range(0, len(stData)): + if ii + stData[0] != stData[ii]: + stCont = False + break + setInfo[ky]['continuous'] = stCont + + # replace the sets + for ky, vl in setInfo.items(): + # if the set is continuous and the flag has been set + if split_continuous_sets and setInfo[ky]['continuous']: + st = setInfo[ky]['setObject'] + stData = st.data() + bgn = stData[0] + ed = stData[-1] + + replacement = ( + r'sum_{ __I_PLACEHOLDER_8675309_GROUP_\1_%s__ = %d }^{%d}' + % (ky, bgn, ed) + ) + ln = re.sub(setInfo[ky]['sumSetRegEx'], replacement, ln) + else: + # if the set is not continuous or the flag has not been set + replacement = ( + r'sum_{ __I_PLACEHOLDER_8675309_GROUP_\1_%s__ \\in __S_PLACEHOLDER_8675309_GROUP_\1_%s__ }' + % (ky, ky) + ) + ln = re.sub(setInfo[ky]['sumSetRegEx'], replacement, ln) + + replacement = defaultSetLatexNames[setInfo[ky]['setObject']] + ln = re.sub(setInfo[ky]['setRegEx'], replacement, ln) + + # groupNumbers = re.findall(r'__I_PLACEHOLDER_8675309_GROUP_([0-9*])_SET[0-9]*__',ln) + setNumbers = re.findall( + r'__I_PLACEHOLDER_8675309_GROUP_[0-9*]_SET([0-9]*)__', ln + ) + groupSetPairs = re.findall( + r'__I_PLACEHOLDER_8675309_GROUP_([0-9*])_SET([0-9]*)__', ln + ) + + groupInfo = {} + for vl in setNumbers: + groupInfo['SET' + vl] = { + 'setObject': setInfo['SET' + vl]['setObject'], + 'indices': [], + } + + for gp in groupSetPairs: + if gp[0] not in groupInfo['SET' + gp[1]]['indices']: + groupInfo['SET' + gp[1]]['indices'].append(gp[0]) + + indexCounter = 0 + for ky, vl in groupInfo.items(): + indexNames = latex_component_map[vl['setObject']][1] + if vl['setObject'] in latex_component_map.keys() and len(indexNames) != 0 : + indexNames = overwrite_dict[vl['setObject']][1] + if len(indexNames) < len(vl['indices']): + raise ValueError( + 'Insufficient number of indices provided to the overwrite dictionary for set %s' + % (vl['setObject'].name) + ) + for i in range(0, len(indexNames)): + ln = ln.replace( + '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' + % (vl['indices'][i], ky), + indexNames[i], + ) + else: + for i in range(0, len(vl['indices'])): + ln = ln.replace( + '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' + % (vl['indices'][i], ky), + alphabetStringGenerator(indexCounter, True), + ) + indexCounter += 1 + + # print('gn',groupInfo) + + latexLines[jj] = ln + + pstr = '\n'.join(latexLines) + # pstr = pstr.replace('\\mathcal{', 'mathcal{') + # pstr = pstr.replace('mathcal{', '\\mathcal{') + + if x_only_mode in [1, 2, 3]: + # Need to preserve only the set elements in the overwrite_dict + new_overwrite_dict = {} + for ky, vl in overwrite_dict.items(): + if isinstance(ky, _GeneralVarData): + pass + elif isinstance(ky, _ParamData): + pass + elif isinstance(ky, _SetData): + new_overwrite_dict[ky] = overwrite_dict[ky] + else: + raise ValueError( + 'The overwrite_dict object has a key of invalid type: %s' + % (str(ky)) + ) + overwrite_dict = new_overwrite_dict + + # # Only x modes + # # Mode 0 : dont use + # # Mode 1 : indexed variables become x_{_{ix}} + # # Mode 2 : uses standard alphabet [a,...,z,aa,...,az,...,aaa,...] with subscripts for indices, ex: abcd_{ix} + # # Mode 3 : unwrap everything into an x_{} list, including the indexed vars themselves + + if x_only_mode == 1: + vrIdx = 0 + new_variableMap = ComponentMap() + for i in range(0, len(variableList)): + vr = variableList[i] + vrIdx += 1 + if isinstance(vr, ScalarVar): + new_variableMap[vr] = 'x_{' + str(vrIdx) + '}' + elif isinstance(vr, IndexedVar): + new_variableMap[vr] = 'x_{' + str(vrIdx) + '}' + for sd in vr.index_set().data(): + # vrIdx += 1 + sdString = str(sd) + if sdString[0] == '(': + sdString = sdString[1:] + if sdString[-1] == ')': + sdString = sdString[0:-1] + new_variableMap[vr[sd]] = ( + 'x_{' + str(vrIdx) + '_{' + sdString + '}' + '}' + ) + else: + raise DeveloperError( + 'Variable is not a variable. Should not happen. Contact developers' + ) + + pmIdx = 0 + new_parameterMap = ComponentMap() + for i in range(0, len(parameterList)): + pm = parameterList[i] + pmIdx += 1 + if isinstance(pm, ScalarParam): + new_parameterMap[pm] = 'p_{' + str(pmIdx) + '}' + elif isinstance(pm, IndexedParam): + new_parameterMap[pm] = 'p_{' + str(pmIdx) + '}' + for sd in pm.index_set().data(): + sdString = str(sd) + if sdString[0] == '(': + sdString = sdString[1:] + if sdString[-1] == ')': + sdString = sdString[0:-1] + new_parameterMap[pm[sd]] = ( + 'p_{' + str(pmIdx) + '_{' + sdString + '}' + '}' + ) + else: + raise DeveloperError( + 'Parameter is not a parameter. Should not happen. Contact developers' + ) + + new_overwrite_dict = ComponentMap() + for ky, vl in new_variableMap.items(): + new_overwrite_dict[ky] = vl + for ky, vl in new_parameterMap.items(): + new_overwrite_dict[ky] = vl + for ky, vl in overwrite_dict.items(): + new_overwrite_dict[ky] = vl + overwrite_dict = new_overwrite_dict + + elif x_only_mode == 2: + vrIdx = 0 + new_variableMap = ComponentMap() + for i in range(0, len(variableList)): + vr = variableList[i] + vrIdx += 1 + if isinstance(vr, ScalarVar): + new_variableMap[vr] = alphabetStringGenerator(i) + elif isinstance(vr, IndexedVar): + new_variableMap[vr] = alphabetStringGenerator(i) + for sd in vr.index_set().data(): + # vrIdx += 1 + sdString = str(sd) + if sdString[0] == '(': + sdString = sdString[1:] + if sdString[-1] == ')': + sdString = sdString[0:-1] + new_variableMap[vr[sd]] = ( + alphabetStringGenerator(i) + '_{' + sdString + '}' + ) + else: + raise DeveloperError( + 'Variable is not a variable. Should not happen. Contact developers' + ) + + pmIdx = vrIdx - 1 + new_parameterMap = ComponentMap() + for i in range(0, len(parameterList)): + pm = parameterList[i] + pmIdx += 1 + if isinstance(pm, ScalarParam): + new_parameterMap[pm] = alphabetStringGenerator(pmIdx) + elif isinstance(pm, IndexedParam): + new_parameterMap[pm] = alphabetStringGenerator(pmIdx) + for sd in pm.index_set().data(): + sdString = str(sd) + if sdString[0] == '(': + sdString = sdString[1:] + if sdString[-1] == ')': + sdString = sdString[0:-1] + new_parameterMap[pm[sd]] = ( + alphabetStringGenerator(pmIdx) + '_{' + sdString + '}' + ) + else: + raise DeveloperError( + 'Parameter is not a parameter. Should not happen. Contact developers' + ) + + new_overwrite_dict = ComponentMap() + for ky, vl in new_variableMap.items(): + new_overwrite_dict[ky] = vl + for ky, vl in new_parameterMap.items(): + new_overwrite_dict[ky] = vl + for ky, vl in overwrite_dict.items(): + new_overwrite_dict[ky] = vl + overwrite_dict = new_overwrite_dict + + elif x_only_mode == 3: + new_overwrite_dict = ComponentMap() + for ky, vl in variableMap.items(): + new_overwrite_dict[ky] = vl + for ky, vl in parameterMap.items(): + new_overwrite_dict[ky] = vl + for ky, vl in overwrite_dict.items(): + new_overwrite_dict[ky] = vl + overwrite_dict = new_overwrite_dict + + else: + vrIdx = 0 + new_variableMap = ComponentMap() + for i in range(0, len(variableList)): + vr = variableList[i] + vrIdx += 1 + if isinstance(vr, ScalarVar): + new_variableMap[vr] = vr.name + elif isinstance(vr, IndexedVar): + new_variableMap[vr] = vr.name + for sd in vr.index_set().data(): + # vrIdx += 1 + sdString = str(sd) + if sdString[0] == '(': + sdString = sdString[1:] + if sdString[-1] == ')': + sdString = sdString[0:-1] + if use_smart_variables: + new_variableMap[vr[sd]] = applySmartVariables( + vr.name + '_' + sdString + ) + else: + new_variableMap[vr[sd]] = vr[sd].name + else: + raise DeveloperError( + 'Variable is not a variable. Should not happen. Contact developers' + ) + + pmIdx = 0 + new_parameterMap = ComponentMap() + for i in range(0, len(parameterList)): + pm = parameterList[i] + pmIdx += 1 + if isinstance(pm, ScalarParam): + new_parameterMap[pm] = pm.name + elif isinstance(pm, IndexedParam): + new_parameterMap[pm] = pm.name + for sd in pm.index_set().data(): + # pmIdx += 1 + sdString = str(sd) + if sdString[0] == '(': + sdString = sdString[1:] + if sdString[-1] == ')': + sdString = sdString[0:-1] + if use_smart_variables: + new_parameterMap[pm[sd]] = applySmartVariables( + pm.name + '_' + sdString + ) + else: + new_parameterMap[pm[sd]] = str(pm[sd]) # .name + else: + raise DeveloperError( + 'Parameter is not a parameter. Should not happen. Contact developers' + ) + + for ky, vl in new_variableMap.items(): + if ky not in overwrite_dict.keys(): + overwrite_dict[ky] = vl + for ky, vl in new_parameterMap.items(): + if ky not in overwrite_dict.keys(): + overwrite_dict[ky] = vl + + rep_dict = {} + for ky in list(reversed(list(overwrite_dict.keys()))): + if isinstance(ky, (pyo.Var, pyo.Param)): + if use_smart_variables and x_only_mode in [0, 3]: + overwrite_value = applySmartVariables(overwrite_dict[ky]) + else: + overwrite_value = overwrite_dict[ky] + rep_dict[variableMap[ky]] = overwrite_value + elif isinstance(ky, (_GeneralVarData, _ParamData)): + if use_smart_variables and x_only_mode in [3]: + overwrite_value = applySmartVariables(overwrite_dict[ky]) + else: + overwrite_value = overwrite_dict[ky] + rep_dict[variableMap[ky]] = overwrite_value + elif isinstance(ky, _SetData): + # already handled + pass + elif isinstance(ky, (float, int)): + # happens when immutable parameters are used, do nothing + pass + else: + raise ValueError( + 'The overwrite_dict object has a key of invalid type: %s' % (str(ky)) + ) + + if not use_smart_variables: + for ky, vl in rep_dict.items(): + rep_dict[ky] = vl.replace('_', '\\_') + + label_rep_dict = copy.deepcopy(rep_dict) + for ky, vl in label_rep_dict.items(): + label_rep_dict[ky] = vl.replace('{', '').replace('}', '').replace('\\', '') + + splitLines = pstr.split('\n') + for i in range(0, len(splitLines)): + if use_equation_environment: + splitLines[i] = multiple_replace(splitLines[i], rep_dict) + else: + if '\\label{' in splitLines[i]: + epr, lbl = splitLines[i].split('\\label{') + epr = multiple_replace(epr, rep_dict) + lbl = multiple_replace(lbl, label_rep_dict) + splitLines[i] = epr + '\\label{' + lbl + + pstr = '\n'.join(splitLines) + + pattern = r'_{([^{]*)}_{([^{]*)}' + replacement = r'_{\1_{\2}}' + pstr = re.sub(pattern, replacement, pstr) + + pattern = r'_(.)_{([^}]*)}' + replacement = r'_{\1_{\2}}' + pstr = re.sub(pattern, replacement, pstr) + + splitLines = pstr.split('\n') + finalLines = [] + for sl in splitLines: + if sl != '': + finalLines.append(sl) + + pstr = '\n'.join(finalLines) + + # optional write to output file + if filename is not None: + fstr = '' + fstr += '\\documentclass{article} \n' + fstr += '\\usepackage{amsmath} \n' + fstr += '\\usepackage{amssymb} \n' + fstr += '\\usepackage{dsfont} \n' + fstr += '\\allowdisplaybreaks \n' + fstr += '\\begin{document} \n' + fstr += pstr + fstr += '\\end{document} \n' + f = open(filename, 'w') + f.write(fstr) + f.close() + + # return the latex string + return pstr From 83069253086f5199c35ee22aadf1b1ea85fdf891 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Sat, 23 Sep 2023 15:08:00 -0400 Subject: [PATCH 0205/1204] add update_solver_timelimit --- pyomo/contrib/mindtpy/algorithm_base_class.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 514c2edaedb..0eb602bdf7e 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -1618,6 +1618,7 @@ def solve_main(self): # setup main problem self.setup_main() mip_args = self.set_up_mip_solver() + update_solver_timelimit(self.mip_opt, config.mip_solver, self.timing, config) try: main_mip_results = self.mip_opt.solve( @@ -1675,6 +1676,9 @@ def solve_fp_main(self): config = self.config self.setup_fp_main() mip_args = self.set_up_mip_solver() + update_solver_timelimit( + self.mip_opt, config.mip_solver, self.timing, config + ) main_mip_results = self.mip_opt.solve( self.mip, From e0c245bb9b4b9b0433e422a280866d5b62945718 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 25 Sep 2023 12:36:16 -0400 Subject: [PATCH 0206/1204] add skip --- pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py | 12 ++++++++++-- .../contrib/pynumero/interfaces/external_grey_box.py | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py index d9ba683d198..f3310e2f1c8 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py @@ -15,7 +15,7 @@ from pyomo.contrib.mindtpy.tests.MINLP_simple import SimpleMINLP as SimpleMINLP from pyomo.environ import SolverFactory, value, maximize from pyomo.opt import TerminationCondition - +from pyomo.common.dependencies import numpy_available, scipy_available model_list = [SimpleMINLP(grey_box=True)] required_solvers = ('cyipopt', 'glpk') @@ -24,7 +24,6 @@ else: subsolvers_available = False - @unittest.skipIf( not subsolvers_available, 'Required subsolvers %s are not available' % (required_solvers,), @@ -32,6 +31,15 @@ @unittest.skipIf( not differentiate_available, 'Symbolic differentiation is not available' ) +@unittest.skipIf( + not numpy_available, + 'Required numpy %s is not available', +) +@unittest.skipIf( + not scipy_available, + 'Required scipy %s is not available', +) + class TestMindtPy(unittest.TestCase): """Tests for the MindtPy solver plugin.""" diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index a1a01d751e7..642fd3bf310 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -11,6 +11,7 @@ import abc import logging +from scipy.sparse import coo_matrix from pyomo.common.dependencies import numpy as np from pyomo.common.deprecation import RenamedClass From 5ffaeb11aa0f09e3dad9ab2d2ddc154a67c6c542 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 25 Sep 2023 12:36:38 -0400 Subject: [PATCH 0207/1204] black format --- pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py index f3310e2f1c8..0618c447104 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py @@ -24,6 +24,7 @@ else: subsolvers_available = False + @unittest.skipIf( not subsolvers_available, 'Required subsolvers %s are not available' % (required_solvers,), @@ -31,15 +32,8 @@ @unittest.skipIf( not differentiate_available, 'Symbolic differentiation is not available' ) -@unittest.skipIf( - not numpy_available, - 'Required numpy %s is not available', -) -@unittest.skipIf( - not scipy_available, - 'Required scipy %s is not available', -) - +@unittest.skipIf(not numpy_available, 'Required numpy %s is not available') +@unittest.skipIf(not scipy_available, 'Required scipy %s is not available') class TestMindtPy(unittest.TestCase): """Tests for the MindtPy solver plugin.""" From 260b2d637d8b99b6066fc25d1b8580dc5cdc498e Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 25 Sep 2023 13:12:11 -0400 Subject: [PATCH 0208/1204] move numpy and scipy check forward --- pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py index 0618c447104..5360cfab687 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py @@ -12,10 +12,12 @@ """Tests for the MindtPy solver.""" from pyomo.core.expr.calculus.diff_with_sympy import differentiate_available import pyomo.common.unittest as unittest -from pyomo.contrib.mindtpy.tests.MINLP_simple import SimpleMINLP as SimpleMINLP from pyomo.environ import SolverFactory, value, maximize from pyomo.opt import TerminationCondition from pyomo.common.dependencies import numpy_available, scipy_available +@unittest.skipIf(not numpy_available, 'Required numpy %s is not available') +@unittest.skipIf(not scipy_available, 'Required scipy %s is not available') +from pyomo.contrib.mindtpy.tests.MINLP_simple import SimpleMINLP as SimpleMINLP model_list = [SimpleMINLP(grey_box=True)] required_solvers = ('cyipopt', 'glpk') @@ -32,8 +34,7 @@ @unittest.skipIf( not differentiate_available, 'Symbolic differentiation is not available' ) -@unittest.skipIf(not numpy_available, 'Required numpy %s is not available') -@unittest.skipIf(not scipy_available, 'Required scipy %s is not available') + class TestMindtPy(unittest.TestCase): """Tests for the MindtPy solver plugin.""" From 0bf7b246a36192baf8e99bdedce1d172fb47e8cf Mon Sep 17 00:00:00 2001 From: jasherma Date: Mon, 25 Sep 2023 14:54:21 -0400 Subject: [PATCH 0209/1204] Use `Preformatted` API for default progress logger --- pyomo/contrib/pyros/pyros.py | 9 +-- pyomo/contrib/pyros/solve_data.py | 2 +- pyomo/contrib/pyros/util.py | 105 ++++++++++++++++++++++-------- 3 files changed, 82 insertions(+), 34 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 8b463d96169..ede378fc461 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -521,12 +521,9 @@ def pyros_config(): Logger (or name thereof) used for reporting PyROS solver progress. If a `str` is specified, then ``progress_logger`` is cast to ``logging.getLogger(progress_logger)``. - In the default case, we also configure the logger - as follows: - set ``propagate=False``, - set ``level=logging.INFO``, - clear all handlers, - and add a single ``StreamHandler`` with default options. + In the default case, `progress_logger` is set to + a :class:`pyomo.contrib.pyros.util.PreformattedLogger` + object of level ``logging.INFO``. """ ), is_optional=True, diff --git a/pyomo/contrib/pyros/solve_data.py b/pyomo/contrib/pyros/solve_data.py index 91645921a10..a3062185233 100644 --- a/pyomo/contrib/pyros/solve_data.py +++ b/pyomo/contrib/pyros/solve_data.py @@ -66,7 +66,7 @@ def __str__(self): ) for attr_name, (attr_desc, fmt_str) in attr_name_format_dict.items(): val = getattr(self, attr_name) - val_str = eval(fmt_str) + val_str = eval(fmt_str) if val is not None else str(val) lines.append(f" {attr_desc:<{attr_desc_pad_length}s} : {val_str}") return "\n".join(lines) diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index fd50df6a92d..b031ac9e2d3 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -42,6 +42,7 @@ from pprint import pprint import math from pyomo.common.timing import HierarchicalTimer +from pyomo.common.log import Preformatted # Tolerances used in the code @@ -323,34 +324,75 @@ def revert_solver_max_time_adjustment( del solver.options[options_key] -def setup_default_pyros_logger(): +class PreformattedLogger(logging.Logger): + """ + A specialized logger object designed to cast log messages + to Pyomo `Preformatted` objects prior to logging the messages. + Useful for circumventing the formatters of the standard Pyomo + logger in the event an instance is a descendant of the Pyomo + logger. """ - Setup default PyROS logger. - Returns - ------- - logging.Logger - Default PyROS logger. Settings: + def critical(self, msg, *args, **kwargs): + """ + Preformat and log ``msg % args`` with severity + `logging.CRITICAL`. + """ + return super(PreformattedLogger, self).critical( + Preformatted(msg % args if args else msg), + **kwargs, + ) - - ``name=DEFAULT_LOGGER_NAME`` - - ``propagate=False`` - - All handlers cleared, and a single ``StreamHandler`` - (with default settings) added. - """ - logger = logging.getLogger(DEFAULT_LOGGER_NAME) + def error(self, msg, *args, **kwargs): + """ + Preformat and log ``msg % args`` with severity + `logging.ERROR`. + """ + return super(PreformattedLogger, self).error( + Preformatted(msg % args if args else msg), + **kwargs, + ) - # avoid possible influence of Pyomo logger customizations - logger.propagate = False + def warning(self, msg, *args, **kwargs): + """ + Preformat and log ``msg % args`` with severity + `logging.WARNING`. + """ + return super(PreformattedLogger, self).warning( + Preformatted(msg % args if args else msg), + **kwargs, + ) - # clear handlers, want just a single stream handler - logger.handlers.clear() - ch = logging.StreamHandler() - logger.addHandler(ch) + def info(self, msg, *args, **kwargs): + """ + Preformat and log ``msg % args`` with severity + `logging.INFO`. + """ + return super(PreformattedLogger, self).info( + Preformatted(msg % args if args else msg), + **kwargs, + ) - # info level logger - logger.setLevel(logging.INFO) + def debug(self, msg, *args, **kwargs): + """ + Preformat and log ``msg % args`` with severity + `logging.DEBUG`. + """ + return super(PreformattedLogger, self).debug( + Preformatted(msg % args if args else msg), + **kwargs, + ) - return logger + def log(self, level, msg, *args, **kwargs): + """ + Preformat and log ``msg % args`` with integer + severity `level`. + """ + return super(PreformattedLogger, self).log( + level, + Preformatted(msg % args if args else msg), + **kwargs, + ) def a_logger(str_or_logger): @@ -367,18 +409,27 @@ def a_logger(str_or_logger): logging.Logger If `str_or_logger` is of type `logging.Logger`,then `str_or_logger` is returned. - Otherwise, a logger with name `str_or_logger`, INFO level, - ``propagate=False``, and handlers reduced to just a single - stream handler, is returned. + Otherwise, ``logging.getLogger(str_or_logger)`` + is returned. In the event `str_or_logger` is + the name of the default PyROS logger, the logger level + is set to `logging.INFO`, and a `PreformattedLogger` + instance is returned in lieu of a standard `Logger` + instance. """ if isinstance(str_or_logger, logging.Logger): - return str_or_logger + logger = logging.getLogger(str_or_logger.name) else: if str_or_logger == DEFAULT_LOGGER_NAME: - logger = setup_default_pyros_logger() + # default logger: INFO level, with preformatted messages + current_logger_class = logging.getLoggerClass() + logging.setLoggerClass(PreformattedLogger) + logger = logging.getLogger(str_or_logger) + logger.setLevel(logging.INFO) + logging.setLoggerClass(current_logger_class) else: logger = logging.getLogger(str_or_logger) - return logger + + return logger def ValidEnum(enum_class): From 4e830441ebe26a975b48a3769157d365aed4bf6c Mon Sep 17 00:00:00 2001 From: jasherma Date: Mon, 25 Sep 2023 14:55:47 -0400 Subject: [PATCH 0210/1204] Apply black --- pyomo/contrib/pyros/util.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index b031ac9e2d3..8ad98e38e89 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -339,8 +339,7 @@ def critical(self, msg, *args, **kwargs): `logging.CRITICAL`. """ return super(PreformattedLogger, self).critical( - Preformatted(msg % args if args else msg), - **kwargs, + Preformatted(msg % args if args else msg), **kwargs ) def error(self, msg, *args, **kwargs): @@ -349,8 +348,7 @@ def error(self, msg, *args, **kwargs): `logging.ERROR`. """ return super(PreformattedLogger, self).error( - Preformatted(msg % args if args else msg), - **kwargs, + Preformatted(msg % args if args else msg), **kwargs ) def warning(self, msg, *args, **kwargs): @@ -359,8 +357,7 @@ def warning(self, msg, *args, **kwargs): `logging.WARNING`. """ return super(PreformattedLogger, self).warning( - Preformatted(msg % args if args else msg), - **kwargs, + Preformatted(msg % args if args else msg), **kwargs ) def info(self, msg, *args, **kwargs): @@ -369,8 +366,7 @@ def info(self, msg, *args, **kwargs): `logging.INFO`. """ return super(PreformattedLogger, self).info( - Preformatted(msg % args if args else msg), - **kwargs, + Preformatted(msg % args if args else msg), **kwargs ) def debug(self, msg, *args, **kwargs): @@ -379,8 +375,7 @@ def debug(self, msg, *args, **kwargs): `logging.DEBUG`. """ return super(PreformattedLogger, self).debug( - Preformatted(msg % args if args else msg), - **kwargs, + Preformatted(msg % args if args else msg), **kwargs ) def log(self, level, msg, *args, **kwargs): @@ -389,9 +384,7 @@ def log(self, level, msg, *args, **kwargs): severity `level`. """ return super(PreformattedLogger, self).log( - level, - Preformatted(msg % args if args else msg), - **kwargs, + level, Preformatted(msg % args if args else msg), **kwargs ) From e9232d228767bd2cc823832d105ea1f3f71a2353 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Mon, 25 Sep 2023 20:27:28 -0600 Subject: [PATCH 0211/1204] adding some paper sizing and font size features --- pyomo/util/latex_printer.py | 157 +++++++++++++++++++++++++++++++++--- 1 file changed, 146 insertions(+), 11 deletions(-) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index 68eb20785ef..6f574253f8b 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -53,6 +53,13 @@ from pyomo.core.base.constraint import ScalarConstraint, IndexedConstraint from pyomo.common.collections.component_map import ComponentMap from pyomo.common.collections.component_set import ComponentSet +from pyomo.core.expr.template_expr import ( + NPV_Numeric_GetItemExpression, + NPV_Structural_GetItemExpression, + Numeric_GetAttrExpression +) +from pyomo.core.expr.numeric_expr import NPV_SumExpression +from pyomo.core.base.block import IndexedBlock from pyomo.core.base.external import _PythonCallbackFunctionID @@ -314,12 +321,17 @@ def handle_functionID_node(visitor, node, *args): def handle_indexTemplate_node(visitor, node, *args): + if node._set in ComponentSet(visitor.setMap.keys()): + # already detected set, do nothing + pass + else: + visitor.setMap[node._set] = 'SET%d'%(len(visitor.setMap.keys())+1) + return '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( node._group, visitor.setMap[node._set], ) - def handle_numericGIE_node(visitor, node, *args): joinedName = args[0] @@ -350,6 +362,40 @@ def handle_templateSumExpression_node(visitor, node, *args): def handle_param_node(visitor, node): return visitor.parameterMap[node] +def handle_str_node(visitor, node): + return node.replace('_', '\\_') + +def handle_npv_numericGetItemExpression_node(visitor, node, *args): + joinedName = args[0] + + pstr = '' + pstr += joinedName + '_{' + for i in range(1, len(args)): + pstr += args[i] + if i <= len(args) - 2: + pstr += ',' + else: + pstr += '}' + return pstr + +def handle_npv_structuralGetItemExpression_node(visitor, node, *args): + joinedName = args[0] + + pstr = '' + pstr += joinedName + '[' + for i in range(1, len(args)): + pstr += args[i] + if i <= len(args) - 2: + pstr += ',' + else: + pstr += ']' + return pstr + +def handle_indexedBlock_node(visitor, node, *args): + return str(node) + +def handle_numericGetAttrExpression_node(visitor, node, *args): + return args[0] + '.' + args[1] class _LatexVisitor(StreamBasedExpressionVisitor): def __init__(self): @@ -388,10 +434,24 @@ def __init__(self): TemplateSumExpression: handle_templateSumExpression_node, ScalarParam: handle_param_node, _ParamData: handle_param_node, + IndexedParam: handle_param_node, + NPV_Numeric_GetItemExpression: handle_npv_numericGetItemExpression_node, + IndexedBlock: handle_indexedBlock_node, + NPV_Structural_GetItemExpression: handle_npv_structuralGetItemExpression_node, + str: handle_str_node, + Numeric_GetAttrExpression: handle_numericGetAttrExpression_node, + NPV_SumExpression: handle_sumExpression_node, } def exitNode(self, node, data): - return self._operator_handles[node.__class__](self, node, *data) + try: + return self._operator_handles[node.__class__](self, node, *data) + except: + print(node.__class__) + print(node) + print(data) + + return 'xxx' def analyze_variable(vr): domainMap = { @@ -571,6 +631,8 @@ def latex_printer( use_equation_environment=False, split_continuous_sets=False, use_short_descriptors=False, + fontsize = None, + paper_dimensions=None, ): """This function produces a string that can be rendered as LaTeX @@ -615,6 +677,49 @@ def latex_printer( isSingle = False + fontSizes = ['\\tiny', '\\scriptsize', '\\footnotesize', '\\small', '\\normalsize', '\\large', '\\Large', '\\LARGE', '\\huge', '\\Huge'] + fontSizes_noSlash = ['tiny', 'scriptsize', 'footnotesize', 'small', 'normalsize', 'large', 'Large', 'LARGE', 'huge', 'Huge'] + fontsizes_ints = [ -4, -3, -2, -1, 0, 1, 2, 3, 4, 5 ] + + if fontsize is None: + fontsize = 0 + + elif fontsize in fontSizes: + #no editing needed + pass + elif fontsize in fontSizes_noSlash: + fontsize = '\\' + fontsize + elif fontsize in fontsizes_ints: + fontsize = fontSizes[fontsizes_ints.index(fontsize)] + else: + raise ValueError('passed an invalid font size option %s'%(fontsize)) + + paper_dimensions_used = {} + paper_dimensions_used['height'] = 11.0 + paper_dimensions_used['width'] = 8.5 + paper_dimensions_used['margin_left'] = 1.0 + paper_dimensions_used['margin_right'] = 1.0 + paper_dimensions_used['margin_top'] = 1.0 + paper_dimensions_used['margin_bottom'] = 1.0 + + if paper_dimensions is not None: + for ky in [ 'height', 'width', 'margin_left', 'margin_right', 'margin_top', 'margin_bottom' ]: + if ky in paper_dimensions.keys(): + paper_dimensions_used[ky] = paper_dimensions[ky] + else: + if paper_dimensions_used['height'] >= 225 : + raise ValueError('Paper height exceeds maximum dimension of 225') + if paper_dimensions_used['width'] >= 225 : + raise ValueError('Paper width exceeds maximum dimension of 225') + if paper_dimensions_used['margin_left'] < 0.0: + raise ValueError('Paper margin_left must be greater than or equal to zero') + if paper_dimensions_used['margin_right'] < 0.0: + raise ValueError('Paper margin_right must be greater than or equal to zero') + if paper_dimensions_used['margin_top'] < 0.0: + raise ValueError('Paper margin_top must be greater than or equal to zero') + if paper_dimensions_used['margin_bottom'] < 0.0: + raise ValueError('Paper margin_bottom must be greater than or equal to zero') + if isinstance(pyomo_component, pyo.Objective): objectives = [pyomo_component] constraints = [] @@ -863,15 +968,22 @@ def latex_printer( + ' %s %s' % (visitor.walk_expression(con_template), trailingAligner) ) + # setMap = visitor.setMap # Multiple constraints are generated using a set if len(indices) > 0: + if indices[0]._set in ComponentSet(visitor.setMap.keys()): + # already detected set, do nothing + pass + else: + visitor.setMap[indices[0]._set] = 'SET%d'%(len(visitor.setMap.keys())+1) + idxTag = '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( indices[0]._group, - setMap[indices[0]._set], + visitor.setMap[indices[0]._set], ) setTag = '__S_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( indices[0]._group, - setMap[indices[0]._set], + visitor.setMap[indices[0]._set], ) conLine += ' \\qquad \\forall %s \\in %s ' % (idxTag, setTag) @@ -933,6 +1045,8 @@ def latex_printer( 'Variable is not a variable. Should not happen. Contact developers' ) + # print(varBoundData) + # print the accumulated data to the string bstr = '' appendBoundString = False @@ -945,8 +1059,8 @@ def latex_printer( and vbd['domainName'] == 'Reals' ): # unbounded all real, do not print - if i <= len(varBoundData) - 2: - bstr = bstr[0:-2] + if i == len(varBoundData) - 1: + bstr = bstr[0:-4] else: if not useThreeAlgn: algn = '& &' @@ -998,11 +1112,19 @@ def latex_printer( pstr += ' \\label{%s} \n' % (pyomo_component.name) pstr += '\\end{equation} \n' + + setMap = visitor.setMap + setMap_inverse = {vl: ky for ky, vl in setMap.items()} + # print(setMap) + + # print('\n\n\n\n') + # print(pstr) + # Handling the iterator indices defaultSetLatexNames = ComponentMap() - for i in range(0, len(setList)): - st = setList[i] - defaultSetLatexNames[st] = setList[i].name.replace('_', '\\_') + for ky,vl in setMap.items(): + st = ky + defaultSetLatexNames[st] = st.name.replace('_', '\\_') if st in ComponentSet(latex_component_map.keys()): defaultSetLatexNames[st] = latex_component_map[st][0]#.replace('_', '\\_') @@ -1034,7 +1156,7 @@ def latex_printer( for ky, vl in setInfo.items(): ix = int(ky[3:]) - 1 - setInfo[ky]['setObject'] = setList[ix] + setInfo[ky]['setObject'] = setMap_inverse[ky]#setList[ix] setInfo[ky][ 'setRegEx' ] = r'__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__' % (ky) @@ -1134,6 +1256,8 @@ def latex_printer( latexLines[jj] = ln pstr = '\n'.join(latexLines) + # print('\n\n\n\n') + # print(pstr) vrIdx = 0 new_variableMap = ComponentMap() @@ -1213,13 +1337,19 @@ def latex_printer( for ky, vl in label_rep_dict.items(): label_rep_dict[ky] = vl.replace('{', '').replace('}', '').replace('\\', '') + # print('\n\n\n\n') + # print(pstr) + splitLines = pstr.split('\n') for i in range(0, len(splitLines)): if use_equation_environment: splitLines[i] = multiple_replace(splitLines[i], rep_dict) else: if '\\label{' in splitLines[i]: - epr, lbl = splitLines[i].split('\\label{') + try: + epr, lbl = splitLines[i].split('\\label{') + except: + print(splitLines[i]) epr = multiple_replace(epr, rep_dict) # rep_dict[ky] = vl.replace('_', '\\_') lbl = multiple_replace(lbl, label_rep_dict) @@ -1249,8 +1379,13 @@ def latex_printer( fstr += '\\usepackage{amsmath} \n' fstr += '\\usepackage{amssymb} \n' fstr += '\\usepackage{dsfont} \n' + fstr += '\\usepackage[paperheight=%.4fin, paperwidth=%.4fin, left=%.4fin,right=%.4fin, top=%.4fin, bottom=%.4fin]{geometry} \n'%( + paper_dimensions_used['height'], paper_dimensions_used['width'], + paper_dimensions_used['margin_left'], paper_dimensions_used['margin_right'], + paper_dimensions_used['margin_top'], paper_dimensions_used['margin_bottom'] ) fstr += '\\allowdisplaybreaks \n' fstr += '\\begin{document} \n' + fstr += fontsize + ' \n' fstr += pstr + '\n' fstr += '\\end{document} \n' From 472b5dfee6a58497b566d3d692d1fac209c6b4b3 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 26 Sep 2023 08:27:44 -0600 Subject: [PATCH 0212/1204] Save point: working on writer --- pyomo/solver/IPOPT.py | 3 +-- pyomo/solver/results.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index cce0017c5be..6501154d7ac 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -78,8 +78,7 @@ def version(self): universal_newlines=True, ) version = results.stdout.splitlines()[0] - version = version.split(' ')[1] - version = version.strip() + version = version.split(' ')[1].strip() version = tuple(int(i) for i in version.split('.')) return version diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index c8c92109040..2fa62027e6c 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -293,6 +293,7 @@ def parse_sol_file(file, results): while i < number_of_vars: line = file.readline() variables.append(float(line)) + # Parse the exit code line and capture it exit_code = [0, 0] line = file.readline() if line and ('objno' in line): @@ -306,8 +307,34 @@ def parse_sol_file(file, results): # Not sure if next two lines are needed # if isinstance(res.solver.message, str): # res.solver.message = res.solver.message.replace(':', '\\x3a') + if (exit_code[1] >= 0) and (exit_code[1] <= 99): + results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied + results.solution_status = SolutionStatus.optimal + elif (exit_code[1] >= 100) and (exit_code[1] <= 199): + exit_code_message = "Optimal solution indicated, but ERROR LIKELY!" + results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied + results.solution_status = SolutionStatus.optimal + if results.extra_info.solver_message: + results.extra_info.solver_message += '; ' + exit_code_message + else: + results.extra_info.solver_message = exit_code_message + elif (exit_code[1] >= 200) and (exit_code[1] <= 299): + results.termination_condition = TerminationCondition.locallyInfeasible + results.solution_status = SolutionStatus.infeasible + elif (exit_code[1] >= 300) and (exit_code[1] <= 399): + results.termination_condition = TerminationCondition.unbounded + results.solution_status = SolutionStatus.infeasible + elif (exit_code[1] >= 400) and (exit_code[1] <= 499): + results.solver.termination_condition = TerminationCondition.iterationLimit + elif (exit_code[1] >= 500) and (exit_code[1] <= 599): + exit_code_message = ( + "FAILURE: the solver stopped by an error condition " + "in the solver routines!" + ) + results.solver.termination_condition = TerminationCondition.error + return results - + return results def parse_yaml(): pass From 786f62282c425681e5f036121ee0d6ec474c502d Mon Sep 17 00:00:00 2001 From: gomesraphael7d Date: Tue, 26 Sep 2023 15:33:59 -0300 Subject: [PATCH 0213/1204] Creating integer marker flag for mps file writer // Adjusting integer marker script // changing set_integer name to in_integer_section // adding marker to the end of integer section --- pyomo/core/base/block.py | 11 +++++++++-- pyomo/repn/plugins/mps.py | 33 +++++++++++++++++++++------------ 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 596f52b1259..d5630b32be8 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -1934,7 +1934,14 @@ def valid_problem_types(self): Model object.""" return [ProblemFormat.pyomo] - def write(self, filename=None, format=None, solver_capability=None, io_options={}): + def write( + self, + filename=None, + format=None, + solver_capability=None, + io_options={}, + int_marker=False, + ): """ Write the model to a file, with a given format. """ @@ -1968,7 +1975,7 @@ def write(self, filename=None, format=None, solver_capability=None, io_options={ "Filename '%s' likely does not match specified " "file format (%s)" % (filename, format) ) - problem_writer = WriterFactory(format) + problem_writer = WriterFactory(format, int_marker=int_marker) if problem_writer is None: raise ValueError( "Cannot write model in format '%s': no model " diff --git a/pyomo/repn/plugins/mps.py b/pyomo/repn/plugins/mps.py index 8ebd45f2327..e187f101da3 100644 --- a/pyomo/repn/plugins/mps.py +++ b/pyomo/repn/plugins/mps.py @@ -55,7 +55,7 @@ def _get_bound(exp): @WriterFactory.register('mps', 'Generate the corresponding MPS file') class ProblemWriter_mps(AbstractProblemWriter): - def __init__(self): + def __init__(self, int_marker=False): AbstractProblemWriter.__init__(self, ProblemFormat.mps) # the MPS writer is responsible for tracking which variables are @@ -78,6 +78,8 @@ def __init__(self): # the number's sign. self._precision_string = '.17g' + self._int_marker = int_marker + def __call__(self, model, output_filename, solver_capability, io_options): # Make sure not to modify the user's dictionary, # they may be reusing it outside of this call @@ -515,21 +517,25 @@ def yield_all_constraints(): column_template = " %s %s %" + self._precision_string + "\n" output_file.write("COLUMNS\n") cnt = 0 - set_integer = False + in_integer_section = False + mark_cnt = 0 for vardata in variable_list: col_entries = column_data[variable_to_column[vardata]] cnt += 1 if len(col_entries) > 0: - if vardata.is_integer() and not set_integer: - set_integer = True - output_file.write( - " %s %s %s\n" % ("INT", "'MARKER'", "'INTORG'") - ) - if not vardata.is_integer() and set_integer: - set_integer = False - output_file.write( - " %s %s %s\n" % ("INTEND", "'MARKER'", "'INTEND'") - ) + if self._int_marker: + if vardata.is_integer(): + if not in_integer_section: + output_file.write( + f" MARK{mark_cnt:04d} 'MARKER' 'INTORG'\n" + ) + in_integer_section = True + elif in_integer_section: # and not vardata.is_integer() + output_file.write( + f" MARK{mark_cnt:04d} 'MARKER' 'INTEND'\n" + ) + in_integer_section = False + mark_cnt += 1 var_label = variable_symbol_dictionary[id(vardata)] for i, (row_label, coef) in enumerate(col_entries): @@ -548,6 +554,9 @@ def yield_all_constraints(): var_label = variable_symbol_dictionary[id(vardata)] output_file.write(column_template % (var_label, objective_label, 0)) + if self._int_marker and in_integer_section: + output_file.write(f" MARK{mark_cnt:04d} 'MARKER' 'INTEND'\n") + assert cnt == len(column_data) - 1 if len(column_data[-1]) > 0: col_entries = column_data[-1] From 38fb412a1cc181b485811e7c51a17aeef7edbe37 Mon Sep 17 00:00:00 2001 From: gomesraphael7d Date: Tue, 26 Sep 2023 15:34:11 -0300 Subject: [PATCH 0214/1204] Adjusting tests --- ...r_variable_declaration_with_marker.mps.baseline} | 4 ++-- ...y_variable_declaration_with_marker.mps.baseline} | 3 ++- pyomo/repn/tests/mps/test_mps.py | 13 ++++++++----- 3 files changed, 12 insertions(+), 8 deletions(-) rename pyomo/repn/tests/mps/{integer_variable_declaration.mps.baseline => integer_variable_declaration_with_marker.mps.baseline} (85%) rename pyomo/repn/tests/mps/{knapsack_problem_binary_variable_declaration.mps.baseline => knapsack_problem_binary_variable_declaration_with_marker.mps.baseline} (91%) diff --git a/pyomo/repn/tests/mps/integer_variable_declaration.mps.baseline b/pyomo/repn/tests/mps/integer_variable_declaration_with_marker.mps.baseline similarity index 85% rename from pyomo/repn/tests/mps/integer_variable_declaration.mps.baseline rename to pyomo/repn/tests/mps/integer_variable_declaration_with_marker.mps.baseline index 91901aad77e..efd2293f8fd 100644 --- a/pyomo/repn/tests/mps/integer_variable_declaration.mps.baseline +++ b/pyomo/repn/tests/mps/integer_variable_declaration_with_marker.mps.baseline @@ -9,11 +9,11 @@ ROWS G c_l_const1_ L c_u_const2_ COLUMNS - INT 'MARKER' 'INTORG' + MARK0000 'MARKER' 'INTORG' x1 obj 3 x1 c_l_const1_ 4 x1 c_u_const2_ 1 - INTEND 'MARKER' 'INTEND' + MARK0000 'MARKER' 'INTEND' x2 obj 2 x2 c_l_const1_ 3 x2 c_u_const2_ 2 diff --git a/pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration.mps.baseline b/pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration_with_marker.mps.baseline similarity index 91% rename from pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration.mps.baseline rename to pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration_with_marker.mps.baseline index cfaac8e7595..3f3a642ea33 100644 --- a/pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration.mps.baseline +++ b/pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration_with_marker.mps.baseline @@ -8,7 +8,7 @@ ROWS N obj G c_l_const1_ COLUMNS - INT 'MARKER' 'INTORG' + MARK0000 'MARKER' 'INTORG' x(_1_) obj 3 x(_1_) c_l_const1_ 30 x(_2_) obj 2 @@ -25,6 +25,7 @@ COLUMNS x(_7_) c_l_const1_ 31 x(_8_) obj 1 x(_8_) c_l_const1_ 18 + MARK0000 'MARKER' 'INTEND' RHS RHS c_l_const1_ 60 BOUNDS diff --git a/pyomo/repn/tests/mps/test_mps.py b/pyomo/repn/tests/mps/test_mps.py index e2e5f7aa6ef..9be45a17870 100644 --- a/pyomo/repn/tests/mps/test_mps.py +++ b/pyomo/repn/tests/mps/test_mps.py @@ -46,11 +46,14 @@ def _get_fnames(self): return prefix + ".mps.baseline", prefix + ".mps.out" def _check_baseline(self, model, **kwds): + int_marker = kwds.pop("int_marker", False) baseline_fname, test_fname = self._get_fnames() self._cleanup(test_fname) io_options = {"symbolic_solver_labels": True} io_options.update(kwds) - model.write(test_fname, format="mps", io_options=io_options) + model.write( + test_fname, format="mps", io_options=io_options, int_marker=int_marker + ) self.assertTrue( cmp(test_fname, baseline_fname), @@ -196,7 +199,7 @@ def test_row_ordering(self): row_order[model.con4[2]] = -1 self._check_baseline(model, row_order=row_order) - def test_knapsack_problem_binary_variable_declaration(self): + def test_knapsack_problem_binary_variable_declaration_with_marker(self): elements_size = [30, 24, 11, 35, 29, 8, 31, 18] elements_weight = [3, 2, 2, 4, 5, 4, 3, 1] capacity = 60 @@ -224,9 +227,9 @@ def test_knapsack_problem_binary_variable_declaration(self): name="const", ) - self._check_baseline(model) + self._check_baseline(model, int_marker=True) - def test_integer_variable_declaration(self): + def test_integer_variable_declaration_with_marker(self): model = ConcreteModel("Example-mix-integer-linear-problem") # Define the decision variables @@ -240,7 +243,7 @@ def test_integer_variable_declaration(self): model.const1 = Constraint(expr=4 * model.x1 + 3 * model.x2 >= 10) model.const2 = Constraint(expr=model.x1 + 2 * model.x2 <= 7) - self._check_baseline(model) + self._check_baseline(model, int_marker=True) if __name__ == "__main__": From b8ff42f101aee51b00ee97c7cbcb0542d0b01713 Mon Sep 17 00:00:00 2001 From: gomesraphael7d Date: Tue, 26 Sep 2023 19:01:30 -0300 Subject: [PATCH 0215/1204] Adjusting int_marker flag logic --- pyomo/core/base/block.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index d5630b32be8..fd5322ba686 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -1975,7 +1975,8 @@ def write( "Filename '%s' likely does not match specified " "file format (%s)" % (filename, format) ) - problem_writer = WriterFactory(format, int_marker=int_marker) + int_marker_kwds = {"int_marker": int_marker} if int_marker else {} + problem_writer = WriterFactory(format, **int_marker_kwds) if problem_writer is None: raise ValueError( "Cannot write model in format '%s': no model " From 14be2cb60e19d5ec942ebd788202d77a6f6d7aa3 Mon Sep 17 00:00:00 2001 From: gomesraphael7d Date: Tue, 26 Sep 2023 19:19:13 -0300 Subject: [PATCH 0216/1204] Adjusting mps writer marker name logic --- pyomo/repn/plugins/mps.py | 1 + .../mps/integer_variable_declaration_with_marker.mps.baseline | 2 +- ...problem_binary_variable_declaration_with_marker.mps.baseline | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyomo/repn/plugins/mps.py b/pyomo/repn/plugins/mps.py index e187f101da3..f40c7666278 100644 --- a/pyomo/repn/plugins/mps.py +++ b/pyomo/repn/plugins/mps.py @@ -530,6 +530,7 @@ def yield_all_constraints(): f" MARK{mark_cnt:04d} 'MARKER' 'INTORG'\n" ) in_integer_section = True + mark_cnt += 1 elif in_integer_section: # and not vardata.is_integer() output_file.write( f" MARK{mark_cnt:04d} 'MARKER' 'INTEND'\n" diff --git a/pyomo/repn/tests/mps/integer_variable_declaration_with_marker.mps.baseline b/pyomo/repn/tests/mps/integer_variable_declaration_with_marker.mps.baseline index efd2293f8fd..d455b38af0c 100644 --- a/pyomo/repn/tests/mps/integer_variable_declaration_with_marker.mps.baseline +++ b/pyomo/repn/tests/mps/integer_variable_declaration_with_marker.mps.baseline @@ -13,7 +13,7 @@ COLUMNS x1 obj 3 x1 c_l_const1_ 4 x1 c_u_const2_ 1 - MARK0000 'MARKER' 'INTEND' + MARK0001 'MARKER' 'INTEND' x2 obj 2 x2 c_l_const1_ 3 x2 c_u_const2_ 2 diff --git a/pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration_with_marker.mps.baseline b/pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration_with_marker.mps.baseline index 3f3a642ea33..d19c9285e5c 100644 --- a/pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration_with_marker.mps.baseline +++ b/pyomo/repn/tests/mps/knapsack_problem_binary_variable_declaration_with_marker.mps.baseline @@ -25,7 +25,7 @@ COLUMNS x(_7_) c_l_const1_ 31 x(_8_) obj 1 x(_8_) c_l_const1_ 18 - MARK0000 'MARKER' 'INTEND' + MARK0001 'MARKER' 'INTEND' RHS RHS c_l_const1_ 60 BOUNDS From 000cd86bb16829a0b8b99d66f4a340589ea5abd7 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 27 Sep 2023 07:00:29 -0600 Subject: [PATCH 0217/1204] Add missing import --- pyomo/repn/util.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index 9f9af6b97b2..2c92292965e 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -21,6 +21,7 @@ from pyomo.common.deprecation import deprecation_warning from pyomo.common.errors import DeveloperError, InvalidValueError from pyomo.common.numeric_types import ( + check_if_numeric_type, native_types, native_numeric_types, native_complex_types, From 09c9c2f1d096f44f2682d0841baefbb85a60ef97 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 27 Sep 2023 07:01:48 -0600 Subject: [PATCH 0218/1204] Improve exception/InvalidNumber messages --- pyomo/repn/util.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index 2c92292965e..e5fc8df59fe 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -316,14 +316,18 @@ def _before_complex(visitor, child): def _before_invalid(visitor, child): return False, ( _CONSTANT, - InvalidNumber(child, "'{child}' is not a valid numeric type"), + InvalidNumber( + child, f"{child!r} ({type(child)}) is not a valid numeric type" + ), ) @staticmethod def _before_string(visitor, child): return False, ( _CONSTANT, - InvalidNumber(child, "'{child}' is not a valid numeric type"), + InvalidNumber( + child, f"{child!r} ({type(child)}) is not a valid numeric type" + ), ) @staticmethod @@ -401,8 +405,9 @@ def register_dispatcher(self, visitor, node, *data, key=None): base_key = base_type if base_key not in self: raise DeveloperError( - f"Base expression key '{key}' not found when inserting dispatcher " - f"for node '{type(node).__class__}' when walking expression tree." + f"Base expression key '{base_key}' not found when inserting dispatcher" + f" for node '{type(node).__name__}' when walking expression tree." + ) ) self[key] = self[base_key] return self[key](visitor, node, *data) From b706fe1315fde3d56447d4792a510f464ef371af Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 27 Sep 2023 07:02:53 -0600 Subject: [PATCH 0219/1204] Only cache type / unary, binary, and ternary dispatchers --- pyomo/repn/util.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index e5fc8df59fe..0631c77eea6 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -395,22 +395,31 @@ def register_dispatcher(self, visitor, node, *data, key=None): elif not node.is_potentially_variable(): base_type = node.potentially_variable_base_class() else: - raise DeveloperError( - f"Unexpected expression node type '{type(node).__name__}' " - "found when walking expression tree." - ) + base_type = node.__class__ if isinstance(key, tuple): base_key = (base_type,) + key[1:] + # Only cache handlers for unary, binary and ternary operators + cache = len(key) <= 4 else: base_key = base_type - if base_key not in self: + cache = True + if base_key in self: + fcn = self[base_key] + elif base_type in self: + fcn = self[base_type] + elif any((k[0] if k.__class__ is tuple else k) is base_type for k in self): raise DeveloperError( f"Base expression key '{base_key}' not found when inserting dispatcher" f" for node '{type(node).__name__}' when walking expression tree." ) + else: + raise DeveloperError( + f"Unexpected expression node type '{type(node).__name__}' " + "found when walking expression tree." ) - self[key] = self[base_key] - return self[key](visitor, node, *data) + if cache: + self[key] = fcn + return fcn(visitor, node, *data) def apply_node_operation(node, args): From 5a217cd3d0464fb5de45d6fd6b340a2477b057f2 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 27 Sep 2023 07:03:33 -0600 Subject: [PATCH 0220/1204] Test BeforeChildDispatcher / ExitNodeDispatcher --- pyomo/repn/tests/test_util.py | 183 ++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/pyomo/repn/tests/test_util.py b/pyomo/repn/tests/test_util.py index 58cbbe049cf..8347c521018 100644 --- a/pyomo/repn/tests/test_util.py +++ b/pyomo/repn/tests/test_util.py @@ -18,6 +18,13 @@ from pyomo.common.collections import ComponentMap from pyomo.common.errors import DeveloperError, InvalidValueError from pyomo.common.log import LoggingIntercept +from pyomo.core.expr import ( + ProductExpression, + NPV_ProductExpression, + SumExpression, + DivisionExpression, + NPV_DivisionExpression, +) from pyomo.environ import ( ConcreteModel, Block, @@ -32,6 +39,9 @@ ) import pyomo.repn.util from pyomo.repn.util import ( + _CONSTANT, + BeforeChildDispatcher, + ExitNodeDispatcher, FileDeterminism, FileDeterminism_to_SortComponents, InvalidNumber, @@ -637,6 +647,179 @@ class MockConfig(object): # verify no side effects self.assertEqual(MockConfig.row_order, ref) + def test_ExitNodeDispatcher_registration(self): + end = ExitNodeDispatcher( + { + ProductExpression: lambda v, n, d1, d2: d1 * d2, + Expression: lambda v, n, d: d, + } + ) + self.assertEqual(len(end), 2) + + node = ProductExpression((3, 4)) + self.assertEqual(end[node.__class__](None, node, *node.args), 12) + self.assertEqual(len(end), 2) + + node = Expression(initialize=5) + node.construct() + self.assertEqual(end[node.__class__](None, node, *node.args), 5) + self.assertEqual(len(end), 3) + self.assertIn(node.__class__, end) + + node = NPV_ProductExpression((6, 7)) + self.assertEqual(end[node.__class__](None, node, *node.args), 42) + self.assertEqual(len(end), 4) + self.assertIn(NPV_ProductExpression, end) + + class NewProductExpression(ProductExpression): + pass + + node = NewProductExpression((6, 7)) + with self.assertRaisesRegex( + DeveloperError, r".*Unexpected expression node type 'NewProductExpression'" + ): + end[node.__class__](None, node, *node.args) + self.assertEqual(len(end), 4) + + end[SumExpression, 2] = lambda v, n, *d: 2 * sum(d) + self.assertEqual(len(end), 5) + + node = SumExpression((1, 2, 3)) + self.assertEqual(end[node.__class__, 2](None, node, *node.args), 12) + self.assertEqual(len(end), 5) + + with self.assertRaisesRegex( + DeveloperError, + r"(?s)Base expression key '\(, 3\)' not found when.*" + r"inserting dispatcher for node 'SumExpression' when walking.*" + r"expression tree.", + ): + end[node.__class__, 3](None, node, *node.args) + self.assertEqual(len(end), 5) + + end[SumExpression] = lambda v, n, *d: sum(d) + self.assertEqual(len(end), 6) + self.assertIn(SumExpression, end) + + self.assertEqual(end[node.__class__, 1](None, node, *node.args), 6) + self.assertEqual(len(end), 7) + self.assertIn((SumExpression, 1), end) + + self.assertEqual(end[node.__class__, 3, 4, 5, 6](None, node, *node.args), 6) + self.assertEqual(len(end), 7) + self.assertNotIn((SumExpression, 3, 4, 5, 6), end) + + def test_BeforeChildDispatcher_registration(self): + class BeforeChildDispatcherTester(BeforeChildDispatcher): + @staticmethod + def _before_var(visitor, child): + return child + + @staticmethod + def _before_named_expression(visitor, child): + return child + + class VisitorTester(object): + def handle_constant(self, value, node): + return value + + def evaluate(self, node): + return node() + + visitor = VisitorTester() + + bcd = BeforeChildDispatcherTester() + self.assertEqual(len(bcd), 0) + + node = 5 + self.assertEqual(bcd[node.__class__](None, node), (False, (_CONSTANT, 5))) + self.assertIs(bcd[int], bcd._before_native) + self.assertEqual(len(bcd), 1) + + node = 'string' + ans = bcd[node.__class__](None, node) + self.assertEqual(ans, (False, (_CONSTANT, InvalidNumber(node)))) + self.assertEqual( + ''.join(ans[1][1].causes), + "'string' () is not a valid numeric type", + ) + self.assertIs(bcd[str], bcd._before_string) + self.assertEqual(len(bcd), 2) + + node = True + ans = bcd[node.__class__](None, node) + self.assertEqual(ans, (False, (_CONSTANT, InvalidNumber(node)))) + self.assertEqual( + ''.join(ans[1][1].causes), + "True () is not a valid numeric type", + ) + self.assertIs(bcd[bool], bcd._before_invalid) + self.assertEqual(len(bcd), 3) + + node = 1j + ans = bcd[node.__class__](None, node) + self.assertEqual(ans, (False, (_CONSTANT, InvalidNumber(node)))) + self.assertEqual( + ''.join(ans[1][1].causes), "Complex number returned from expression" + ) + self.assertIs(bcd[complex], bcd._before_complex) + self.assertEqual(len(bcd), 4) + + class new_int(int): + pass + + node = new_int(5) + self.assertEqual(bcd[node.__class__](None, node), (False, (_CONSTANT, 5))) + self.assertIs(bcd[new_int], bcd._before_native) + self.assertEqual(len(bcd), 5) + + node = [] + ans = bcd[node.__class__](None, node) + self.assertEqual(ans, (False, (_CONSTANT, InvalidNumber([])))) + self.assertEqual( + ''.join(ans[1][1].causes), "[] () is not a valid numeric type" + ) + self.assertIs(bcd[list], bcd._before_invalid) + self.assertEqual(len(bcd), 6) + + node = Var(initialize=7) + node.construct() + self.assertIs(bcd[node.__class__](None, node), node) + self.assertIs(bcd[node.__class__], bcd._before_var) + self.assertEqual(len(bcd), 7) + + node = Param(initialize=8) + node.construct() + self.assertEqual(bcd[node.__class__](visitor, node), (False, (_CONSTANT, 8))) + self.assertIs(bcd[node.__class__], bcd._before_param) + self.assertEqual(len(bcd), 8) + + node = Expression(initialize=9) + node.construct() + self.assertIs(bcd[node.__class__](None, node), node) + self.assertIs(bcd[node.__class__], bcd._before_named_expression) + self.assertEqual(len(bcd), 9) + + node = SumExpression((3, 5)) + self.assertEqual(bcd[node.__class__](None, node), (True, None)) + self.assertIs(bcd[node.__class__], bcd._before_general_expression) + self.assertEqual(len(bcd), 10) + + node = NPV_ProductExpression((3, 5)) + self.assertEqual(bcd[node.__class__](visitor, node), (False, (_CONSTANT, 15))) + self.assertEqual(len(bcd), 12) + self.assertIs(bcd[NPV_ProductExpression], bcd._before_npv) + self.assertIs(bcd[ProductExpression], bcd._before_general_expression) + self.assertEqual(len(bcd), 12) + + node = NPV_DivisionExpression((3, 0)) + self.assertEqual(bcd[node.__class__](visitor, node), (True, None)) + self.assertEqual(len(bcd), 14) + self.assertIs(bcd[NPV_DivisionExpression], bcd._before_npv) + self.assertIs(bcd[DivisionExpression], bcd._before_general_expression) + self.assertEqual(len(bcd), 14) + if __name__ == "__main__": unittest.main() From 4ffc85b7a56991beff390b43190f0fee28c37bb7 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 28 Sep 2023 13:45:48 -0600 Subject: [PATCH 0221/1204] Moving the fbbt leaf-to-root visitor onto StreamBasedExpressionVisitor, which may or may not matter for this exercise... --- pyomo/contrib/fbbt/fbbt.py | 239 +++++++++++++++++++++++++++---------- 1 file changed, 176 insertions(+), 63 deletions(-) diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index 5c486488540..62abb83b1ac 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -9,9 +9,12 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +from collections import defaultdict from pyomo.common.collections import ComponentMap, ComponentSet import pyomo.core.expr.numeric_expr as numeric_expr -from pyomo.core.expr.visitor import ExpressionValueVisitor, identify_variables +from pyomo.core.expr.visitor import ( + ExpressionValueVisitor, identify_variables, StreamBasedExpressionVisitor +) from pyomo.core.expr.numvalue import nonpyomo_leaf_types, value from pyomo.core.expr.numvalue import is_fixed import pyomo.contrib.fbbt.interval as interval @@ -450,8 +453,10 @@ def _prop_bnds_leaf_to_root_GeneralExpression(node, bnds_dict, feasibility_tol): expr_lb, expr_ub = bnds_dict[expr] bnds_dict[node] = (expr_lb, expr_ub) +def _prop_no_bounds(node, bnds_dict, feasibility_tol): + bnds_dict[node] = (-interval.inf, interval.inf) -_prop_bnds_leaf_to_root_map = dict() +_prop_bnds_leaf_to_root_map = defaultdict(lambda: _prop_no_bounds) _prop_bnds_leaf_to_root_map[ numeric_expr.ProductExpression ] = _prop_bnds_leaf_to_root_ProductExpression @@ -1031,7 +1036,6 @@ def _prop_bnds_root_to_leaf_GeneralExpression(node, bnds_dict, feasibility_tol): expr_lb, expr_ub = bnds_dict[node] bnds_dict[node.expr] = (expr_lb, expr_ub) - _prop_bnds_root_to_leaf_map = dict() _prop_bnds_root_to_leaf_map[ numeric_expr.ProductExpression @@ -1083,13 +1087,70 @@ def _check_and_reset_bounds(var, lb, ub): ub = orig_ub return lb, ub +def _before_constant(visitor, child): + visitor.bnds_dict[child] = (child, child) + return False, None + +def _before_var(visitor, child): + if child in visitor.bnds_dict: + return False, None + elif child.is_fixed() and not visitor.ignore_fixed: + lb = value(child.value) + ub = lb + else: + lb = value(child.lb) + ub = value(child.ub) + if lb is None: + lb = -interval.inf + if ub is None: + ub = interval.inf + if lb - visitor.feasibility_tol > ub: + raise InfeasibleConstraintException( + 'Variable has a lower bound that is larger than its ' + 'upper bound: {0}'.format( + str(child) + ) + ) + visitor.bnds_dict[child] = (lb, ub) + return False, None + +def _before_NPV(visitor, child): + val = value(child) + visitor.bnds_dict[child] = (val, val) + return False, None + +def _before_other(visitor, child): + return True, None + +def _before_external_function(visitor, child): + # TODO: provide some mechanism for users to provide interval + # arithmetic callback functions for general external + # functions + visitor.bnds_dict[child] = (-interval.inf, interval.inf) + return False, None + +def _register_new_before_child_handler(visitor, child): + handlers = _before_child_handlers + child_type = child.__class__ + if child.is_variable_type(): + handlers[child_type] = _before_var + elif not child.is_potentially_variable(): + handlers[child_type] = _before_NPV + else: + handlers[child_type] = _before_other + return handlers[child_type](visitor, child) + +_before_child_handlers = defaultdict(lambda: _register_new_before_child_handler) +_before_child_handlers[ + numeric_expr.ExternalFunctionExpression] = _before_external_function +for _type in nonpyomo_leaf_types: + _before_child_handlers[_type] = _before_constant -class _FBBTVisitorLeafToRoot(ExpressionValueVisitor): +class _FBBTVisitorLeafToRoot(StreamBasedExpressionVisitor): """ This walker propagates bounds from the variables to each node in the expression tree (all the way to the root node). """ - def __init__( self, bnds_dict, integer_tol=1e-4, feasibility_tol=1e-8, ignore_fixed=False ): @@ -1099,68 +1160,120 @@ def __init__( bnds_dict: ComponentMap integer_tol: float feasibility_tol: float - If the bounds computed on the body of a constraint violate the bounds of the constraint by more than - feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance - is also used when performing certain interval arithmetic operations to ensure that none of the feasible - region is removed due to floating point arithmetic and to prevent math domain errors (a larger value - is more conservative). + If the bounds computed on the body of a constraint violate the bounds of + the constraint by more than feasibility_tol, then the constraint is + considered infeasible and an exception is raised. This tolerance is also + used when performing certain interval arithmetic operations to ensure that + none of the feasible region is removed due to floating point arithmetic and + to prevent math domain errors (a larger value is more conservative). """ + super().__init__() self.bnds_dict = bnds_dict self.integer_tol = integer_tol self.feasibility_tol = feasibility_tol self.ignore_fixed = ignore_fixed - def visit(self, node, values): - if node.__class__ in _prop_bnds_leaf_to_root_map: - _prop_bnds_leaf_to_root_map[node.__class__]( - node, self.bnds_dict, self.feasibility_tol - ) - else: - self.bnds_dict[node] = (-interval.inf, interval.inf) - return None - - def visiting_potential_leaf(self, node): - if node.__class__ in nonpyomo_leaf_types: - self.bnds_dict[node] = (node, node) - return True, None - - if node.is_variable_type(): - if node in self.bnds_dict: - return True, None - if node.is_fixed() and not self.ignore_fixed: - lb = value(node.value) - ub = lb - else: - lb = value(node.lb) - ub = value(node.ub) - if lb is None: - lb = -interval.inf - if ub is None: - ub = interval.inf - if lb - self.feasibility_tol > ub: - raise InfeasibleConstraintException( - 'Variable has a lower bound that is larger than its upper bound: {0}'.format( - str(node) - ) - ) - self.bnds_dict[node] = (lb, ub) - return True, None - - if not node.is_potentially_variable(): - # NPV nodes are effectively constant leaves. Evaluate it - # and return the value. - val = value(node) - self.bnds_dict[node] = (val, val) - return True, None - - if node.__class__ is numeric_expr.ExternalFunctionExpression: - # TODO: provide some mechanism for users to provide interval - # arithmetic callback functions for general external - # functions - self.bnds_dict[node] = (-interval.inf, interval.inf) - return True, None - - return False, None + def initializeWalker(self, expr): + walk, result = self.beforeChild(None, expr, 0) + if not walk: + return False, result#self.finalizeResult(result) + return True, expr + + def beforeChild(self, node, child, child_idx): + return _before_child_handlers[child.__class__](self, child) + + def exitNode(self, node, data): + _prop_bnds_leaf_to_root_map[node.__class__](node, self.bnds_dict, + self.feasibility_tol) + # if node.__class__ in _prop_bnds_leaf_to_root_map: + # _prop_bnds_leaf_to_root_map[node.__class__]( + # node, self.bnds_dict, self.feasibility_tol + # ) + # else: + # self.bnds_dict[node] = (-interval.inf, interval.inf) + # return None + + # def finalizeResult(self, result): + # return result + + +# class _FBBTVisitorLeafToRoot(ExpressionValueVisitor): +# """ +# This walker propagates bounds from the variables to each node in +# the expression tree (all the way to the root node). +# """ + +# def __init__( +# self, bnds_dict, integer_tol=1e-4, feasibility_tol=1e-8, ignore_fixed=False +# ): +# """ +# Parameters +# ---------- +# bnds_dict: ComponentMap +# integer_tol: float +# feasibility_tol: float +# If the bounds computed on the body of a constraint violate the bounds of the constraint by more than +# feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance +# is also used when performing certain interval arithmetic operations to ensure that none of the feasible +# region is removed due to floating point arithmetic and to prevent math domain errors (a larger value +# is more conservative). +# """ +# self.bnds_dict = bnds_dict +# self.integer_tol = integer_tol +# self.feasibility_tol = feasibility_tol +# self.ignore_fixed = ignore_fixed + +# def visit(self, node, values): +# if node.__class__ in _prop_bnds_leaf_to_root_map: +# _prop_bnds_leaf_to_root_map[node.__class__]( +# node, self.bnds_dict, self.feasibility_tol +# ) +# else: +# self.bnds_dict[node] = (-interval.inf, interval.inf) +# return None + +# def visiting_potential_leaf(self, node): +# if node.__class__ in nonpyomo_leaf_types: +# self.bnds_dict[node] = (node, node) +# return True, None + +# if node.is_variable_type(): +# if node in self.bnds_dict: +# return True, None +# if node.is_fixed() and not self.ignore_fixed: +# lb = value(node.value) +# ub = lb +# else: +# lb = value(node.lb) +# ub = value(node.ub) +# if lb is None: +# lb = -interval.inf +# if ub is None: +# ub = interval.inf +# if lb - self.feasibility_tol > ub: +# raise InfeasibleConstraintException( +# 'Variable has a lower bound that is larger than its upper bound: {0}'.format( +# str(node) +# ) +# ) +# self.bnds_dict[node] = (lb, ub) +# return True, None + +# if not node.is_potentially_variable(): +# # NPV nodes are effectively constant leaves. Evaluate it +# # and return the value. +# val = value(node) +# self.bnds_dict[node] = (val, val) +# return True, None + +# if node.__class__ is numeric_expr.ExternalFunctionExpression: +# # TODO: provide some mechanism for users to provide interval +# # arithmetic callback functions for general external +# # functions +# self.bnds_dict[node] = (-interval.inf, interval.inf) +# return True, None + +# return False, None class _FBBTVisitorRootToLeaf(ExpressionValueVisitor): @@ -1331,7 +1444,7 @@ def _fbbt_con(con, config): # a walker to propagate bounds from the variables to the root visitorA = _FBBTVisitorLeafToRoot(bnds_dict, feasibility_tol=config.feasibility_tol) - visitorA.dfs_postorder_stack(con.body) + visitorA.walk_expression(con.body) # Now we need to replace the bounds in bnds_dict for the root # node with the bounds on the constraint (if those bounds are @@ -1582,7 +1695,7 @@ def compute_bounds_on_expr(expr, ignore_fixed=False): """ bnds_dict = ComponentMap() visitor = _FBBTVisitorLeafToRoot(bnds_dict, ignore_fixed=ignore_fixed) - visitor.dfs_postorder_stack(expr) + visitor.walk_expression(expr) lb, ub = bnds_dict[expr] if lb == -interval.inf: lb = None From 231b26c69561e0222177e946c6696dff5603aae7 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 28 Sep 2023 13:46:41 -0600 Subject: [PATCH 0222/1204] Keeping the fbbt visitor (and it's bounds dictionary) around during bigm, which basically means caching Var bounds info for the whole transformation --- pyomo/gdp/plugins/bigm.py | 5 +++++ pyomo/gdp/plugins/bigm_mixin.py | 13 ++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pyomo/gdp/plugins/bigm.py b/pyomo/gdp/plugins/bigm.py index bb731363898..0402a8c69fa 100644 --- a/pyomo/gdp/plugins/bigm.py +++ b/pyomo/gdp/plugins/bigm.py @@ -20,6 +20,7 @@ from pyomo.contrib.cp.transform.logical_to_disjunctive_program import ( LogicalToDisjunctive, ) +from pyomo.contrib.fbbt.fbbt import _FBBTVisitorLeafToRoot from pyomo.core import ( Block, BooleanVar, @@ -178,6 +179,10 @@ def _apply_to(self, instance, **kwds): def _apply_to_impl(self, instance, **kwds): self._process_arguments(instance, **kwds) + bnds_dict = ComponentMap() + self._fbbt_visitor = _FBBTVisitorLeafToRoot( + bnds_dict, ignore_fixed=not self._config.assume_fixed_vars_permanent) + # filter out inactive targets and handle case where targets aren't # specified. targets = self._filter_targets(instance) diff --git a/pyomo/gdp/plugins/bigm_mixin.py b/pyomo/gdp/plugins/bigm_mixin.py index ba25dfeffd0..45395e213db 100644 --- a/pyomo/gdp/plugins/bigm_mixin.py +++ b/pyomo/gdp/plugins/bigm_mixin.py @@ -11,7 +11,8 @@ from pyomo.gdp import GDP_Error from pyomo.common.collections import ComponentSet -from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr +import pyomo.contrib.fbbt.interval as interval +#from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr from pyomo.core import Suffix @@ -210,10 +211,12 @@ def _get_M_from_args(self, constraint, bigMargs, arg_list, lower, upper): return lower, upper def _estimate_M(self, expr, constraint): - expr_lb, expr_ub = compute_bounds_on_expr( - expr, ignore_fixed=not self._config.assume_fixed_vars_permanent - ) - if expr_lb is None or expr_ub is None: + # expr_lb, expr_ub = compute_bounds_on_expr( + # expr, ignore_fixed=not self._config.assume_fixed_vars_permanent + # ) + self._fbbt_visitor.walk_expression(expr) + expr_lb, expr_ub = self._fbbt_visitor.bnds_dict[expr] + if expr_lb == -interval.inf or expr_ub == interval.inf: raise GDP_Error( "Cannot estimate M for unbounded " "expressions.\n\t(found while processing " From 35641b0f3824a768cc1f17336b83314d35c30e0b Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 28 Sep 2023 14:41:22 -0600 Subject: [PATCH 0223/1204] Passing args to the handlers to avoid the assertions --- pyomo/contrib/fbbt/fbbt.py | 127 ++++++++++++++----------------------- 1 file changed, 49 insertions(+), 78 deletions(-) diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index 62abb83b1ac..011a998d4c4 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -76,7 +76,7 @@ class FBBTException(PyomoException): pass -def _prop_bnds_leaf_to_root_ProductExpression(node, bnds_dict, feasibility_tol): +def _prop_bnds_leaf_to_root_ProductExpression(visitor, node, arg1, arg2): """ Parameters @@ -90,17 +90,16 @@ def _prop_bnds_leaf_to_root_ProductExpression(node, bnds_dict, feasibility_tol): region is removed due to floating point arithmetic and to prevent math domain errors (a larger value is more conservative). """ - assert len(node.args) == 2 - arg1, arg2 = node.args + bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg1] lb2, ub2 = bnds_dict[arg2] if arg1 is arg2: - bnds_dict[node] = interval.power(lb1, ub1, 2, 2, feasibility_tol) + bnds_dict[node] = interval.power(lb1, ub1, 2, 2, visitor.feasibility_tol) else: bnds_dict[node] = interval.mul(lb1, ub1, lb2, ub2) -def _prop_bnds_leaf_to_root_SumExpression(node, bnds_dict, feasibility_tol): +def _prop_bnds_leaf_to_root_SumExpression(visitor, node, *args): """ Parameters @@ -114,13 +113,14 @@ def _prop_bnds_leaf_to_root_SumExpression(node, bnds_dict, feasibility_tol): region is removed due to floating point arithmetic and to prevent math domain errors (a larger value is more conservative). """ + bnds_dict = visitor.bnds_dict bnds = (0, 0) - for arg in node.args: + for arg in args: bnds = interval.add(*bnds, *bnds_dict[arg]) bnds_dict[node] = bnds -def _prop_bnds_leaf_to_root_DivisionExpression(node, bnds_dict, feasibility_tol): +def _prop_bnds_leaf_to_root_DivisionExpression(visitor, node, arg1, arg2): """ Parameters @@ -134,14 +134,14 @@ def _prop_bnds_leaf_to_root_DivisionExpression(node, bnds_dict, feasibility_tol) region is removed due to floating point arithmetic and to prevent math domain errors (a larger value is more conservative). """ - assert len(node.args) == 2 - arg1, arg2 = node.args + bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg1] lb2, ub2 = bnds_dict[arg2] - bnds_dict[node] = interval.div(lb1, ub1, lb2, ub2, feasibility_tol=feasibility_tol) + bnds_dict[node] = interval.div(lb1, ub1, lb2, ub2, + feasibility_tol=visitor.feasibility_tol) -def _prop_bnds_leaf_to_root_PowExpression(node, bnds_dict, feasibility_tol): +def _prop_bnds_leaf_to_root_PowExpression(visitor, node, arg1, arg2): """ Parameters @@ -155,16 +155,15 @@ def _prop_bnds_leaf_to_root_PowExpression(node, bnds_dict, feasibility_tol): region is removed due to floating point arithmetic and to prevent math domain errors (a larger value is more conservative). """ - assert len(node.args) == 2 - arg1, arg2 = node.args + bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg1] lb2, ub2 = bnds_dict[arg2] bnds_dict[node] = interval.power( - lb1, ub1, lb2, ub2, feasibility_tol=feasibility_tol + lb1, ub1, lb2, ub2, feasibility_tol=visitor.feasibility_tol ) -def _prop_bnds_leaf_to_root_NegationExpression(node, bnds_dict, feasibility_tol): +def _prop_bnds_leaf_to_root_NegationExpression(visitor, node, arg): """ Parameters @@ -178,13 +177,12 @@ def _prop_bnds_leaf_to_root_NegationExpression(node, bnds_dict, feasibility_tol) region is removed due to floating point arithmetic and to prevent math domain errors (a larger value is more conservative). """ - assert len(node.args) == 1 - arg = node.args[0] + bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] bnds_dict[node] = interval.sub(0, 0, lb1, ub1) -def _prop_bnds_leaf_to_root_exp(node, bnds_dict, feasibility_tol): +def _prop_bnds_leaf_to_root_exp(visitor, node, arg): """ Parameters @@ -198,13 +196,12 @@ def _prop_bnds_leaf_to_root_exp(node, bnds_dict, feasibility_tol): region is removed due to floating point arithmetic and to prevent math domain errors (a larger value is more conservative). """ - assert len(node.args) == 1 - arg = node.args[0] + bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] bnds_dict[node] = interval.exp(lb1, ub1) -def _prop_bnds_leaf_to_root_log(node, bnds_dict, feasibility_tol): +def _prop_bnds_leaf_to_root_log(visitor, node, arg): """ Parameters @@ -218,13 +215,12 @@ def _prop_bnds_leaf_to_root_log(node, bnds_dict, feasibility_tol): region is removed due to floating point arithmetic and to prevent math domain errors (a larger value is more conservative). """ - assert len(node.args) == 1 - arg = node.args[0] + bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] bnds_dict[node] = interval.log(lb1, ub1) -def _prop_bnds_leaf_to_root_log10(node, bnds_dict, feasibility_tol): +def _prop_bnds_leaf_to_root_log10(visitor, node, arg): """ Parameters @@ -238,13 +234,12 @@ def _prop_bnds_leaf_to_root_log10(node, bnds_dict, feasibility_tol): region is removed due to floating point arithmetic and to prevent math domain errors (a larger value is more conservative). """ - assert len(node.args) == 1 - arg = node.args[0] + bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] bnds_dict[node] = interval.log10(lb1, ub1) -def _prop_bnds_leaf_to_root_sin(node, bnds_dict, feasibility_tol): +def _prop_bnds_leaf_to_root_sin(visitor, node, arg): """ Parameters @@ -258,13 +253,12 @@ def _prop_bnds_leaf_to_root_sin(node, bnds_dict, feasibility_tol): region is removed due to floating point arithmetic and to prevent math domain errors (a larger value is more conservative). """ - assert len(node.args) == 1 - arg = node.args[0] + bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] bnds_dict[node] = interval.sin(lb1, ub1) -def _prop_bnds_leaf_to_root_cos(node, bnds_dict, feasibility_tol): +def _prop_bnds_leaf_to_root_cos(visitor, node, arg): """ Parameters @@ -278,13 +272,12 @@ def _prop_bnds_leaf_to_root_cos(node, bnds_dict, feasibility_tol): region is removed due to floating point arithmetic and to prevent math domain errors (a larger value is more conservative). """ - assert len(node.args) == 1 - arg = node.args[0] + bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] bnds_dict[node] = interval.cos(lb1, ub1) -def _prop_bnds_leaf_to_root_tan(node, bnds_dict, feasibility_tol): +def _prop_bnds_leaf_to_root_tan(visitor, node, arg): """ Parameters @@ -298,13 +291,12 @@ def _prop_bnds_leaf_to_root_tan(node, bnds_dict, feasibility_tol): region is removed due to floating point arithmetic and to prevent math domain errors (a larger value is more conservative). """ - assert len(node.args) == 1 - arg = node.args[0] + bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] bnds_dict[node] = interval.tan(lb1, ub1) -def _prop_bnds_leaf_to_root_asin(node, bnds_dict, feasibility_tol): +def _prop_bnds_leaf_to_root_asin(visitor, node, arg): """ Parameters @@ -318,15 +310,14 @@ def _prop_bnds_leaf_to_root_asin(node, bnds_dict, feasibility_tol): region is removed due to floating point arithmetic and to prevent math domain errors (a larger value is more conservative). """ - assert len(node.args) == 1 - arg = node.args[0] + bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] bnds_dict[node] = interval.asin( - lb1, ub1, -interval.inf, interval.inf, feasibility_tol + lb1, ub1, -interval.inf, interval.inf, visitor.feasibility_tol ) -def _prop_bnds_leaf_to_root_acos(node, bnds_dict, feasibility_tol): +def _prop_bnds_leaf_to_root_acos(visitor, node, arg): """ Parameters @@ -340,15 +331,14 @@ def _prop_bnds_leaf_to_root_acos(node, bnds_dict, feasibility_tol): region is removed due to floating point arithmetic and to prevent math domain errors (a larger value is more conservative). """ - assert len(node.args) == 1 - arg = node.args[0] + bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] bnds_dict[node] = interval.acos( - lb1, ub1, -interval.inf, interval.inf, feasibility_tol + lb1, ub1, -interval.inf, interval.inf, visitor.feasibility_tol ) -def _prop_bnds_leaf_to_root_atan(node, bnds_dict, feasibility_tol): +def _prop_bnds_leaf_to_root_atan(visitor, node, arg): """ Parameters @@ -362,13 +352,12 @@ def _prop_bnds_leaf_to_root_atan(node, bnds_dict, feasibility_tol): region is removed due to floating point arithmetic and to prevent math domain errors (a larger value is more conservative). """ - assert len(node.args) == 1 - arg = node.args[0] + bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] bnds_dict[node] = interval.atan(lb1, ub1, -interval.inf, interval.inf) -def _prop_bnds_leaf_to_root_sqrt(node, bnds_dict, feasibility_tol): +def _prop_bnds_leaf_to_root_sqrt(visitor, node, arg): """ Parameters @@ -382,22 +371,22 @@ def _prop_bnds_leaf_to_root_sqrt(node, bnds_dict, feasibility_tol): region is removed due to floating point arithmetic and to prevent math domain errors (a larger value is more conservative). """ - assert len(node.args) == 1 - arg = node.args[0] + bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] bnds_dict[node] = interval.power( - lb1, ub1, 0.5, 0.5, feasibility_tol=feasibility_tol + lb1, ub1, 0.5, 0.5, feasibility_tol=visitor.feasibility_tol ) -def _prop_bnds_leaf_to_root_abs(node, bnds_dict, feasibility_tol): - assert len(node.args) == 1 - arg = node.args[0] +def _prop_bnds_leaf_to_root_abs(visitor, node, arg): + bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] bnds_dict[node] = interval.interval_abs(lb1, ub1) +def _prop_no_bounds(visitor, node, *args): + visitor.bnds_dict[node] = (-interval.inf, interval.inf) -_unary_leaf_to_root_map = dict() +_unary_leaf_to_root_map = defaultdict(lambda: _prop_no_bounds) _unary_leaf_to_root_map['exp'] = _prop_bnds_leaf_to_root_exp _unary_leaf_to_root_map['log'] = _prop_bnds_leaf_to_root_log _unary_leaf_to_root_map['log10'] = _prop_bnds_leaf_to_root_log10 @@ -411,7 +400,7 @@ def _prop_bnds_leaf_to_root_abs(node, bnds_dict, feasibility_tol): _unary_leaf_to_root_map['abs'] = _prop_bnds_leaf_to_root_abs -def _prop_bnds_leaf_to_root_UnaryFunctionExpression(node, bnds_dict, feasibility_tol): +def _prop_bnds_leaf_to_root_UnaryFunctionExpression(visitor, node, arg): """ Parameters @@ -425,13 +414,10 @@ def _prop_bnds_leaf_to_root_UnaryFunctionExpression(node, bnds_dict, feasibility region is removed due to floating point arithmetic and to prevent math domain errors (a larger value is more conservative). """ - if node.getname() in _unary_leaf_to_root_map: - _unary_leaf_to_root_map[node.getname()](node, bnds_dict, feasibility_tol) - else: - bnds_dict[node] = (-interval.inf, interval.inf) + _unary_leaf_to_root_map[node.getname()](visitor, node, arg) -def _prop_bnds_leaf_to_root_GeneralExpression(node, bnds_dict, feasibility_tol): +def _prop_bnds_leaf_to_root_GeneralExpression(visitor, node, expr): """ Propagate bounds from children to parent @@ -446,16 +432,13 @@ def _prop_bnds_leaf_to_root_GeneralExpression(node, bnds_dict, feasibility_tol): region is removed due to floating point arithmetic and to prevent math domain errors (a larger value is more conservative). """ - (expr,) = node.args + bnds_dict = visitor.bnds_dict if expr.__class__ in native_types: expr_lb = expr_ub = expr else: expr_lb, expr_ub = bnds_dict[expr] bnds_dict[node] = (expr_lb, expr_ub) -def _prop_no_bounds(node, bnds_dict, feasibility_tol): - bnds_dict[node] = (-interval.inf, interval.inf) - _prop_bnds_leaf_to_root_map = defaultdict(lambda: _prop_no_bounds) _prop_bnds_leaf_to_root_map[ numeric_expr.ProductExpression @@ -1176,26 +1159,14 @@ def __init__( def initializeWalker(self, expr): walk, result = self.beforeChild(None, expr, 0) if not walk: - return False, result#self.finalizeResult(result) + return False, result return True, expr def beforeChild(self, node, child, child_idx): return _before_child_handlers[child.__class__](self, child) def exitNode(self, node, data): - _prop_bnds_leaf_to_root_map[node.__class__](node, self.bnds_dict, - self.feasibility_tol) - # if node.__class__ in _prop_bnds_leaf_to_root_map: - # _prop_bnds_leaf_to_root_map[node.__class__]( - # node, self.bnds_dict, self.feasibility_tol - # ) - # else: - # self.bnds_dict[node] = (-interval.inf, interval.inf) - # return None - - # def finalizeResult(self, result): - # return result - + _prop_bnds_leaf_to_root_map[node.__class__](self, node, *node.args) # class _FBBTVisitorLeafToRoot(ExpressionValueVisitor): # """ From 78d3d1c25f45c7d883db372debf8a6d3394df40f Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 28 Sep 2023 14:42:13 -0600 Subject: [PATCH 0224/1204] Running black --- pyomo/contrib/fbbt/fbbt.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index 011a998d4c4..36313597521 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -13,7 +13,9 @@ from pyomo.common.collections import ComponentMap, ComponentSet import pyomo.core.expr.numeric_expr as numeric_expr from pyomo.core.expr.visitor import ( - ExpressionValueVisitor, identify_variables, StreamBasedExpressionVisitor + ExpressionValueVisitor, + identify_variables, + StreamBasedExpressionVisitor, ) from pyomo.core.expr.numvalue import nonpyomo_leaf_types, value from pyomo.core.expr.numvalue import is_fixed @@ -137,8 +139,9 @@ def _prop_bnds_leaf_to_root_DivisionExpression(visitor, node, arg1, arg2): bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg1] lb2, ub2 = bnds_dict[arg2] - bnds_dict[node] = interval.div(lb1, ub1, lb2, ub2, - feasibility_tol=visitor.feasibility_tol) + bnds_dict[node] = interval.div( + lb1, ub1, lb2, ub2, feasibility_tol=visitor.feasibility_tol + ) def _prop_bnds_leaf_to_root_PowExpression(visitor, node, arg1, arg2): @@ -383,9 +386,11 @@ def _prop_bnds_leaf_to_root_abs(visitor, node, arg): lb1, ub1 = bnds_dict[arg] bnds_dict[node] = interval.interval_abs(lb1, ub1) + def _prop_no_bounds(visitor, node, *args): visitor.bnds_dict[node] = (-interval.inf, interval.inf) + _unary_leaf_to_root_map = defaultdict(lambda: _prop_no_bounds) _unary_leaf_to_root_map['exp'] = _prop_bnds_leaf_to_root_exp _unary_leaf_to_root_map['log'] = _prop_bnds_leaf_to_root_log @@ -439,6 +444,7 @@ def _prop_bnds_leaf_to_root_GeneralExpression(visitor, node, expr): expr_lb, expr_ub = bnds_dict[expr] bnds_dict[node] = (expr_lb, expr_ub) + _prop_bnds_leaf_to_root_map = defaultdict(lambda: _prop_no_bounds) _prop_bnds_leaf_to_root_map[ numeric_expr.ProductExpression @@ -1019,6 +1025,7 @@ def _prop_bnds_root_to_leaf_GeneralExpression(node, bnds_dict, feasibility_tol): expr_lb, expr_ub = bnds_dict[node] bnds_dict[node.expr] = (expr_lb, expr_ub) + _prop_bnds_root_to_leaf_map = dict() _prop_bnds_root_to_leaf_map[ numeric_expr.ProductExpression @@ -1070,10 +1077,12 @@ def _check_and_reset_bounds(var, lb, ub): ub = orig_ub return lb, ub + def _before_constant(visitor, child): visitor.bnds_dict[child] = (child, child) return False, None + def _before_var(visitor, child): if child in visitor.bnds_dict: return False, None @@ -1090,21 +1099,22 @@ def _before_var(visitor, child): if lb - visitor.feasibility_tol > ub: raise InfeasibleConstraintException( 'Variable has a lower bound that is larger than its ' - 'upper bound: {0}'.format( - str(child) - ) + 'upper bound: {0}'.format(str(child)) ) visitor.bnds_dict[child] = (lb, ub) return False, None + def _before_NPV(visitor, child): val = value(child) visitor.bnds_dict[child] = (val, val) return False, None + def _before_other(visitor, child): return True, None + def _before_external_function(visitor, child): # TODO: provide some mechanism for users to provide interval # arithmetic callback functions for general external @@ -1112,6 +1122,7 @@ def _before_external_function(visitor, child): visitor.bnds_dict[child] = (-interval.inf, interval.inf) return False, None + def _register_new_before_child_handler(visitor, child): handlers = _before_child_handlers child_type = child.__class__ @@ -1123,17 +1134,21 @@ def _register_new_before_child_handler(visitor, child): handlers[child_type] = _before_other return handlers[child_type](visitor, child) + _before_child_handlers = defaultdict(lambda: _register_new_before_child_handler) _before_child_handlers[ - numeric_expr.ExternalFunctionExpression] = _before_external_function + numeric_expr.ExternalFunctionExpression +] = _before_external_function for _type in nonpyomo_leaf_types: _before_child_handlers[_type] = _before_constant + class _FBBTVisitorLeafToRoot(StreamBasedExpressionVisitor): """ This walker propagates bounds from the variables to each node in the expression tree (all the way to the root node). """ + def __init__( self, bnds_dict, integer_tol=1e-4, feasibility_tol=1e-8, ignore_fixed=False ): @@ -1168,6 +1183,7 @@ def beforeChild(self, node, child, child_idx): def exitNode(self, node, data): _prop_bnds_leaf_to_root_map[node.__class__](self, node, *node.args) + # class _FBBTVisitorLeafToRoot(ExpressionValueVisitor): # """ # This walker propagates bounds from the variables to each node in From afd0b1bd055654b1601d5a2cd7a264ef1d11b994 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 28 Sep 2023 14:43:16 -0600 Subject: [PATCH 0225/1204] More black --- pyomo/gdp/plugins/bigm.py | 3 ++- pyomo/gdp/plugins/bigm_mixin.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/gdp/plugins/bigm.py b/pyomo/gdp/plugins/bigm.py index 0402a8c69fa..854faa68887 100644 --- a/pyomo/gdp/plugins/bigm.py +++ b/pyomo/gdp/plugins/bigm.py @@ -181,7 +181,8 @@ def _apply_to_impl(self, instance, **kwds): bnds_dict = ComponentMap() self._fbbt_visitor = _FBBTVisitorLeafToRoot( - bnds_dict, ignore_fixed=not self._config.assume_fixed_vars_permanent) + bnds_dict, ignore_fixed=not self._config.assume_fixed_vars_permanent + ) # filter out inactive targets and handle case where targets aren't # specified. diff --git a/pyomo/gdp/plugins/bigm_mixin.py b/pyomo/gdp/plugins/bigm_mixin.py index 45395e213db..3a74504abc7 100644 --- a/pyomo/gdp/plugins/bigm_mixin.py +++ b/pyomo/gdp/plugins/bigm_mixin.py @@ -12,7 +12,6 @@ from pyomo.gdp import GDP_Error from pyomo.common.collections import ComponentSet import pyomo.contrib.fbbt.interval as interval -#from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr from pyomo.core import Suffix From 1550f2e5d16e5f19c29d9469341e8753bfb47c4d Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 28 Sep 2023 15:09:00 -0600 Subject: [PATCH 0226/1204] NFC: cleaning up a lot of docstrings and comments --- pyomo/contrib/fbbt/fbbt.py | 237 ++++++------------------------------- 1 file changed, 38 insertions(+), 199 deletions(-) diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index 36313597521..5ec8890ca6f 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -83,14 +83,10 @@ def _prop_bnds_leaf_to_root_ProductExpression(visitor, node, arg1, arg2): Parameters ---------- + visitor: _FBBTVisitorLeafToRoot node: pyomo.core.expr.numeric_expr.ProductExpression - bnds_dict: ComponentMap - feasibility_tol: float - If the bounds computed on the body of a constraint violate the bounds of the constraint by more than - feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance - is also used when performing certain interval arithmetic operations to ensure that none of the feasible - region is removed due to floating point arithmetic and to prevent math domain errors (a larger value - is more conservative). + arg1: First arg in product expression + arg2: Second arg in product expression """ bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg1] @@ -106,14 +102,9 @@ def _prop_bnds_leaf_to_root_SumExpression(visitor, node, *args): Parameters ---------- + visitor: _FBBTVisitorLeafToRoot node: pyomo.core.expr.numeric_expr.SumExpression - bnds_dict: ComponentMap - feasibility_tol: float - If the bounds computed on the body of a constraint violate the bounds of the constraint by more than - feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance - is also used when performing certain interval arithmetic operations to ensure that none of the feasible - region is removed due to floating point arithmetic and to prevent math domain errors (a larger value - is more conservative). + args: summands in SumExpression """ bnds_dict = visitor.bnds_dict bnds = (0, 0) @@ -127,14 +118,10 @@ def _prop_bnds_leaf_to_root_DivisionExpression(visitor, node, arg1, arg2): Parameters ---------- + visitor: _FBBTVisitorLeafToRoot node: pyomo.core.expr.numeric_expr.DivisionExpression - bnds_dict: ComponentMap - feasibility_tol: float - If the bounds computed on the body of a constraint violate the bounds of the constraint by more than - feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance - is also used when performing certain interval arithmetic operations to ensure that none of the feasible - region is removed due to floating point arithmetic and to prevent math domain errors (a larger value - is more conservative). + arg1: dividend + arg2: divisor """ bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg1] @@ -149,14 +136,10 @@ def _prop_bnds_leaf_to_root_PowExpression(visitor, node, arg1, arg2): Parameters ---------- + visitor: _FBBTVisitorLeafToRoot node: pyomo.core.expr.numeric_expr.PowExpression - bnds_dict: ComponentMap - feasibility_tol: float - If the bounds computed on the body of a constraint violate the bounds of the constraint by more than - feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance - is also used when performing certain interval arithmetic operations to ensure that none of the feasible - region is removed due to floating point arithmetic and to prevent math domain errors (a larger value - is more conservative). + arg1: base + arg2: exponent """ bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg1] @@ -171,14 +154,9 @@ def _prop_bnds_leaf_to_root_NegationExpression(visitor, node, arg): Parameters ---------- - node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression - bnds_dict: ComponentMap - feasibility_tol: float - If the bounds computed on the body of a constraint violate the bounds of the constraint by more than - feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance - is also used when performing certain interval arithmetic operations to ensure that none of the feasible - region is removed due to floating point arithmetic and to prevent math domain errors (a larger value - is more conservative). + visitor: _FBBTVisitorLeafToRoot + node: pyomo.core.expr.numeric_expr.NegationExpression + arg: NegationExpression arg """ bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] @@ -190,14 +168,9 @@ def _prop_bnds_leaf_to_root_exp(visitor, node, arg): Parameters ---------- + visitor: _FBBTVisitorLeafToRoot node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression - bnds_dict: ComponentMap - feasibility_tol: float - If the bounds computed on the body of a constraint violate the bounds of the constraint by more than - feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance - is also used when performing certain interval arithmetic operations to ensure that none of the feasible - region is removed due to floating point arithmetic and to prevent math domain errors (a larger value - is more conservative). + arg: UnaryFunctionExpression arg """ bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] @@ -209,14 +182,9 @@ def _prop_bnds_leaf_to_root_log(visitor, node, arg): Parameters ---------- + visitor: _FBBTVisitorLeafToRoot node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression - bnds_dict: ComponentMap - feasibility_tol: float - If the bounds computed on the body of a constraint violate the bounds of the constraint by more than - feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance - is also used when performing certain interval arithmetic operations to ensure that none of the feasible - region is removed due to floating point arithmetic and to prevent math domain errors (a larger value - is more conservative). + arg: UnaryFunctionExpression arg """ bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] @@ -228,14 +196,9 @@ def _prop_bnds_leaf_to_root_log10(visitor, node, arg): Parameters ---------- + visitor: _FBBTVisitorLeafToRoot node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression - bnds_dict: ComponentMap - feasibility_tol: float - If the bounds computed on the body of a constraint violate the bounds of the constraint by more than - feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance - is also used when performing certain interval arithmetic operations to ensure that none of the feasible - region is removed due to floating point arithmetic and to prevent math domain errors (a larger value - is more conservative). + arg: UnaryFunctionExpression arg """ bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] @@ -247,14 +210,9 @@ def _prop_bnds_leaf_to_root_sin(visitor, node, arg): Parameters ---------- + visitor: _FBBTVisitorLeafToRoot node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression - bnds_dict: ComponentMap - feasibility_tol: float - If the bounds computed on the body of a constraint violate the bounds of the constraint by more than - feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance - is also used when performing certain interval arithmetic operations to ensure that none of the feasible - region is removed due to floating point arithmetic and to prevent math domain errors (a larger value - is more conservative). + arg: UnaryFunctionExpression arg """ bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] @@ -266,14 +224,9 @@ def _prop_bnds_leaf_to_root_cos(visitor, node, arg): Parameters ---------- + visitor: _FBBTVisitorLeafToRoot node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression - bnds_dict: ComponentMap - feasibility_tol: float - If the bounds computed on the body of a constraint violate the bounds of the constraint by more than - feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance - is also used when performing certain interval arithmetic operations to ensure that none of the feasible - region is removed due to floating point arithmetic and to prevent math domain errors (a larger value - is more conservative). + arg: UnaryFunctionExpression arg """ bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] @@ -285,14 +238,9 @@ def _prop_bnds_leaf_to_root_tan(visitor, node, arg): Parameters ---------- + visitor: _FBBTVisitorLeafToRoot node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression - bnds_dict: ComponentMap - feasibility_tol: float - If the bounds computed on the body of a constraint violate the bounds of the constraint by more than - feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance - is also used when performing certain interval arithmetic operations to ensure that none of the feasible - region is removed due to floating point arithmetic and to prevent math domain errors (a larger value - is more conservative). + arg: UnaryFunctionExpression arg """ bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] @@ -304,14 +252,9 @@ def _prop_bnds_leaf_to_root_asin(visitor, node, arg): Parameters ---------- + visitor: _FBBTVisitorLeafToRoot node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression - bnds_dict: ComponentMap - feasibility_tol: float - If the bounds computed on the body of a constraint violate the bounds of the constraint by more than - feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance - is also used when performing certain interval arithmetic operations to ensure that none of the feasible - region is removed due to floating point arithmetic and to prevent math domain errors (a larger value - is more conservative). + arg: UnaryFunctionExpression arg """ bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] @@ -325,14 +268,9 @@ def _prop_bnds_leaf_to_root_acos(visitor, node, arg): Parameters ---------- + visitor: _FBBTVisitorLeafToRoot node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression - bnds_dict: ComponentMap - feasibility_tol: float - If the bounds computed on the body of a constraint violate the bounds of the constraint by more than - feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance - is also used when performing certain interval arithmetic operations to ensure that none of the feasible - region is removed due to floating point arithmetic and to prevent math domain errors (a larger value - is more conservative). + arg: UnaryFunctionExpression arg """ bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] @@ -346,14 +284,9 @@ def _prop_bnds_leaf_to_root_atan(visitor, node, arg): Parameters ---------- + visitor: _FBBTVisitorLeafToRoot node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression - bnds_dict: ComponentMap - feasibility_tol: float - If the bounds computed on the body of a constraint violate the bounds of the constraint by more than - feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance - is also used when performing certain interval arithmetic operations to ensure that none of the feasible - region is removed due to floating point arithmetic and to prevent math domain errors (a larger value - is more conservative). + """ bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] @@ -365,14 +298,9 @@ def _prop_bnds_leaf_to_root_sqrt(visitor, node, arg): Parameters ---------- + visitor: _FBBTVisitorLeafToRoot node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression - bnds_dict: ComponentMap - feasibility_tol: float - If the bounds computed on the body of a constraint violate the bounds of the constraint by more than - feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance - is also used when performing certain interval arithmetic operations to ensure that none of the feasible - region is removed due to floating point arithmetic and to prevent math domain errors (a larger value - is more conservative). + arg: UnaryFunctionExpression arg """ bnds_dict = visitor.bnds_dict lb1, ub1 = bnds_dict[arg] @@ -410,14 +338,9 @@ def _prop_bnds_leaf_to_root_UnaryFunctionExpression(visitor, node, arg): Parameters ---------- + visitor: _FBBTVisitorLeafToRoot node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression - bnds_dict: ComponentMap - feasibility_tol: float - If the bounds computed on the body of a constraint violate the bounds of the constraint by more than - feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance - is also used when performing certain interval arithmetic operations to ensure that none of the feasible - region is removed due to floating point arithmetic and to prevent math domain errors (a larger value - is more conservative). + arg: UnaryFunctionExpression arg """ _unary_leaf_to_root_map[node.getname()](visitor, node, arg) @@ -428,14 +351,9 @@ def _prop_bnds_leaf_to_root_GeneralExpression(visitor, node, expr): Parameters ---------- + visitor: _FBBTVisitorLeafToRoot node: pyomo.core.base.expression._GeneralExpressionData - bnds_dict: ComponentMap - feasibility_tol: float - If the bounds computed on the body of a constraint violate the bounds of the constraint by more than - feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance - is also used when performing certain interval arithmetic operations to ensure that none of the feasible - region is removed due to floating point arithmetic and to prevent math domain errors (a larger value - is more conservative). + expr: GeneralExpression arg """ bnds_dict = visitor.bnds_dict if expr.__class__ in native_types: @@ -1184,85 +1102,6 @@ def exitNode(self, node, data): _prop_bnds_leaf_to_root_map[node.__class__](self, node, *node.args) -# class _FBBTVisitorLeafToRoot(ExpressionValueVisitor): -# """ -# This walker propagates bounds from the variables to each node in -# the expression tree (all the way to the root node). -# """ - -# def __init__( -# self, bnds_dict, integer_tol=1e-4, feasibility_tol=1e-8, ignore_fixed=False -# ): -# """ -# Parameters -# ---------- -# bnds_dict: ComponentMap -# integer_tol: float -# feasibility_tol: float -# If the bounds computed on the body of a constraint violate the bounds of the constraint by more than -# feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance -# is also used when performing certain interval arithmetic operations to ensure that none of the feasible -# region is removed due to floating point arithmetic and to prevent math domain errors (a larger value -# is more conservative). -# """ -# self.bnds_dict = bnds_dict -# self.integer_tol = integer_tol -# self.feasibility_tol = feasibility_tol -# self.ignore_fixed = ignore_fixed - -# def visit(self, node, values): -# if node.__class__ in _prop_bnds_leaf_to_root_map: -# _prop_bnds_leaf_to_root_map[node.__class__]( -# node, self.bnds_dict, self.feasibility_tol -# ) -# else: -# self.bnds_dict[node] = (-interval.inf, interval.inf) -# return None - -# def visiting_potential_leaf(self, node): -# if node.__class__ in nonpyomo_leaf_types: -# self.bnds_dict[node] = (node, node) -# return True, None - -# if node.is_variable_type(): -# if node in self.bnds_dict: -# return True, None -# if node.is_fixed() and not self.ignore_fixed: -# lb = value(node.value) -# ub = lb -# else: -# lb = value(node.lb) -# ub = value(node.ub) -# if lb is None: -# lb = -interval.inf -# if ub is None: -# ub = interval.inf -# if lb - self.feasibility_tol > ub: -# raise InfeasibleConstraintException( -# 'Variable has a lower bound that is larger than its upper bound: {0}'.format( -# str(node) -# ) -# ) -# self.bnds_dict[node] = (lb, ub) -# return True, None - -# if not node.is_potentially_variable(): -# # NPV nodes are effectively constant leaves. Evaluate it -# # and return the value. -# val = value(node) -# self.bnds_dict[node] = (val, val) -# return True, None - -# if node.__class__ is numeric_expr.ExternalFunctionExpression: -# # TODO: provide some mechanism for users to provide interval -# # arithmetic callback functions for general external -# # functions -# self.bnds_dict[node] = (-interval.inf, interval.inf) -# return True, None - -# return False, None - - class _FBBTVisitorRootToLeaf(ExpressionValueVisitor): """ This walker propagates bounds from the constraint back to the From cbedd1f1a306a075bc70945b24ef88cdcae04979 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 28 Sep 2023 15:16:14 -0600 Subject: [PATCH 0227/1204] Putting fbbt visitor in bigm mixin so that mbigm can use it too --- pyomo/gdp/plugins/bigm.py | 6 +----- pyomo/gdp/plugins/bigm_mixin.py | 9 ++++++++- pyomo/gdp/plugins/multiple_bigm.py | 1 + 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pyomo/gdp/plugins/bigm.py b/pyomo/gdp/plugins/bigm.py index 854faa68887..fffabf652e5 100644 --- a/pyomo/gdp/plugins/bigm.py +++ b/pyomo/gdp/plugins/bigm.py @@ -178,11 +178,7 @@ def _apply_to(self, instance, **kwds): def _apply_to_impl(self, instance, **kwds): self._process_arguments(instance, **kwds) - - bnds_dict = ComponentMap() - self._fbbt_visitor = _FBBTVisitorLeafToRoot( - bnds_dict, ignore_fixed=not self._config.assume_fixed_vars_permanent - ) + self._set_up_fbbt_visitor() # filter out inactive targets and handle case where targets aren't # specified. diff --git a/pyomo/gdp/plugins/bigm_mixin.py b/pyomo/gdp/plugins/bigm_mixin.py index 3a74504abc7..fc64c5bd9db 100644 --- a/pyomo/gdp/plugins/bigm_mixin.py +++ b/pyomo/gdp/plugins/bigm_mixin.py @@ -10,7 +10,8 @@ # ___________________________________________________________________________ from pyomo.gdp import GDP_Error -from pyomo.common.collections import ComponentSet +from pyomo.common.collections import ComponentMap, ComponentSet +from pyomo.contrib.fbbt.fbbt import _FBBTVisitorLeafToRoot import pyomo.contrib.fbbt.interval as interval from pyomo.core import Suffix @@ -103,6 +104,12 @@ def _get_bigM_arg_list(self, bigm_args, block): block = block.parent_block() return arg_list + def _set_up_fbbt_visitor(self): + bnds_dict = ComponentMap() + self._fbbt_visitor = _FBBTVisitorLeafToRoot( + bnds_dict, ignore_fixed=not self._config.assume_fixed_vars_permanent + ) + def _process_M_value( self, m, diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index 8f0592f204d..929f6072863 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -214,6 +214,7 @@ def _apply_to(self, instance, **kwds): def _apply_to_impl(self, instance, **kwds): self._process_arguments(instance, **kwds) + self._set_up_fbbt_visitor() if ( self._config.only_mbigm_bound_constraints From 1e9723f55f084c711d318d8e3c6bc8057fbff1ff Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 28 Sep 2023 15:50:57 -0600 Subject: [PATCH 0228/1204] Removing unused import --- pyomo/gdp/plugins/bigm.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/gdp/plugins/bigm.py b/pyomo/gdp/plugins/bigm.py index fffabf652e5..82ba55adfa0 100644 --- a/pyomo/gdp/plugins/bigm.py +++ b/pyomo/gdp/plugins/bigm.py @@ -20,7 +20,6 @@ from pyomo.contrib.cp.transform.logical_to_disjunctive_program import ( LogicalToDisjunctive, ) -from pyomo.contrib.fbbt.fbbt import _FBBTVisitorLeafToRoot from pyomo.core import ( Block, BooleanVar, From 86ee9507e64bba2a4d08f0dea0f3c312a396c622 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 28 Sep 2023 16:27:36 -0600 Subject: [PATCH 0229/1204] Moving the setup of fbbt visitor to init because gdpopt caught me assuming it was there from before config... --- pyomo/gdp/plugins/bigm.py | 5 ++++- pyomo/gdp/plugins/bigm_mixin.py | 9 +++------ pyomo/gdp/plugins/multiple_bigm.py | 4 +++- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pyomo/gdp/plugins/bigm.py b/pyomo/gdp/plugins/bigm.py index 82ba55adfa0..6502a5eab0e 100644 --- a/pyomo/gdp/plugins/bigm.py +++ b/pyomo/gdp/plugins/bigm.py @@ -161,6 +161,7 @@ class BigM_Transformation(GDP_to_MIP_Transformation, _BigM_MixIn): def __init__(self): super().__init__(logger) + self._set_up_fbbt_visitor() def _apply_to(self, instance, **kwds): self.used_args = ComponentMap() # If everything was sure to go well, @@ -174,10 +175,12 @@ def _apply_to(self, instance, **kwds): finally: self._restore_state() self.used_args.clear() + self._fbbt_visitor.ignore_fixed = True def _apply_to_impl(self, instance, **kwds): self._process_arguments(instance, **kwds) - self._set_up_fbbt_visitor() + if self._config.assume_fixed_vars_permanent: + self._fbbt_visitor.ignore_fixed = False # filter out inactive targets and handle case where targets aren't # specified. diff --git a/pyomo/gdp/plugins/bigm_mixin.py b/pyomo/gdp/plugins/bigm_mixin.py index fc64c5bd9db..39242db248b 100644 --- a/pyomo/gdp/plugins/bigm_mixin.py +++ b/pyomo/gdp/plugins/bigm_mixin.py @@ -106,9 +106,9 @@ def _get_bigM_arg_list(self, bigm_args, block): def _set_up_fbbt_visitor(self): bnds_dict = ComponentMap() - self._fbbt_visitor = _FBBTVisitorLeafToRoot( - bnds_dict, ignore_fixed=not self._config.assume_fixed_vars_permanent - ) + # we assume the default config arg for 'assume_fixed_vars_permanent,` + # and we will change it during apply_to if we need to + self._fbbt_visitor = _FBBTVisitorLeafToRoot(bnds_dict, ignore_fixed=True) def _process_M_value( self, @@ -217,9 +217,6 @@ def _get_M_from_args(self, constraint, bigMargs, arg_list, lower, upper): return lower, upper def _estimate_M(self, expr, constraint): - # expr_lb, expr_ub = compute_bounds_on_expr( - # expr, ignore_fixed=not self._config.assume_fixed_vars_permanent - # ) self._fbbt_visitor.walk_expression(expr) expr_lb, expr_ub = self._fbbt_visitor.bnds_dict[expr] if expr_lb == -interval.inf or expr_ub == interval.inf: diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index 929f6072863..edc511d089d 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -202,6 +202,7 @@ def __init__(self): super().__init__(logger) self.handlers[Suffix] = self._warn_for_active_suffix self._arg_list = {} + self._set_up_fbbt_visitor() def _apply_to(self, instance, **kwds): self.used_args = ComponentMap() @@ -214,7 +215,8 @@ def _apply_to(self, instance, **kwds): def _apply_to_impl(self, instance, **kwds): self._process_arguments(instance, **kwds) - self._set_up_fbbt_visitor() + if self._config.assume_fixed_vars_permanent: + self._fbbt_visitor.ignore_fixed = False if ( self._config.only_mbigm_bound_constraints From 0ab57d4317578a4a29005470122d7594a79eaac3 Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 28 Sep 2023 19:48:27 -0600 Subject: [PATCH 0230/1204] add halt_on_evaluation_error option --- .../pynumero/interfaces/cyipopt_interface.py | 55 ++++++++++++++----- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py index cddc2ce000f..093c9c7ecc1 100644 --- a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py +++ b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py @@ -253,7 +253,7 @@ def intermediate( class CyIpoptNLP(CyIpoptProblemInterface): - def __init__(self, nlp, intermediate_callback=None): + def __init__(self, nlp, intermediate_callback=None, halt_on_evaluation_error=None): """This class provides a CyIpoptProblemInterface for use with the CyIpoptSolver class that can take in an NLP as long as it provides vectors as numpy ndarrays and @@ -264,6 +264,18 @@ def __init__(self, nlp, intermediate_callback=None): self._nlp = nlp self._intermediate_callback = intermediate_callback + if halt_on_evaluation_error is None: + # If using cyipopt >= 1.3, the default is to halt. + # Otherwise, the default is not to halt (because we can't). + self._halt_on_evaluation_error = hasattr(cyipopt, "CyIpoptEvaluationError") + elif halt_on_evaluation_error and not hasattr(cyipopt, "CyIpoptEvaluationError"): + raise ValueError( + "halt_on_evaluation_error is only supported for cyipopt >= 1.3.0" + ) + else: + self._halt_on_evaluation_error = halt_on_evaluation_error + + x = nlp.init_primals() y = nlp.init_duals() if np.any(np.isnan(y)): @@ -335,25 +347,34 @@ def objective(self, x): except PyNumeroEvaluationError: # TODO: halt_on_evaluation_error option. If set, we re-raise the # original exception. - raise cyipopt.CyIpoptEvaluationError( - "Error in objective function evaluation" - ) + if self._halt_on_evaluation_error: + raise cyipopt.CyIpoptEvaluationError( + "Error in objective function evaluation" + ) + else: + raise def gradient(self, x): try: self._set_primals_if_necessary(x) return self._nlp.evaluate_grad_objective() except PyNumeroEvaluationError: - raise cyipopt.CyIpoptEvaluationError( - "Error in objective gradient evaluation" - ) + if self._halt_on_evaluation_error: + raise cyipopt.CyIpoptEvaluationError( + "Error in objective gradient evaluation" + ) + else: + raise def constraints(self, x): try: self._set_primals_if_necessary(x) return self._nlp.evaluate_constraints() except PyNumeroEvaluationError: - raise cyipopt.CyIpoptEvaluationError("Error in constraint evaluation") + if self._halt_on_evaluation_error: + raise cyipopt.CyIpoptEvaluationError("Error in constraint evaluation") + else: + raise def jacobianstructure(self): return self._jac_g.row, self._jac_g.col @@ -364,9 +385,12 @@ def jacobian(self, x): self._nlp.evaluate_jacobian(out=self._jac_g) return self._jac_g.data except PyNumeroEvaluationError: - raise cyipopt.CyIpoptEvaluationError( - "Error in constraint Jacobian evaluation" - ) + if self._halt_on_evaluation_error: + raise cyipopt.CyIpoptEvaluationError( + "Error in constraint Jacobian evaluation" + ) + else: + raise def hessianstructure(self): if not self._hessian_available: @@ -388,9 +412,12 @@ def hessian(self, x, y, obj_factor): data = np.compress(self._hess_lower_mask, self._hess_lag.data) return data except PyNumeroEvaluationError: - raise cyipopt.CyIpoptEvaluationError( - "Error in Lagrangian Hessian evaluation" - ) + if self._halt_on_evaluation_error: + raise cyipopt.CyIpoptEvaluationError( + "Error in Lagrangian Hessian evaluation" + ) + else: + raise def intermediate( self, From 761277cc5da5a1062e49d34366c523762a72c94c Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 28 Sep 2023 19:58:43 -0600 Subject: [PATCH 0231/1204] add halt_on_evaluation_error to PyomoCyIpoptSolver --- .../pynumero/algorithms/solvers/cyipopt_solver.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py b/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py index 766ef96322a..cedbf430a12 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py @@ -289,6 +289,13 @@ class PyomoCyIpoptSolver(object): description="Set the function that will be called each iteration.", ), ) + CONFIG.declare( + "halt_on_evaluation_error", + ConfigValue( + default=None, + description="Whether to halt if a function or derivative evaluation fails", + ), + ) def __init__(self, **kwds): """Create an instance of the CyIpoptSolver. You must @@ -332,7 +339,9 @@ def solve(self, model, **kwds): nlp = pyomo_nlp.PyomoNLP(model) problem = cyipopt_interface.CyIpoptNLP( - nlp, intermediate_callback=config.intermediate_callback + nlp, + intermediate_callback=config.intermediate_callback, + halt_on_evaluation_error=config.halt_on_evaluation_error, ) ng = len(problem.g_lb()) nx = len(problem.x_lb()) From 463e434eab721238d968fd9f12ac6fb88a768c4e Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 28 Sep 2023 20:52:39 -0600 Subject: [PATCH 0232/1204] bug fix: reraise exception if halt=*True* --- .../pynumero/interfaces/cyipopt_interface.py | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py index 093c9c7ecc1..5ac98cdeecc 100644 --- a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py +++ b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py @@ -265,9 +265,9 @@ def __init__(self, nlp, intermediate_callback=None, halt_on_evaluation_error=Non self._intermediate_callback = intermediate_callback if halt_on_evaluation_error is None: - # If using cyipopt >= 1.3, the default is to halt. - # Otherwise, the default is not to halt (because we can't). - self._halt_on_evaluation_error = hasattr(cyipopt, "CyIpoptEvaluationError") + # If using cyipopt >= 1.3, the default is to continue. + # Otherwise, the default is to halt (because we are forced to). + self._halt_on_evaluation_error = not hasattr(cyipopt, "CyIpoptEvaluationError") elif halt_on_evaluation_error and not hasattr(cyipopt, "CyIpoptEvaluationError"): raise ValueError( "halt_on_evaluation_error is only supported for cyipopt >= 1.3.0" @@ -348,11 +348,11 @@ def objective(self, x): # TODO: halt_on_evaluation_error option. If set, we re-raise the # original exception. if self._halt_on_evaluation_error: + raise + else: raise cyipopt.CyIpoptEvaluationError( "Error in objective function evaluation" ) - else: - raise def gradient(self, x): try: @@ -360,11 +360,11 @@ def gradient(self, x): return self._nlp.evaluate_grad_objective() except PyNumeroEvaluationError: if self._halt_on_evaluation_error: + raise + else: raise cyipopt.CyIpoptEvaluationError( "Error in objective gradient evaluation" ) - else: - raise def constraints(self, x): try: @@ -372,9 +372,9 @@ def constraints(self, x): return self._nlp.evaluate_constraints() except PyNumeroEvaluationError: if self._halt_on_evaluation_error: - raise cyipopt.CyIpoptEvaluationError("Error in constraint evaluation") - else: raise + else: + raise cyipopt.CyIpoptEvaluationError("Error in constraint evaluation") def jacobianstructure(self): return self._jac_g.row, self._jac_g.col @@ -386,11 +386,11 @@ def jacobian(self, x): return self._jac_g.data except PyNumeroEvaluationError: if self._halt_on_evaluation_error: + raise + else: raise cyipopt.CyIpoptEvaluationError( "Error in constraint Jacobian evaluation" ) - else: - raise def hessianstructure(self): if not self._hessian_available: @@ -413,11 +413,11 @@ def hessian(self, x, y, obj_factor): return data except PyNumeroEvaluationError: if self._halt_on_evaluation_error: + raise + else: raise cyipopt.CyIpoptEvaluationError( "Error in Lagrangian Hessian evaluation" ) - else: - raise def intermediate( self, From 9723b0fcb6fe89e6dbeefaccf36f7bff5a77318f Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 28 Sep 2023 20:53:19 -0600 Subject: [PATCH 0233/1204] tests for halt_on_evaluation_error and its version-dependent default --- .../tests/test_cyipopt_interface.py | 119 +++++++++++++++--- 1 file changed, 102 insertions(+), 17 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_cyipopt_interface.py b/pyomo/contrib/pynumero/interfaces/tests/test_cyipopt_interface.py index dbff12121b0..b2ab837d9c7 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_cyipopt_interface.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_cyipopt_interface.py @@ -21,6 +21,7 @@ if not (numpy_available and scipy_available): raise unittest.SkipTest("Pynumero needs scipy and numpy to run CyIpopt tests") +from pyomo.contrib.pynumero.exceptions import PyNumeroEvaluationError from pyomo.contrib.pynumero.asl import AmplInterface if not AmplInterface.available(): @@ -28,6 +29,7 @@ from pyomo.contrib.pynumero.interfaces.pyomo_nlp import PyomoNLP from pyomo.contrib.pynumero.interfaces.cyipopt_interface import ( + cyipopt, cyipopt_available, CyIpoptProblemInterface, CyIpoptNLP, @@ -36,7 +38,8 @@ if not cyipopt_available: raise unittest.SkipTest("CyIpopt is not available") -import cyipopt + +cyipopt_ge_1_3 = hasattr(cyipopt, "CyIpoptEvaluationError") class TestSubclassCyIpoptInterface(unittest.TestCase): @@ -93,49 +96,131 @@ def hessian(self, x, y, obj_factor): problem.solve(x0) -class TestCyIpoptEvaluationErrors(unittest.TestCase): - def _get_model_nlp_interface(self): - m = pyo.ConcreteModel() - m.x = pyo.Var([1, 2, 3], initialize=1.0) - m.obj = pyo.Objective(expr=m.x[1] * pyo.sqrt(m.x[2]) + m.x[1] * m.x[3]) - m.eq1 = pyo.Constraint(expr=m.x[1] * pyo.sqrt(m.x[2]) == 1.0) - nlp = PyomoNLP(m) +def _get_model_nlp_interface(halt_on_evaluation_error=None): + m = pyo.ConcreteModel() + m.x = pyo.Var([1, 2, 3], initialize=1.0) + m.obj = pyo.Objective(expr=m.x[1] * pyo.sqrt(m.x[2]) + m.x[1] * m.x[3]) + m.eq1 = pyo.Constraint(expr=m.x[1] * pyo.sqrt(m.x[2]) == 1.0) + nlp = PyomoNLP(m) + interface = CyIpoptNLP(nlp, halt_on_evaluation_error=halt_on_evaluation_error) + bad_primals = np.array([1.0, -2.0, 3.0]) + indices = nlp.get_primal_indices([m.x[1], m.x[2], m.x[3]]) + bad_primals = bad_primals[indices] + return m, nlp, interface, bad_primals + + +class TestCyIpoptVersionDependentConfig(unittest.TestCase): + + @unittest.skipIf(cyipopt_ge_1_3, "cyipopt version >= 1.3.0") + def test_config_error(self): + _, nlp, _, _ = _get_model_nlp_interface() + with self.assertRaisesRegex(ValueError, "halt_on_evaluation_error"): + interface = CyIpoptNLP(nlp, halt_on_evaluation_error=False) + + @unittest.skipIf(cyipopt_ge_1_3, "cyipopt version >= 1.3.0") + def test_default_config_with_old_cyipopt(self): + _, nlp, _, bad_x = _get_model_nlp_interface() interface = CyIpoptNLP(nlp) - bad_primals = np.array([1.0, -2.0, 3.0]) - indices = nlp.get_primal_indices([m.x[1], m.x[2], m.x[3]]) - bad_primals = bad_primals[indices] - return m, nlp, interface, bad_primals + msg = "Error in AMPL evaluation" + with self.assertRaisesRegex(PyNumeroEvaluationError, msg): + interface.objective(bad_x) + + @unittest.skipIf(not cyipopt_ge_1_3, "cyipopt version < 1.3.0") + def test_default_config_with_new_cyipopt(self): + _, nlp, _, bad_x = _get_model_nlp_interface() + interface = CyIpoptNLP(nlp) + msg = "Error in objective function" + with self.assertRaisesRegex(cyipopt.CyIpoptEvaluationError, msg): + interface.objective(bad_x) + +class TestCyIpoptEvaluationErrors(unittest.TestCase): + + @unittest.skipUnless(cyipopt_ge_1_3, "cyipopt version < 1.3.0") def test_error_in_objective(self): - m, nlp, interface, bad_x = self._get_model_nlp_interface() + m, nlp, interface, bad_x = _get_model_nlp_interface( + halt_on_evaluation_error=False + ) msg = "Error in objective function" with self.assertRaisesRegex(cyipopt.CyIpoptEvaluationError, msg): interface.objective(bad_x) + def test_error_in_objective_halt(self): + m, nlp, interface, bad_x = _get_model_nlp_interface( + halt_on_evaluation_error=True + ) + msg = "Error in AMPL evaluation" + with self.assertRaisesRegex(PyNumeroEvaluationError, msg): + interface.objective(bad_x) + + @unittest.skipUnless(cyipopt_ge_1_3, "cyipopt version < 1.3.0") def test_error_in_gradient(self): - m, nlp, interface, bad_x = self._get_model_nlp_interface() + m, nlp, interface, bad_x = _get_model_nlp_interface( + halt_on_evaluation_error=False + ) msg = "Error in objective gradient" with self.assertRaisesRegex(cyipopt.CyIpoptEvaluationError, msg): interface.gradient(bad_x) + def test_error_in_gradient_halt(self): + m, nlp, interface, bad_x = _get_model_nlp_interface( + halt_on_evaluation_error=True + ) + msg = "Error in AMPL evaluation" + with self.assertRaisesRegex(PyNumeroEvaluationError, msg): + interface.gradient(bad_x) + + @unittest.skipUnless(cyipopt_ge_1_3, "cyipopt version < 1.3.0") def test_error_in_constraints(self): - m, nlp, interface, bad_x = self._get_model_nlp_interface() + m, nlp, interface, bad_x = _get_model_nlp_interface( + halt_on_evaluation_error=False + ) msg = "Error in constraint evaluation" with self.assertRaisesRegex(cyipopt.CyIpoptEvaluationError, msg): interface.constraints(bad_x) + def test_error_in_constraints_halt(self): + m, nlp, interface, bad_x = _get_model_nlp_interface( + halt_on_evaluation_error=True + ) + msg = "Error in AMPL evaluation" + with self.assertRaisesRegex(PyNumeroEvaluationError, msg): + interface.constraints(bad_x) + + @unittest.skipUnless(cyipopt_ge_1_3, "cyipopt version < 1.3.0") def test_error_in_jacobian(self): - m, nlp, interface, bad_x = self._get_model_nlp_interface() + m, nlp, interface, bad_x = _get_model_nlp_interface( + halt_on_evaluation_error=False + ) msg = "Error in constraint Jacobian" with self.assertRaisesRegex(cyipopt.CyIpoptEvaluationError, msg): interface.jacobian(bad_x) + def test_error_in_jacobian_halt(self): + m, nlp, interface, bad_x = _get_model_nlp_interface( + halt_on_evaluation_error=True + ) + msg = "Error in AMPL evaluation" + with self.assertRaisesRegex(PyNumeroEvaluationError, msg): + interface.jacobian(bad_x) + + @unittest.skipUnless(cyipopt_ge_1_3, "cyipopt version < 1.3.0") def test_error_in_hessian(self): - m, nlp, interface, bad_x = self._get_model_nlp_interface() + m, nlp, interface, bad_x = _get_model_nlp_interface( + halt_on_evaluation_error=False + ) msg = "Error in Lagrangian Hessian" with self.assertRaisesRegex(cyipopt.CyIpoptEvaluationError, msg): interface.hessian(bad_x, [1.0], 0.0) + def test_error_in_hessian_halt(self): + m, nlp, interface, bad_x = _get_model_nlp_interface( + halt_on_evaluation_error=True + ) + msg = "Error in AMPL evaluation" + with self.assertRaisesRegex(PyNumeroEvaluationError, msg): + interface.hessian(bad_x, [1.0], 0.0) + if __name__ == "__main__": unittest.main() From 1f63223d6976c2ab7ae75e961660071ac3d8cfe7 Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 28 Sep 2023 21:01:55 -0600 Subject: [PATCH 0234/1204] test HS071-with-eval-error with halt_on_evaluation_error and cyipopt < 1.3 --- .../solvers/tests/test_cyipopt_solver.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py index 7a670a2e41c..bdd32c6788e 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py @@ -24,6 +24,7 @@ if not (numpy_available and scipy_available): raise unittest.SkipTest("Pynumero needs scipy and numpy to run NLP tests") +from pyomo.contrib.pynumero.exceptions import PyNumeroEvaluationError from pyomo.contrib.pynumero.asl import AmplInterface if not AmplInterface.available(): @@ -34,12 +35,15 @@ from pyomo.contrib.pynumero.interfaces.pyomo_nlp import PyomoNLP from pyomo.contrib.pynumero.interfaces.cyipopt_interface import ( + cyipopt, cyipopt_available, CyIpoptNLP, ) from pyomo.contrib.pynumero.algorithms.solvers.cyipopt_solver import CyIpoptSolver +cyipopt_ge_1_3 = hasattr(cyipopt, "CyIpoptEvaluationError") + def create_model1(): m = pyo.ConcreteModel() @@ -281,6 +285,7 @@ def test_options(self): nlp.set_primals(x) self.assertAlmostEqual(nlp.evaluate_objective(), -5.0879028e02, places=5) + @unittest.skipUnless(cyipopt_ge_1_3, "cyipopt version < 1.3.0") def test_hs071_evalerror(self): m = make_hs071_model() solver = pyo.SolverFactory("cyipopt") @@ -289,3 +294,18 @@ def test_hs071_evalerror(self): x = list(m.x[:].value) expected_x = np.array([1.0, 4.74299964, 3.82114998, 1.37940829]) np.testing.assert_allclose(x, expected_x) + + def test_hs071_evalerror_halt(self): + m = make_hs071_model() + solver = pyo.SolverFactory("cyipopt", halt_on_evaluation_error=True) + msg = "Error in AMPL evaluation" + with self.assertRaisesRegex(PyNumeroEvaluationError, msg): + res = solver.solve(m, tee=True) + + @unittest.skipIf(cyipopt_ge_1_3, "cyipopt version >= 1.3.0") + def test_hs071_evalerror_old_cyipopt(self): + m = make_hs071_model() + solver = pyo.SolverFactory("cyipopt") + msg = "Error in AMPL evaluation" + with self.assertRaisesRegex(PyNumeroEvaluationError, msg): + res = solver.solve(m, tee=True) From 49f49765b74cfa27621ecb165162a53674b236b5 Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 28 Sep 2023 21:40:04 -0600 Subject: [PATCH 0235/1204] nfc:black --- pyomo/contrib/pynumero/interfaces/cyipopt_interface.py | 8 ++++++-- .../pynumero/interfaces/tests/test_cyipopt_interface.py | 2 -- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py index 5ac98cdeecc..6f9251434cb 100644 --- a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py +++ b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py @@ -267,8 +267,12 @@ def __init__(self, nlp, intermediate_callback=None, halt_on_evaluation_error=Non if halt_on_evaluation_error is None: # If using cyipopt >= 1.3, the default is to continue. # Otherwise, the default is to halt (because we are forced to). - self._halt_on_evaluation_error = not hasattr(cyipopt, "CyIpoptEvaluationError") - elif halt_on_evaluation_error and not hasattr(cyipopt, "CyIpoptEvaluationError"): + self._halt_on_evaluation_error = not hasattr( + cyipopt, "CyIpoptEvaluationError" + ) + elif halt_on_evaluation_error and not hasattr( + cyipopt, "CyIpoptEvaluationError" + ): raise ValueError( "halt_on_evaluation_error is only supported for cyipopt >= 1.3.0" ) diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_cyipopt_interface.py b/pyomo/contrib/pynumero/interfaces/tests/test_cyipopt_interface.py index b2ab837d9c7..f28b7b9b549 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_cyipopt_interface.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_cyipopt_interface.py @@ -110,7 +110,6 @@ def _get_model_nlp_interface(halt_on_evaluation_error=None): class TestCyIpoptVersionDependentConfig(unittest.TestCase): - @unittest.skipIf(cyipopt_ge_1_3, "cyipopt version >= 1.3.0") def test_config_error(self): _, nlp, _, _ = _get_model_nlp_interface() @@ -135,7 +134,6 @@ def test_default_config_with_new_cyipopt(self): class TestCyIpoptEvaluationErrors(unittest.TestCase): - @unittest.skipUnless(cyipopt_ge_1_3, "cyipopt version < 1.3.0") def test_error_in_objective(self): m, nlp, interface, bad_x = _get_model_nlp_interface( From 7a9209782f46ad641d8e13c2ee8fe760665745cf Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 28 Sep 2023 22:08:28 -0600 Subject: [PATCH 0236/1204] remove whitespace --- pyomo/contrib/pynumero/interfaces/cyipopt_interface.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py index 6f9251434cb..c327dc516a2 100644 --- a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py +++ b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py @@ -279,7 +279,6 @@ def __init__(self, nlp, intermediate_callback=None, halt_on_evaluation_error=Non else: self._halt_on_evaluation_error = halt_on_evaluation_error - x = nlp.init_primals() y = nlp.init_duals() if np.any(np.isnan(y)): From 40fd91e4569af7574412ce3ff27e10b001aaf01b Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 28 Sep 2023 23:11:19 -0600 Subject: [PATCH 0237/1204] only assign cyipopt_ge_1_3 if cyipopt_available --- .../pynumero/algorithms/solvers/tests/test_cyipopt_solver.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py index bdd32c6788e..1b185334316 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py @@ -42,7 +42,10 @@ from pyomo.contrib.pynumero.algorithms.solvers.cyipopt_solver import CyIpoptSolver -cyipopt_ge_1_3 = hasattr(cyipopt, "CyIpoptEvaluationError") +if cyipopt_available: + # We don't raise unittest.SkipTest if not cyipopt_available as there is a + # test below that tests an exception when cyipopt is unavailable. + cyipopt_ge_1_3 = hasattr(cyipopt, "CyIpoptEvaluationError") def create_model1(): From f22157905b783a9cdf92ff902511ed1e80f3e73e Mon Sep 17 00:00:00 2001 From: robbybp Date: Fri, 29 Sep 2023 10:07:02 -0600 Subject: [PATCH 0238/1204] dont evaluate cyipopt_ge_1_3 when cyipopt not available --- .../algorithms/solvers/tests/test_cyipopt_solver.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py index 1b185334316..7ead30117cb 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py @@ -288,7 +288,9 @@ def test_options(self): nlp.set_primals(x) self.assertAlmostEqual(nlp.evaluate_objective(), -5.0879028e02, places=5) - @unittest.skipUnless(cyipopt_ge_1_3, "cyipopt version < 1.3.0") + @unittest.skipUnless( + cyipopt_available and cyipopt_ge_1_3, "cyipopt version < 1.3.0" + ) def test_hs071_evalerror(self): m = make_hs071_model() solver = pyo.SolverFactory("cyipopt") @@ -305,7 +307,9 @@ def test_hs071_evalerror_halt(self): with self.assertRaisesRegex(PyNumeroEvaluationError, msg): res = solver.solve(m, tee=True) - @unittest.skipIf(cyipopt_ge_1_3, "cyipopt version >= 1.3.0") + @unittest.skipIf( + not cyipopt_available or cyipopt_ge_1_3, "cyipopt version >= 1.3.0" + ) def test_hs071_evalerror_old_cyipopt(self): m = make_hs071_model() solver = pyo.SolverFactory("cyipopt") From a7ed2900a647a6a6f485ce6b1afd278803a3e4d2 Mon Sep 17 00:00:00 2001 From: robbybp Date: Fri, 29 Sep 2023 11:31:28 -0600 Subject: [PATCH 0239/1204] only check hasattr(cyipopt,...) if cyipopt_available --- .../pynumero/interfaces/cyipopt_interface.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py index c327dc516a2..3bc69fd35ab 100644 --- a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py +++ b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py @@ -264,17 +264,19 @@ def __init__(self, nlp, intermediate_callback=None, halt_on_evaluation_error=Non self._nlp = nlp self._intermediate_callback = intermediate_callback + cyipopt_has_eval_error = ( + cyipopt_available and hasattr(cyipopt, "CyIpoptEvaluationError") + ) if halt_on_evaluation_error is None: # If using cyipopt >= 1.3, the default is to continue. # Otherwise, the default is to halt (because we are forced to). - self._halt_on_evaluation_error = not hasattr( - cyipopt, "CyIpoptEvaluationError" - ) - elif halt_on_evaluation_error and not hasattr( - cyipopt, "CyIpoptEvaluationError" - ): + # + # If CyIpopt is not available, we "halt" (re-raise the original + # exception). + self._halt_on_evaluation_error = not cyipopt_has_eval_error + elif not halt_on_evaluation_error and not has_cyipopt_eval_error: raise ValueError( - "halt_on_evaluation_error is only supported for cyipopt >= 1.3.0" + "halt_on_evaluation_error=False is only supported for cyipopt >= 1.3.0" ) else: self._halt_on_evaluation_error = halt_on_evaluation_error From c39006db2344281bc860aac71d9b42626677e37c Mon Sep 17 00:00:00 2001 From: robbybp Date: Fri, 29 Sep 2023 11:40:19 -0600 Subject: [PATCH 0240/1204] variable name typo --- pyomo/contrib/pynumero/interfaces/cyipopt_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py index 3bc69fd35ab..ee5783cba79 100644 --- a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py +++ b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py @@ -274,7 +274,7 @@ def __init__(self, nlp, intermediate_callback=None, halt_on_evaluation_error=Non # If CyIpopt is not available, we "halt" (re-raise the original # exception). self._halt_on_evaluation_error = not cyipopt_has_eval_error - elif not halt_on_evaluation_error and not has_cyipopt_eval_error: + elif not halt_on_evaluation_error and not cyipopt_has_eval_error: raise ValueError( "halt_on_evaluation_error=False is only supported for cyipopt >= 1.3.0" ) From 565c4cd7eb7c3f9ee52785e21f279b63d5fa81c4 Mon Sep 17 00:00:00 2001 From: robbybp Date: Fri, 29 Sep 2023 11:52:30 -0600 Subject: [PATCH 0241/1204] black --- pyomo/contrib/pynumero/interfaces/cyipopt_interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py index ee5783cba79..f277fca6231 100644 --- a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py +++ b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py @@ -264,8 +264,8 @@ def __init__(self, nlp, intermediate_callback=None, halt_on_evaluation_error=Non self._nlp = nlp self._intermediate_callback = intermediate_callback - cyipopt_has_eval_error = ( - cyipopt_available and hasattr(cyipopt, "CyIpoptEvaluationError") + cyipopt_has_eval_error = cyipopt_available and hasattr( + cyipopt, "CyIpoptEvaluationError" ) if halt_on_evaluation_error is None: # If using cyipopt >= 1.3, the default is to continue. From f8ee13e6e5e6729c6712394276261c455c8d1c29 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 30 Sep 2023 17:03:05 -0400 Subject: [PATCH 0242/1204] Update separation problem loop progress log msgs --- .../pyros/separation_problem_methods.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index 81fe06ba6cb..61f347b418d 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -681,21 +681,23 @@ def perform_separation_loop(model_data, config, solve_globally): ) all_solve_call_results = ComponentMap() - for priority, perf_constraints in sorted_priority_groups.items(): + priority_groups_enum = enumerate(sorted_priority_groups.items()) + for group_idx, (priority, perf_constraints) in priority_groups_enum: priority_group_solve_call_results = ComponentMap() for idx, perf_con in enumerate(perf_constraints): + # log progress of separation loop solve_adverb = "Globally" if solve_globally else "Locally" config.progress_logger.debug( - f"{solve_adverb} separating constraint " + f"{solve_adverb} separating performance constraint " f"{get_con_name_repr(model_data.separation_model, perf_con)} " - f"(group priority {priority}, " - f"constraint {idx + 1} of {len(perf_constraints)})" + f"(priority {priority}, priority group {group_idx + 1} of " + f"{len(sorted_priority_groups)}, " + f"constraint {idx + 1} of {len(perf_constraints)} " + "in priority group, " + f"{len(all_solve_call_results) + idx + 1} of " + f"{len(all_performance_constraints)} total)" ) - # config.progress_logger.info( - # f"Separating constraint {perf_con.name}" - # ) - # solve separation problem for this performance constraint if uncertainty_set_is_discrete: solve_call_results = get_worst_discrete_separation_solution( From c913dd2b43a1190cb43cd8b30d98b8fa4f8e13c4 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 30 Sep 2023 17:05:01 -0400 Subject: [PATCH 0243/1204] Fix numpy import --- pyomo/contrib/pyros/pyros_algorithm_methods.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index 1b94d61a710..5b234b150c8 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -18,7 +18,7 @@ from pyomo.common.collections import ComponentSet, ComponentMap from pyomo.core.base.var import _VarData as VarData from itertools import chain -import numpy as np +from pyomo.common.dependencies import numpy as np def update_grcs_solve_data( From 4572eb4d1b4b11a41d4a5963e3f0e24e36ab56db Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 30 Sep 2023 19:41:18 -0400 Subject: [PATCH 0244/1204] Restructure default logger setup --- pyomo/contrib/pyros/pyros.py | 7 +++++-- pyomo/contrib/pyros/util.py | 28 ++++++++++++++++------------ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index ede378fc461..07c05142164 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -38,7 +38,7 @@ turn_bounds_to_constraints, replace_uncertain_bounds_with_constraints, IterationLogRecord, - DEFAULT_LOGGER_NAME, + setup_pyros_logger, TimingData, ) from pyomo.contrib.pyros.solve_data import ROSolveResults @@ -52,6 +52,9 @@ __version__ = "1.2.7" +default_pyros_solver_logger = setup_pyros_logger() + + def _get_pyomo_git_info(): """ Get Pyomo git commit hash. @@ -514,7 +517,7 @@ def pyros_config(): CONFIG.declare( "progress_logger", PyROSConfigValue( - default=DEFAULT_LOGGER_NAME, + default=default_pyros_solver_logger, domain=a_logger, doc=( """ diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index 8ad98e38e89..cbfd20ccd17 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -388,6 +388,20 @@ def log(self, level, msg, *args, **kwargs): ) +def setup_pyros_logger(name=DEFAULT_LOGGER_NAME): + """ + Set up pyros logger. + """ + # default logger: INFO level, with preformatted messages + current_logger_class = logging.getLoggerClass() + logging.setLoggerClass(PreformattedLogger) + logger = logging.getLogger(DEFAULT_LOGGER_NAME) + logger.setLevel(logging.INFO) + logging.setLoggerClass(current_logger_class) + + return logger + + def a_logger(str_or_logger): """ Standardize a string or logger object to a logger object. @@ -410,19 +424,9 @@ def a_logger(str_or_logger): instance. """ if isinstance(str_or_logger, logging.Logger): - logger = logging.getLogger(str_or_logger.name) + return logging.getLogger(str_or_logger.name) else: - if str_or_logger == DEFAULT_LOGGER_NAME: - # default logger: INFO level, with preformatted messages - current_logger_class = logging.getLoggerClass() - logging.setLoggerClass(PreformattedLogger) - logger = logging.getLogger(str_or_logger) - logger.setLevel(logging.INFO) - logging.setLoggerClass(current_logger_class) - else: - logger = logging.getLogger(str_or_logger) - - return logger + return logging.getLogger(str_or_logger) def ValidEnum(enum_class): From 0fff1c68332efe10ee40adceb79d029188b22c44 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 30 Sep 2023 19:49:28 -0400 Subject: [PATCH 0245/1204] Add tests for `IterationLogRecord` class --- pyomo/contrib/pyros/tests/test_grcs.py | 203 +++++++++++++++++++++++++ 1 file changed, 203 insertions(+) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 0b67251e7bd..1ee438530e1 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -20,6 +20,7 @@ pyrosTerminationCondition, coefficient_matching, TimingData, + IterationLogRecord, ) from pyomo.contrib.pyros.util import replace_uncertain_bounds_with_constraints from pyomo.contrib.pyros.util import get_vars_from_component @@ -63,6 +64,9 @@ import logging +logger = logging.getLogger(__name__) + + if not (numpy_available and scipy_available): raise unittest.SkipTest('PyROS unit tests require numpy and scipy') @@ -5639,5 +5643,204 @@ def test_two_stg_mod_with_intersection_set(self): ) +class TestIterationLogRecord(unittest.TestCase): + """ + Test the PyROS `IterationLogRecord` class. + """ + + def test_log_header(self): + """Test method for logging iteration log table header.""" + ans = ( + "------------------------------------------------------------------------------\n" + "Itn Objective 1-Stg Shift DR Shift #CViol Max Viol Wall Time (s)\n" + "------------------------------------------------------------------------------\n" + ) + with LoggingIntercept(level=logging.INFO) as LOG: + IterationLogRecord.log_header(logger.info) + + self.assertEqual( + LOG.getvalue(), + ans, + msg="Messages logged for iteration table header do not match expected result", + ) + + def test_log_standard_iter_record(self): + """Test logging function for PyROS IterationLogRecord.""" + + # for some fields, we choose floats with more than four + # four decimal points to ensure rounding also matches + iter_record = IterationLogRecord( + iteration=4, + objective=1.234567, + first_stage_var_shift=2.3456789e-8, + dr_var_shift=3.456789e-7, + num_violated_cons=10, + max_violation=7.654321e-3, + elapsed_time=21.2, + dr_polishing_success=True, + all_sep_problems_solved=True, + global_separation=False, + ) + + # now check record logged as expected + ans = ( + "4 1.2346e+00 2.3457e-08 3.4568e-07 10 7.6543e-03 " + "21.200 \n" + ) + with LoggingIntercept(level=logging.INFO) as LOG: + iter_record.log(logger.info) + result = LOG.getvalue() + + self.assertEqual( + ans, + result, + msg="Iteration log record message does not match expected result", + ) + + def test_log_iter_record_polishing_failed(self): + """Test iteration log record in event of polishing failure.""" + # for some fields, we choose floats with more than four + # four decimal points to ensure rounding also matches + iter_record = IterationLogRecord( + iteration=4, + objective=1.234567, + first_stage_var_shift=2.3456789e-8, + dr_var_shift=3.456789e-7, + num_violated_cons=10, + max_violation=7.654321e-3, + elapsed_time=21.2, + dr_polishing_success=False, + all_sep_problems_solved=True, + global_separation=False, + ) + + # now check record logged as expected + ans = ( + "4 1.2346e+00 2.3457e-08 3.4568e-07* 10 7.6543e-03 " + "21.200 \n" + ) + with LoggingIntercept(level=logging.INFO) as LOG: + iter_record.log(logger.info) + result = LOG.getvalue() + + self.assertEqual( + ans, + result, + msg="Iteration log record message does not match expected result", + ) + + def test_log_iter_record_global_separation(self): + """ + Test iteration log record in event global separation performed. + In this case, a 'g' should be appended to the max violation + reported. Useful in the event neither local nor global separation + was bypassed. + """ + # for some fields, we choose floats with more than four + # four decimal points to ensure rounding also matches + iter_record = IterationLogRecord( + iteration=4, + objective=1.234567, + first_stage_var_shift=2.3456789e-8, + dr_var_shift=3.456789e-7, + num_violated_cons=10, + max_violation=7.654321e-3, + elapsed_time=21.2, + dr_polishing_success=True, + all_sep_problems_solved=True, + global_separation=True, + ) + + # now check record logged as expected + ans = ( + "4 1.2346e+00 2.3457e-08 3.4568e-07 10 7.6543e-03g " + "21.200 \n" + ) + with LoggingIntercept(level=logging.INFO) as LOG: + iter_record.log(logger.info) + result = LOG.getvalue() + + self.assertEqual( + ans, + result, + msg="Iteration log record message does not match expected result", + ) + + def test_log_iter_record_not_all_sep_solved(self): + """ + Test iteration log record in event not all separation problems + were solved successfully. This may have occurred if the PyROS + solver time limit was reached, or the user-provides subordinate + optimizer(s) were unable to solve a separation subproblem + to an acceptable level. + A '+' should be appended to the number of performance constraints + found to be violated. + """ + # for some fields, we choose floats with more than four + # four decimal points to ensure rounding also matches + iter_record = IterationLogRecord( + iteration=4, + objective=1.234567, + first_stage_var_shift=2.3456789e-8, + dr_var_shift=3.456789e-7, + num_violated_cons=10, + max_violation=7.654321e-3, + elapsed_time=21.2, + dr_polishing_success=True, + all_sep_problems_solved=False, + global_separation=False, + ) + + # now check record logged as expected + ans = ( + "4 1.2346e+00 2.3457e-08 3.4568e-07 10+ 7.6543e-03 " + "21.200 \n" + ) + with LoggingIntercept(level=logging.INFO) as LOG: + iter_record.log(logger.info) + result = LOG.getvalue() + + self.assertEqual( + ans, + result, + msg="Iteration log record message does not match expected result", + ) + + def test_log_iter_record_all_special(self): + """ + Test iteration log record in event DR polishing and global + separation failed. + """ + # for some fields, we choose floats with more than four + # four decimal points to ensure rounding also matches + iter_record = IterationLogRecord( + iteration=4, + objective=1.234567, + first_stage_var_shift=2.3456789e-8, + dr_var_shift=3.456789e-7, + num_violated_cons=10, + max_violation=7.654321e-3, + elapsed_time=21.2, + dr_polishing_success=False, + all_sep_problems_solved=False, + global_separation=True, + ) + + # now check record logged as expected + ans = ( + "4 1.2346e+00 2.3457e-08 3.4568e-07* 10+ 7.6543e-03g " + "21.200 \n" + ) + with LoggingIntercept(level=logging.INFO) as LOG: + iter_record.log(logger.info) + result = LOG.getvalue() + + self.assertEqual( + ans, + result, + msg="Iteration log record message does not match expected result", + ) + + if __name__ == "__main__": unittest.main() From 5b071c342d9693d75227b058f754ae54b6d4c52b Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 30 Sep 2023 22:07:11 -0400 Subject: [PATCH 0246/1204] Update max iter termination test --- pyomo/contrib/pyros/tests/test_grcs.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 1ee438530e1..8bdf5af8397 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -3970,7 +3970,8 @@ def test_identifying_violating_param_realization(self): baron_license_is_valid, "Global NLP solver is not available and licensed." ) @unittest.skipUnless( - baron_version < (23, 1, 5), "Test known to fail beginning with Baron 23.1.5" + baron_version < (23, 1, 5) or baron_version >= (23, 6, 23), + "Test known to fail for BARON 23.1.5 and versions preceding 23.6.23", ) def test_terminate_with_max_iter(self): m = ConcreteModel() @@ -4017,6 +4018,15 @@ def test_terminate_with_max_iter(self): msg="Returned termination condition is not return max_iter.", ) + self.assertEqual( + results.iterations, + 1, + msg=( + f"Number of iterations in results object is {results.iterations}, " + f"but expected value 1." + ) + ) + @unittest.skipUnless( baron_license_is_valid, "Global NLP solver is not available and licensed." ) From f11792f4f56839bf0e40e3a238b2f8a64a1be436 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 30 Sep 2023 22:14:52 -0400 Subject: [PATCH 0247/1204] Apply black --- pyomo/contrib/pyros/tests/test_grcs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 8bdf5af8397..e37524e4f56 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -4024,7 +4024,7 @@ def test_terminate_with_max_iter(self): msg=( f"Number of iterations in results object is {results.iterations}, " f"but expected value 1." - ) + ), ) @unittest.skipUnless( From 5dce537e389ee8b096f51c94e7978ecf909ea487 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 30 Sep 2023 22:39:41 -0400 Subject: [PATCH 0248/1204] Fix accounting for model objective sense --- pyomo/contrib/pyros/pyros.py | 11 +++++++---- pyomo/contrib/pyros/tests/test_grcs.py | 16 +++++++++++++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 07c05142164..128f59226c8 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -961,6 +961,7 @@ def solve( ) assert len(active_objs) == 1 active_obj = active_objs[0] + active_obj_original_sense = active_obj.sense recast_to_min_obj(model_data.working_model, active_obj) # === Determine first and second-stage objectives @@ -1037,12 +1038,14 @@ def solve( # since maximization objective is changed to # minimization objective during preprocessing if config.objective_focus == ObjectiveType.nominal: - return_soln.final_objective_value = active_obj.sense * value( - pyros_soln.master_soln.master_model.obj + return_soln.final_objective_value = ( + active_obj_original_sense + * value(pyros_soln.master_soln.master_model.obj) ) elif config.objective_focus == ObjectiveType.worst_case: - return_soln.final_objective_value = active_obj.sense * value( - pyros_soln.master_soln.master_model.zeta + return_soln.final_objective_value = ( + active_obj_original_sense + * value(pyros_soln.master_soln.master_model.zeta) ) return_soln.pyros_termination_condition = ( pyros_soln.pyros_termination_condition diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index e37524e4f56..40c0d745cb9 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -5221,12 +5221,26 @@ def test_multiple_objs(self): # and solve again m.obj_max = Objective(expr=-m.obj.expr, sense=pyo_max) m.obj.deactivate() - res = pyros_solver.solve(**solve_kwargs) + max_obj_res = pyros_solver.solve(**solve_kwargs) # check active objectives self.assertEqual(len(list(m.component_data_objects(Objective, active=True))), 1) self.assertTrue(m.obj_max.active) + self.assertTrue( + math.isclose( + res.final_objective_value, + -max_obj_res.final_objective_value, + abs_tol=2e-4, # 2x the default robust feasibility tolerance + ), + msg=( + f"Robust optimal objective value {res.final_objective_value} " + "for problem with minimization objective not close to " + f"negative of value {max_obj_res.final_objective_value} " + "of equivalent maximization objective." + ), + ) + class testModelIdentifyObjectives(unittest.TestCase): """ From 3d5fe78e9da7dfef026044f260a3fd76bb5bd949 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 1 Oct 2023 17:01:23 -0400 Subject: [PATCH 0249/1204] Tweak `ROSolveResults.__str__`; add tests --- pyomo/contrib/pyros/solve_data.py | 2 +- pyomo/contrib/pyros/tests/test_grcs.py | 66 +++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pyros/solve_data.py b/pyomo/contrib/pyros/solve_data.py index a3062185233..40a52757bae 100644 --- a/pyomo/contrib/pyros/solve_data.py +++ b/pyomo/contrib/pyros/solve_data.py @@ -61,7 +61,7 @@ def __str__(self): "final_objective_value": ("Final objective value", "f'{val:.4e}'"), "pyros_termination_condition": ("Termination condition", "f'{val}'"), } - attr_desc_pad_length = 1 + max( + attr_desc_pad_length = max( len(desc) for desc, _ in attr_name_format_dict.values() ) for attr_name, (attr_desc, fmt_str) in attr_name_format_dict.items(): diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 40c0d745cb9..44c96f14499 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -35,7 +35,7 @@ solve_master, minimize_dr_vars, ) -from pyomo.contrib.pyros.solve_data import MasterProblemData +from pyomo.contrib.pyros.solve_data import MasterProblemData, ROSolveResults from pyomo.common.dependencies import numpy as np, numpy_available from pyomo.common.dependencies import scipy as sp, scipy_available from pyomo.environ import maximize as pyo_max @@ -5866,5 +5866,69 @@ def test_log_iter_record_all_special(self): ) +class TestROSolveResults(unittest.TestCase): + """ + Test PyROS solver results object. + """ + + def test_ro_solve_results_str(self): + """ + Test string representation of RO solve results object. + """ + res = ROSolveResults( + config=SolverFactory("pyros").CONFIG(), + iterations=4, + final_objective_value=123.456789, + time=300.34567, + pyros_termination_condition=pyrosTerminationCondition.robust_optimal, + ) + ans = ( + "Termination stats:\n" + " Iterations : 4\n" + " Solve time (wall s) : 300.346\n" + " Final objective value : 1.2346e+02\n" + " Termination condition : pyrosTerminationCondition.robust_optimal" + ) + self.assertEqual( + str(res), + ans, + msg=( + "String representation of PyROS results object does not " + "match expected value" + ), + ) + + def test_ro_solve_results_str_attrs_none(self): + """ + Test string representation of PyROS solve results in event + one of the printed attributes is of value `None`. + This may occur at instantiation or, for example, + whenever the PyROS solver confirms robust infeasibility through + coefficient matching. + """ + res = ROSolveResults( + config=SolverFactory("pyros").CONFIG(), + iterations=0, + final_objective_value=None, + time=300.34567, + pyros_termination_condition=pyrosTerminationCondition.robust_optimal, + ) + ans = ( + "Termination stats:\n" + " Iterations : 0\n" + " Solve time (wall s) : 300.346\n" + " Final objective value : None\n" + " Termination condition : pyrosTerminationCondition.robust_optimal" + ) + self.assertEqual( + str(res), + ans, + msg=( + "String representation of PyROS results object does not " + "match expected value" + ), + ) + + if __name__ == "__main__": unittest.main() From 832853a956121d429c7e678ae38f8f6e098f9aaf Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 1 Oct 2023 18:04:08 -0400 Subject: [PATCH 0250/1204] Test logging of PyROS solver options --- pyomo/contrib/pyros/tests/test_grcs.py | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 44c96f14499..d9dc6a9d1eb 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -5930,5 +5930,54 @@ def test_ro_solve_results_str_attrs_none(self): ) +class TestPyROSSolverLogIntros(unittest.TestCase): + """ + Test logging of introductory information by PyROS solver. + """ + def test_log_config(self): + """ + Test method for logging PyROS solver config dict. + """ + pyros_solver = SolverFactory("pyros") + config = pyros_solver.CONFIG(dict( + nominal_uncertain_param_vals=[0.5], + )) + with LoggingIntercept(level=logging.INFO) as LOG: + pyros_solver._log_config(logger=logger, config=config, level=logging.INFO) + + ans = ( + "Solver options:\n" + " time_limit=None\n" + " keepfiles=False\n" + " tee=False\n" + " load_solution=True\n" + " objective_focus=\n" + " nominal_uncertain_param_vals=[0.5]\n" + " decision_rule_order=0\n" + " solve_master_globally=False\n" + " max_iter=-1\n" + " robust_feasibility_tolerance=0.0001\n" + " separation_priority_order={}\n" + " progress_logger=\n" + " backup_local_solvers=[]\n" + " backup_global_solvers=[]\n" + " subproblem_file_directory=None\n" + " bypass_local_separation=False\n" + " bypass_global_separation=False\n" + " p_robustness={}\n" + + "-" * 78 + "\n" + ) + + logged_str = LOG.getvalue() + self.assertEqual( + logged_str, + ans, + msg=( + "Logger output for PyROS solver config (default case) " + "does not match expected result." + ), + ) + + if __name__ == "__main__": unittest.main() From 0303469fad6e26d95f9d45d38e24e5c9cf235ac6 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 1 Oct 2023 22:06:34 -0400 Subject: [PATCH 0251/1204] Test introductory PyROS solver message --- pyomo/contrib/pyros/tests/test_grcs.py | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index d9dc6a9d1eb..1052b13425f 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -5978,6 +5978,39 @@ def test_log_config(self): ), ) + def test_log_intro(self): + """ + Test logging of PyROS solver introductory messages. + """ + pyros_solver = SolverFactory("pyros") + with LoggingIntercept(level=logging.INFO) as LOG: + pyros_solver._log_intro(logger=logger, level=logging.INFO) + + intro_msgs = LOG.getvalue() + + # last character should be newline; disregard it + intro_msg_lines = intro_msgs.split("\n")[:-1] + + # check number of lines is as expected + self.assertEqual( + len(intro_msg_lines), + 13, + msg=( + "PyROS solver introductory message does not contain" + "the expected number of lines." + ), + ) + + # first and last lines of the introductory section + self.assertEqual(intro_msg_lines[0], "=" * 78) + self.assertEqual(intro_msg_lines[-1], "=" * 78) + + # check regex main text + self.assertRegex( + " ".join(intro_msg_lines[1:-1]), + r"PyROS: The Pyomo Robust Optimization Solver\..* \(IDAES\)\.", + ) + if __name__ == "__main__": unittest.main() From f69378dfd2be3b75453bc90fb1c848b25a98e6d9 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 1 Oct 2023 22:23:07 -0400 Subject: [PATCH 0252/1204] Test PyROS solver disclaimer messages --- pyomo/contrib/pyros/tests/test_grcs.py | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 1052b13425f..a11547eed46 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -6011,6 +6011,43 @@ def test_log_intro(self): r"PyROS: The Pyomo Robust Optimization Solver\..* \(IDAES\)\.", ) + def test_log_disclaimer(self): + """ + Test logging of PyROS solver disclaimer messages. + """ + pyros_solver = SolverFactory("pyros") + with LoggingIntercept(level=logging.INFO) as LOG: + pyros_solver._log_disclaimer(logger=logger, level=logging.INFO) + + disclaimer_msgs = LOG.getvalue() + + # last character should be newline; disregard it + disclaimer_msg_lines = disclaimer_msgs.split("\n")[:-1] + + # check number of lines is as expected + self.assertEqual( + len(disclaimer_msg_lines), + 5, + msg=( + "PyROS solver disclaimer message does not contain" + "the expected number of lines." + ), + ) + + # regex first line of disclaimer section + self.assertRegex( + disclaimer_msg_lines[0], + r"=.* DISCLAIMER .*=", + ) + # check last line of disclaimer section + self.assertEqual(disclaimer_msg_lines[-1], "=" * 78) + + # check regex main text + self.assertRegex( + " ".join(disclaimer_msg_lines[1:-1]), + r"PyROS is still under development.*ticket at.*", + ) + if __name__ == "__main__": unittest.main() From f47dbe95b44cd98d9f6d65528ed2bd0236c067f0 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 1 Oct 2023 22:23:35 -0400 Subject: [PATCH 0253/1204] Apply black --- pyomo/contrib/pyros/tests/test_grcs.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index a11547eed46..d3d8b76483a 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -5934,14 +5934,13 @@ class TestPyROSSolverLogIntros(unittest.TestCase): """ Test logging of introductory information by PyROS solver. """ + def test_log_config(self): """ Test method for logging PyROS solver config dict. """ pyros_solver = SolverFactory("pyros") - config = pyros_solver.CONFIG(dict( - nominal_uncertain_param_vals=[0.5], - )) + config = pyros_solver.CONFIG(dict(nominal_uncertain_param_vals=[0.5])) with LoggingIntercept(level=logging.INFO) as LOG: pyros_solver._log_config(logger=logger, config=config, level=logging.INFO) @@ -5964,8 +5963,7 @@ def test_log_config(self): " subproblem_file_directory=None\n" " bypass_local_separation=False\n" " bypass_global_separation=False\n" - " p_robustness={}\n" - + "-" * 78 + "\n" + " p_robustness={}\n" + "-" * 78 + "\n" ) logged_str = LOG.getvalue() @@ -6035,10 +6033,7 @@ def test_log_disclaimer(self): ) # regex first line of disclaimer section - self.assertRegex( - disclaimer_msg_lines[0], - r"=.* DISCLAIMER .*=", - ) + self.assertRegex(disclaimer_msg_lines[0], r"=.* DISCLAIMER .*=") # check last line of disclaimer section self.assertEqual(disclaimer_msg_lines[-1], "=" * 78) From b0b124fb08e51bf08b20eb4bc5016f29751be9ad Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 1 Oct 2023 22:40:10 -0400 Subject: [PATCH 0254/1204] Test coefficient matching detailed error message --- pyomo/contrib/pyros/tests/test_grcs.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index d3d8b76483a..c949af1ed15 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -4887,13 +4887,16 @@ def test_coefficient_matching_raises_error_4_3(self): # solve with PyROS dr_orders = [1, 2] for dr_order in dr_orders: - with self.assertRaisesRegex( + regex_assert_mgr = self.assertRaisesRegex( ValueError, expected_regex=( "Coefficient matching unsuccessful. See the solver logs." ), - ): - res = pyros_solver.solve( + ) + logging_intercept_mgr = LoggingIntercept(level=logging.ERROR) + + with regex_assert_mgr, logging_intercept_mgr as LOG: + pyros_solver.solve( model=m, first_stage_variables=[], second_stage_variables=[m.x1, m.x2, m.x3], @@ -4908,6 +4911,16 @@ def test_coefficient_matching_raises_error_4_3(self): robust_feasibility_tolerance=1e-4, ) + detailed_error_msg = LOG.getvalue() + self.assertRegex( + detailed_error_msg[:-1], + ( + r"Equality constraint.*cannot be guaranteed to " + r"be robustly feasible.*" + r"Consider editing this constraint.*" + ), + ) + def test_coefficient_matching_robust_infeasible_proof_in_pyros(self): # Write the deterministic Pyomo model m = ConcreteModel() From b322c7c0351dfbb43a0e071cc9c6d67a7319c621 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 1 Oct 2023 23:01:09 -0400 Subject: [PATCH 0255/1204] Cleanup docs and implementation of `TimingData` --- pyomo/contrib/pyros/util.py | 42 ++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index cbfd20ccd17..a956e17b089 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -59,7 +59,7 @@ class TimingData: """ PyROS solver timing data object. - A wrapper around `common.timing.HierarchicalTimer`, + Implemented as a wrapper around `common.timing.HierarchicalTimer`, with added functionality for enforcing a standardized hierarchy of identifiers. @@ -100,10 +100,15 @@ def _validate_full_identifier(self, full_identifier): """ Validate identifier for hierarchical timer. + Parameters + ---------- + full_identifier : str + Identifier to validate. + Raises ------ ValueError - If identifier not in `self.hierarchical_timer_full_ids`. + If identifier not in `TimingData.hierarchical_timer_full_ids`. """ if full_identifier not in self.hierarchical_timer_full_ids: raise ValueError( @@ -112,13 +117,31 @@ def _validate_full_identifier(self, full_identifier): ) def start_timer(self, full_identifier): - """Start timer for `self.hierarchical_timer`.""" + """ + Start timer for `self.hierarchical_timer`. + + Parameters + ---------- + full_identifier : str + Full identifier for the timer to be started. + Must be an entry of + `TimingData.hierarchical_timer_full_ids`. + """ self._validate_full_identifier(full_identifier) identifier = full_identifier.split(".")[-1] return self._hierarchical_timer.start(identifier=identifier) def stop_timer(self, full_identifier): - """Stop timer for `self.hierarchical_timer`.""" + """ + Stop timer for `self.hierarchical_timer`. + + Parameters + ---------- + full_identifier : str + Full identifier for the timer to be stopped. + Must be an entry of + `TimingData.hierarchical_timer_full_ids`. + """ self._validate_full_identifier(full_identifier) identifier = full_identifier.split(".")[-1] return self._hierarchical_timer.stop(identifier=identifier) @@ -126,8 +149,17 @@ def stop_timer(self, full_identifier): def get_total_time(self, full_identifier): """ Get total time spent with identifier active. + + Parameters + ---------- + full_identifier : str + Full identifier for the timer of interest. + + Returns + ------- + float + Total time spent with identifier active. """ - self._validate_full_identifier(full_identifier) return self._hierarchical_timer.get_total_time(identifier=full_identifier) def get_main_elapsed_time(self): From 2ce72647eb59e5b5969c547a7f97f58b4fc84d2f Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 1 Oct 2023 23:08:56 -0400 Subject: [PATCH 0256/1204] Update total solve time retrieval at termination --- pyomo/contrib/pyros/pyros.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 128f59226c8..85e1a470aeb 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -1065,7 +1065,7 @@ def solve( return_soln.iterations = 0 return_soln.config = config - return_soln.time = model_data.timing.get_main_elapsed_time() + return_soln.time = model_data.timing.get_total_time("main") # log termination-related messages config.progress_logger.info(return_soln.pyros_termination_condition.message) From a866ad4c72d8fd015484adbc8c196b06e89e620a Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 1 Oct 2023 23:27:15 -0400 Subject: [PATCH 0257/1204] Ensure bypass global separation message tested --- pyomo/contrib/pyros/tests/test_grcs.py | 44 +++++++++++++++++--------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index c949af1ed15..af9dbc63255 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -5046,28 +5046,42 @@ def test_bypass_global_separation(self): global_subsolver = SolverFactory("baron") # Call the PyROS solver - results = pyros_solver.solve( - model=m, - first_stage_variables=[m.x1], - second_stage_variables=[m.x2], - uncertain_params=[m.u], - uncertainty_set=interval, - local_solver=local_subsolver, - global_solver=global_subsolver, - options={ - "objective_focus": ObjectiveType.worst_case, - "solve_master_globally": True, - "decision_rule_order": 0, - "bypass_global_separation": True, - }, - ) + with LoggingIntercept(level=logging.WARNING) as LOG: + results = pyros_solver.solve( + model=m, + first_stage_variables=[m.x1], + second_stage_variables=[m.x2], + uncertain_params=[m.u], + uncertainty_set=interval, + local_solver=local_subsolver, + global_solver=global_subsolver, + options={ + "objective_focus": ObjectiveType.worst_case, + "solve_master_globally": True, + "decision_rule_order": 0, + "bypass_global_separation": True, + }, + ) + # check termination robust optimal self.assertEqual( results.pyros_termination_condition, pyrosTerminationCondition.robust_optimal, msg="Returned termination condition is not return robust_optimal.", ) + # since robust optimal, we also expect warning-level logger + # message about bypassing of global separation subproblems + warning_msgs = LOG.getvalue() + self.assertRegex( + warning_msgs, + ( + r".*Option to bypass global separation was chosen\. " + r"Robust feasibility and optimality of the reported " + r"solution are not guaranteed\." + ), + ) + @unittest.skipUnless( baron_available and baron_license_is_valid, From 40e1b6db183f6b3f71ce21c990e2680fda9eeef2 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 1 Oct 2023 23:34:28 -0400 Subject: [PATCH 0258/1204] Add another `IterationLogRecord` test --- pyomo/contrib/pyros/tests/test_grcs.py | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index af9dbc63255..b661f18752c 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -5892,6 +5892,44 @@ def test_log_iter_record_all_special(self): msg="Iteration log record message does not match expected result", ) + def test_log_iter_record_attrs_none(self): + """ + Test logging of iteration record in event some + attributes are of value `None`. In this case, a '-' + should be printed in lieu of a numerical value. + Example where this occurs: the first iteration, + in which there is no first-stage shift or DR shift. + """ + # for some fields, we choose floats with more than four + # four decimal points to ensure rounding also matches + iter_record = IterationLogRecord( + iteration=0, + objective=-1.234567, + first_stage_var_shift=None, + dr_var_shift=None, + num_violated_cons=10, + max_violation=7.654321e-3, + elapsed_time=21.2, + dr_polishing_success=True, + all_sep_problems_solved=False, + global_separation=True, + ) + + # now check record logged as expected + ans = ( + "0 -1.2346e+00 - - 10+ 7.6543e-03g " + "21.200 \n" + ) + with LoggingIntercept(level=logging.INFO) as LOG: + iter_record.log(logger.info) + result = LOG.getvalue() + + self.assertEqual( + ans, + result, + msg="Iteration log record message does not match expected result", + ) + class TestROSolveResults(unittest.TestCase): """ From 936182af22b57bb8f4304ff580c69b9fe9d336c6 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 1 Oct 2023 23:51:26 -0400 Subject: [PATCH 0259/1204] Update example solver log in online docs --- doc/OnlineDocs/contributed_packages/pyros.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/OnlineDocs/contributed_packages/pyros.rst b/doc/OnlineDocs/contributed_packages/pyros.rst index 485c253e5ce..81fbeae7f1c 100644 --- a/doc/OnlineDocs/contributed_packages/pyros.rst +++ b/doc/OnlineDocs/contributed_packages/pyros.rst @@ -856,7 +856,7 @@ Observe that the log contains the following information: ============================================================================== PyROS: The Pyomo Robust Optimization Solver. Version 1.2.7 | Git branch: unknown, commit hash: unknown - Invoked at UTC 2023-09-09T18:13:21.893626 + Invoked at UTC 2023-10-02T03:42:54.264507 Developed by: Natalie M. Isenberg (1), Jason A. F. Sherman (1), John D. Siirola (2), Chrysanthos E. Gounaris (1) @@ -883,7 +883,7 @@ Observe that the log contains the following information: max_iter=-1 robust_feasibility_tolerance=0.0001 separation_priority_order={} - progress_logger= + progress_logger= backup_local_solvers=[] backup_global_solvers=[] subproblem_file_directory=None From 4061af9df05e2565931f93f3366adf08e9e6f998 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 2 Oct 2023 15:14:41 -0600 Subject: [PATCH 0260/1204] SAVE POINT: Adding Datetime checker --- pyomo/common/config.py | 11 +++++++++++ pyomo/solver/results.py | 9 +++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index 61e4f682a2a..1e11fbdc431 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -18,6 +18,7 @@ import argparse import builtins +import datetime import enum import importlib import inspect @@ -203,6 +204,16 @@ def NonNegativeFloat(val): return ans +def Datetime(val): + """Domain validation function to check for datetime.datetime type. + + This domain will return the original object, assuming it is of the right type. + """ + if not isinstance(val, datetime.datetime): + raise ValueError(f"Expected datetime object, but received {type(val)}.") + return val + + class In(object): """In(domain, cast=None) Domain validation class admitting a Container of possible values diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index 2fa62027e6c..8e4b6cf21a7 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -16,6 +16,7 @@ from pyomo.common.config import ( ConfigDict, ConfigValue, + Datetime, NonNegativeInt, In, NonNegativeFloat, @@ -213,9 +214,9 @@ def __init__( 'iteration_count', ConfigValue(domain=NonNegativeInt) ) self.timing_info: ConfigDict = self.declare('timing_info', ConfigDict()) - # TODO: Implement type checking for datetime + self.timing_info.start_time: datetime = self.timing_info.declare( - 'start_time', ConfigValue() + 'start_time', ConfigValue(domain=Datetime) ) self.timing_info.wall_time: Optional[float] = self.timing_info.declare( 'wall_time', ConfigValue(domain=NonNegativeFloat) @@ -331,6 +332,10 @@ def parse_sol_file(file, results): "FAILURE: the solver stopped by an error condition " "in the solver routines!" ) + if results.extra_info.solver_message: + results.extra_info.solver_message += '; ' + exit_code_message + else: + results.extra_info.solver_message = exit_code_message results.solver.termination_condition = TerminationCondition.error return results From b5af408e17d0b65f4b270397817be5343f6edd3e Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 2 Oct 2023 15:16:39 -0600 Subject: [PATCH 0261/1204] Swap FullLicense and LimitedLicense --- pyomo/solver/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index f7e5c4c58c5..07f19fbb58c 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -39,11 +39,11 @@ class SolverBase(abc.ABC): class Availability(enum.IntEnum): + FullLicense = 2 + LimitedLicense = 1 NotFound = 0 BadVersion = -1 BadLicense = -2 - FullLicense = 1 - LimitedLicense = 2 NeedsCompiledExtension = -3 def __bool__(self): From 94efcb16d18d1ae98e875445f5101455ffa3f825 Mon Sep 17 00:00:00 2001 From: jasherma Date: Mon, 2 Oct 2023 17:59:52 -0400 Subject: [PATCH 0262/1204] Add warning-level DR polishing failure log message --- pyomo/contrib/pyros/master_problem_methods.py | 8 ++++++++ pyomo/contrib/pyros/tests/test_grcs.py | 1 + 2 files changed, 9 insertions(+) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index 572e0b790a8..c0f6093b3a1 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -543,6 +543,14 @@ def minimize_dr_vars(model_data, config): acceptable = {tc.globallyOptimal, tc.optimal, tc.locallyOptimal, tc.feasible} if results.solver.termination_condition not in acceptable: # continue with "unpolished" master model solution + config.progress_logger.warning( + "Could not successfully solve DR polishing problem " + f"of iteration {model_data.iteration} with primary subordinate " + f"{'global' if config.solve_master_globally else 'local'} solver " + "to acceptable level. " + f"Termination stats:\n{results.solver}\n" + "Maintaining unpolished master problem solution." + ) return results, False # update master model second-stage, state, and decision rule diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index b661f18752c..a76e531d666 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -3902,6 +3902,7 @@ def test_minimize_dr_norm(self): master_data.master_model = master master_data.master_model.const_efficiency_applied = False master_data.master_model.linear_efficiency_applied = False + master_data.iteration = 0 master_data.timing = TimingData() with time_code(master_data.timing, "main", is_main_timer=True): From 0c3f069cb400bc0a40ce5fe3013c30e35bfb8881 Mon Sep 17 00:00:00 2001 From: jasherma Date: Mon, 2 Oct 2023 19:09:40 -0400 Subject: [PATCH 0263/1204] Tweak master feasibility failure message --- pyomo/contrib/pyros/master_problem_methods.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index c0f6093b3a1..dc4b6b957bb 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -293,10 +293,10 @@ def solve_master_feasibility_problem(model_data, config): else: config.progress_logger.warning( "Could not successfully solve master feasibility problem " - f" of iteration {model_data.iteration} with primary subordinate " + f"of iteration {model_data.iteration} with primary subordinate " f"{'global' if config.solve_master_globally else 'local'} solver " "to acceptable level. " - f"Termination stats:\n{results.solver}" + f"Termination stats:\n{results.solver}\n" "Maintaining unoptimized point for master problem initialization." ) From c6cfd1c94d158e0ba69e1df0187fd0bd8bdc7ec1 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 4 Oct 2023 13:11:28 -0600 Subject: [PATCH 0264/1204] Rewriting mul for performance --- pyomo/contrib/fbbt/interval.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/fbbt/interval.py b/pyomo/contrib/fbbt/interval.py index aca6531c8df..9f784922d19 100644 --- a/pyomo/contrib/fbbt/interval.py +++ b/pyomo/contrib/fbbt/interval.py @@ -26,14 +26,24 @@ def sub(xl, xu, yl, yu): def mul(xl, xu, yl, yu): - options = [xl * yl, xl * yu, xu * yl, xu * yu] - if any(math.isnan(i) for i in options): - lb = -inf - ub = inf - else: - lb = min(options) - ub = max(options) + lb = inf + ub = -inf + for i in (xl * yl, xu * yu, xu * yl, xl * yu): + if i < lb: + lb = i + if i > ub: + ub = i + if i != i: # math.isnan(i) + return (-inf, inf) return lb, ub + # options = [xl * yl, xl * yu, xu * yl, xu * yu] + # if any(math.isnan(i) for i in options): + # lb = -inf + # ub = inf + # else: + # lb = min(options) + # ub = max(options) + # return lb, ub def inv(xl, xu, feasibility_tol): From b2520e2901fd7b9ea78db5a547f31e17c07ba247 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 4 Oct 2023 15:28:21 -0600 Subject: [PATCH 0265/1204] Wrapping the visitor and turning off the garbage collector --- pyomo/contrib/fbbt/fbbt.py | 73 ++++++++++++++++++++++++++---- pyomo/contrib/fbbt/interval.py | 11 +---- pyomo/gdp/plugins/bigm.py | 4 +- pyomo/gdp/plugins/bigm_mixin.py | 4 +- pyomo/gdp/plugins/multiple_bigm.py | 3 +- 5 files changed, 72 insertions(+), 23 deletions(-) diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index 5ec8890ca6f..9ac49b50ba4 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -29,12 +29,14 @@ import logging from pyomo.common.errors import InfeasibleConstraintException, PyomoException from pyomo.common.config import ( - ConfigBlock, + ConfigDict, ConfigValue, + document_kwargs_from_configdict, In, NonNegativeFloat, NonNegativeInt, ) +from pyomo.common.gc_manager import PauseGC from pyomo.common.numeric_types import native_types logger = logging.getLogger(__name__) @@ -89,12 +91,11 @@ def _prop_bnds_leaf_to_root_ProductExpression(visitor, node, arg1, arg2): arg2: Second arg in product expression """ bnds_dict = visitor.bnds_dict - lb1, ub1 = bnds_dict[arg1] - lb2, ub2 = bnds_dict[arg2] if arg1 is arg2: - bnds_dict[node] = interval.power(lb1, ub1, 2, 2, visitor.feasibility_tol) + bnds_dict[node] = interval.power(*bnds_dict[arg1], 2, 2, + visitor.feasibility_tol) else: - bnds_dict[node] = interval.mul(lb1, ub1, lb2, ub2) + bnds_dict[node] = interval.mul(*bnds_dict[arg1], *bnds_dict[arg2]) def _prop_bnds_leaf_to_root_SumExpression(visitor, node, *args): @@ -1061,6 +1062,62 @@ def _register_new_before_child_handler(visitor, child): _before_child_handlers[_type] = _before_constant +class FBBTVisitorLeafToRoot(object): + CONFIG = ConfigDict('fbbt_leaf_to_root') + # CONFIG.declare( + # 'bnds_dict', + # ConfigValue( + # domain=dict + # ) + # ) + CONFIG.declare( + 'integer_tol', + ConfigValue( + default=1e-4, + domain=float, + description="Integer tolerance" + ) + ) + CONFIG.declare( + 'feasibility_tol', + ConfigValue( + default=1e-8, + domain=float, + description="Constraint feasibility tolerance", + doc=""" + If the bounds computed on the body of a constraint violate the bounds of + the constraint by more than feasibility_tol, then the constraint is + considered infeasible and an exception is raised. This tolerance is also + used when performing certain interval arithmetic operations to ensure that + none of the feasible region is removed due to floating point arithmetic and + to prevent math domain errors (a larger value is more conservative). + """ + ) + ) + CONFIG.declare( + 'ignore_fixed', + ConfigValue( + default=False, + domain=bool, + description="Whether or not to treat fixed Vars as constants" + ) + ) + + @document_kwargs_from_configdict(CONFIG) + def __init__(self, bnds_dict, **kwds): + self.bnds_dict = bnds_dict + self.config = self.CONFIG(kwds) + print(kwds) + print(self.config.ignore_fixed) + + def walk_expression(self, expr): + with PauseGC(): + _FBBTVisitorLeafToRoot( + self.bnds_dict, integer_tol=self.config.integer_tol, + feasibility_tol=self.config.feasibility_tol, + ignore_fixed=self.config.ignore_fixed).walk_expression(expr) + + class _FBBTVisitorLeafToRoot(StreamBasedExpressionVisitor): """ This walker propagates bounds from the variables to each node in @@ -1252,7 +1309,7 @@ def _fbbt_con(con, config): ---------- con: pyomo.core.base.constraint.Constraint constraint on which to perform fbbt - config: ConfigBlock + config: ConfigDict see documentation for fbbt Returns @@ -1337,7 +1394,7 @@ def _fbbt_block(m, config): Parameters ---------- m: pyomo.core.base.block.Block or pyomo.core.base.PyomoModel.ConcreteModel - config: ConfigBlock + config: ConfigDict See the docs for fbbt Returns @@ -1468,7 +1525,7 @@ def fbbt( A ComponentMap mapping from variables a tuple containing the lower and upper bounds, respectively, computed from FBBT. """ - config = ConfigBlock() + config = ConfigDict() dsc_config = ConfigValue( default=deactivate_satisfied_constraints, domain=In({True, False}) ) diff --git a/pyomo/contrib/fbbt/interval.py b/pyomo/contrib/fbbt/interval.py index 9f784922d19..db036f50f01 100644 --- a/pyomo/contrib/fbbt/interval.py +++ b/pyomo/contrib/fbbt/interval.py @@ -36,14 +36,6 @@ def mul(xl, xu, yl, yu): if i != i: # math.isnan(i) return (-inf, inf) return lb, ub - # options = [xl * yl, xl * yu, xu * yl, xu * yu] - # if any(math.isnan(i) for i in options): - # lb = -inf - # ub = inf - # else: - # lb = min(options) - # ub = max(options) - # return lb, ub def inv(xl, xu, feasibility_tol): @@ -89,8 +81,7 @@ def inv(xl, xu, feasibility_tol): def div(xl, xu, yl, yu, feasibility_tol): - lb, ub = mul(xl, xu, *inv(yl, yu, feasibility_tol)) - return lb, ub + return mul(xl, xu, *inv(yl, yu, feasibility_tol)) def power(xl, xu, yl, yu, feasibility_tol): diff --git a/pyomo/gdp/plugins/bigm.py b/pyomo/gdp/plugins/bigm.py index 6502a5eab0e..be511ef04f3 100644 --- a/pyomo/gdp/plugins/bigm.py +++ b/pyomo/gdp/plugins/bigm.py @@ -175,12 +175,12 @@ def _apply_to(self, instance, **kwds): finally: self._restore_state() self.used_args.clear() - self._fbbt_visitor.ignore_fixed = True + self._fbbt_visitor.config.ignore_fixed = True def _apply_to_impl(self, instance, **kwds): self._process_arguments(instance, **kwds) if self._config.assume_fixed_vars_permanent: - self._fbbt_visitor.ignore_fixed = False + self._fbbt_visitor.config.ignore_fixed = False # filter out inactive targets and handle case where targets aren't # specified. diff --git a/pyomo/gdp/plugins/bigm_mixin.py b/pyomo/gdp/plugins/bigm_mixin.py index 39242db248b..22bd109ef44 100644 --- a/pyomo/gdp/plugins/bigm_mixin.py +++ b/pyomo/gdp/plugins/bigm_mixin.py @@ -11,7 +11,7 @@ from pyomo.gdp import GDP_Error from pyomo.common.collections import ComponentMap, ComponentSet -from pyomo.contrib.fbbt.fbbt import _FBBTVisitorLeafToRoot +from pyomo.contrib.fbbt.fbbt import FBBTVisitorLeafToRoot import pyomo.contrib.fbbt.interval as interval from pyomo.core import Suffix @@ -108,7 +108,7 @@ def _set_up_fbbt_visitor(self): bnds_dict = ComponentMap() # we assume the default config arg for 'assume_fixed_vars_permanent,` # and we will change it during apply_to if we need to - self._fbbt_visitor = _FBBTVisitorLeafToRoot(bnds_dict, ignore_fixed=True) + self._fbbt_visitor = FBBTVisitorLeafToRoot(bnds_dict, ignore_fixed=True) def _process_M_value( self, diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index edc511d089d..42f4c2a6cd7 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -212,11 +212,12 @@ def _apply_to(self, instance, **kwds): self._restore_state() self.used_args.clear() self._arg_list.clear() + self._fbbt_visitor.config.ignore_fixed = True def _apply_to_impl(self, instance, **kwds): self._process_arguments(instance, **kwds) if self._config.assume_fixed_vars_permanent: - self._fbbt_visitor.ignore_fixed = False + self._fbbt_visitor.config.ignore_fixed = False if ( self._config.only_mbigm_bound_constraints From c5b9c139ba942b07db4e37c4cb644fe94c9f3fe9 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 4 Oct 2023 16:03:50 -0600 Subject: [PATCH 0266/1204] Pausing GC for all of bigm transformations --- pyomo/contrib/fbbt/fbbt.py | 58 ------------------------------ pyomo/gdp/plugins/bigm.py | 16 +++++---- pyomo/gdp/plugins/bigm_mixin.py | 4 +-- pyomo/gdp/plugins/multiple_bigm.py | 18 +++++----- 4 files changed, 21 insertions(+), 75 deletions(-) diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index 9ac49b50ba4..a5656e825db 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -31,12 +31,10 @@ from pyomo.common.config import ( ConfigDict, ConfigValue, - document_kwargs_from_configdict, In, NonNegativeFloat, NonNegativeInt, ) -from pyomo.common.gc_manager import PauseGC from pyomo.common.numeric_types import native_types logger = logging.getLogger(__name__) @@ -1062,62 +1060,6 @@ def _register_new_before_child_handler(visitor, child): _before_child_handlers[_type] = _before_constant -class FBBTVisitorLeafToRoot(object): - CONFIG = ConfigDict('fbbt_leaf_to_root') - # CONFIG.declare( - # 'bnds_dict', - # ConfigValue( - # domain=dict - # ) - # ) - CONFIG.declare( - 'integer_tol', - ConfigValue( - default=1e-4, - domain=float, - description="Integer tolerance" - ) - ) - CONFIG.declare( - 'feasibility_tol', - ConfigValue( - default=1e-8, - domain=float, - description="Constraint feasibility tolerance", - doc=""" - If the bounds computed on the body of a constraint violate the bounds of - the constraint by more than feasibility_tol, then the constraint is - considered infeasible and an exception is raised. This tolerance is also - used when performing certain interval arithmetic operations to ensure that - none of the feasible region is removed due to floating point arithmetic and - to prevent math domain errors (a larger value is more conservative). - """ - ) - ) - CONFIG.declare( - 'ignore_fixed', - ConfigValue( - default=False, - domain=bool, - description="Whether or not to treat fixed Vars as constants" - ) - ) - - @document_kwargs_from_configdict(CONFIG) - def __init__(self, bnds_dict, **kwds): - self.bnds_dict = bnds_dict - self.config = self.CONFIG(kwds) - print(kwds) - print(self.config.ignore_fixed) - - def walk_expression(self, expr): - with PauseGC(): - _FBBTVisitorLeafToRoot( - self.bnds_dict, integer_tol=self.config.integer_tol, - feasibility_tol=self.config.feasibility_tol, - ignore_fixed=self.config.ignore_fixed).walk_expression(expr) - - class _FBBTVisitorLeafToRoot(StreamBasedExpressionVisitor): """ This walker propagates bounds from the variables to each node in diff --git a/pyomo/gdp/plugins/bigm.py b/pyomo/gdp/plugins/bigm.py index be511ef04f3..a73846c8aa5 100644 --- a/pyomo/gdp/plugins/bigm.py +++ b/pyomo/gdp/plugins/bigm.py @@ -15,6 +15,7 @@ from pyomo.common.collections import ComponentMap from pyomo.common.config import ConfigDict, ConfigValue +from pyomo.common.gc_manager import PauseGC from pyomo.common.modeling import unique_component_name from pyomo.common.deprecation import deprecated, deprecation_warning from pyomo.contrib.cp.transform.logical_to_disjunctive_program import ( @@ -170,17 +171,18 @@ def _apply_to(self, instance, **kwds): # as a key in bigMargs, I need the error # not to be when I try to put it into # this map! - try: - self._apply_to_impl(instance, **kwds) - finally: - self._restore_state() - self.used_args.clear() - self._fbbt_visitor.config.ignore_fixed = True + with PauseGC(): + try: + self._apply_to_impl(instance, **kwds) + finally: + self._restore_state() + self.used_args.clear() + self._fbbt_visitor.ignore_fixed = True def _apply_to_impl(self, instance, **kwds): self._process_arguments(instance, **kwds) if self._config.assume_fixed_vars_permanent: - self._fbbt_visitor.config.ignore_fixed = False + self._fbbt_visitor.ignore_fixed = False # filter out inactive targets and handle case where targets aren't # specified. diff --git a/pyomo/gdp/plugins/bigm_mixin.py b/pyomo/gdp/plugins/bigm_mixin.py index 22bd109ef44..39242db248b 100644 --- a/pyomo/gdp/plugins/bigm_mixin.py +++ b/pyomo/gdp/plugins/bigm_mixin.py @@ -11,7 +11,7 @@ from pyomo.gdp import GDP_Error from pyomo.common.collections import ComponentMap, ComponentSet -from pyomo.contrib.fbbt.fbbt import FBBTVisitorLeafToRoot +from pyomo.contrib.fbbt.fbbt import _FBBTVisitorLeafToRoot import pyomo.contrib.fbbt.interval as interval from pyomo.core import Suffix @@ -108,7 +108,7 @@ def _set_up_fbbt_visitor(self): bnds_dict = ComponentMap() # we assume the default config arg for 'assume_fixed_vars_permanent,` # and we will change it during apply_to if we need to - self._fbbt_visitor = FBBTVisitorLeafToRoot(bnds_dict, ignore_fixed=True) + self._fbbt_visitor = _FBBTVisitorLeafToRoot(bnds_dict, ignore_fixed=True) def _process_M_value( self, diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index 42f4c2a6cd7..5d76575d514 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -14,6 +14,7 @@ from pyomo.common.collections import ComponentMap from pyomo.common.config import ConfigDict, ConfigValue +from pyomo.common.gc_manager import PauseGC from pyomo.common.modeling import unique_component_name from pyomo.core import ( @@ -206,18 +207,19 @@ def __init__(self): def _apply_to(self, instance, **kwds): self.used_args = ComponentMap() - try: - self._apply_to_impl(instance, **kwds) - finally: - self._restore_state() - self.used_args.clear() - self._arg_list.clear() - self._fbbt_visitor.config.ignore_fixed = True + with PauseGC(): + try: + self._apply_to_impl(instance, **kwds) + finally: + self._restore_state() + self.used_args.clear() + self._arg_list.clear() + self._fbbt_visitor.ignore_fixed = True def _apply_to_impl(self, instance, **kwds): self._process_arguments(instance, **kwds) if self._config.assume_fixed_vars_permanent: - self._fbbt_visitor.config.ignore_fixed = False + self._fbbt_visitor.ignore_fixed = False if ( self._config.only_mbigm_bound_constraints From 71b19c66b7584a4760721c095f7eef083169e51e Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 5 Oct 2023 09:56:25 -0600 Subject: [PATCH 0267/1204] Purging leaf to root handlers of unncessary local vars --- pyomo/contrib/fbbt/fbbt.py | 44 ++++++++++++-------------------------- 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index a5656e825db..30879aea9f4 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -123,10 +123,8 @@ def _prop_bnds_leaf_to_root_DivisionExpression(visitor, node, arg1, arg2): arg2: divisor """ bnds_dict = visitor.bnds_dict - lb1, ub1 = bnds_dict[arg1] - lb2, ub2 = bnds_dict[arg2] bnds_dict[node] = interval.div( - lb1, ub1, lb2, ub2, feasibility_tol=visitor.feasibility_tol + *bnds_dict[arg1], *bnds_dict[arg2], feasibility_tol=visitor.feasibility_tol ) @@ -141,10 +139,8 @@ def _prop_bnds_leaf_to_root_PowExpression(visitor, node, arg1, arg2): arg2: exponent """ bnds_dict = visitor.bnds_dict - lb1, ub1 = bnds_dict[arg1] - lb2, ub2 = bnds_dict[arg2] bnds_dict[node] = interval.power( - lb1, ub1, lb2, ub2, feasibility_tol=visitor.feasibility_tol + *bnds_dict[arg1], *bnds_dict[arg2], feasibility_tol=visitor.feasibility_tol ) @@ -158,8 +154,7 @@ def _prop_bnds_leaf_to_root_NegationExpression(visitor, node, arg): arg: NegationExpression arg """ bnds_dict = visitor.bnds_dict - lb1, ub1 = bnds_dict[arg] - bnds_dict[node] = interval.sub(0, 0, lb1, ub1) + bnds_dict[node] = interval.sub(0, 0, *bnds_dict[arg]) def _prop_bnds_leaf_to_root_exp(visitor, node, arg): @@ -172,8 +167,7 @@ def _prop_bnds_leaf_to_root_exp(visitor, node, arg): arg: UnaryFunctionExpression arg """ bnds_dict = visitor.bnds_dict - lb1, ub1 = bnds_dict[arg] - bnds_dict[node] = interval.exp(lb1, ub1) + bnds_dict[node] = interval.exp(*bnds_dict[arg]) def _prop_bnds_leaf_to_root_log(visitor, node, arg): @@ -186,8 +180,7 @@ def _prop_bnds_leaf_to_root_log(visitor, node, arg): arg: UnaryFunctionExpression arg """ bnds_dict = visitor.bnds_dict - lb1, ub1 = bnds_dict[arg] - bnds_dict[node] = interval.log(lb1, ub1) + bnds_dict[node] = interval.log(*bnds_dict[arg]) def _prop_bnds_leaf_to_root_log10(visitor, node, arg): @@ -200,8 +193,7 @@ def _prop_bnds_leaf_to_root_log10(visitor, node, arg): arg: UnaryFunctionExpression arg """ bnds_dict = visitor.bnds_dict - lb1, ub1 = bnds_dict[arg] - bnds_dict[node] = interval.log10(lb1, ub1) + bnds_dict[node] = interval.log10(*bnds_dict[arg]) def _prop_bnds_leaf_to_root_sin(visitor, node, arg): @@ -214,8 +206,7 @@ def _prop_bnds_leaf_to_root_sin(visitor, node, arg): arg: UnaryFunctionExpression arg """ bnds_dict = visitor.bnds_dict - lb1, ub1 = bnds_dict[arg] - bnds_dict[node] = interval.sin(lb1, ub1) + bnds_dict[node] = interval.sin(*bnds_dict[arg]) def _prop_bnds_leaf_to_root_cos(visitor, node, arg): @@ -228,8 +219,7 @@ def _prop_bnds_leaf_to_root_cos(visitor, node, arg): arg: UnaryFunctionExpression arg """ bnds_dict = visitor.bnds_dict - lb1, ub1 = bnds_dict[arg] - bnds_dict[node] = interval.cos(lb1, ub1) + bnds_dict[node] = interval.cos(*bnds_dict[arg]) def _prop_bnds_leaf_to_root_tan(visitor, node, arg): @@ -242,8 +232,7 @@ def _prop_bnds_leaf_to_root_tan(visitor, node, arg): arg: UnaryFunctionExpression arg """ bnds_dict = visitor.bnds_dict - lb1, ub1 = bnds_dict[arg] - bnds_dict[node] = interval.tan(lb1, ub1) + bnds_dict[node] = interval.tan(*bnds_dict[arg]) def _prop_bnds_leaf_to_root_asin(visitor, node, arg): @@ -256,9 +245,8 @@ def _prop_bnds_leaf_to_root_asin(visitor, node, arg): arg: UnaryFunctionExpression arg """ bnds_dict = visitor.bnds_dict - lb1, ub1 = bnds_dict[arg] bnds_dict[node] = interval.asin( - lb1, ub1, -interval.inf, interval.inf, visitor.feasibility_tol + *bnds_dict[arg], -interval.inf, interval.inf, visitor.feasibility_tol ) @@ -272,9 +260,8 @@ def _prop_bnds_leaf_to_root_acos(visitor, node, arg): arg: UnaryFunctionExpression arg """ bnds_dict = visitor.bnds_dict - lb1, ub1 = bnds_dict[arg] bnds_dict[node] = interval.acos( - lb1, ub1, -interval.inf, interval.inf, visitor.feasibility_tol + *bnds_dict[arg], -interval.inf, interval.inf, visitor.feasibility_tol ) @@ -288,8 +275,7 @@ def _prop_bnds_leaf_to_root_atan(visitor, node, arg): """ bnds_dict = visitor.bnds_dict - lb1, ub1 = bnds_dict[arg] - bnds_dict[node] = interval.atan(lb1, ub1, -interval.inf, interval.inf) + bnds_dict[node] = interval.atan(*bnds_dict[arg], -interval.inf, interval.inf) def _prop_bnds_leaf_to_root_sqrt(visitor, node, arg): @@ -302,16 +288,14 @@ def _prop_bnds_leaf_to_root_sqrt(visitor, node, arg): arg: UnaryFunctionExpression arg """ bnds_dict = visitor.bnds_dict - lb1, ub1 = bnds_dict[arg] bnds_dict[node] = interval.power( - lb1, ub1, 0.5, 0.5, feasibility_tol=visitor.feasibility_tol + *bnds_dict[arg], 0.5, 0.5, feasibility_tol=visitor.feasibility_tol ) def _prop_bnds_leaf_to_root_abs(visitor, node, arg): bnds_dict = visitor.bnds_dict - lb1, ub1 = bnds_dict[arg] - bnds_dict[node] = interval.interval_abs(lb1, ub1) + bnds_dict[node] = interval.interval_abs(*bnds_dict[arg]) def _prop_no_bounds(visitor, node, *args): From 6a83a24f48725029acfaf8483bd1d09a5c0c9e5c Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 5 Oct 2023 13:18:53 -0600 Subject: [PATCH 0268/1204] Caching leaf bounds and other bounds separately --- pyomo/contrib/fbbt/fbbt.py | 62 ++++++++++++++++++++++++------ pyomo/gdp/plugins/bigm.py | 10 +++-- pyomo/gdp/plugins/bigm_mixin.py | 25 +++++++----- pyomo/gdp/plugins/multiple_bigm.py | 10 +++-- 4 files changed, 77 insertions(+), 30 deletions(-) diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index 30879aea9f4..05dc47460c4 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -339,11 +339,18 @@ def _prop_bnds_leaf_to_root_GeneralExpression(visitor, node, expr): expr: GeneralExpression arg """ bnds_dict = visitor.bnds_dict + if node in bnds_dict: + return + elif node in visitor.leaf_bnds_dict: + bnds_dict[node] = visitor.leaf_bnds_dict[node] + return + if expr.__class__ in native_types: expr_lb = expr_ub = expr else: expr_lb, expr_ub = bnds_dict[expr] bnds_dict[node] = (expr_lb, expr_ub) + visitor.leaf_bnds_dict[node] = (expr_lb, expr_ub) _prop_bnds_leaf_to_root_map = defaultdict(lambda: _prop_no_bounds) @@ -980,13 +987,22 @@ def _check_and_reset_bounds(var, lb, ub): def _before_constant(visitor, child): - visitor.bnds_dict[child] = (child, child) + if child in visitor.bnds_dict: + pass + elif child in visitor.leaf_bnds_dict: + visitor.bnds_dict[child] = visitor.leaf_bnds_dict[child] + else: + visitor.bnds_dict[child] = (child, child) + visitor.leaf_bnds_dict[child] = (child, child) return False, None def _before_var(visitor, child): if child in visitor.bnds_dict: return False, None + elif child in visitor.leaf_bnds_dict: + visitor.bnds_dict[child] = visitor.leaf_bnds_dict[child] + return False, None elif child.is_fixed() and not visitor.ignore_fixed: lb = value(child.value) ub = lb @@ -1003,12 +1019,18 @@ def _before_var(visitor, child): 'upper bound: {0}'.format(str(child)) ) visitor.bnds_dict[child] = (lb, ub) + visitor.leaf_bnds_dict[child] = (lb, ub) return False, None def _before_NPV(visitor, child): + if child in visitor.bnds_dict: + return False, None + if child in visitor.leaf_bnds_dict: + visitor.bnds_dict[child] = visitor.leaf_bnds_dict[child] val = value(child) visitor.bnds_dict[child] = (val, val) + visitor.leaf_bnds_dict[child] = (val, val) return False, None @@ -1050,13 +1072,13 @@ class _FBBTVisitorLeafToRoot(StreamBasedExpressionVisitor): the expression tree (all the way to the root node). """ - def __init__( - self, bnds_dict, integer_tol=1e-4, feasibility_tol=1e-8, ignore_fixed=False - ): + def __init__(self, leaf_bnds_dict=None, bnds_dict=None, integer_tol=1e-4, + feasibility_tol=1e-8, ignore_fixed=False ): """ Parameters ---------- - bnds_dict: ComponentMap + leaf_bnds_dict: ComponentMap, if you want to cache leaf-node bounds + bnds_dict: ComponentMap, if you want to cache non-leaf bounds integer_tol: float feasibility_tol: float If the bounds computed on the body of a constraint violate the bounds of @@ -1067,7 +1089,9 @@ def __init__( to prevent math domain errors (a larger value is more conservative). """ super().__init__() - self.bnds_dict = bnds_dict + self.bnds_dict = bnds_dict if bnds_dict is not None else ComponentMap() + self.leaf_bnds_dict = leaf_bnds_dict if leaf_bnds_dict is not None else \ + ComponentMap() self.integer_tol = integer_tol self.feasibility_tol = feasibility_tol self.ignore_fixed = ignore_fixed @@ -1085,6 +1109,13 @@ def exitNode(self, node, data): _prop_bnds_leaf_to_root_map[node.__class__](self, node, *node.args) +# class FBBTVisitorLeafToRoot(_FBBTVisitorLeafToRoot): +# def __init__(self, leaf_bnds_dict, bnds_dict=None, integer_tol=1e-4, +# feasibility_tol=1e-8, ignore_fixed=False): +# if bnds_dict is None: +# bnds_dict = {} + + class _FBBTVisitorRootToLeaf(ExpressionValueVisitor): """ This walker propagates bounds from the constraint back to the @@ -1252,7 +1283,8 @@ def _fbbt_con(con, config): ) # a dictionary to store the bounds of every node in the tree # a walker to propagate bounds from the variables to the root - visitorA = _FBBTVisitorLeafToRoot(bnds_dict, feasibility_tol=config.feasibility_tol) + visitorA = _FBBTVisitorLeafToRoot(bnds_dict=bnds_dict, + feasibility_tol=config.feasibility_tol) visitorA.walk_expression(con.body) # Now we need to replace the bounds in bnds_dict for the root @@ -1489,23 +1521,29 @@ def fbbt( return new_var_bounds -def compute_bounds_on_expr(expr, ignore_fixed=False): +def compute_bounds_on_expr(expr, ignore_fixed=False, leaf_bnds_dict=None): """ - Compute bounds on an expression based on the bounds on the variables in the expression. + Compute bounds on an expression based on the bounds on the variables in + the expression. Parameters ---------- expr: pyomo.core.expr.numeric_expr.NumericExpression + ignore_fixed: bool, treats fixed Vars as constants if False, else treats + them as Vars + leaf_bnds_dict: ComponentMap, caches bounds for Vars, Params, and + Expressions, that could be helpful in future bound + computations on the same model. Returns ------- lb: float ub: float """ - bnds_dict = ComponentMap() - visitor = _FBBTVisitorLeafToRoot(bnds_dict, ignore_fixed=ignore_fixed) + visitor = _FBBTVisitorLeafToRoot(leaf_bnds_dict=leaf_bnds_dict, + ignore_fixed=ignore_fixed) visitor.walk_expression(expr) - lb, ub = bnds_dict[expr] + lb, ub = visitor.bnds_dict[expr] if lb == -interval.inf: lb = None if ub == interval.inf: diff --git a/pyomo/gdp/plugins/bigm.py b/pyomo/gdp/plugins/bigm.py index a73846c8aa5..d9f78a6caa1 100644 --- a/pyomo/gdp/plugins/bigm.py +++ b/pyomo/gdp/plugins/bigm.py @@ -162,7 +162,7 @@ class BigM_Transformation(GDP_to_MIP_Transformation, _BigM_MixIn): def __init__(self): super().__init__(logger) - self._set_up_fbbt_visitor() + #self._set_up_fbbt_visitor() def _apply_to(self, instance, **kwds): self.used_args = ComponentMap() # If everything was sure to go well, @@ -173,16 +173,18 @@ def _apply_to(self, instance, **kwds): # this map! with PauseGC(): try: + self._leaf_bnds_dict = ComponentMap() self._apply_to_impl(instance, **kwds) finally: self._restore_state() self.used_args.clear() - self._fbbt_visitor.ignore_fixed = True + self._leaf_bnds_dict = ComponentMap() + #self._fbbt_visitor.ignore_fixed = True def _apply_to_impl(self, instance, **kwds): self._process_arguments(instance, **kwds) - if self._config.assume_fixed_vars_permanent: - self._fbbt_visitor.ignore_fixed = False + #if self._config.assume_fixed_vars_permanent: + #self._fbbt_visitor.ignore_fixed = False # filter out inactive targets and handle case where targets aren't # specified. diff --git a/pyomo/gdp/plugins/bigm_mixin.py b/pyomo/gdp/plugins/bigm_mixin.py index 39242db248b..ca840f1aeee 100644 --- a/pyomo/gdp/plugins/bigm_mixin.py +++ b/pyomo/gdp/plugins/bigm_mixin.py @@ -11,8 +11,8 @@ from pyomo.gdp import GDP_Error from pyomo.common.collections import ComponentMap, ComponentSet -from pyomo.contrib.fbbt.fbbt import _FBBTVisitorLeafToRoot -import pyomo.contrib.fbbt.interval as interval +from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr#_FBBTVisitorLeafToRoot +#import pyomo.contrib.fbbt.interval as interval from pyomo.core import Suffix @@ -104,11 +104,11 @@ def _get_bigM_arg_list(self, bigm_args, block): block = block.parent_block() return arg_list - def _set_up_fbbt_visitor(self): - bnds_dict = ComponentMap() - # we assume the default config arg for 'assume_fixed_vars_permanent,` - # and we will change it during apply_to if we need to - self._fbbt_visitor = _FBBTVisitorLeafToRoot(bnds_dict, ignore_fixed=True) + # def _set_up_fbbt_visitor(self): + # bnds_dict = ComponentMap() + # # we assume the default config arg for 'assume_fixed_vars_permanent,` + # # and we will change it during apply_to if we need to + # self._fbbt_visitor = _FBBTVisitorLeafToRoot(bnds_dict, ignore_fixed=True) def _process_M_value( self, @@ -217,9 +217,14 @@ def _get_M_from_args(self, constraint, bigMargs, arg_list, lower, upper): return lower, upper def _estimate_M(self, expr, constraint): - self._fbbt_visitor.walk_expression(expr) - expr_lb, expr_ub = self._fbbt_visitor.bnds_dict[expr] - if expr_lb == -interval.inf or expr_ub == interval.inf: + expr_lb, expr_ub = compute_bounds_on_expr( + expr, ignore_fixed=not + self._config.assume_fixed_vars_permanent, + leaf_bnds_dict=self._leaf_bnds_dict) + # self._fbbt_visitor.walk_expression(expr) + # expr_lb, expr_ub = self._fbbt_visitor.bnds_dict[expr] + #if expr_lb == -interval.inf or expr_ub == interval.inf: + if expr_lb is None or expr_ub is None: raise GDP_Error( "Cannot estimate M for unbounded " "expressions.\n\t(found while processing " diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index 5d76575d514..afc362117cb 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -203,23 +203,25 @@ def __init__(self): super().__init__(logger) self.handlers[Suffix] = self._warn_for_active_suffix self._arg_list = {} - self._set_up_fbbt_visitor() + #self._set_up_fbbt_visitor() def _apply_to(self, instance, **kwds): self.used_args = ComponentMap() with PauseGC(): try: + self._leaf_bnds_dict = ComponentMap() self._apply_to_impl(instance, **kwds) finally: self._restore_state() self.used_args.clear() self._arg_list.clear() - self._fbbt_visitor.ignore_fixed = True + self._leaf_bnds_dict = ComponentMap() + #self._fbbt_visitor.ignore_fixed = True def _apply_to_impl(self, instance, **kwds): self._process_arguments(instance, **kwds) - if self._config.assume_fixed_vars_permanent: - self._fbbt_visitor.ignore_fixed = False + # if self._config.assume_fixed_vars_permanent: + # self._fbbt_visitor.ignore_fixed = False if ( self._config.only_mbigm_bound_constraints From 8fdc42b0c4860b5f5b1f92ddb28c063e5242d714 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 5 Oct 2023 13:42:26 -0600 Subject: [PATCH 0269/1204] Having walk_expression manage the caching situation, going back to the bigm transformations only building on visitor, and caching the leaf bounds --- pyomo/contrib/fbbt/fbbt.py | 42 ++++++++++++++++++------------ pyomo/gdp/plugins/bigm.py | 8 +++--- pyomo/gdp/plugins/bigm_mixin.py | 25 +++++++----------- pyomo/gdp/plugins/multiple_bigm.py | 8 +++--- 4 files changed, 44 insertions(+), 39 deletions(-) diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index 05dc47460c4..46acf833f23 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -1072,8 +1072,8 @@ class _FBBTVisitorLeafToRoot(StreamBasedExpressionVisitor): the expression tree (all the way to the root node). """ - def __init__(self, leaf_bnds_dict=None, bnds_dict=None, integer_tol=1e-4, - feasibility_tol=1e-8, ignore_fixed=False ): + def __init__(self, integer_tol=1e-4, feasibility_tol=1e-8, + ignore_fixed=False ): """ Parameters ---------- @@ -1089,9 +1089,9 @@ def __init__(self, leaf_bnds_dict=None, bnds_dict=None, integer_tol=1e-4, to prevent math domain errors (a larger value is more conservative). """ super().__init__() - self.bnds_dict = bnds_dict if bnds_dict is not None else ComponentMap() - self.leaf_bnds_dict = leaf_bnds_dict if leaf_bnds_dict is not None else \ - ComponentMap() + # self.bnds_dict = bnds_dict if bnds_dict is not None else ComponentMap() + # self.leaf_bnds_dict = leaf_bnds_dict if leaf_bnds_dict is not None else \ + # ComponentMap() self.integer_tol = integer_tol self.feasibility_tol = feasibility_tol self.ignore_fixed = ignore_fixed @@ -1108,6 +1108,20 @@ def beforeChild(self, node, child, child_idx): def exitNode(self, node, data): _prop_bnds_leaf_to_root_map[node.__class__](self, node, *node.args) + def walk_expression(self, expr, bnds_dict=None, leaf_bnds_dict=None): + try: + self.bnds_dict = bnds_dict if bnds_dict is not None else ComponentMap() + self.leaf_bnds_dict = leaf_bnds_dict if leaf_bnds_dict is not None else \ + ComponentMap() + super().walk_expression(expr) + result = self.bnds_dict[expr] + finally: + if bnds_dict is None: + self.bnds_dict.clear() + if leaf_bnds_dict is None: + self.leaf_bnds_dict.clear() + return result + # class FBBTVisitorLeafToRoot(_FBBTVisitorLeafToRoot): # def __init__(self, leaf_bnds_dict, bnds_dict=None, integer_tol=1e-4, @@ -1283,9 +1297,8 @@ def _fbbt_con(con, config): ) # a dictionary to store the bounds of every node in the tree # a walker to propagate bounds from the variables to the root - visitorA = _FBBTVisitorLeafToRoot(bnds_dict=bnds_dict, - feasibility_tol=config.feasibility_tol) - visitorA.walk_expression(con.body) + visitorA = _FBBTVisitorLeafToRoot(feasibility_tol=config.feasibility_tol) + visitorA.walk_expression(con.body, bnds_dict=bnds_dict) # Now we need to replace the bounds in bnds_dict for the root # node with the bounds on the constraint (if those bounds are @@ -1521,7 +1534,7 @@ def fbbt( return new_var_bounds -def compute_bounds_on_expr(expr, ignore_fixed=False, leaf_bnds_dict=None): +def compute_bounds_on_expr(expr, ignore_fixed=False): """ Compute bounds on an expression based on the bounds on the variables in the expression. @@ -1531,19 +1544,16 @@ def compute_bounds_on_expr(expr, ignore_fixed=False, leaf_bnds_dict=None): expr: pyomo.core.expr.numeric_expr.NumericExpression ignore_fixed: bool, treats fixed Vars as constants if False, else treats them as Vars - leaf_bnds_dict: ComponentMap, caches bounds for Vars, Params, and - Expressions, that could be helpful in future bound - computations on the same model. Returns ------- lb: float ub: float """ - visitor = _FBBTVisitorLeafToRoot(leaf_bnds_dict=leaf_bnds_dict, - ignore_fixed=ignore_fixed) - visitor.walk_expression(expr) - lb, ub = visitor.bnds_dict[expr] + bnds_dict = ComponentMap() + visitor = _FBBTVisitorLeafToRoot(ignore_fixed=ignore_fixed) + visitor.walk_expression(expr, bnds_dict=bnds_dict) + lb, ub = bnds_dict[expr] if lb == -interval.inf: lb = None if ub == interval.inf: diff --git a/pyomo/gdp/plugins/bigm.py b/pyomo/gdp/plugins/bigm.py index d9f78a6caa1..7fd683c9f47 100644 --- a/pyomo/gdp/plugins/bigm.py +++ b/pyomo/gdp/plugins/bigm.py @@ -162,7 +162,7 @@ class BigM_Transformation(GDP_to_MIP_Transformation, _BigM_MixIn): def __init__(self): super().__init__(logger) - #self._set_up_fbbt_visitor() + self._set_up_fbbt_visitor() def _apply_to(self, instance, **kwds): self.used_args = ComponentMap() # If everything was sure to go well, @@ -179,12 +179,12 @@ def _apply_to(self, instance, **kwds): self._restore_state() self.used_args.clear() self._leaf_bnds_dict = ComponentMap() - #self._fbbt_visitor.ignore_fixed = True + self._fbbt_visitor.ignore_fixed = True def _apply_to_impl(self, instance, **kwds): self._process_arguments(instance, **kwds) - #if self._config.assume_fixed_vars_permanent: - #self._fbbt_visitor.ignore_fixed = False + if self._config.assume_fixed_vars_permanent: + self._fbbt_visitor.ignore_fixed = False # filter out inactive targets and handle case where targets aren't # specified. diff --git a/pyomo/gdp/plugins/bigm_mixin.py b/pyomo/gdp/plugins/bigm_mixin.py index ca840f1aeee..5a306832b09 100644 --- a/pyomo/gdp/plugins/bigm_mixin.py +++ b/pyomo/gdp/plugins/bigm_mixin.py @@ -11,8 +11,8 @@ from pyomo.gdp import GDP_Error from pyomo.common.collections import ComponentMap, ComponentSet -from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr#_FBBTVisitorLeafToRoot -#import pyomo.contrib.fbbt.interval as interval +from pyomo.contrib.fbbt.fbbt import _FBBTVisitorLeafToRoot +import pyomo.contrib.fbbt.interval as interval from pyomo.core import Suffix @@ -104,11 +104,11 @@ def _get_bigM_arg_list(self, bigm_args, block): block = block.parent_block() return arg_list - # def _set_up_fbbt_visitor(self): - # bnds_dict = ComponentMap() - # # we assume the default config arg for 'assume_fixed_vars_permanent,` - # # and we will change it during apply_to if we need to - # self._fbbt_visitor = _FBBTVisitorLeafToRoot(bnds_dict, ignore_fixed=True) + def _set_up_fbbt_visitor(self): + #bnds_dict = ComponentMap() + # we assume the default config arg for 'assume_fixed_vars_permanent,` + # and we will change it during apply_to if we need to + self._fbbt_visitor = _FBBTVisitorLeafToRoot(ignore_fixed=True) def _process_M_value( self, @@ -217,14 +217,9 @@ def _get_M_from_args(self, constraint, bigMargs, arg_list, lower, upper): return lower, upper def _estimate_M(self, expr, constraint): - expr_lb, expr_ub = compute_bounds_on_expr( - expr, ignore_fixed=not - self._config.assume_fixed_vars_permanent, - leaf_bnds_dict=self._leaf_bnds_dict) - # self._fbbt_visitor.walk_expression(expr) - # expr_lb, expr_ub = self._fbbt_visitor.bnds_dict[expr] - #if expr_lb == -interval.inf or expr_ub == interval.inf: - if expr_lb is None or expr_ub is None: + expr_lb, expr_ub = self._fbbt_visitor.walk_expression( + expr, leaf_bnds_dict=self._leaf_bnds_dict) + if expr_lb == -interval.inf or expr_ub == interval.inf: raise GDP_Error( "Cannot estimate M for unbounded " "expressions.\n\t(found while processing " diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index afc362117cb..1e23121602b 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -203,7 +203,7 @@ def __init__(self): super().__init__(logger) self.handlers[Suffix] = self._warn_for_active_suffix self._arg_list = {} - #self._set_up_fbbt_visitor() + self._set_up_fbbt_visitor() def _apply_to(self, instance, **kwds): self.used_args = ComponentMap() @@ -216,12 +216,12 @@ def _apply_to(self, instance, **kwds): self.used_args.clear() self._arg_list.clear() self._leaf_bnds_dict = ComponentMap() - #self._fbbt_visitor.ignore_fixed = True + self._fbbt_visitor.ignore_fixed = True def _apply_to_impl(self, instance, **kwds): self._process_arguments(instance, **kwds) - # if self._config.assume_fixed_vars_permanent: - # self._fbbt_visitor.ignore_fixed = False + if self._config.assume_fixed_vars_permanent: + self._fbbt_visitor.ignore_fixed = False if ( self._config.only_mbigm_bound_constraints From 2ae56fb4f1ab4b25c6dea237d74e594933cbded6 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 6 Oct 2023 11:00:53 -0600 Subject: [PATCH 0270/1204] fixing a typo in before NPV visitor --- pyomo/contrib/fbbt/fbbt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index 46acf833f23..7f192c4a0b9 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -1028,6 +1028,7 @@ def _before_NPV(visitor, child): return False, None if child in visitor.leaf_bnds_dict: visitor.bnds_dict[child] = visitor.leaf_bnds_dict[child] + return False, None val = value(child) visitor.bnds_dict[child] = (val, val) visitor.leaf_bnds_dict[child] = (val, val) From 94de29a0c35145b9544b3310b47a63125c9a7ab4 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Mon, 9 Oct 2023 10:11:50 -0600 Subject: [PATCH 0271/1204] Adding new expression bounds walker, and it passes a test --- .../contrib/fbbt/expression_bounds_walker.py | 243 ++++++++++++++++++ pyomo/contrib/fbbt/fbbt.py | 7 +- pyomo/contrib/fbbt/tests/test_fbbt.py | 1 + 3 files changed, 247 insertions(+), 4 deletions(-) create mode 100644 pyomo/contrib/fbbt/expression_bounds_walker.py diff --git a/pyomo/contrib/fbbt/expression_bounds_walker.py b/pyomo/contrib/fbbt/expression_bounds_walker.py new file mode 100644 index 00000000000..a6373108d51 --- /dev/null +++ b/pyomo/contrib/fbbt/expression_bounds_walker.py @@ -0,0 +1,243 @@ +# ___________________________________________________________________________ +# +# 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 collections import defaultdict +from pyomo.common.collections import ComponentMap +from pyomo.contrib.fbbt.interval import ( + add, acos, asin, atan, cos, div, exp, interval_abs, log, + log10, mul, power, sin, sub, tan, +) +from pyomo.core.base.expression import _GeneralExpressionData, ScalarExpression +from pyomo.core.expr.numeric_expr import ( + NegationExpression, + ProductExpression, + DivisionExpression, + PowExpression, + AbsExpression, + UnaryFunctionExpression, + MonomialTermExpression, + LinearExpression, + SumExpression, + ExternalFunctionExpression, +) +from pyomo.core.expr.numvalue import native_numeric_types, native_types, value +from pyomo.core.expr.visitor import StreamBasedExpressionVisitor + +inf = float('inf') + + +def _before_external_function(visitor, child): + # [ESJ 10/6/23]: If external functions ever implement callbacks to help with + # this then this should use them + return False, (-inf, inf) + + +def _before_var(visitor, child): + leaf_bounds = visitor.leaf_bounds + if child in leaf_bounds: + pass + elif child.is_fixed() and visitor.use_fixed_var_values_as_bounds: + val = child.value + if val is None: + raise ValueError( + "Var '%s' is fixed to None. This value cannot be used to " + "calculate bounds." % child.name) + leaf_bounds[child] = (child.value, child.value) + else: + lb = value(child.lb) + ub = value(child.ub) + if lb is None: + lb = -inf + if ub is None: + ub = inf + leaf_bounds[child] = (lb, ub) + return False, leaf_bounds[child] + + +def _before_named_expression(visitor, child): + leaf_bounds = visitor.leaf_bounds + if child in leaf_bounds: + return False, leaf_bounds[child] + else: + return True, None + + +def _before_param(visitor, child): + return False, (child.value, child.value) + + +def _before_constant(visitor, child): + return False, (child, child) + + +def _before_other(visitor, child): + return True, None + + +def _register_new_before_child_handler(visitor, child): + handlers = _before_child_handlers + child_type = child.__class__ + if child_type in native_numeric_types: + handlers[child_type] = _before_constant + elif child_type in native_types: + pass + # TODO: catch this, it's bad. + elif not child.is_expression_type(): + if child.is_potentially_variable(): + handlers[child_type] = _before_var + else: + handlers[child_type] = _before_param + elif issubclass(child_type, _GeneralExpressionData): + handlers[child_type] = _before_named_expression + else: + handlers[child_type] = _before_other + return handlers[child_type](visitor, child) + + +_before_child_handlers = defaultdict(lambda: _register_new_before_child_handler) +_before_child_handlers[ExternalFunctionExpression] = _before_external_function + + +def _handle_ProductExpression(visitor, node, arg1, arg2): + return mul(*arg1, *arg2) + + +def _handle_SumExpression(visitor, node, *args): + bnds = (0, 0) + for arg in args: + bnds = add(*bnds, *arg) + return bnds + + +def _handle_DivisionExpression(visitor, node, arg1, arg2): + return div(*arg1, *arg2, feasibility_tol=visitor.feasibility_tol) + + +def _handle_PowExpression(visitor, node, arg1, arg2): + return power(*arg1, *arg2, feasibility_tol=visitor.feasibility_tol) + + +def _handle_NegationExpression(visitor, node, arg): + return sub(0, 0, *arg) + + +def _handle_exp(visitor, node, arg): + return exp(*arg) + + +def _handle_log(visitor, node, arg): + return log(*arg) + + +def _handle_log10(visitor, node, arg): + return log10(*arg) + + +def _handle_sin(visitor, node, arg): + return sin(*arg) + + +def _handle_cos(visitor, node, arg): + return cos(*arg) + + +def _handle_tan(visitor, node, arg): + return tan(*arg) + + +def _handle_asin(visitor, node, arg): + return asin(*arg) + + +def _handle_acos(visitor, node, arg): + return acos(*arg) + + +def _handle_atan(visitor, node, arg): + return atan(*arg) + + +def _handle_sqrt(visitor, node, arg): + return power(*arg, 0.5, 0.5, feasibility_tol=visitor.feasibility_tol) + + +def _handle_abs(visitor, node, arg): + return interval_abs(*arg) + + +def _handle_no_bounds(visitor, node, *args): + return (-inf, inf) + + +def _handle_UnaryFunctionExpression(visitor, node, arg): + return _unary_function_dispatcher[node.getname()](visitor, node, arg) + + +def _handle_named_expression(visitor, node, arg): + visitor.leaf_bounds[node] = arg + return arg + + +_unary_function_dispatcher = { + 'exp': _handle_exp, + 'log': _handle_log, + 'log10': _handle_log10, + 'sin': _handle_sin, + 'cos': _handle_cos, + 'tan': _handle_tan, + 'asin': _handle_asin, + 'acos': _handle_acos, + 'atan': _handle_atan, + 'sqrt': _handle_sqrt, + 'abs': _handle_abs, +} + +_operator_dispatcher = defaultdict( + lambda: _handle_no_bounds, { + ProductExpression: _handle_ProductExpression, + DivisionExpression: _handle_DivisionExpression, + PowExpression: _handle_PowExpression, + SumExpression: _handle_SumExpression, + MonomialTermExpression: _handle_ProductExpression, + NegationExpression: _handle_NegationExpression, + UnaryFunctionExpression: _handle_UnaryFunctionExpression, + LinearExpression: _handle_SumExpression, + _GeneralExpressionData: _handle_named_expression, + ScalarExpression: _handle_named_expression, + } +) + +class ExpressionBoundsVisitor(StreamBasedExpressionVisitor): + """ + Walker to calculate bounds on an expression, from leaf to root, with + caching of terminal node bounds (Vars and Expressions) + """ + def __init__(self, leaf_bounds=None, feasibility_tol=1e-8, + use_fixed_var_values_as_bounds=False): + super().__init__() + self.leaf_bounds = leaf_bounds if leaf_bounds is not None \ + else ComponentMap() + self.feasibility_tol = feasibility_tol + self.use_fixed_var_values_as_bounds = use_fixed_var_values_as_bounds + + def initializeWalker(self, expr): + print(expr) + print(self.beforeChild(None, expr, 0)) + walk, result = self.beforeChild(None, expr, 0) + if not walk: + return False, result + return True, expr + + def beforeChild(self, node, child, child_idx): + return _before_child_handlers[child.__class__](self, child) + + def exitNode(self, node, data): + return _operator_dispatcher[node.__class__](self, node, *data) diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index 7f192c4a0b9..4fbad47c427 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -11,6 +11,7 @@ from collections import defaultdict from pyomo.common.collections import ComponentMap, ComponentSet +from pyomo.contrib.fbbt.expression_bounds_walker import ExpressionBoundsVisitor import pyomo.core.expr.numeric_expr as numeric_expr from pyomo.core.expr.visitor import ( ExpressionValueVisitor, @@ -1551,10 +1552,8 @@ def compute_bounds_on_expr(expr, ignore_fixed=False): lb: float ub: float """ - bnds_dict = ComponentMap() - visitor = _FBBTVisitorLeafToRoot(ignore_fixed=ignore_fixed) - visitor.walk_expression(expr, bnds_dict=bnds_dict) - lb, ub = bnds_dict[expr] + lb, ub = ExpressionBoundsVisitor( + use_fixed_var_values_as_bounds=not ignore_fixed).walk_expression(expr) if lb == -interval.inf: lb = None if ub == interval.inf: diff --git a/pyomo/contrib/fbbt/tests/test_fbbt.py b/pyomo/contrib/fbbt/tests/test_fbbt.py index 5e8d656eeab..7fa17bfbb9a 100644 --- a/pyomo/contrib/fbbt/tests/test_fbbt.py +++ b/pyomo/contrib/fbbt/tests/test_fbbt.py @@ -1110,6 +1110,7 @@ def test_compute_expr_bounds(self): m.y = pyo.Var(bounds=(-1, 1)) e = m.x + m.y lb, ub = compute_bounds_on_expr(e) + print(lb, ub) self.assertAlmostEqual(lb, -2, 14) self.assertAlmostEqual(ub, 2, 14) From f45d417e1c1644ecad2a01848ef7ca9f6ac5a39e Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Mon, 9 Oct 2023 14:09:01 -0600 Subject: [PATCH 0272/1204] Moving bigm transformations onto new expression bounds visitor --- pyomo/gdp/plugins/bigm.py | 6 +++--- pyomo/gdp/plugins/bigm_mixin.py | 10 +++++----- pyomo/gdp/plugins/multiple_bigm.py | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyomo/gdp/plugins/bigm.py b/pyomo/gdp/plugins/bigm.py index 8ca4efd4be8..cbc03bafb18 100644 --- a/pyomo/gdp/plugins/bigm.py +++ b/pyomo/gdp/plugins/bigm.py @@ -162,7 +162,7 @@ class BigM_Transformation(GDP_to_MIP_Transformation, _BigM_MixIn): def __init__(self): super().__init__(logger) - self._set_up_fbbt_visitor() + self._set_up_expr_bound_visitor() def _apply_to(self, instance, **kwds): self.used_args = ComponentMap() # If everything was sure to go well, @@ -179,12 +179,12 @@ def _apply_to(self, instance, **kwds): self._restore_state() self.used_args.clear() self._leaf_bnds_dict = ComponentMap() - self._fbbt_visitor.ignore_fixed = True + self._expr_bound_visitor.use_fixed_var_values_as_bounds = False def _apply_to_impl(self, instance, **kwds): self._process_arguments(instance, **kwds) if self._config.assume_fixed_vars_permanent: - self._fbbt_visitor.ignore_fixed = False + self._expr_bound_visitor.use_fixed_var_values_as_bounds = True # filter out inactive targets and handle case where targets aren't # specified. diff --git a/pyomo/gdp/plugins/bigm_mixin.py b/pyomo/gdp/plugins/bigm_mixin.py index 5a306832b09..5209dad0860 100644 --- a/pyomo/gdp/plugins/bigm_mixin.py +++ b/pyomo/gdp/plugins/bigm_mixin.py @@ -11,7 +11,7 @@ from pyomo.gdp import GDP_Error from pyomo.common.collections import ComponentMap, ComponentSet -from pyomo.contrib.fbbt.fbbt import _FBBTVisitorLeafToRoot +from pyomo.contrib.fbbt.expression_bounds_walker import ExpressionBoundsVisitor import pyomo.contrib.fbbt.interval as interval from pyomo.core import Suffix @@ -104,11 +104,12 @@ def _get_bigM_arg_list(self, bigm_args, block): block = block.parent_block() return arg_list - def _set_up_fbbt_visitor(self): + def _set_up_expr_bound_visitor(self): #bnds_dict = ComponentMap() # we assume the default config arg for 'assume_fixed_vars_permanent,` # and we will change it during apply_to if we need to - self._fbbt_visitor = _FBBTVisitorLeafToRoot(ignore_fixed=True) + self._expr_bound_visitor = ExpressionBoundsVisitor( + use_fixed_var_values_as_bounds=False) def _process_M_value( self, @@ -217,8 +218,7 @@ def _get_M_from_args(self, constraint, bigMargs, arg_list, lower, upper): return lower, upper def _estimate_M(self, expr, constraint): - expr_lb, expr_ub = self._fbbt_visitor.walk_expression( - expr, leaf_bnds_dict=self._leaf_bnds_dict) + expr_lb, expr_ub = self._expr_bound_visitor.walk_expression(expr) if expr_lb == -interval.inf or expr_ub == interval.inf: raise GDP_Error( "Cannot estimate M for unbounded " diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index 7641ddd4e83..ca6a01cee52 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -203,7 +203,7 @@ def __init__(self): super().__init__(logger) self.handlers[Suffix] = self._warn_for_active_suffix self._arg_list = {} - self._set_up_fbbt_visitor() + self._set_up_expr_bound_visitor() def _apply_to(self, instance, **kwds): self.used_args = ComponentMap() @@ -216,12 +216,12 @@ def _apply_to(self, instance, **kwds): self.used_args.clear() self._arg_list.clear() self._leaf_bnds_dict = ComponentMap() - self._fbbt_visitor.ignore_fixed = True + self._expr_bound_visitor.use_fixed_var_values_as_bounds = False def _apply_to_impl(self, instance, **kwds): self._process_arguments(instance, **kwds) if self._config.assume_fixed_vars_permanent: - self._fbbt_visitor.ignore_fixed = False + self._bound_visitor.use_fixed_var_values_as_bounds = True if ( self._config.only_mbigm_bound_constraints From 091ab3223495227c6cc6ef76c623371cb8066403 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Mon, 9 Oct 2023 14:15:41 -0600 Subject: [PATCH 0273/1204] Removing some debugging --- pyomo/contrib/fbbt/expression_bounds_walker.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyomo/contrib/fbbt/expression_bounds_walker.py b/pyomo/contrib/fbbt/expression_bounds_walker.py index a6373108d51..fb55a779017 100644 --- a/pyomo/contrib/fbbt/expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/expression_bounds_walker.py @@ -229,8 +229,6 @@ def __init__(self, leaf_bounds=None, feasibility_tol=1e-8, self.use_fixed_var_values_as_bounds = use_fixed_var_values_as_bounds def initializeWalker(self, expr): - print(expr) - print(self.beforeChild(None, expr, 0)) walk, result = self.beforeChild(None, expr, 0) if not walk: return False, result From 816e3d66492da945e01bad7a906810b400e90f71 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 9 Oct 2023 18:16:09 -0400 Subject: [PATCH 0274/1204] handle appsi solver unbounded situation --- pyomo/contrib/mindtpy/algorithm_base_class.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 0eb602bdf7e..89575f5c4f2 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -1644,7 +1644,8 @@ def solve_main(self): "No-good cuts are added and GOA algorithm doesn't converge within the time limit. " 'No integer solution is found, so the CPLEX solver will report an error status. ' ) - return None, None + # Value error will be raised if the MIP problem is unbounded and appsi solver is used when loading solutions. Although the problem is unbounded, a valid result is provided and we do not return None to let the algorithm continue. + return self.mip, main_mip_results if config.solution_pool: main_mip_results._solver_model = self.mip_opt._solver_model main_mip_results._pyomo_var_to_solver_var_map = ( From a523eea90f835b79cc62c1a63da1a2382733da7a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 10 Oct 2023 12:08:13 -0600 Subject: [PATCH 0275/1204] Update tests to include 3.12; update wheel builder workflow --- .github/workflows/release_wheel_creation.yml | 128 ++++--------------- .github/workflows/test_branches.yml | 10 +- .github/workflows/test_pr_and_main.yml | 10 +- 3 files changed, 34 insertions(+), 114 deletions(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index da183eebfe2..314eb115ae4 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -18,49 +18,33 @@ env: PYOMO_SETUP_ARGS: --with-distributable-extensions jobs: - manylinux: - name: ${{ matrix.TARGET }}/${{ matrix.wheel-version }}_wheel_creation + bdist_wheel: + name: Build wheels (3.8+) on ${{ matrix.os }} for ${{ matrix.arch }} runs-on: ${{ matrix.os }} strategy: - fail-fast: false matrix: - wheel-version: ['cp38-cp38', 'cp39-cp39', 'cp310-cp310', 'cp311-cp311'] - os: [ubuntu-latest] + os: [ubuntu-20.04, windows-latest, macos-latest] + arch: [auto] include: - - os: ubuntu-latest - TARGET: manylinux - python-version: [3.8] + - os: ubuntu-20.04 + arch: aarch64 + - os: macos-latest + arch: arm64 steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install twine wheel setuptools pybind11 - # TODO: Update the manylinux builder to next tagged release - - name: Build manylinux Python wheels - uses: RalfG/python-wheels-manylinux-build@a1e012c58ed3960f81b7ed2759a037fb0ad28e2d - with: - python-versions: ${{ matrix.wheel-version }} - build-requirements: 'cython pybind11' - package-path: '' - pip-wheel-args: '' - # When locally testing, --no-deps flag is necessary (PyUtilib dependency will trigger an error otherwise) - - name: Consolidate wheels - run: | - sudo test -d dist || mkdir -v dist - sudo find . -name \*.whl | grep -v /dist/ | xargs -n1 -i mv -v "{}" dist/ - - name: Delete linux wheels - run: | - sudo rm -rfv dist/*-linux_x86_64.whl - - name: Upload artifact - uses: actions/upload-artifact@v3 - with: - name: manylinux-wheels - path: dist + - uses: actions/checkout@v4 + - name: Build wheels + uses: pypa/cibuildwheel@v2.16.2 + with: + output-dir: dist + env: + WRAPT_INSTALL_EXTENSIONS: true + CIBW_SKIP: "pp* cp36* cp37*" + CIBW_BUILD_VERBOSITY: 1 + CIBW_ARCHS: ${{ matrix.arch }} + - uses: actions/upload-artifact@v3 + with: + name: wheels + path: dist/*.whl generictarball: name: ${{ matrix.TARGET }} @@ -74,9 +58,9 @@ jobs: TARGET: generic_tarball python-version: [3.8] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -92,67 +76,3 @@ jobs: name: generictarball path: dist - osx: - name: ${{ matrix.TARGET }}py${{ matrix.python-version }}/wheel_creation - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [macos-latest] - include: - - os: macos-latest - TARGET: osx - python-version: [ 3.8, 3.9, '3.10', '3.11' ] - steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install twine wheel setuptools cython pybind11 - - name: Build OSX Python wheels - run: | - python setup.py --with-cython --with-distributable-extensions sdist --format=gztar bdist_wheel - - - name: Upload artifact - uses: actions/upload-artifact@v3 - with: - name: osx-wheels - path: dist - - windows: - name: ${{ matrix.TARGET }}py${{ matrix.python-version }}/wheel_creation - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [windows-latest] - include: - - os: windows-latest - TARGET: win - python-version: [ 3.8, 3.9, '3.10', '3.11' ] - steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - shell: pwsh - run: | - $env:PYTHONWARNINGS="ignore::UserWarning" - Invoke-Expression "python -m pip install --upgrade pip" - Invoke-Expression "pip install setuptools twine wheel cython pybind11" - - name: Build Windows Python wheels - shell: pwsh - run: | - $env:PYTHONWARNINGS="ignore::UserWarning" - Invoke-Expression "python setup.py --with-cython --with-distributable-extensions sdist --format=gztar bdist_wheel" - - name: Upload artifact - uses: actions/upload-artifact@v3 - with: - name: win-wheels - path: dist diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index ab91bf86ca1..ebbc4d6e165 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Pyomo source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: @@ -62,7 +62,7 @@ jobs: include: - os: ubuntu-latest - python: '3.11' + python: '3.12' TARGET: linux PYENV: pip @@ -112,7 +112,7 @@ jobs: steps: - name: Checkout Pyomo source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure job parameters run: | @@ -657,7 +657,7 @@ jobs: timeout-minutes: 10 steps: - name: Checkout Pyomo source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python 3.8 uses: actions/setup-python@v4 @@ -712,7 +712,7 @@ jobs: steps: - name: Checkout Pyomo source - uses: actions/checkout@v3 + uses: actions/checkout@v4 # We need the source for .codecov.yml and running "coverage xml" #- name: Pip package cache diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 5c27474d9d9..bc43ffefa74 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Pyomo source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: @@ -60,7 +60,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python: [ 3.8, 3.9, '3.10', '3.11' ] + python: [ 3.8, 3.9, '3.10', '3.11', '3.12' ] other: [""] category: [""] @@ -142,7 +142,7 @@ jobs: steps: - name: Checkout Pyomo source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure job parameters run: | @@ -688,7 +688,7 @@ jobs: timeout-minutes: 10 steps: - name: Checkout Pyomo source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python 3.8 uses: actions/setup-python@v4 @@ -743,7 +743,7 @@ jobs: steps: - name: Checkout Pyomo source - uses: actions/checkout@v3 + uses: actions/checkout@v4 # We need the source for .codecov.yml and running "coverage xml" #- name: Pip package cache From b9486ec60f1da0d7e4fa461e038206c38d134546 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 10 Oct 2023 12:09:44 -0600 Subject: [PATCH 0276/1204] Remove unnecessary env var --- .github/workflows/release_wheel_creation.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index 314eb115ae4..b1814af55cb 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -37,7 +37,6 @@ jobs: with: output-dir: dist env: - WRAPT_INSTALL_EXTENSIONS: true CIBW_SKIP: "pp* cp36* cp37*" CIBW_BUILD_VERBOSITY: 1 CIBW_ARCHS: ${{ matrix.arch }} From 2b3e191d32e23d8b8278978fe892e22cdfe15142 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 10 Oct 2023 12:11:25 -0600 Subject: [PATCH 0277/1204] Update branch tests --- .github/workflows/test_branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index ebbc4d6e165..9fea047122c 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -56,7 +56,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python: ['3.11'] + python: ['3.12'] other: [""] category: [""] From 2b43da4031d97ce05c05f8c7ba93eaa0256bcf9d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 10 Oct 2023 12:21:02 -0600 Subject: [PATCH 0278/1204] Pass extra config settings --- .github/workflows/release_wheel_creation.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index b1814af55cb..3afdf9d8e03 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -15,7 +15,7 @@ concurrency: cancel-in-progress: true env: - PYOMO_SETUP_ARGS: --with-distributable-extensions + PYOMO_SETUP_ARGS: "--with-cython --with-distributable-extensions" jobs: bdist_wheel: @@ -40,6 +40,7 @@ jobs: CIBW_SKIP: "pp* cp36* cp37*" CIBW_BUILD_VERBOSITY: 1 CIBW_ARCHS: ${{ matrix.arch }} + CIBW_CONFIG_SETTINGS: $PYOMO_SETUP_ARGS - uses: actions/upload-artifact@v3 with: name: wheels From 706d27c8deea5812bb77f19436421bb157d26db7 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 10 Oct 2023 13:10:55 -0600 Subject: [PATCH 0279/1204] Clarify docstring / exception messages --- pyomo/common/dependencies.py | 6 +++ pyomo/common/numeric_types.py | 96 +++++++++++++++++++++++++---------- pyomo/repn/tests/test_util.py | 2 +- pyomo/repn/util.py | 4 +- 4 files changed, 78 insertions(+), 30 deletions(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 9f29d211232..55a21916299 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -801,6 +801,9 @@ def _finalize_numpy(np, available): # finally remove all support for it numeric_types._native_boolean_types.add(t) _floats = [np.float_, np.float16, np.float32, np.float64] + # float96 and float128 may or may not be defined in this particular + # numpy build (it depends on platform and version). + # Register them only if they are present if hasattr(np, 'float96'): _floats.append(np.float96) if hasattr(np, 'float128'): @@ -812,6 +815,9 @@ def _finalize_numpy(np, available): # finally remove all support for it numeric_types._native_boolean_types.add(t) _complex = [np.complex_, np.complex64, np.complex128] + # complex192 and complex256 may or may not be defined in this + # particular numpy build (it depends ono platform and version). + # Register them only if they are present if hasattr(np, 'complex192'): _complex.append(np.complex192) if hasattr(np, 'complex256'): diff --git a/pyomo/common/numeric_types.py b/pyomo/common/numeric_types.py index 7b23bdf716f..f822275a907 100644 --- a/pyomo/common/numeric_types.py +++ b/pyomo/common/numeric_types.py @@ -79,11 +79,18 @@ nonpyomo_leaf_types.update(native_types) -def RegisterNumericType(new_type): - """A utility function for updating the set of types that are recognized - to handle numeric values. +def RegisterNumericType(new_type: type): + """Register the specified type as a "numeric type". - The argument should be a class (e.g, numpy.float64). + A utility function for registering new types as "native numeric + types" that can be leaf nodes in Pyomo numeric expressions. The + type should be compatible with :py:class:`float` (that is, store a + scalar and be castable to a Python float). + + Parameters + ---------- + new_type: type + The new numeric type (e.g, numpy.float64) """ native_numeric_types.add(new_type) @@ -91,12 +98,23 @@ def RegisterNumericType(new_type): nonpyomo_leaf_types.add(new_type) -def RegisterIntegerType(new_type): - """A utility function for updating the set of types that are recognized - to handle integer values. This also adds the type to the numeric - and native type sets (but not the Boolean / logical sets). +def RegisterIntegerType(new_type: type): + """Register the specified type as an "integer type". + + A utility function for registering new types as "native integer + types". Integer types can be leaf nodes in Pyomo numeric + expressions. The type should be compatible with :py:class:`float` + (that is, store a scalar and be castable to a Python float). + + Registering a type as an integer type implies + :py:func:`RegisterNumericType`. + + Note that integer types are NOT registered as logical / Boolean types. - The argument should be a class (e.g., numpy.int64). + Parameters + ---------- + new_type: type + The new integer type (e.g, numpy.int64) """ native_numeric_types.add(new_type) @@ -110,26 +128,41 @@ def RegisterIntegerType(new_type): "is deprecated. Users likely should use RegisterLogicalType.", version='6.6.0', ) -def RegisterBooleanType(new_type): - """A utility function for updating the set of types that are recognized - as handling boolean values. This function does not add the type - with the integer or numeric sets. +def RegisterBooleanType(new_type: type): + """Register the specified type as a "logical type". - The argument should be a class (e.g., numpy.bool_). + A utility function for registering new types as "native logical + types". Logical types can be leaf nodes in Pyomo logical + expressions. The type should be compatible with :py:class:`bool` + (that is, store a scalar and be castable to a Python bool). + + Note that logical types are NOT registered as numeric types. + + Parameters + ---------- + new_type: type + The new logical type (e.g, numpy.bool_) """ _native_boolean_types.add(new_type) native_types.add(new_type) nonpyomo_leaf_types.add(new_type) +def RegisterComplexType(new_type: type): + """Register the specified type as an "complex type". -def RegisterComplexType(new_type): - """A utility function for updating the set of types that are recognized - as handling complex values. This function does not add the type - with the integer or numeric sets. + A utility function for registering new types as "native complex + types". Complex types can NOT be leaf nodes in Pyomo numeric + expressions. The type should be compatible with :py:class:`complex` + (that is, store a scalar complex value and be castable to a Python + complex). + Note that complex types are NOT registered as logical or numeric types. - The argument should be a class (e.g., numpy.complex_). + Parameters + ---------- + new_type: type + The new complex type (e.g, numpy.complex128) """ native_types.add(new_type) @@ -137,12 +170,20 @@ def RegisterComplexType(new_type): nonpyomo_leaf_types.add(new_type) -def RegisterLogicalType(new_type): - """A utility function for updating the set of types that are recognized - as handling boolean values. This function does not add the type - with the integer or numeric sets. +def RegisterLogicalType(new_type: type): + """Register the specified type as a "logical type". + + A utility function for registering new types as "native logical + types". Logical types can be leaf nodes in Pyomo logical + expressions. The type should be compatible with :py:class:`bool` + (that is, store a scalar and be castable to a Python bool). + + Note that logical types are NOT registered as numeric types. - The argument should be a class (e.g., numpy.bool_). + Parameters + ---------- + new_type: type + The new logical type (e.g, numpy.bool_) """ _native_boolean_types.add(new_type) @@ -155,8 +196,9 @@ def check_if_numeric_type(obj): """Test if the argument behaves like a numeric type. We check for "numeric types" by checking if we can add zero to it - without changing the object's type. If that works, then we register - the type in native_numeric_types. + without changing the object's type, and that the object compares to + 0 in a meaningful way. If that works, then we register the type in + :py:attr:`native_numeric_types`. """ obj_class = obj.__class__ @@ -212,7 +254,7 @@ def value(obj, exception=True): then the __call__ method is executed. exception (bool): If :const:`True`, then an exception should be raised when instances of NumericValue fail to - s evaluate due to one or more objects not being + evaluate due to one or more objects not being initialized to a numeric value (e.g, one or more variables in an algebraic expression having the value None). If :const:`False`, then the function diff --git a/pyomo/repn/tests/test_util.py b/pyomo/repn/tests/test_util.py index 8347c521018..58ee09a1006 100644 --- a/pyomo/repn/tests/test_util.py +++ b/pyomo/repn/tests/test_util.py @@ -692,7 +692,7 @@ class NewProductExpression(ProductExpression): DeveloperError, r"(?s)Base expression key '\(, 3\)' not found when.*" - r"inserting dispatcher for node 'SumExpression' when walking.*" + r"inserting dispatcher for node 'SumExpression' while walking.*" r"expression tree.", ): end[node.__class__, 3](None, node, *node.args) diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index 0631c77eea6..5e76892ddeb 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -410,12 +410,12 @@ def register_dispatcher(self, visitor, node, *data, key=None): elif any((k[0] if k.__class__ is tuple else k) is base_type for k in self): raise DeveloperError( f"Base expression key '{base_key}' not found when inserting dispatcher" - f" for node '{type(node).__name__}' when walking expression tree." + f" for node '{type(node).__name__}' while walking expression tree." ) else: raise DeveloperError( f"Unexpected expression node type '{type(node).__name__}' " - "found when walking expression tree." + "found while walking expression tree." ) if cache: self[key] = fcn From 917188606c5d504e9bfc5aa796c0b69587de136b Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 10 Oct 2023 13:12:06 -0600 Subject: [PATCH 0280/1204] Slotize derived dispatchers --- pyomo/repn/plugins/nl_writer.py | 2 +- pyomo/repn/util.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index d532bceb768..2d0cd32f3d8 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -2234,7 +2234,7 @@ def handle_external_function_node(visitor, node, *args): class AMPLBeforeChildDispatcher(BeforeChildDispatcher): - operator_handles = _operator_handles + __slots__ = () def __init__(self): # Special linear / summation expressions diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index 5e76892ddeb..e7ad40fa169 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -258,6 +258,8 @@ class BeforeChildDispatcher(collections.defaultdict): """ + __slots__ = () + def __missing__(self, key): return self.register_dispatcher @@ -380,6 +382,8 @@ class ExitNodeDispatcher(collections.defaultdict): """ + __slots__ = () + def __init__(self, *args, **kwargs): super().__init__(None, *args, **kwargs) From 5c47040d5b0fee4bb54656e49dff54f670ec6680 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 10 Oct 2023 15:57:39 -0400 Subject: [PATCH 0281/1204] add skip_validation when ignore integrality --- pyomo/contrib/mindtpy/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index 4a4b77767a9..4dfb912e611 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -983,7 +983,7 @@ def copy_var_list_values(from_list, to_list, config, if var_val in v_to.domain: v_to.set_value(value(v_from, exception=False)) elif ignore_integrality and v_to.is_integer(): - v_to.set_value(value(v_from, exception=False)) + v_to.set_value(value(v_from, exception=False), skip_validation=True) elif abs(var_val) <= config.zero_tolerance and 0 in v_to.domain: v_to.set_value(0) elif v_to.is_integer() and (math.fabs(var_val - rounded_val) <= From a767f281acd08629aea99f6c616e59b11372d2f5 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 10 Oct 2023 16:28:58 -0400 Subject: [PATCH 0282/1204] add special handle for rnlp infeasible --- pyomo/contrib/mindtpy/algorithm_base_class.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 89575f5c4f2..f6192ad4687 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -829,6 +829,23 @@ def init_rNLP(self, add_oa_cuts=True): if len(results.solution) > 0: self.rnlp.solutions.load_from(results) subprob_terminate_cond = results.solver.termination_condition + + # Sometimes, the NLP solver might be trapped in a infeasible solution if the objective function is nonlinear and partition_obj_nonlinear_terms is True. If this happens, we will use the original objective function instead. + if subprob_terminate_cond == tc.infeasible and config.partition_obj_nonlinear_terms: + config.logger.info( + 'Initial relaxed NLP problem is infeasible. This might be related to partition_obj_nonlinear_terms. Try to solve it again without partitioning nonlinear objective function.') + self.rnlp.MindtPy_utils.objective.deactivate() + self.rnlp.MindtPy_utils.objective_list[0].activate() + results = self.nlp_opt.solve( + self.rnlp, + tee=config.nlp_solver_tee, + load_solutions=config.load_solutions, + **nlp_args, + ) + if len(results.solution) > 0: + self.rnlp.solutions.load_from(results) + subprob_terminate_cond = results.solver.termination_condition + if subprob_terminate_cond in {tc.optimal, tc.feasible, tc.locallyOptimal}: main_objective = MindtPy.objective_list[-1] if subprob_terminate_cond == tc.optimal: From a30cf823fbe9e370f0feb2c7ce5ed659d5db112e Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 10 Oct 2023 16:30:08 -0400 Subject: [PATCH 0283/1204] fix bug --- pyomo/contrib/mindtpy/algorithm_base_class.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index f6192ad4687..c254a8db72b 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -1662,7 +1662,10 @@ def solve_main(self): 'No integer solution is found, so the CPLEX solver will report an error status. ' ) # Value error will be raised if the MIP problem is unbounded and appsi solver is used when loading solutions. Although the problem is unbounded, a valid result is provided and we do not return None to let the algorithm continue. - return self.mip, main_mip_results + if 'main_mip_results' in dir(): + return self.mip, main_mip_results + else: + return None, None if config.solution_pool: main_mip_results._solver_model = self.mip_opt._solver_model main_mip_results._pyomo_var_to_solver_var_map = ( From 6b4c1eac538077387423cde3e87bcc8b3a45cc6c Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 10 Oct 2023 14:46:53 -0600 Subject: [PATCH 0284/1204] Try again with global option --- .github/workflows/release_wheel_creation.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index 3afdf9d8e03..28dd42159fe 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -40,7 +40,8 @@ jobs: CIBW_SKIP: "pp* cp36* cp37*" CIBW_BUILD_VERBOSITY: 1 CIBW_ARCHS: ${{ matrix.arch }} - CIBW_CONFIG_SETTINGS: $PYOMO_SETUP_ARGS + CIBW_BEFORE_BUILD: pip install cython pybind11 + CIBW_CONFIG_SETTINGS: '--global-option="--with-cython --with-distributable-extensions"' - uses: actions/upload-artifact@v3 with: name: wheels From a1cc04a4e5fb2c37150612117c73378aceb3099a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 11 Oct 2023 07:04:32 -0600 Subject: [PATCH 0285/1204] Remove some versions from being built --- .github/workflows/release_wheel_creation.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index 28dd42159fe..ac18fd15090 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -24,7 +24,7 @@ jobs: strategy: matrix: os: [ubuntu-20.04, windows-latest, macos-latest] - arch: [auto] + arch: [native] include: - os: ubuntu-20.04 arch: aarch64 @@ -37,7 +37,7 @@ jobs: with: output-dir: dist env: - CIBW_SKIP: "pp* cp36* cp37*" + CIBW_SKIP: "pp* cp36* cp37* *-musllinux-*" CIBW_BUILD_VERBOSITY: 1 CIBW_ARCHS: ${{ matrix.arch }} CIBW_BEFORE_BUILD: pip install cython pybind11 From deb8bc997332737637c513f4bc342247e8c65181 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 11 Oct 2023 10:18:16 -0600 Subject: [PATCH 0286/1204] Install setuptools first; correct skip statement --- .github/workflows/release_wheel_creation.yml | 3 ++- .github/workflows/test_branches.yml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index ac18fd15090..eda26808c0f 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -37,7 +37,8 @@ jobs: with: output-dir: dist env: - CIBW_SKIP: "pp* cp36* cp37* *-musllinux-*" + CIBW_PLATFORM: auto + CIBW_SKIP: "pp* cp36* cp37* *-musllinux*" CIBW_BUILD_VERBOSITY: 1 CIBW_ARCHS: ${{ matrix.arch }} CIBW_BEFORE_BUILD: pip install cython pybind11 diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 9fea047122c..ddded4f8a69 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -235,6 +235,7 @@ jobs: run: | python -c 'import sys;print(sys.executable)' python -m pip install --cache-dir cache/pip --upgrade pip + python -m pip install --cache-dir cache/pip setuptools PYOMO_DEPENDENCIES=`python setup.py dependencies \ --extras "$EXTRAS" | tail -1` PACKAGES="${PYTHON_CORE_PKGS} ${PYTHON_PACKAGES} ${PYOMO_DEPENDENCIES} " From aa2523f157ac7894eb3247c6b2ddc752b397a2fa Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 11 Oct 2023 10:30:28 -0600 Subject: [PATCH 0287/1204] Change ubuntu version --- .github/workflows/release_wheel_creation.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index eda26808c0f..5717845bdc3 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -23,10 +23,10 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-20.04, windows-latest, macos-latest] + os: [ubuntu-22.04, windows-latest, macos-latest] arch: [native] include: - - os: ubuntu-20.04 + - os: ubuntu-22.04 arch: aarch64 - os: macos-latest arch: arm64 From ba7ce5971e3b2d19a7a0d0c00542f53465095447 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 11 Oct 2023 10:44:34 -0600 Subject: [PATCH 0288/1204] Turn off aarch64 --- .github/workflows/release_wheel_creation.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index 5717845bdc3..5ae36645584 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -26,8 +26,9 @@ jobs: os: [ubuntu-22.04, windows-latest, macos-latest] arch: [native] include: - - os: ubuntu-22.04 - arch: aarch64 + # This doesn't work yet - have to explore why + # - os: ubuntu-22.04 + # arch: aarch64 - os: macos-latest arch: arm64 steps: From 923ed8bc3d631c16b59234639d72498263bbb1af Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Wed, 11 Oct 2023 12:36:16 -0600 Subject: [PATCH 0289/1204] working on unit tests --- .../model_debugging/latex_printing.rst | 102 +- pyomo/util/latex_printer.py | 130 +- pyomo/util/latex_printer_1.py | 1506 ----------------- pyomo/util/tests/test_latex_printer.py | 313 ++-- 4 files changed, 337 insertions(+), 1714 deletions(-) delete mode 100644 pyomo/util/latex_printer_1.py diff --git a/doc/OnlineDocs/model_debugging/latex_printing.rst b/doc/OnlineDocs/model_debugging/latex_printing.rst index 0bdb0de735c..63ecd09f950 100644 --- a/doc/OnlineDocs/model_debugging/latex_printing.rst +++ b/doc/OnlineDocs/model_debugging/latex_printing.rst @@ -3,24 +3,31 @@ Latex Printing Pyomo models can be printed to a LaTeX compatible format using the ``pyomo.util.latex_printer`` function: -.. py:function:: latex_printer(pyomoElement, filename=None, useAlignEnvironment=False, splitContinuousSets=False) +.. py:function:: latex_printer(pyomo_component, latex_component_map=None, write_object=None, use_equation_environment=False, split_continuous_sets=False, use_short_descriptors=False, fontsize = None, paper_dimensions=None) Prints a pyomo element (Block, Model, Objective, Constraint, or Expression) to a LaTeX compatible string - :param pyomoElement: The pyomo element to be printed - :type pyomoElement: _BlockData or Model or Objective or Constraint or Expression - :param filename: An optional filename where the latex will be saved - :type filename: str - :param useAlignEnvironment: If False, the equation/aligned construction is used to create a single LaTeX equation. If True, then the align environment is used in LaTeX and each constraint and objective will be given an individual equation number - :type useAlignEnvironment: bool - :param splitContinuousSets: If False, all sums will be done over 'index in set' or similar. If True, sums will be done over 'i=1' to 'N' or similar if the set is a continuous set - :type splitContinuousSets: bool + :param pyomo_component: The Pyomo component to be printed + :type pyomo_component: _BlockData or Model or Objective or Constraint or Expression + :param latex_component_map: A map keyed by Pyomo component, values become the latex representation in the printer + :type latex_component_map: pyomo.common.collections.component_map.ComponentMap + :param write_object: The object to print the latex string to. Can be an open file object, string I/O object, or a string for a filename to write to + :type write_object: io.TextIOWrapper or io.StringIO or str + :param use_equation_environment: If False, the equation/aligned construction is used to create a single LaTeX equation. If True, then the align environment is used in LaTeX and each constraint and objective will be given an individual equation number + :type use_equation_environment: bool + :param split_continuous_sets: If False, all sums will be done over 'index in set' or similar. If True, sums will be done over 'i=1' to 'N' or similar if the set is a continuous set + :type split_continuous_sets: bool + :param use_short_descriptors: If False, will print full 'minimize' and 'subject to' etc. If true, uses 'min' and 's.t.' instead + :type use_short_descriptors: bool + :param fontsize: Sets the font size of the latex output when writing to a file. Can take in any of the latex font size keywords ['tiny', 'scriptsize', 'footnotesize', 'small', 'normalsize', 'large', 'Large', 'LARGE', 'huge', 'Huge'], or an integer referenced off of 'normalsize' (ex: small is -1, Large is +2) + :type fontsize: str or int + :param paper_dimensions: A dictionary that controls the paper margins and size. Keys are: [ 'height', 'width', 'margin_left', 'margin_right', 'margin_top', 'margin_bottom' ]. Default is standard 8.5x11 with one inch margins. Values are in inches + :type paper_dimensions: dict :return: A LaTeX style string that represents the passed in pyomoElement :rtype: str - .. note:: If operating in a Jupyter Notebook, it may be helpful to use: @@ -29,14 +36,6 @@ Pyomo models can be printed to a LaTeX compatible format using the ``pyomo.util. ``display(Math(latex_printer(m))`` -The LaTeX printer will auto detect the following structures in variable names: - - * ``_``: underscores will get rendered as subscripts, ie ``x_var`` is rendered as ``x_{var}`` - * ``_dot``: will format as a ``\dot{}`` and remove from the underscore formatting. Ex: ``x_dot_1`` becomes ``\dot{x}_1`` - * ``_hat``: will format as a ``\hat{}`` and remove from the underscore formatting. Ex: ``x_hat_1`` becomes ``\hat{x}_1`` - * ``_bar``: will format as a ``\bar{}`` and remove from the underscore formatting. Ex: ``x_bar_1`` becomes ``\bar{x}_1`` - - Examples -------- @@ -45,39 +44,16 @@ A Model .. doctest:: - >>> # Note: this model is not mathematically sensible - >>> import pyomo.environ as pe - >>> from pyomo.core.expr import Expr_if - >>> from pyomo.core.base import ExternalFunction >>> from pyomo.util.latex_printer import latex_printer >>> m = pe.ConcreteModel(name = 'basicFormulation') >>> m.x = pe.Var() >>> m.y = pe.Var() >>> m.z = pe.Var() + >>> m.c = pe.Param(initialize=1.0, mutable=True) >>> m.objective = pe.Objective( expr = m.x + m.y + m.z ) - >>> m.constraint_1 = pe.Constraint(expr = m.x**2 + m.y**-2.0 - m.x*m.y*m.z + 1 == 2.0) - >>> m.constraint_2 = pe.Constraint(expr = abs(m.x/m.z**-2) * (m.x + m.y) <= 2.0) - >>> m.constraint_3 = pe.Constraint(expr = pe.sqrt(m.x/m.z**-2) <= 2.0) - >>> m.constraint_4 = pe.Constraint(expr = (1,m.x,2)) - >>> m.constraint_5 = pe.Constraint(expr = Expr_if(m.x<=1.0, m.z, m.y) <= 1.0) - - >>> def blackbox(a, b): return sin(a - b) - >>> m.bb = ExternalFunction(blackbox) - >>> m.constraint_6 = pe.Constraint(expr= m.x + m.bb(m.x,m.y) == 2 ) - - >>> m.I = pe.Set(initialize=[1,2,3,4,5]) - >>> m.J = pe.Set(initialize=[1,2,3]) - >>> m.u = pe.Var(m.I*m.I) - >>> m.v = pe.Var(m.I) - >>> m.w = pe.Var(m.J) - - >>> def ruleMaker(m,j): return (m.x + m.y) * sum( m.v[i] + m.u[i,j]**2 for i in m.I ) <= 0 - >>> m.constraint_7 = pe.Constraint(m.I, rule = ruleMaker) - - >>> def ruleMaker(m): return (m.x + m.y) * sum( m.w[j] for j in m.J ) - >>> m.objective_2 = pe.Objective(rule = ruleMaker) + >>> m.constraint_1 = pe.Constraint(expr = m.x**2 + m.y**2.0 - m.z**2.0 <= m.c ) >>> pstr = latex_printer(m) @@ -98,6 +74,46 @@ A Constraint >>> pstr = latex_printer(m.constraint_1) +A Constraint with a Set ++++++++++++++++++++++++ + +.. doctest:: + + >>> import pyomo.environ as pe + >>> from pyomo.util.latex_printer import latex_printer + >>> m = pe.ConcreteModel(name='basicFormulation') + >>> m.I = pe.Set(initialize=[1, 2, 3, 4, 5]) + >>> m.v = pe.Var(m.I) + + >>> def ruleMaker(m): return sum(m.v[i] for i in m.I) <= 0 + + >>> m.constraint = pe.Constraint(rule=ruleMaker) + + >>> pstr = latex_printer(m.constraint) + +Using a ComponentMap +++++++++++++++++++++ + +.. doctest:: + + >>> import pyomo.environ as pe + >>> from pyomo.util.latex_printer import latex_printer + >>> from pyomo.common.collections.component_map import ComponentMap + + >>> m = pe.ConcreteModel(name='basicFormulation') + >>> m.I = pe.Set(initialize=[1, 2, 3, 4, 5]) + >>> m.v = pe.Var(m.I) + + >>> def ruleMaker(m): return sum(m.v[i] for i in m.I) <= 0 + + >>> m.constraint = pe.Constraint(rule=ruleMaker) + + >>> lcm = ComponentMap() + >>> lcm[m.v] = 'x' + >>> lcm[m.I] = ['\\mathcal{A}',['j','k']] + + >>> pstr = latex_printer(m.constraint, latex_component_map=lcm) + An Expression +++++++++++++ diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index 6f574253f8b..0ea0c2ab9d4 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -75,21 +75,25 @@ def decoder(num, base): - if isinstance(base, float): - if not base.is_integer(): - raise ValueError('Invalid base') - else: - base = int(base) - - if base <= 1: - raise ValueError('Invalid base') - - if num == 0: - numDigs = 1 - else: - numDigs = math.ceil(math.log(num, base)) - if math.log(num, base).is_integer(): - numDigs += 1 + # Needed in the general case, but not as implemented + # if isinstance(base, float): + # if not base.is_integer(): + # raise ValueError('Invalid base') + # else: + # base = int(base) + + # Needed in the general case, but not as implemented + # if base <= 1: + # raise ValueError('Invalid base') + + # Needed in the general case, but not as implemented + # if num == 0: + # numDigs = 1 + # else: + numDigs = math.ceil(math.log(num, base)) + if math.log(num, base).is_integer(): + numDigs += 1 + digs = [0.0 for i in range(0, numDigs)] rem = num for i in range(0, numDigs): @@ -125,7 +129,7 @@ def alphabetStringGenerator(num, indexMode=False): 'q', 'r', ] - + else: alphabet = [ '.', @@ -447,11 +451,7 @@ def exitNode(self, node, data): try: return self._operator_handles[node.__class__](self, node, *data) except: - print(node.__class__) - print(node) - print(data) - - return 'xxx' + raise DeveloperError('Latex printer encountered an error when processing type %s, contact the developers'%(node.__class__)) def analyze_variable(vr): domainMap = { @@ -640,21 +640,44 @@ def latex_printer( Parameters ---------- - pyomo_component: _BlockData or Model or Constraint or Expression or Objective - The thing to be printed to LaTeX. Accepts Blocks (including models), Constraints, and Expressions - - write_object: str - An optional file to write the LaTeX to. Default of None produces no file - + pyomo_component: _BlockData or Model or Objective or Constraint or Expression + The Pyomo component to be printed + + latex_component_map: pyomo.common.collections.component_map.ComponentMap + A map keyed by Pyomo component, values become the latex representation in + the printer + + write_object: io.TextIOWrapper or io.StringIO or str + The object to print the latex string to. Can be an open file object, + string I/O object, or a string for a filename to write to + use_equation_environment: bool - Default behavior uses equation/aligned and produces a single LaTeX Equation (ie, ==False). - Setting this input to True will instead use the align environment, and produce equation numbers for each - objective and constraint. Each objective and constraint will be labeled with its name in the pyomo model. - This flag is only relevant for Models and Blocks. - - splitContinuous: bool - Default behavior has all sum indices be over "i \\in I" or similar. Setting this flag to - True makes the sums go from: \\sum_{i=1}^{5} if the set I is continuous and has 5 elements + If False, the equation/aligned construction is used to create a single + LaTeX equation. If True, then the align environment is used in LaTeX and + each constraint and objective will be given an individual equation number + + split_continuous_sets: bool + If False, all sums will be done over 'index in set' or similar. If True, + sums will be done over 'i=1' to 'N' or similar if the set is a continuous + set + + use_short_descriptors: bool + If False, will print full 'minimize' and 'subject to' etc. If true, uses + 'min' and 's.t.' instead + + fontsize: str or int + Sets the font size of the latex output when writing to a file. Can take + in any of the latex font size keywords ['tiny', 'scriptsize', + 'footnotesize', 'small', 'normalsize', 'large', 'Large', 'LARGE', huge', + 'Huge'], or an integer referenced off of 'normalsize' (ex: small is -1, + Large is +2) + + paper_dimensions: dict + A dictionary that controls the paper margins and size. Keys are: + [ 'height', 'width', 'margin_left', 'margin_right', 'margin_top', + 'margin_bottom' ]. Default is standard 8.5x11 with one inch margins. + Values are in inches + Returns ------- @@ -682,7 +705,7 @@ def latex_printer( fontsizes_ints = [ -4, -3, -2, -1, 0, 1, 2, 3, 4, 5 ] if fontsize is None: - fontsize = 0 + fontsize = '\\normalsize' elif fontsize in fontSizes: #no editing needed @@ -800,10 +823,8 @@ def latex_printer( if p not in ComponentSet(parameterList): parameterList.append(p) - # TODO: cannot extract this information, waiting on resolution of an issue - # For now, will raise an error - raise RuntimeError('Printing of non-models is not currently supported, but will be added soon') - # setList = identify_components(pyomo_component.expr, pyo.Set) + # Will grab the sets as the expression is walked + setList = [] else: variableList = [ @@ -900,7 +921,7 @@ def latex_printer( tbSpc = 8 else: tbSpc = 4 - trailingAligner = '&' + trailingAligner = '' # Iterate over the objectives and print for obj in objectives: @@ -950,7 +971,10 @@ def latex_printer( else: algn = '' - tail = '\\\\ \n' + if not isSingle: + tail = '\\\\ \n' + else: + tail = '\n' # grab the constraint and templatize con = constraints[i] @@ -1198,7 +1222,7 @@ def latex_printer( ) ln = re.sub(setInfo[ky]['sumSetRegEx'], replacement, ln) - replacement = defaultSetLatexNames[setInfo[ky]['setObject']] + replacement = repr(defaultSetLatexNames[setInfo[ky]['setObject']])[1:-1] ln = re.sub(setInfo[ky]['setRegEx'], replacement, ln) # groupNumbers = re.findall(r'__I_PLACEHOLDER_8675309_GROUP_([0-9*])_SET[0-9]*__',ln) @@ -1230,7 +1254,7 @@ def latex_printer( 'Insufficient number of indices provided to the overwrite dictionary for set %s' % (vl['setObject'].name) ) - for i in range(0, len(indexNames)): + for i in range(0, len(vl['indices'])): ln = ln.replace( '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % (vl['indices'][i], ky), @@ -1389,15 +1413,15 @@ def latex_printer( fstr += pstr + '\n' fstr += '\\end{document} \n' - # optional write to output file - if isinstance(write_object, (io.TextIOWrapper, io.StringIO)): - write_object.write(fstr) - elif isinstance(write_object,str): - f = open(write_object, 'w') - f.write(fstr) - f.close() - else: - raise ValueError('Invalid type %s encountered when parsing the write_object. Must be a StringIO, FileIO, or valid filename string') + # optional write to output file + if isinstance(write_object, (io.TextIOWrapper, io.StringIO)): + write_object.write(fstr) + elif isinstance(write_object,str): + f = open(write_object, 'w') + f.write(fstr) + f.close() + else: + raise ValueError('Invalid type %s encountered when parsing the write_object. Must be a StringIO, FileIO, or valid filename string') # return the latex string return pstr diff --git a/pyomo/util/latex_printer_1.py b/pyomo/util/latex_printer_1.py deleted file mode 100644 index e251dda5927..00000000000 --- a/pyomo/util/latex_printer_1.py +++ /dev/null @@ -1,1506 +0,0 @@ -# ___________________________________________________________________________ -# -# Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2023 -# 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 math -import copy -import re -import pyomo.environ as pyo -from pyomo.core.expr.visitor import StreamBasedExpressionVisitor -from pyomo.core.expr import ( - NegationExpression, - ProductExpression, - DivisionExpression, - PowExpression, - AbsExpression, - UnaryFunctionExpression, - MonomialTermExpression, - LinearExpression, - SumExpression, - EqualityExpression, - InequalityExpression, - RangedExpression, - Expr_ifExpression, - ExternalFunctionExpression, -) - -from pyomo.core.expr.visitor import identify_components -from pyomo.core.expr.base import ExpressionBase -from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData -from pyomo.core.base.objective import ScalarObjective, _GeneralObjectiveData -import pyomo.core.kernel as kernel -from pyomo.core.expr.template_expr import ( - GetItemExpression, - GetAttrExpression, - TemplateSumExpression, - IndexTemplate, - Numeric_GetItemExpression, - templatize_constraint, - resolve_template, - templatize_rule, -) -from pyomo.core.base.var import ScalarVar, _GeneralVarData, IndexedVar -from pyomo.core.base.param import _ParamData, ScalarParam, IndexedParam -from pyomo.core.base.set import _SetData -from pyomo.core.base.constraint import ScalarConstraint, IndexedConstraint -from pyomo.common.collections.component_map import ComponentMap -from pyomo.common.collections.component_set import ComponentSet - -from pyomo.core.base.external import _PythonCallbackFunctionID - -from pyomo.core.base.block import _BlockData - -from pyomo.repn.util import ExprType - -from pyomo.common import DeveloperError - -_CONSTANT = ExprType.CONSTANT -_MONOMIAL = ExprType.MONOMIAL -_GENERAL = ExprType.GENERAL - - -def decoder(num, base): - if isinstance(base, float): - if not base.is_integer(): - raise ValueError('Invalid base') - else: - base = int(base) - - if base <= 1: - raise ValueError('Invalid base') - - if num == 0: - numDigs = 1 - else: - numDigs = math.ceil(math.log(num, base)) - if math.log(num, base).is_integer(): - numDigs += 1 - digs = [0.0 for i in range(0, numDigs)] - rem = num - for i in range(0, numDigs): - ix = numDigs - i - 1 - dg = math.floor(rem / base**ix) - rem = rem % base**ix - digs[i] = dg - return digs - - -def indexCorrector(ixs, base): - for i in range(0, len(ixs)): - ix = ixs[i] - if i + 1 < len(ixs): - if ixs[i + 1] == 0: - ixs[i] -= 1 - ixs[i + 1] = base - if ixs[i] == 0: - ixs = indexCorrector(ixs, base) - return ixs - - -def alphabetStringGenerator(num, indexMode=False): - if indexMode: - alphabet = [ - '.', - 'i', - 'j', - 'k', - 'm', - 'n', - 'p', - 'q', - 'r', - # 'a', - # 'b', - # 'c', - # 'd', - # 'e', - # 'f', - # 'g', - # 'h', - # 'l', - # 'o', - # 's', - # 't', - # 'u', - # 'v', - # 'w', - # 'x', - # 'y', - # 'z', - ] - - else: - alphabet = [ - '.', - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i', - 'j', - 'k', - 'l', - 'm', - 'n', - 'o', - 'p', - 'q', - 'r', - 's', - 't', - 'u', - 'v', - 'w', - 'x', - 'y', - 'z', - ] - ixs = decoder(num + 1, len(alphabet) - 1) - pstr = '' - ixs = indexCorrector(ixs, len(alphabet) - 1) - for i in range(0, len(ixs)): - ix = ixs[i] - pstr += alphabet[ix] - pstr = pstr.replace('.', '') - return pstr - - -def templatize_expression(expr): - expr, indices = templatize_rule(expr.parent_block(), expr._rule, expr.index_set()) - return (expr, indices) - - -def templatize_passthrough(con): - return (con, []) - - -def precedenceChecker(node, arg1, arg2=None): - childPrecedence = [] - for a in node.args: - if hasattr(a, 'PRECEDENCE'): - if a.PRECEDENCE is None: - childPrecedence.append(-1) - else: - childPrecedence.append(a.PRECEDENCE) - else: - childPrecedence.append(-1) - - if hasattr(node, 'PRECEDENCE'): - precedence = node.PRECEDENCE - else: - # Should never hit this - raise DeveloperError( - 'This error should never be thrown, node does not have a precedence. Report to developers' - ) - - if childPrecedence[0] > precedence: - arg1 = ' \\left( ' + arg1 + ' \\right) ' - - if arg2 is not None: - if childPrecedence[1] > precedence: - arg2 = ' \\left( ' + arg2 + ' \\right) ' - - return arg1, arg2 - - -def handle_negation_node(visitor, node, arg1): - arg1, tsh = precedenceChecker(node, arg1) - return '-' + arg1 - - -def handle_product_node(visitor, node, arg1, arg2): - arg1, arg2 = precedenceChecker(node, arg1, arg2) - return ' '.join([arg1, arg2]) - - -def handle_pow_node(visitor, node, arg1, arg2): - arg1, arg2 = precedenceChecker(node, arg1, arg2) - return "%s^{%s}" % (arg1, arg2) - - -def handle_division_node(visitor, node, arg1, arg2): - return '\\frac{%s}{%s}' % (arg1, arg2) - - -def handle_abs_node(visitor, node, arg1): - return ' \\left| ' + arg1 + ' \\right| ' - - -def handle_unary_node(visitor, node, arg1): - fcn_handle = node.getname() - if fcn_handle == 'log10': - fcn_handle = 'log_{10}' - - if fcn_handle == 'sqrt': - return '\\sqrt { ' + arg1 + ' }' - else: - return '\\' + fcn_handle + ' \\left( ' + arg1 + ' \\right) ' - - -def handle_equality_node(visitor, node, arg1, arg2): - return arg1 + ' = ' + arg2 - - -def handle_inequality_node(visitor, node, arg1, arg2): - return arg1 + ' \\leq ' + arg2 - - -def handle_var_node(visitor, node): - return visitor.variableMap[node] - - -def handle_num_node(visitor, node): - if isinstance(node, float): - if node.is_integer(): - node = int(node) - return str(node) - - -def handle_sumExpression_node(visitor, node, *args): - rstr = args[0] - for i in range(1, len(args)): - if args[i][0] == '-': - rstr += ' - ' + args[i][1:] - else: - rstr += ' + ' + args[i] - return rstr - - -def handle_monomialTermExpression_node(visitor, node, arg1, arg2): - if arg1 == '1': - return arg2 - elif arg1 == '-1': - return '-' + arg2 - else: - return arg1 + ' ' + arg2 - - -def handle_named_expression_node(visitor, node, arg1): - # needed to preserve consistencency with the exitNode function call - # prevents the need to type check in the exitNode function - return arg1 - - -def handle_ranged_inequality_node(visitor, node, arg1, arg2, arg3): - return arg1 + ' \\leq ' + arg2 + ' \\leq ' + arg3 - - -def handle_exprif_node(visitor, node, arg1, arg2, arg3): - return 'f_{\\text{exprIf}}(' + arg1 + ',' + arg2 + ',' + arg3 + ')' - - ## Could be handled in the future using cases or similar - - ## Raises not implemented error - # raise NotImplementedError('Expr_if objects not supported by the Latex Printer') - - ## Puts cases in a bracketed matrix - # pstr = '' - # pstr += '\\begin{Bmatrix} ' - # pstr += arg2 + ' , & ' + arg1 + '\\\\ ' - # pstr += arg3 + ' , & \\text{otherwise}' + '\\\\ ' - # pstr += '\\end{Bmatrix}' - # return pstr - - -def handle_external_function_node(visitor, node, *args): - pstr = '' - pstr += 'f(' - for i in range(0, len(args) - 1): - pstr += args[i] - if i <= len(args) - 3: - pstr += ',' - else: - pstr += ')' - return pstr - - -def handle_functionID_node(visitor, node, *args): - # seems to just be a placeholder empty wrapper object - return '' - - -def handle_indexTemplate_node(visitor, node, *args): - return '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( - node._group, - visitor.setMap[node._set], - ) - - -def handle_numericGIE_node(visitor, node, *args): - joinedName = args[0] - - pstr = '' - pstr += joinedName + '_{' - for i in range(1, len(args)): - pstr += args[i] - if i <= len(args) - 2: - pstr += ',' - else: - pstr += '}' - return pstr - - -def handle_templateSumExpression_node(visitor, node, *args): - pstr = '' - for i in range(0, len(node._iters)): - pstr += '\\sum_{__S_PLACEHOLDER_8675309_GROUP_%s_%s__} ' % ( - node._iters[i][0]._group, - visitor.setMap[node._iters[i][0]._set], - ) - - pstr += args[0] - - return pstr - - -def handle_param_node(visitor, node): - return visitor.parameterMap[node] - - -class _LatexVisitor(StreamBasedExpressionVisitor): - def __init__(self): - super().__init__() - - self._operator_handles = { - ScalarVar: handle_var_node, - int: handle_num_node, - float: handle_num_node, - NegationExpression: handle_negation_node, - ProductExpression: handle_product_node, - DivisionExpression: handle_division_node, - PowExpression: handle_pow_node, - AbsExpression: handle_abs_node, - UnaryFunctionExpression: handle_unary_node, - Expr_ifExpression: handle_exprif_node, - EqualityExpression: handle_equality_node, - InequalityExpression: handle_inequality_node, - RangedExpression: handle_ranged_inequality_node, - _GeneralExpressionData: handle_named_expression_node, - ScalarExpression: handle_named_expression_node, - kernel.expression.expression: handle_named_expression_node, - kernel.expression.noclone: handle_named_expression_node, - _GeneralObjectiveData: handle_named_expression_node, - _GeneralVarData: handle_var_node, - ScalarObjective: handle_named_expression_node, - kernel.objective.objective: handle_named_expression_node, - ExternalFunctionExpression: handle_external_function_node, - _PythonCallbackFunctionID: handle_functionID_node, - LinearExpression: handle_sumExpression_node, - SumExpression: handle_sumExpression_node, - MonomialTermExpression: handle_monomialTermExpression_node, - IndexedVar: handle_var_node, - IndexTemplate: handle_indexTemplate_node, - Numeric_GetItemExpression: handle_numericGIE_node, - TemplateSumExpression: handle_templateSumExpression_node, - ScalarParam: handle_param_node, - _ParamData: handle_param_node, - } - - def exitNode(self, node, data): - return self._operator_handles[node.__class__](self, node, *data) - - -def applySmartVariables(name): - splitName = name.split('_') - # print(splitName) - - filteredName = [] - - prfx = '' - psfx = '' - for i in range(0, len(splitName)): - se = splitName[i] - if se != 0: - if se == 'dot': - prfx = '\\dot{' - psfx = '}' - elif se == 'hat': - prfx = '\\hat{' - psfx = '}' - elif se == 'bar': - prfx = '\\bar{' - psfx = '}' - elif se == 'mathcal': - prfx = '\\mathcal{' - psfx = '}' - else: - filteredName.append(se) - else: - filteredName.append(se) - - joinedName = prfx + filteredName[0] + psfx - # print(joinedName) - # print(filteredName) - for i in range(1, len(filteredName)): - joinedName += '_{' + filteredName[i] - - joinedName += '}' * (len(filteredName) - 1) - # print(joinedName) - - return joinedName - - -def analyze_variable(vr, visitor): - domainMap = { - 'Reals': '\\mathds{R}', - 'PositiveReals': '\\mathds{R}_{> 0}', - 'NonPositiveReals': '\\mathds{R}_{\\leq 0}', - 'NegativeReals': '\\mathds{R}_{< 0}', - 'NonNegativeReals': '\\mathds{R}_{\\geq 0}', - 'Integers': '\\mathds{Z}', - 'PositiveIntegers': '\\mathds{Z}_{> 0}', - 'NonPositiveIntegers': '\\mathds{Z}_{\\leq 0}', - 'NegativeIntegers': '\\mathds{Z}_{< 0}', - 'NonNegativeIntegers': '\\mathds{Z}_{\\geq 0}', - 'Boolean': '\\left\\{ 0 , 1 \\right \\}', - 'Binary': '\\left\\{ 0 , 1 \\right \\}', - # 'Any': None, - # 'AnyWithNone': None, - 'EmptySet': '\\varnothing', - 'UnitInterval': '\\mathds{R}', - 'PercentFraction': '\\mathds{R}', - # 'RealInterval' : None , - # 'IntegerInterval' : None , - } - - domainName = vr.domain.name - varBounds = vr.bounds - lowerBoundValue = varBounds[0] - upperBoundValue = varBounds[1] - - if domainName in ['Reals', 'Integers']: - if lowerBoundValue is not None: - lowerBound = str(lowerBoundValue) + ' \\leq ' - else: - lowerBound = '' - - if upperBoundValue is not None: - upperBound = ' \\leq ' + str(upperBoundValue) - else: - upperBound = '' - - elif domainName in ['PositiveReals', 'PositiveIntegers']: - if lowerBoundValue is not None: - if lowerBoundValue > 0: - lowerBound = str(lowerBoundValue) + ' \\leq ' - else: - lowerBound = ' 0 < ' - else: - lowerBound = ' 0 < ' - - if upperBoundValue is not None: - if upperBoundValue <= 0: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' % (vr.name) - ) - else: - upperBound = ' \\leq ' + str(upperBoundValue) - else: - upperBound = '' - - elif domainName in ['NonPositiveReals', 'NonPositiveIntegers']: - if lowerBoundValue is not None: - if lowerBoundValue > 0: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' % (vr.name) - ) - elif lowerBoundValue == 0: - lowerBound = ' 0 = ' - else: - lowerBound = str(lowerBoundValue) + ' \\leq ' - else: - lowerBound = '' - - if upperBoundValue is not None: - if upperBoundValue >= 0: - upperBound = ' \\leq 0 ' - else: - upperBound = ' \\leq ' + str(upperBoundValue) - else: - upperBound = ' \\leq 0 ' - - elif domainName in ['NegativeReals', 'NegativeIntegers']: - if lowerBoundValue is not None: - if lowerBoundValue >= 0: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' % (vr.name) - ) - else: - lowerBound = str(lowerBoundValue) + ' \\leq ' - else: - lowerBound = '' - - if upperBoundValue is not None: - if upperBoundValue >= 0: - upperBound = ' < 0 ' - else: - upperBound = ' \\leq ' + str(upperBoundValue) - else: - upperBound = ' < 0 ' - - elif domainName in ['NonNegativeReals', 'NonNegativeIntegers']: - if lowerBoundValue is not None: - if lowerBoundValue > 0: - lowerBound = str(lowerBoundValue) + ' \\leq ' - else: - lowerBound = ' 0 \\leq ' - else: - lowerBound = ' 0 \\leq ' - - if upperBoundValue is not None: - if upperBoundValue < 0: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' % (vr.name) - ) - elif upperBoundValue == 0: - upperBound = ' = 0 ' - else: - upperBound = ' \\leq ' + str(upperBoundValue) - else: - upperBound = '' - - elif domainName in ['Boolean', 'Binary', 'Any', 'AnyWithNone', 'EmptySet']: - lowerBound = '' - upperBound = '' - - elif domainName in ['UnitInterval', 'PercentFraction']: - if lowerBoundValue is not None: - if lowerBoundValue > 0: - lowerBound = str(lowerBoundValue) + ' \\leq ' - elif lowerBoundValue > 1: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' % (vr.name) - ) - elif lowerBoundValue == 1: - lowerBound = ' = 1 ' - else: - lowerBound = ' 0 \\leq ' - else: - lowerBound = ' 0 \\leq ' - - if upperBoundValue is not None: - if upperBoundValue < 1: - upperBound = ' \\leq ' + str(upperBoundValue) - elif upperBoundValue < 0: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' % (vr.name) - ) - elif upperBoundValue == 0: - upperBound = ' = 0 ' - else: - upperBound = ' \\leq 1 ' - else: - upperBound = ' \\leq 1 ' - - else: - raise ValueError('Domain %s not supported by the latex printer' % (domainName)) - - varBoundData = { - 'variable': vr, - 'lowerBound': lowerBound, - 'upperBound': upperBound, - 'domainName': domainName, - 'domainLatex': domainMap[domainName], - } - - return varBoundData - - -def multiple_replace(pstr, rep_dict): - pattern = re.compile("|".join(rep_dict.keys()), flags=re.DOTALL) - return pattern.sub(lambda x: rep_dict[x.group(0)], pstr) - - -def latex_printer( - pyomo_component, - filename=None, - use_equation_environment=False, - split_continuous_sets=False, - use_smart_variables=False, - x_only_mode=0, - use_short_descriptors=False, - overwrite_dict=None, -): - """This function produces a string that can be rendered as LaTeX - - As described, this function produces a string that can be rendered as LaTeX - - Parameters - ---------- - pyomo_component: _BlockData or Model or Constraint or Expression or Objective - The thing to be printed to LaTeX. Accepts Blocks (including models), Constraints, and Expressions - - filename: str - An optional file to write the LaTeX to. Default of None produces no file - - use_equation_environment: bool - Default behavior uses equation/aligned and produces a single LaTeX Equation (ie, ==False). - Setting this input to True will instead use the align environment, and produce equation numbers for each - objective and constraint. Each objective and constraint will be labeled with its name in the pyomo model. - This flag is only relevant for Models and Blocks. - - splitContinuous: bool - Default behavior has all sum indices be over "i \\in I" or similar. Setting this flag to - True makes the sums go from: \\sum_{i=1}^{5} if the set I is continuous and has 5 elements - - Returns - ------- - str - A LaTeX string of the pyomo_component - - """ - - # Various setup things - - # is Single implies Objective, constraint, or expression - # these objects require a slight modification of behavior - # isSingle==False means a model or block - - if overwrite_dict is None: - overwrite_dict = ComponentMap() - - isSingle = False - - if isinstance(pyomo_component, pyo.Objective): - objectives = [pyomo_component] - constraints = [] - expressions = [] - templatize_fcn = templatize_constraint - use_equation_environment = True - isSingle = True - - elif isinstance(pyomo_component, pyo.Constraint): - objectives = [] - constraints = [pyomo_component] - expressions = [] - templatize_fcn = templatize_constraint - use_equation_environment = True - isSingle = True - - elif isinstance(pyomo_component, pyo.Expression): - objectives = [] - constraints = [] - expressions = [pyomo_component] - templatize_fcn = templatize_expression - use_equation_environment = True - isSingle = True - - elif isinstance(pyomo_component, (ExpressionBase, pyo.Var)): - objectives = [] - constraints = [] - expressions = [pyomo_component] - templatize_fcn = templatize_passthrough - use_equation_environment = True - isSingle = True - - elif isinstance(pyomo_component, _BlockData): - objectives = [ - obj - for obj in pyomo_component.component_data_objects( - pyo.Objective, descend_into=True, active=True - ) - ] - constraints = [ - con - for con in pyomo_component.component_objects( - pyo.Constraint, descend_into=True, active=True - ) - ] - expressions = [] - templatize_fcn = templatize_constraint - - else: - raise ValueError( - "Invalid type %s passed into the latex printer" - % (str(type(pyomo_component))) - ) - - if isSingle: - temp_comp, temp_indexes = templatize_fcn(pyomo_component) - variableList = [] - for v in identify_components( - temp_comp, [ScalarVar, _GeneralVarData, IndexedVar] - ): - if isinstance(v, _GeneralVarData): - v_write = v.parent_component() - if v_write not in ComponentSet(variableList): - variableList.append(v_write) - else: - if v not in ComponentSet(variableList): - variableList.append(v) - - parameterList = [] - for p in identify_components( - temp_comp, [ScalarParam, _ParamData, IndexedParam] - ): - if isinstance(p, _ParamData): - p_write = p.parent_component() - if p_write not in ComponentSet(parameterList): - parameterList.append(p_write) - else: - if p not in ComponentSet(parameterList): - parameterList.append(p) - - # TODO: cannot extract this information, waiting on resolution of an issue - # setList = identify_components(pyomo_component.expr, pyo.Set) - - else: - variableList = [ - vr - for vr in pyomo_component.component_objects( - pyo.Var, descend_into=True, active=True - ) - ] - - parameterList = [ - pm - for pm in pyomo_component.component_objects( - pyo.Param, descend_into=True, active=True - ) - ] - - setList = [ - st - for st in pyomo_component.component_objects( - pyo.Set, descend_into=True, active=True - ) - ] - - forallTag = ' \\qquad \\forall' - - descriptorDict = {} - if use_short_descriptors: - descriptorDict['minimize'] = '\\min' - descriptorDict['maximize'] = '\\max' - descriptorDict['subject to'] = '\\text{s.t.}' - descriptorDict['with bounds'] = '\\text{w.b.}' - else: - descriptorDict['minimize'] = '\\text{minimize}' - descriptorDict['maximize'] = '\\text{maximize}' - descriptorDict['subject to'] = '\\text{subject to}' - descriptorDict['with bounds'] = '\\text{with bounds}' - - # In the case where just a single expression is passed, add this to the constraint list for printing - constraints = constraints + expressions - - # Declare a visitor/walker - visitor = _LatexVisitor() - - variableMap = ComponentMap() - vrIdx = 0 - for i in range(0, len(variableList)): - vr = variableList[i] - vrIdx += 1 - if isinstance(vr, ScalarVar): - variableMap[vr] = 'x_' + str(vrIdx) - elif isinstance(vr, IndexedVar): - variableMap[vr] = 'x_' + str(vrIdx) - for sd in vr.index_set().data(): - vrIdx += 1 - variableMap[vr[sd]] = 'x_' + str(vrIdx) - else: - raise DeveloperError( - 'Variable is not a variable. Should not happen. Contact developers' - ) - visitor.variableMap = variableMap - - parameterMap = ComponentMap() - pmIdx = 0 - for i in range(0, len(parameterList)): - vr = parameterList[i] - pmIdx += 1 - if isinstance(vr, ScalarParam): - parameterMap[vr] = 'p_' + str(pmIdx) - elif isinstance(vr, IndexedParam): - parameterMap[vr] = 'p_' + str(pmIdx) - for sd in vr.index_set().data(): - pmIdx += 1 - parameterMap[vr[sd]] = 'p_' + str(pmIdx) - else: - raise DeveloperError( - 'Parameter is not a parameter. Should not happen. Contact developers' - ) - visitor.parameterMap = parameterMap - - setMap = ComponentMap() - for i in range(0, len(setList)): - st = setList[i] - setMap[st] = 'SET' + str(i + 1) - visitor.setMap = setMap - - # starts building the output string - pstr = '' - if not use_equation_environment: - pstr += '\\begin{align} \n' - tbSpc = 4 - trailingAligner = '& ' - else: - pstr += '\\begin{equation} \n' - if not isSingle: - pstr += ' \\begin{aligned} \n' - tbSpc = 8 - else: - tbSpc = 4 - trailingAligner = '&' - - # Iterate over the objectives and print - for obj in objectives: - try: - obj_template, obj_indices = templatize_fcn(obj) - except: - raise RuntimeError( - "An objective has been constructed that cannot be templatized" - ) - - if obj.sense == 1: - pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['minimize']) - else: - pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['maximize']) - - pstr += ' ' * tbSpc + '& & %s %s' % ( - visitor.walk_expression(obj_template), - trailingAligner, - ) - if not use_equation_environment: - pstr += '\\label{obj:' + pyomo_component.name + '_' + obj.name + '} ' - if not isSingle: - pstr += '\\\\ \n' - else: - pstr += '\n' - - # Iterate over the constraints - if len(constraints) > 0: - # only print this if printing a full formulation - if not isSingle: - pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['subject to']) - - # first constraint needs different alignment because of the 'subject to': - # & minimize & & [Objective] - # & subject to & & [Constraint 1] - # & & & [Constraint 2] - # & & & [Constraint N] - - # The double '& &' renders better for some reason - - for i in range(0, len(constraints)): - if not isSingle: - if i == 0: - algn = '& &' - else: - algn = '&&&' - else: - algn = '' - - tail = '\\\\ \n' - - # grab the constraint and templatize - con = constraints[i] - try: - con_template, indices = templatize_fcn(con) - except: - raise RuntimeError( - "A constraint has been constructed that cannot be templatized" - ) - - # Walk the constraint - conLine = ( - ' ' * tbSpc - + algn - + ' %s %s' % (visitor.walk_expression(con_template), trailingAligner) - ) - - # Multiple constraints are generated using a set - if len(indices) > 0: - idxTag = '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( - indices[0]._group, - setMap[indices[0]._set], - ) - setTag = '__S_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( - indices[0]._group, - setMap[indices[0]._set], - ) - - conLine += '%s %s \\in %s ' % (forallTag, idxTag, setTag) - pstr += conLine - - # Add labels as needed - if not use_equation_environment: - pstr += '\\label{con:' + pyomo_component.name + '_' + con.name + '} ' - - # prevents an emptly blank line from being at the end of the latex output - if i <= len(constraints) - 2: - pstr += tail - else: - pstr += tail - # pstr += '\n' - - # Print bounds and sets - if not isSingle: - variableList = [ - vr - for vr in pyomo_component.component_objects( - pyo.Var, descend_into=True, active=True - ) - ] - - varBoundData = [] - for i in range(0, len(variableList)): - vr = variableList[i] - if isinstance(vr, ScalarVar): - varBoundDataEntry = analyze_variable(vr, visitor) - varBoundData.append(varBoundDataEntry) - elif isinstance(vr, IndexedVar): - varBoundData_indexedVar = [] - setData = vr.index_set().data() - for sd in setData: - varBoundDataEntry = analyze_variable(vr[sd], visitor) - varBoundData_indexedVar.append(varBoundDataEntry) - globIndexedVariables = True - for j in range(0, len(varBoundData_indexedVar) - 1): - chks = [] - chks.append( - varBoundData_indexedVar[j]['lowerBound'] - == varBoundData_indexedVar[j + 1]['lowerBound'] - ) - chks.append( - varBoundData_indexedVar[j]['upperBound'] - == varBoundData_indexedVar[j + 1]['upperBound'] - ) - chks.append( - varBoundData_indexedVar[j]['domainName'] - == varBoundData_indexedVar[j + 1]['domainName'] - ) - if not all(chks): - globIndexedVariables = False - break - if globIndexedVariables: - varBoundData.append( - { - 'variable': vr, - 'lowerBound': varBoundData_indexedVar[0]['lowerBound'], - 'upperBound': varBoundData_indexedVar[0]['upperBound'], - 'domainName': varBoundData_indexedVar[0]['domainName'], - 'domainLatex': varBoundData_indexedVar[0]['domainLatex'], - } - ) - else: - varBoundData += varBoundData_indexedVar - else: - raise DeveloperError( - 'Variable is not a variable. Should not happen. Contact developers' - ) - - # print the accumulated data to the string - bstr = '' - appendBoundString = False - useThreeAlgn = False - for i in range(0, len(varBoundData)): - vbd = varBoundData[i] - if ( - vbd['lowerBound'] == '' - and vbd['upperBound'] == '' - and vbd['domainName'] == 'Reals' - ): - # unbounded all real, do not print - if i <= len(varBoundData) - 2: - bstr = bstr[0:-2] - else: - if not useThreeAlgn: - algn = '& &' - useThreeAlgn = True - else: - algn = '&&&' - - if use_equation_environment: - conLabel = '' - else: - conLabel = ( - ' \\label{con:' - + pyomo_component.name - + '_' - + variableMap[vbd['variable']] - + '_bound' - + '} ' - ) - - appendBoundString = True - coreString = ( - vbd['lowerBound'] - + variableMap[vbd['variable']] - + vbd['upperBound'] - + ' ' - + trailingAligner - + '\\qquad \\in ' - + vbd['domainLatex'] - + conLabel - ) - bstr += ' ' * tbSpc + algn + ' %s' % (coreString) - if i <= len(varBoundData) - 2: - bstr += '\\\\ \n' - else: - bstr += '\n' - - if appendBoundString: - pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['with bounds']) - pstr += bstr + '\n' - else: - pstr = pstr[0:-4] + '\n' - - # close off the print string - if not use_equation_environment: - pstr += '\\end{align} \n' - else: - if not isSingle: - pstr += ' \\end{aligned} \n' - pstr += ' \\label{%s} \n' % (pyomo_component.name) - pstr += '\\end{equation} \n' - - # Handling the iterator indices - defaultSetLatexNames = ComponentMap() - for i in range(0, len(setList)): - st = setList[i] - if use_smart_variables: - chkName = setList[i].name - if len(chkName) == 1 and chkName.upper() == chkName: - chkName += '_mathcal' - defaultSetLatexNames[st] = applySmartVariables(chkName) - else: - defaultSetLatexNames[st] = setList[i].name.replace('_', '\\_') - - ## Could be used in the future if someone has a lot of sets - # defaultSetLatexNames[st] = 'mathcal{' + alphabetStringGenerator(i).upper() + '}' - - if st in overwrite_dict.keys(): - if use_smart_variables: - defaultSetLatexNames[st] = applySmartVariables(overwrite_dict[st][0]) - else: - defaultSetLatexNames[st] = overwrite_dict[st][0].replace('_', '\\_') - - defaultSetLatexNames[st] = defaultSetLatexNames[st].replace( - '\\mathcal', r'\\mathcal' - ) - - latexLines = pstr.split('\n') - for jj in range(0, len(latexLines)): - groupMap = {} - uniqueSets = [] - ln = latexLines[jj] - # only modify if there is a placeholder in the line - if "PLACEHOLDER_8675309_GROUP_" in ln: - splitLatex = ln.split('__') - # Find the unique combinations of group numbers and set names - for word in splitLatex: - if "PLACEHOLDER_8675309_GROUP_" in word: - ifo = word.split("PLACEHOLDER_8675309_GROUP_")[1] - gpNum, stName = ifo.split('_') - if gpNum not in groupMap.keys(): - groupMap[gpNum] = [stName] - if stName not in uniqueSets: - uniqueSets.append(stName) - - # Determine if the set is continuous - setInfo = dict( - zip( - uniqueSets, - [{'continuous': False} for i in range(0, len(uniqueSets))], - ) - ) - - for ky, vl in setInfo.items(): - ix = int(ky[3:]) - 1 - setInfo[ky]['setObject'] = setList[ix] - setInfo[ky][ - 'setRegEx' - ] = r'__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__' % (ky) - setInfo[ky][ - 'sumSetRegEx' - ] = r'sum_{__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__}' % (ky) - # setInfo[ky]['idxRegEx'] = r'__I_PLACEHOLDER_8675309_GROUP_[0-9*]_%s__'%(ky) - - if split_continuous_sets: - for ky, vl in setInfo.items(): - st = vl['setObject'] - stData = st.data() - stCont = True - for ii in range(0, len(stData)): - if ii + stData[0] != stData[ii]: - stCont = False - break - setInfo[ky]['continuous'] = stCont - - # replace the sets - for ky, vl in setInfo.items(): - # if the set is continuous and the flag has been set - if split_continuous_sets and setInfo[ky]['continuous']: - st = setInfo[ky]['setObject'] - stData = st.data() - bgn = stData[0] - ed = stData[-1] - - replacement = ( - r'sum_{ __I_PLACEHOLDER_8675309_GROUP_\1_%s__ = %d }^{%d}' - % (ky, bgn, ed) - ) - ln = re.sub(setInfo[ky]['sumSetRegEx'], replacement, ln) - else: - # if the set is not continuous or the flag has not been set - replacement = ( - r'sum_{ __I_PLACEHOLDER_8675309_GROUP_\1_%s__ \\in __S_PLACEHOLDER_8675309_GROUP_\1_%s__ }' - % (ky, ky) - ) - ln = re.sub(setInfo[ky]['sumSetRegEx'], replacement, ln) - - replacement = defaultSetLatexNames[setInfo[ky]['setObject']] - ln = re.sub(setInfo[ky]['setRegEx'], replacement, ln) - - # groupNumbers = re.findall(r'__I_PLACEHOLDER_8675309_GROUP_([0-9*])_SET[0-9]*__',ln) - setNumbers = re.findall( - r'__I_PLACEHOLDER_8675309_GROUP_[0-9*]_SET([0-9]*)__', ln - ) - groupSetPairs = re.findall( - r'__I_PLACEHOLDER_8675309_GROUP_([0-9*])_SET([0-9]*)__', ln - ) - - groupInfo = {} - for vl in setNumbers: - groupInfo['SET' + vl] = { - 'setObject': setInfo['SET' + vl]['setObject'], - 'indices': [], - } - - for gp in groupSetPairs: - if gp[0] not in groupInfo['SET' + gp[1]]['indices']: - groupInfo['SET' + gp[1]]['indices'].append(gp[0]) - - indexCounter = 0 - for ky, vl in groupInfo.items(): - indexNames = latex_component_map[vl['setObject']][1] - if vl['setObject'] in latex_component_map.keys() and len(indexNames) != 0 : - indexNames = overwrite_dict[vl['setObject']][1] - if len(indexNames) < len(vl['indices']): - raise ValueError( - 'Insufficient number of indices provided to the overwrite dictionary for set %s' - % (vl['setObject'].name) - ) - for i in range(0, len(indexNames)): - ln = ln.replace( - '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' - % (vl['indices'][i], ky), - indexNames[i], - ) - else: - for i in range(0, len(vl['indices'])): - ln = ln.replace( - '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' - % (vl['indices'][i], ky), - alphabetStringGenerator(indexCounter, True), - ) - indexCounter += 1 - - # print('gn',groupInfo) - - latexLines[jj] = ln - - pstr = '\n'.join(latexLines) - # pstr = pstr.replace('\\mathcal{', 'mathcal{') - # pstr = pstr.replace('mathcal{', '\\mathcal{') - - if x_only_mode in [1, 2, 3]: - # Need to preserve only the set elements in the overwrite_dict - new_overwrite_dict = {} - for ky, vl in overwrite_dict.items(): - if isinstance(ky, _GeneralVarData): - pass - elif isinstance(ky, _ParamData): - pass - elif isinstance(ky, _SetData): - new_overwrite_dict[ky] = overwrite_dict[ky] - else: - raise ValueError( - 'The overwrite_dict object has a key of invalid type: %s' - % (str(ky)) - ) - overwrite_dict = new_overwrite_dict - - # # Only x modes - # # Mode 0 : dont use - # # Mode 1 : indexed variables become x_{_{ix}} - # # Mode 2 : uses standard alphabet [a,...,z,aa,...,az,...,aaa,...] with subscripts for indices, ex: abcd_{ix} - # # Mode 3 : unwrap everything into an x_{} list, including the indexed vars themselves - - if x_only_mode == 1: - vrIdx = 0 - new_variableMap = ComponentMap() - for i in range(0, len(variableList)): - vr = variableList[i] - vrIdx += 1 - if isinstance(vr, ScalarVar): - new_variableMap[vr] = 'x_{' + str(vrIdx) + '}' - elif isinstance(vr, IndexedVar): - new_variableMap[vr] = 'x_{' + str(vrIdx) + '}' - for sd in vr.index_set().data(): - # vrIdx += 1 - sdString = str(sd) - if sdString[0] == '(': - sdString = sdString[1:] - if sdString[-1] == ')': - sdString = sdString[0:-1] - new_variableMap[vr[sd]] = ( - 'x_{' + str(vrIdx) + '_{' + sdString + '}' + '}' - ) - else: - raise DeveloperError( - 'Variable is not a variable. Should not happen. Contact developers' - ) - - pmIdx = 0 - new_parameterMap = ComponentMap() - for i in range(0, len(parameterList)): - pm = parameterList[i] - pmIdx += 1 - if isinstance(pm, ScalarParam): - new_parameterMap[pm] = 'p_{' + str(pmIdx) + '}' - elif isinstance(pm, IndexedParam): - new_parameterMap[pm] = 'p_{' + str(pmIdx) + '}' - for sd in pm.index_set().data(): - sdString = str(sd) - if sdString[0] == '(': - sdString = sdString[1:] - if sdString[-1] == ')': - sdString = sdString[0:-1] - new_parameterMap[pm[sd]] = ( - 'p_{' + str(pmIdx) + '_{' + sdString + '}' + '}' - ) - else: - raise DeveloperError( - 'Parameter is not a parameter. Should not happen. Contact developers' - ) - - new_overwrite_dict = ComponentMap() - for ky, vl in new_variableMap.items(): - new_overwrite_dict[ky] = vl - for ky, vl in new_parameterMap.items(): - new_overwrite_dict[ky] = vl - for ky, vl in overwrite_dict.items(): - new_overwrite_dict[ky] = vl - overwrite_dict = new_overwrite_dict - - elif x_only_mode == 2: - vrIdx = 0 - new_variableMap = ComponentMap() - for i in range(0, len(variableList)): - vr = variableList[i] - vrIdx += 1 - if isinstance(vr, ScalarVar): - new_variableMap[vr] = alphabetStringGenerator(i) - elif isinstance(vr, IndexedVar): - new_variableMap[vr] = alphabetStringGenerator(i) - for sd in vr.index_set().data(): - # vrIdx += 1 - sdString = str(sd) - if sdString[0] == '(': - sdString = sdString[1:] - if sdString[-1] == ')': - sdString = sdString[0:-1] - new_variableMap[vr[sd]] = ( - alphabetStringGenerator(i) + '_{' + sdString + '}' - ) - else: - raise DeveloperError( - 'Variable is not a variable. Should not happen. Contact developers' - ) - - pmIdx = vrIdx - 1 - new_parameterMap = ComponentMap() - for i in range(0, len(parameterList)): - pm = parameterList[i] - pmIdx += 1 - if isinstance(pm, ScalarParam): - new_parameterMap[pm] = alphabetStringGenerator(pmIdx) - elif isinstance(pm, IndexedParam): - new_parameterMap[pm] = alphabetStringGenerator(pmIdx) - for sd in pm.index_set().data(): - sdString = str(sd) - if sdString[0] == '(': - sdString = sdString[1:] - if sdString[-1] == ')': - sdString = sdString[0:-1] - new_parameterMap[pm[sd]] = ( - alphabetStringGenerator(pmIdx) + '_{' + sdString + '}' - ) - else: - raise DeveloperError( - 'Parameter is not a parameter. Should not happen. Contact developers' - ) - - new_overwrite_dict = ComponentMap() - for ky, vl in new_variableMap.items(): - new_overwrite_dict[ky] = vl - for ky, vl in new_parameterMap.items(): - new_overwrite_dict[ky] = vl - for ky, vl in overwrite_dict.items(): - new_overwrite_dict[ky] = vl - overwrite_dict = new_overwrite_dict - - elif x_only_mode == 3: - new_overwrite_dict = ComponentMap() - for ky, vl in variableMap.items(): - new_overwrite_dict[ky] = vl - for ky, vl in parameterMap.items(): - new_overwrite_dict[ky] = vl - for ky, vl in overwrite_dict.items(): - new_overwrite_dict[ky] = vl - overwrite_dict = new_overwrite_dict - - else: - vrIdx = 0 - new_variableMap = ComponentMap() - for i in range(0, len(variableList)): - vr = variableList[i] - vrIdx += 1 - if isinstance(vr, ScalarVar): - new_variableMap[vr] = vr.name - elif isinstance(vr, IndexedVar): - new_variableMap[vr] = vr.name - for sd in vr.index_set().data(): - # vrIdx += 1 - sdString = str(sd) - if sdString[0] == '(': - sdString = sdString[1:] - if sdString[-1] == ')': - sdString = sdString[0:-1] - if use_smart_variables: - new_variableMap[vr[sd]] = applySmartVariables( - vr.name + '_' + sdString - ) - else: - new_variableMap[vr[sd]] = vr[sd].name - else: - raise DeveloperError( - 'Variable is not a variable. Should not happen. Contact developers' - ) - - pmIdx = 0 - new_parameterMap = ComponentMap() - for i in range(0, len(parameterList)): - pm = parameterList[i] - pmIdx += 1 - if isinstance(pm, ScalarParam): - new_parameterMap[pm] = pm.name - elif isinstance(pm, IndexedParam): - new_parameterMap[pm] = pm.name - for sd in pm.index_set().data(): - # pmIdx += 1 - sdString = str(sd) - if sdString[0] == '(': - sdString = sdString[1:] - if sdString[-1] == ')': - sdString = sdString[0:-1] - if use_smart_variables: - new_parameterMap[pm[sd]] = applySmartVariables( - pm.name + '_' + sdString - ) - else: - new_parameterMap[pm[sd]] = str(pm[sd]) # .name - else: - raise DeveloperError( - 'Parameter is not a parameter. Should not happen. Contact developers' - ) - - for ky, vl in new_variableMap.items(): - if ky not in overwrite_dict.keys(): - overwrite_dict[ky] = vl - for ky, vl in new_parameterMap.items(): - if ky not in overwrite_dict.keys(): - overwrite_dict[ky] = vl - - rep_dict = {} - for ky in list(reversed(list(overwrite_dict.keys()))): - if isinstance(ky, (pyo.Var, pyo.Param)): - if use_smart_variables and x_only_mode in [0, 3]: - overwrite_value = applySmartVariables(overwrite_dict[ky]) - else: - overwrite_value = overwrite_dict[ky] - rep_dict[variableMap[ky]] = overwrite_value - elif isinstance(ky, (_GeneralVarData, _ParamData)): - if use_smart_variables and x_only_mode in [3]: - overwrite_value = applySmartVariables(overwrite_dict[ky]) - else: - overwrite_value = overwrite_dict[ky] - rep_dict[variableMap[ky]] = overwrite_value - elif isinstance(ky, _SetData): - # already handled - pass - elif isinstance(ky, (float, int)): - # happens when immutable parameters are used, do nothing - pass - else: - raise ValueError( - 'The overwrite_dict object has a key of invalid type: %s' % (str(ky)) - ) - - if not use_smart_variables: - for ky, vl in rep_dict.items(): - rep_dict[ky] = vl.replace('_', '\\_') - - label_rep_dict = copy.deepcopy(rep_dict) - for ky, vl in label_rep_dict.items(): - label_rep_dict[ky] = vl.replace('{', '').replace('}', '').replace('\\', '') - - splitLines = pstr.split('\n') - for i in range(0, len(splitLines)): - if use_equation_environment: - splitLines[i] = multiple_replace(splitLines[i], rep_dict) - else: - if '\\label{' in splitLines[i]: - epr, lbl = splitLines[i].split('\\label{') - epr = multiple_replace(epr, rep_dict) - lbl = multiple_replace(lbl, label_rep_dict) - splitLines[i] = epr + '\\label{' + lbl - - pstr = '\n'.join(splitLines) - - pattern = r'_{([^{]*)}_{([^{]*)}' - replacement = r'_{\1_{\2}}' - pstr = re.sub(pattern, replacement, pstr) - - pattern = r'_(.)_{([^}]*)}' - replacement = r'_{\1_{\2}}' - pstr = re.sub(pattern, replacement, pstr) - - splitLines = pstr.split('\n') - finalLines = [] - for sl in splitLines: - if sl != '': - finalLines.append(sl) - - pstr = '\n'.join(finalLines) - - # optional write to output file - if filename is not None: - fstr = '' - fstr += '\\documentclass{article} \n' - fstr += '\\usepackage{amsmath} \n' - fstr += '\\usepackage{amssymb} \n' - fstr += '\\usepackage{dsfont} \n' - fstr += '\\allowdisplaybreaks \n' - fstr += '\\begin{document} \n' - fstr += pstr - fstr += '\\end{document} \n' - f = open(filename, 'w') - f.write(fstr) - f.close() - - # return the latex string - return pstr diff --git a/pyomo/util/tests/test_latex_printer.py b/pyomo/util/tests/test_latex_printer.py index aff6932616e..8649bcc462a 100644 --- a/pyomo/util/tests/test_latex_printer.py +++ b/pyomo/util/tests/test_latex_printer.py @@ -14,6 +14,7 @@ import pyomo.environ as pyo from textwrap import dedent from pyomo.common.tempfiles import TempfileManager +from pyomo.common.collections.component_map import ComponentMap def generate_model(): @@ -127,6 +128,115 @@ def generate_simple_model_2(): class TestLatexPrinter(unittest.TestCase): + def test_latexPrinter_simpleDocTests(self): + # Ex 1 ----------------------- + m = pyo.ConcreteModel(name = 'basicFormulation') + m.x = pyo.Var() + m.y = pyo.Var() + pstr = latex_printer(m.x + m.y) + bstr = dedent( + r""" + \begin{equation} + x + y + \end{equation} + """ + ) + self.assertEqual('\n' + pstr + '\n', bstr) + + # Ex 2 ----------------------- + m = pyo.ConcreteModel(name = 'basicFormulation') + m.x = pyo.Var() + m.y = pyo.Var() + m.expression_1 = pyo.Expression(expr = m.x**2 + m.y**2) + pstr = latex_printer(m.expression_1) + bstr = dedent( + r""" + \begin{equation} + x^{2} + y^{2} + \end{equation} + """ + ) + self.assertEqual('\n' + pstr + '\n', bstr) + + # Ex 3 ----------------------- + m = pyo.ConcreteModel(name = 'basicFormulation') + m.x = pyo.Var() + m.y = pyo.Var() + m.constraint_1 = pyo.Constraint(expr = m.x**2 + m.y**2 <= 1.0) + pstr = latex_printer(m.constraint_1) + bstr = dedent( + r""" + \begin{equation} + x^{2} + y^{2} \leq 1 + \end{equation} + """ + ) + self.assertEqual('\n' + pstr + '\n', bstr) + + # Ex 4 ----------------------- + m = pyo.ConcreteModel(name='basicFormulation') + m.I = pyo.Set(initialize=[1, 2, 3, 4, 5]) + m.v = pyo.Var(m.I) + def ruleMaker(m): return sum(m.v[i] for i in m.I) <= 0 + m.constraint = pyo.Constraint(rule=ruleMaker) + pstr = latex_printer(m.constraint) + bstr = dedent( + r""" + \begin{equation} + \sum_{ i \in I } v_{i} \leq 0 + \end{equation} + """ + ) + self.assertEqual('\n' + pstr + '\n', bstr) + + # Ex 5 ----------------------- + m = pyo.ConcreteModel(name = 'basicFormulation') + m.x = pyo.Var() + m.y = pyo.Var() + m.z = pyo.Var() + m.c = pyo.Param(initialize=1.0, mutable=True) + m.objective = pyo.Objective( expr = m.x + m.y + m.z ) + m.constraint_1 = pyo.Constraint(expr = m.x**2 + m.y**2.0 - m.z**2.0 <= m.c ) + pstr = latex_printer(m) + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x + y + z & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} + y^{2} - z^{2} \leq c & \label{con:basicFormulation_constraint_1} + \end{align} + """ + ) + self.assertEqual('\n' + pstr + '\n', bstr) + + # Ex 6 ----------------------- + m = pyo.ConcreteModel(name='basicFormulation') + m.I = pyo.Set(initialize=[1, 2, 3, 4, 5]) + m.v = pyo.Var(m.I) + def ruleMaker(m): return sum(m.v[i] for i in m.I) <= 0 + m.constraint = pyo.Constraint(rule=ruleMaker) + lcm = ComponentMap() + lcm[m.v] = 'x' + lcm[m.I] = ['\\mathcal{A}',['j','k']] + pstr = latex_printer(m.constraint, latex_component_map=lcm) + bstr = dedent( + r""" + \begin{equation} + \sum_{ j \in \mathcal{A} } x_{j} \leq 0 + \end{equation} + """ + ) + self.assertEqual('\n' + pstr + '\n', bstr) + + def test_latexPrinter_checkAlphabetFunction(self): + from pyomo.util.latex_printer import alphabetStringGenerator + self.assertEqual('z',alphabetStringGenerator(25)) + self.assertEqual('aa',alphabetStringGenerator(26)) + self.assertEqual('alm',alphabetStringGenerator(1000)) + self.assertEqual('iqni',alphabetStringGenerator(1000,True)) + + def test_latexPrinter_objective(self): m = generate_model() pstr = latex_printer(m.objective_1) @@ -138,7 +248,7 @@ def test_latexPrinter_objective(self): \end{equation} """ ) - self.assertEqual('\n' + pstr, bstr) + self.assertEqual('\n' + pstr + '\n', bstr) pstr = latex_printer(m.objective_3) bstr = dedent( @@ -149,7 +259,7 @@ def test_latexPrinter_objective(self): \end{equation} """ ) - self.assertEqual('\n' + pstr, bstr) + self.assertEqual('\n' + pstr + '\n', bstr) def test_latexPrinter_constraint(self): m = generate_model() @@ -163,7 +273,7 @@ def test_latexPrinter_constraint(self): """ ) - self.assertEqual('\n' + pstr, bstr) + self.assertEqual('\n' + pstr + '\n', bstr) def test_latexPrinter_expression(self): m = generate_model() @@ -180,7 +290,7 @@ def test_latexPrinter_expression(self): """ ) - self.assertEqual('\n' + pstr, bstr) + self.assertEqual('\n' + pstr + '\n', bstr) def test_latexPrinter_simpleExpression(self): m = generate_model() @@ -193,7 +303,7 @@ def test_latexPrinter_simpleExpression(self): \end{equation} """ ) - self.assertEqual('\n' + pstr, bstr) + self.assertEqual('\n' + pstr + '\n', bstr) pstr = latex_printer(m.x - 2 * m.y) bstr = dedent( @@ -203,7 +313,7 @@ def test_latexPrinter_simpleExpression(self): \end{equation} """ ) - self.assertEqual('\n' + pstr, bstr) + self.assertEqual('\n' + pstr + '\n', bstr) def test_latexPrinter_unary(self): m = generate_model() @@ -216,7 +326,7 @@ def test_latexPrinter_unary(self): \end{equation} """ ) - self.assertEqual('\n' + pstr, bstr) + self.assertEqual('\n' + pstr + '\n', bstr) pstr = latex_printer(pyo.Constraint(expr=pyo.sin(m.x) == 1)) bstr = dedent( @@ -226,7 +336,7 @@ def test_latexPrinter_unary(self): \end{equation} """ ) - self.assertEqual('\n' + pstr, bstr) + self.assertEqual('\n' + pstr + '\n', bstr) pstr = latex_printer(pyo.Constraint(expr=pyo.log10(m.x) == 1)) bstr = dedent( @@ -236,7 +346,7 @@ def test_latexPrinter_unary(self): \end{equation} """ ) - self.assertEqual('\n' + pstr, bstr) + self.assertEqual('\n' + pstr + '\n', bstr) pstr = latex_printer(pyo.Constraint(expr=pyo.sqrt(m.x) == 1)) bstr = dedent( @@ -246,7 +356,7 @@ def test_latexPrinter_unary(self): \end{equation} """ ) - self.assertEqual('\n' + pstr, bstr) + self.assertEqual('\n' + pstr + '\n', bstr) def test_latexPrinter_rangedConstraint(self): m = generate_model() @@ -259,7 +369,7 @@ def test_latexPrinter_rangedConstraint(self): \end{equation} """ ) - self.assertEqual('\n' + pstr, bstr) + self.assertEqual('\n' + pstr + '\n', bstr) def test_latexPrinter_exprIf(self): m = generate_model() @@ -272,7 +382,7 @@ def test_latexPrinter_exprIf(self): \end{equation} """ ) - self.assertEqual('\n' + pstr, bstr) + self.assertEqual('\n' + pstr + '\n', bstr) def test_latexPrinter_blackBox(self): m = generate_model() @@ -285,7 +395,7 @@ def test_latexPrinter_blackBox(self): \end{equation} """ ) - self.assertEqual('\n' + pstr, bstr) + self.assertEqual('\n' + pstr + '\n', bstr) def test_latexPrinter_iteratedConstraints(self): m = generate_model() @@ -294,122 +404,101 @@ def test_latexPrinter_iteratedConstraints(self): bstr = dedent( r""" \begin{equation} - \left( x + y \right) \sum_{i \in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I + \left( x + y \right) \sum_{ i \in I } v_{i} + u_{i,j}^{2} \leq 0 \qquad \forall j \in I \end{equation} """ ) - self.assertEqual('\n' + pstr, bstr) + self.assertEqual('\n' + pstr + '\n', bstr) pstr = latex_printer(m.constraint_8) bstr = dedent( r""" \begin{equation} - \sum_{k \in K} p_{k} = 1 - \end{equation} - """ - ) - self.assertEqual('\n' + pstr, bstr) - - def test_latexPrinter_model(self): - m = generate_simple_model() - - pstr = latex_printer(m) - bstr = dedent( - r""" - \begin{equation} - \begin{aligned} - & \text{minimize} - & & x + y \\ - & \text{subject to} - & & x^{2} + y^{2} \leq 1 \\ - &&& 0 \leq x \\ - &&& \left( x + y \right) \sum_{i \in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I \\ - &&& \sum_{k \in K} p_{k} = 1 - \end{aligned} - \label{basicFormulation} - \end{equation} - """ - ) - self.assertEqual('\n' + pstr, bstr) - - pstr = latex_printer(m, None, True) - bstr = dedent( - r""" - \begin{align} - & \text{minimize} - & & x + y & \label{obj:basicFormulation_objective_1} \\ - & \text{subject to} - & & x^{2} + y^{2} \leq 1 & \label{con:basicFormulation_constraint_1} \\ - &&& 0 \leq x & \label{con:basicFormulation_constraint_2} \\ - &&& \left( x + y \right) \sum_{i \in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I & \label{con:basicFormulation_constraint_7} \\ - &&& \sum_{k \in K} p_{k} = 1 & \label{con:basicFormulation_constraint_8} - \end{align} - """ - ) - self.assertEqual('\n' + pstr, bstr) - - pstr = latex_printer(m, None, False, True) - bstr = dedent( - r""" - \begin{equation} - \begin{aligned} - & \text{minimize} - & & x + y \\ - & \text{subject to} - & & x^{2} + y^{2} \leq 1 \\ - &&& 0 \leq x \\ - &&& \left( x + y \right) \sum_{i = 1}^{5} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I \\ - &&& \sum_{k \in K} p_{k} = 1 - \end{aligned} - \label{basicFormulation} - \end{equation} - """ - ) - self.assertEqual('\n' + pstr, bstr) - - pstr = latex_printer(m, None, True, True) - bstr = dedent( - r""" - \begin{align} - & \text{minimize} - & & x + y & \label{obj:basicFormulation_objective_1} \\ - & \text{subject to} - & & x^{2} + y^{2} \leq 1 & \label{con:basicFormulation_constraint_1} \\ - &&& 0 \leq x & \label{con:basicFormulation_constraint_2} \\ - &&& \left( x + y \right) \sum_{i = 1}^{5} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I & \label{con:basicFormulation_constraint_7} \\ - &&& \sum_{k \in K} p_{k} = 1 & \label{con:basicFormulation_constraint_8} - \end{align} - """ - ) - self.assertEqual('\n' + pstr, bstr) - - def test_latexPrinter_advancedVariables(self): - m = generate_simple_model_2() - - pstr = latex_printer(m, use_smart_variables=True) - bstr = dedent( - r""" - \begin{equation} - \begin{aligned} - & \text{minimize} - & & y_{sub1_{sub2_{sub3}}} \\ - & \text{subject to} - & & \left( \dot{x} + \bar{x} + x_{star} + \hat{x} + \hat{x}_{1} \right) ^{2} \leq y_{sub1_{sub2_{sub3}}} \\ - &&& \left( \dot{x} + \bar{x} \right) ^{ \left( - \left( x_{star} + \hat{x} \right) \right) } \leq y_{sub1_{sub2_{sub3}}} \\ - &&& - \left( \dot{x} + \bar{x} \right) - \left( x_{star} + \hat{x} \right) \leq y_{sub1_{sub2_{sub3}}} - \end{aligned} - \label{basicFormulation} + \sum_{ i \in K } p_{i} = 1 \end{equation} """ ) - self.assertEqual('\n' + pstr, bstr) + self.assertEqual('\n' + pstr + '\n', bstr) + + # def test_latexPrinter_model(self): + # m = generate_simple_model() + + # pstr = latex_printer(m) + # bstr = dedent( + # r""" + # \begin{equation} + # \begin{aligned} + # & \text{minimize} + # & & x + y \\ + # & \text{subject to} + # & & x^{2} + y^{2} \leq 1 \\ + # &&& 0 \leq x \\ + # &&& \left( x + y \right) \sum_{i \in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I \\ + # &&& \sum_{k \in K} p_{k} = 1 + # \end{aligned} + # \label{basicFormulation} + # \end{equation} + # """ + # ) + # self.assertEqual('\n' + pstr, bstr) + + # pstr = latex_printer(m, None, True) + # bstr = dedent( + # r""" + # \begin{align} + # & \text{minimize} + # & & x + y & \label{obj:basicFormulation_objective_1} \\ + # & \text{subject to} + # & & x^{2} + y^{2} \leq 1 & \label{con:basicFormulation_constraint_1} \\ + # &&& 0 \leq x & \label{con:basicFormulation_constraint_2} \\ + # &&& \left( x + y \right) \sum_{i \in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I & \label{con:basicFormulation_constraint_7} \\ + # &&& \sum_{k \in K} p_{k} = 1 & \label{con:basicFormulation_constraint_8} + # \end{align} + # """ + # ) + # self.assertEqual('\n' + pstr, bstr) + + # pstr = latex_printer(m, None, False, True) + # bstr = dedent( + # r""" + # \begin{equation} + # \begin{aligned} + # & \text{minimize} + # & & x + y \\ + # & \text{subject to} + # & & x^{2} + y^{2} \leq 1 \\ + # &&& 0 \leq x \\ + # &&& \left( x + y \right) \sum_{i = 1}^{5} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I \\ + # &&& \sum_{k \in K} p_{k} = 1 + # \end{aligned} + # \label{basicFormulation} + # \end{equation} + # """ + # ) + # self.assertEqual('\n' + pstr, bstr) + + # pstr = latex_printer(m, None, True, True) + # bstr = dedent( + # r""" + # \begin{align} + # & \text{minimize} + # & & x + y & \label{obj:basicFormulation_objective_1} \\ + # & \text{subject to} + # & & x^{2} + y^{2} \leq 1 & \label{con:basicFormulation_constraint_1} \\ + # &&& 0 \leq x & \label{con:basicFormulation_constraint_2} \\ + # &&& \left( x + y \right) \sum_{i = 1}^{5} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I & \label{con:basicFormulation_constraint_7} \\ + # &&& \sum_{k \in K} p_{k} = 1 & \label{con:basicFormulation_constraint_8} + # \end{align} + # """ + # ) + # self.assertEqual('\n' + pstr, bstr) def test_latexPrinter_fileWriter(self): m = generate_simple_model() with TempfileManager.new_context() as tempfile: fd, fname = tempfile.mkstemp() - pstr = latex_printer(m, fname) + pstr = latex_printer(m, write_object=fname) f = open(fname) bstr = f.read() From 7178027debb9b433c45657dc4f2c5b7cad6c08cf Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 11 Oct 2023 13:24:20 -0600 Subject: [PATCH 0290/1204] Parallel builds --- .github/workflows/release_wheel_creation.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index 5ae36645584..935249733a7 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -25,12 +25,15 @@ jobs: matrix: os: [ubuntu-22.04, windows-latest, macos-latest] arch: [native] + wheel-version: ['cp38-cp38', 'cp39-cp39', 'cp310-cp310', 'cp311-cp311'] include: # This doesn't work yet - have to explore why # - os: ubuntu-22.04 # arch: aarch64 - os: macos-latest arch: arm64 + - os: windows-latest + arch: arm64 steps: - uses: actions/checkout@v4 - name: Build wheels @@ -39,7 +42,8 @@ jobs: output-dir: dist env: CIBW_PLATFORM: auto - CIBW_SKIP: "pp* cp36* cp37* *-musllinux*" + CIBW_SKIP: "*-musllinux*" + CIBW_BUILD: ${{ matrix.wheel-version }} CIBW_BUILD_VERBOSITY: 1 CIBW_ARCHS: ${{ matrix.arch }} CIBW_BEFORE_BUILD: pip install cython pybind11 From e213d5cd9ec5531c7b462f6d0a768a192e207d9e Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 11 Oct 2023 13:36:50 -0600 Subject: [PATCH 0291/1204] Change regex for wheel-version --- .github/workflows/release_wheel_creation.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index 935249733a7..e5b72d3e0d4 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -19,13 +19,13 @@ env: jobs: bdist_wheel: - name: Build wheels (3.8+) on ${{ matrix.os }} for ${{ matrix.arch }} + name: Build wheels (${{ matrix.wheel-version }}) on ${{ matrix.os }} for ${{ matrix.arch }} runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-22.04, windows-latest, macos-latest] arch: [native] - wheel-version: ['cp38-cp38', 'cp39-cp39', 'cp310-cp310', 'cp311-cp311'] + wheel-version: ['cp38*', 'cp39*', 'cp310*', 'cp311*'] include: # This doesn't work yet - have to explore why # - os: ubuntu-22.04 From 3c27e75115041ceb315df8b13885cea725ca4353 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 11 Oct 2023 13:40:11 -0600 Subject: [PATCH 0292/1204] Fix Windows/ARM and add wheel versions --- .github/workflows/release_wheel_creation.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index e5b72d3e0d4..f5676ccc554 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -32,8 +32,10 @@ jobs: # arch: aarch64 - os: macos-latest arch: arm64 + wheel-version: ['cp38*', 'cp39*', 'cp310*', 'cp311*'] - os: windows-latest - arch: arm64 + arch: ARM64 + wheel-version: ['cp38*', 'cp39*', 'cp310*', 'cp311*'] steps: - uses: actions/checkout@v4 - name: Build wheels From 2a82cd72e10de0f6756b9e6c34266786c55816f3 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 11 Oct 2023 13:49:49 -0600 Subject: [PATCH 0293/1204] Different attempt for building wheels including emulation --- .github/workflows/release_wheel_creation.yml | 24 ++++++++------------ 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index f5676ccc554..4f2f37961eb 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -19,35 +19,31 @@ env: jobs: bdist_wheel: - name: Build wheels (${{ matrix.wheel-version }}) on ${{ matrix.os }} for ${{ matrix.arch }} + name: Build wheels (${{ matrix.wheel-version }}) on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-22.04, windows-latest, macos-latest] - arch: [native] + arch: [all] wheel-version: ['cp38*', 'cp39*', 'cp310*', 'cp311*'] - include: - # This doesn't work yet - have to explore why - # - os: ubuntu-22.04 - # arch: aarch64 - - os: macos-latest - arch: arm64 - wheel-version: ['cp38*', 'cp39*', 'cp310*', 'cp311*'] - - os: windows-latest - arch: ARM64 - wheel-version: ['cp38*', 'cp39*', 'cp310*', 'cp311*'] steps: - uses: actions/checkout@v4 + - name: Set up QEMU + if: runner.os == 'Linux' + uses: docker/setup-qemu-action@v3 + with: + platforms: all - name: Build wheels uses: pypa/cibuildwheel@v2.16.2 with: output-dir: dist env: - CIBW_PLATFORM: auto + CIBW_ARCHS_LINUX: "auto aarch64" + CIBW_ARCHS_MACOS: "auto arm64" + CIBW_ARCHS_WINDOWS: "auto ARM64" CIBW_SKIP: "*-musllinux*" CIBW_BUILD: ${{ matrix.wheel-version }} CIBW_BUILD_VERBOSITY: 1 - CIBW_ARCHS: ${{ matrix.arch }} CIBW_BEFORE_BUILD: pip install cython pybind11 CIBW_CONFIG_SETTINGS: '--global-option="--with-cython --with-distributable-extensions"' - uses: actions/upload-artifact@v3 From df1073609d674b126d9dd5a182d57bdd4adde00d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 11 Oct 2023 14:02:31 -0600 Subject: [PATCH 0294/1204] Turn of 32-bit wheels --- .github/workflows/release_wheel_creation.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index 4f2f37961eb..b991b8b2d02 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -38,9 +38,9 @@ jobs: with: output-dir: dist env: - CIBW_ARCHS_LINUX: "auto aarch64" - CIBW_ARCHS_MACOS: "auto arm64" - CIBW_ARCHS_WINDOWS: "auto ARM64" + CIBW_ARCHS_LINUX: "native aarch64" + CIBW_ARCHS_MACOS: "native arm64" + CIBW_ARCHS_WINDOWS: "native ARM64" CIBW_SKIP: "*-musllinux*" CIBW_BUILD: ${{ matrix.wheel-version }} CIBW_BUILD_VERBOSITY: 1 From ddb8eb02af2ebf605c5f6e968a30aa155d23a848 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 11 Oct 2023 14:48:26 -0600 Subject: [PATCH 0295/1204] Separate native and alt arches --- .github/workflows/release_wheel_creation.yml | 40 +++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index b991b8b2d02..886bb43342a 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -18,8 +18,36 @@ env: PYOMO_SETUP_ARGS: "--with-cython --with-distributable-extensions" jobs: - bdist_wheel: - name: Build wheels (${{ matrix.wheel-version }}) on ${{ matrix.os }} + native_wheels: + name: Build wheels (${{ matrix.wheel-version }}) on ${{ matrix.os }} for native architecture + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-22.04, windows-latest, macos-latest] + arch: [all] + wheel-version: ['cp38*', 'cp39*', 'cp310*', 'cp311*'] + steps: + - uses: actions/checkout@v4 + - name: Build wheels + uses: pypa/cibuildwheel@v2.16.2 + with: + output-dir: dist + env: + CIBW_ARCHS_LINUX: "native" + CIBW_ARCHS_MACOS: "native" + CIBW_ARCHS_WINDOWS: "native" + CIBW_SKIP: "*-musllinux*" + CIBW_BUILD: ${{ matrix.wheel-version }} + CIBW_BUILD_VERBOSITY: 1 + CIBW_BEFORE_BUILD: pip install cython pybind11 + CIBW_CONFIG_SETTINGS: '--global-option="--with-cython --with-distributable-extensions"' + - uses: actions/upload-artifact@v3 + with: + name: native_wheels + path: dist/*.whl + + alternative_wheels: + name: Build wheels (${{ matrix.wheel-version }}) on ${{ matrix.os }} for alternative architecture runs-on: ${{ matrix.os }} strategy: matrix: @@ -38,9 +66,9 @@ jobs: with: output-dir: dist env: - CIBW_ARCHS_LINUX: "native aarch64" - CIBW_ARCHS_MACOS: "native arm64" - CIBW_ARCHS_WINDOWS: "native ARM64" + CIBW_ARCHS_LINUX: "aarch64" + CIBW_ARCHS_MACOS: "arm64" + CIBW_ARCHS_WINDOWS: "ARM64" CIBW_SKIP: "*-musllinux*" CIBW_BUILD: ${{ matrix.wheel-version }} CIBW_BUILD_VERBOSITY: 1 @@ -48,7 +76,7 @@ jobs: CIBW_CONFIG_SETTINGS: '--global-option="--with-cython --with-distributable-extensions"' - uses: actions/upload-artifact@v3 with: - name: wheels + name: alt_wheels path: dist/*.whl generictarball: From 786dc93bef9eb2e5d1eb46f7cf416de56ce59329 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 11 Oct 2023 14:54:15 -0600 Subject: [PATCH 0296/1204] Try a different combination --- .github/workflows/release_wheel_creation.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index 886bb43342a..fba293b3a8f 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -19,7 +19,7 @@ env: jobs: native_wheels: - name: Build wheels (${{ matrix.wheel-version }}) on ${{ matrix.os }} for native architecture + name: Build wheels (${{ matrix.wheel-version }}) on ${{ matrix.os }} for native and cross-compiled architecture runs-on: ${{ matrix.os }} strategy: matrix: @@ -34,8 +34,8 @@ jobs: output-dir: dist env: CIBW_ARCHS_LINUX: "native" - CIBW_ARCHS_MACOS: "native" - CIBW_ARCHS_WINDOWS: "native" + CIBW_ARCHS_MACOS: "native arm64" + CIBW_ARCHS_WINDOWS: "native ARM64" CIBW_SKIP: "*-musllinux*" CIBW_BUILD: ${{ matrix.wheel-version }} CIBW_BUILD_VERBOSITY: 1 @@ -47,11 +47,11 @@ jobs: path: dist/*.whl alternative_wheels: - name: Build wheels (${{ matrix.wheel-version }}) on ${{ matrix.os }} for alternative architecture + name: Build wheels (${{ matrix.wheel-version }}) on ${{ matrix.os }} for aarch64 runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-22.04, windows-latest, macos-latest] + os: [ubuntu-22.04] arch: [all] wheel-version: ['cp38*', 'cp39*', 'cp310*', 'cp311*'] steps: @@ -67,8 +67,6 @@ jobs: output-dir: dist env: CIBW_ARCHS_LINUX: "aarch64" - CIBW_ARCHS_MACOS: "arm64" - CIBW_ARCHS_WINDOWS: "ARM64" CIBW_SKIP: "*-musllinux*" CIBW_BUILD: ${{ matrix.wheel-version }} CIBW_BUILD_VERBOSITY: 1 From 2a6f1d7c9b41f3d018a6095c86d4d5f54dd2bbdf Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 11 Oct 2023 23:12:28 -0400 Subject: [PATCH 0297/1204] add comments --- pyomo/contrib/mindtpy/single_tree.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index f3be27cbc4c..8b8e171c577 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -707,8 +707,8 @@ def __call__(self): # Reference: https://www.ibm.com/docs/en/icos/22.1.1?topic=SSSA5P_22.1.1/ilog.odms.cplex.help/refpythoncplex/html/cplex.callbacks.SolutionSource-class.htm # Another solution source is user_solution = 118, but it will not be encountered in LazyConstraintCallback. - config.logger.debug( - "Solution source: %s (111 node_solution, 117 heuristic_solution, 119 mipstart_solution)".format( + config.logger.info( + "Solution source: {} (111 node_solution, 117 heuristic_solution, 119 mipstart_solution)".format( self.get_solution_source() ) ) @@ -717,6 +717,7 @@ def __call__(self): # Lazy constraints separated when processing a MIP start will be discarded after that MIP start has been processed. # This means that the callback may have to separate the same constraint again for the next MIP start or for a solution that is found later in the solution process. # https://www.ibm.com/docs/en/icos/22.1.1?topic=SSSA5P_22.1.1/ilog.odms.cplex.help/refpythoncplex/html/cplex.callbacks.LazyConstraintCallback-class.htm + # For the MINLP3_simple example, all the solutions are obtained from mip_start (solution source). Therefore, it will not go to a branch and bound process.Cause an error output. if ( self.get_solution_source() != cplex.callbacks.SolutionSource.mipstart_solution From 0fdd4dc6e64b5c58fac1d59f8b4634e6f7f97a6c Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 12 Oct 2023 11:35:01 -0400 Subject: [PATCH 0298/1204] Update version number, changelog --- pyomo/contrib/pyros/CHANGELOG.txt | 12 ++++++++++++ pyomo/contrib/pyros/pyros.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/CHANGELOG.txt b/pyomo/contrib/pyros/CHANGELOG.txt index b1866ed955c..7977c37fb95 100644 --- a/pyomo/contrib/pyros/CHANGELOG.txt +++ b/pyomo/contrib/pyros/CHANGELOG.txt @@ -2,6 +2,18 @@ PyROS CHANGELOG =============== +------------------------------------------------------------------------------- +PyROS 1.2.8 12 Oct 2023 +------------------------------------------------------------------------------- +- Refactor PyROS separation routine, fix scenario selection heuristic +- Add efficiency for discrete uncertainty set separation +- Fix coefficient matching routine +- Fix subproblem timers and time accumulators +- Update and document PyROS solver logging system +- Fix iteration overcounting in event of `max_iter` termination status +- Fixes to (assembly of) PyROS `ROSolveResults` object + + ------------------------------------------------------------------------------- PyROS 1.2.7 26 Apr 2023 ------------------------------------------------------------------------------- diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 85e1a470aeb..e48690da5d6 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -49,7 +49,7 @@ from datetime import datetime -__version__ = "1.2.7" +__version__ = "1.2.8" default_pyros_solver_logger = setup_pyros_logger() From 7d86a3410d5bd0d7f177072591b0d1c6abd24b7d Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 12 Oct 2023 11:40:48 -0400 Subject: [PATCH 0299/1204] Update solver log docs --- doc/OnlineDocs/contributed_packages/pyros.rst | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/doc/OnlineDocs/contributed_packages/pyros.rst b/doc/OnlineDocs/contributed_packages/pyros.rst index 81fbeae7f1c..0bf8fa93be6 100644 --- a/doc/OnlineDocs/contributed_packages/pyros.rst +++ b/doc/OnlineDocs/contributed_packages/pyros.rst @@ -855,8 +855,8 @@ Observe that the log contains the following information: ============================================================================== PyROS: The Pyomo Robust Optimization Solver. - Version 1.2.7 | Git branch: unknown, commit hash: unknown - Invoked at UTC 2023-10-02T03:42:54.264507 + Version 1.2.8 | Git branch: unknown, commit hash: unknown + Invoked at UTC 2023-10-12T15:36:19.035916 Developed by: Natalie M. Isenberg (1), Jason A. F. Sherman (1), John D. Siirola (2), Chrysanthos E. Gounaris (1) @@ -892,7 +892,7 @@ Observe that the log contains the following information: p_robustness={} ------------------------------------------------------------------------------ Preprocessing... - Done preprocessing; required wall time of 0.232s. + Done preprocessing; required wall time of 0.175s. ------------------------------------------------------------------------------ Model statistics: Number of variables : 62 @@ -911,16 +911,16 @@ Observe that the log contains the following information: First-stage inequalities (incl. certain var bounds) : 10 Performance constraints (incl. var bounds) : 47 ------------------------------------------------------------------------------ - Itn Objective 1-Stg Shift DR Shift #CViol Max Viol Wall Time (s) + Itn Objective 1-Stg Shift DR Shift #CViol Max Viol Wall Time (s) ------------------------------------------------------------------------------ - 0 3.5838e+07 - - 5 1.8832e+04 1.212 - 1 3.5838e+07 7.4506e-09 1.6105e+03 7 3.7766e+04 2.712 - 2 3.6116e+07 2.7803e+05 1.2918e+03 8 1.3466e+06 4.548 - 3 3.6285e+07 1.6957e+05 5.8386e+03 6 4.8734e+03 6.542 - 4 3.6285e+07 1.4901e-08 3.3097e+03 1 3.5036e+01 8.916 - 5 3.6285e+07 2.9786e-10 3.3597e+03 6 2.9103e+00 11.204 - 6 3.6285e+07 7.4506e-07 8.7228e+02 5 4.1726e-01 13.546 - 7 3.6285e+07 7.4506e-07 8.1995e+02 0 9.3279e-10g 20.666 + 0 3.5838e+07 - - 5 1.8832e+04 1.198 + 1 3.5838e+07 7.4506e-09 1.6105e+03 7 3.7766e+04 2.893 + 2 3.6116e+07 2.7803e+05 1.2918e+03 8 1.3466e+06 4.732 + 3 3.6285e+07 1.6957e+05 5.8386e+03 6 4.8734e+03 6.740 + 4 3.6285e+07 1.4901e-08 3.3097e+03 1 3.5036e+01 9.099 + 5 3.6285e+07 2.9786e-10 3.3597e+03 6 2.9103e+00 11.588 + 6 3.6285e+07 7.4506e-07 8.7228e+02 5 4.1726e-01 14.360 + 7 3.6285e+07 7.4506e-07 8.1995e+02 0 9.3279e-10g 21.597 ------------------------------------------------------------------------------ Robust optimal solution identified. ------------------------------------------------------------------------------ @@ -928,24 +928,24 @@ Observe that the log contains the following information: Identifier ncalls cumtime percall % ----------------------------------------------------------- - main 1 20.668 20.668 100.0 + main 1 21.598 21.598 100.0 ------------------------------------------------------ - dr_polishing 7 1.459 0.208 7.1 - global_separation 47 1.281 0.027 6.2 - local_separation 376 9.105 0.024 44.1 - master 8 5.356 0.669 25.9 - master_feasibility 7 0.456 0.065 2.2 - preprocessing 1 0.232 0.232 1.1 - other n/a 2.779 n/a 13.4 + dr_polishing 7 1.502 0.215 7.0 + global_separation 47 1.300 0.028 6.0 + local_separation 376 9.779 0.026 45.3 + master 8 5.385 0.673 24.9 + master_feasibility 7 0.531 0.076 2.5 + preprocessing 1 0.175 0.175 0.8 + other n/a 2.926 n/a 13.5 ====================================================== =========================================================== ------------------------------------------------------------------------------ Termination stats: - Iterations : 8 - Solve time (wall s) : 20.668 - Final objective value : 3.6285e+07 - Termination condition : pyrosTerminationCondition.robust_optimal + Iterations : 8 + Solve time (wall s) : 21.598 + Final objective value : 3.6285e+07 + Termination condition : pyrosTerminationCondition.robust_optimal ------------------------------------------------------------------------------ All done. Exiting PyROS. ============================================================================== From 5e7c0b118906e42b30750fbea2236c4de0af5754 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Thu, 12 Oct 2023 12:07:59 -0600 Subject: [PATCH 0300/1204] staging printer things --- pyomo/util/latex_map_generator.py | 15 +- pyomo/util/latex_printer.py | 296 +- pyomo/util/tests/test_latex_printer.py | 648 +++- .../util/tests/test_latex_printer_vartypes.py | 3221 +++++++++++++++++ 4 files changed, 3950 insertions(+), 230 deletions(-) create mode 100644 pyomo/util/tests/test_latex_printer_vartypes.py diff --git a/pyomo/util/latex_map_generator.py b/pyomo/util/latex_map_generator.py index afa383d3217..7b5a74534d3 100644 --- a/pyomo/util/latex_map_generator.py +++ b/pyomo/util/latex_map_generator.py @@ -65,6 +65,7 @@ _MONOMIAL = ExprType.MONOMIAL _GENERAL = ExprType.GENERAL + def applySmartVariables(name): splitName = name.split('_') # print(splitName) @@ -104,6 +105,7 @@ def applySmartVariables(name): return joinedName + # def multiple_replace(pstr, rep_dict): # pattern = re.compile("|".join(rep_dict.keys()), flags=re.DOTALL) # return pattern.sub(lambda x: rep_dict[x.group(0)], pstr) @@ -156,7 +158,10 @@ def latex_component_map_generator( isSingle = False - if isinstance(pyomo_component, (pyo.Objective, pyo.Constraint, pyo.Expression, ExpressionBase, pyo.Var)): + if isinstance( + pyomo_component, + (pyo.Objective, pyo.Constraint, pyo.Expression, ExpressionBase, pyo.Var), + ): isSingle = True elif isinstance(pyomo_component, _BlockData): # is not single, leave alone @@ -195,7 +200,9 @@ def latex_component_map_generator( # TODO: cannot extract this information, waiting on resolution of an issue # For now, will raise an error - raise RuntimeError('Printing of non-models is not currently supported, but will be added soon') + raise RuntimeError( + 'Printing of non-models is not currently supported, but will be added soon' + ) # setList = identify_components(pyomo_component.expr, pyo.Set) else: @@ -428,8 +435,6 @@ def latex_component_map_generator( else: overwrite_dict[ky] = vl.replace('_', '\\_') - - defaultSetLatexNames = ComponentMap() for i in range(0, len(setList)): st = setList[i] @@ -455,6 +460,6 @@ def latex_component_map_generator( ) for ky, vl in defaultSetLatexNames.items(): - overwrite_dict[ky] = [ vl , [] ] + overwrite_dict[ky] = [vl, []] return overwrite_dict diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index 0ea0c2ab9d4..22cefd745f8 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -56,7 +56,7 @@ from pyomo.core.expr.template_expr import ( NPV_Numeric_GetItemExpression, NPV_Structural_GetItemExpression, - Numeric_GetAttrExpression + Numeric_GetAttrExpression, ) from pyomo.core.expr.numeric_expr import NPV_SumExpression from pyomo.core.base.block import IndexedBlock @@ -93,7 +93,7 @@ def decoder(num, base): numDigs = math.ceil(math.log(num, base)) if math.log(num, base).is_integer(): numDigs += 1 - + digs = [0.0 for i in range(0, numDigs)] rem = num for i in range(0, numDigs): @@ -118,18 +118,8 @@ def indexCorrector(ixs, base): def alphabetStringGenerator(num, indexMode=False): if indexMode: - alphabet = [ - '.', - 'i', - 'j', - 'k', - 'm', - 'n', - 'p', - 'q', - 'r', - ] - + alphabet = ['.', 'i', 'j', 'k', 'm', 'n', 'p', 'q', 'r'] + else: alphabet = [ '.', @@ -329,13 +319,14 @@ def handle_indexTemplate_node(visitor, node, *args): # already detected set, do nothing pass else: - visitor.setMap[node._set] = 'SET%d'%(len(visitor.setMap.keys())+1) + visitor.setMap[node._set] = 'SET%d' % (len(visitor.setMap.keys()) + 1) return '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( node._group, visitor.setMap[node._set], ) + def handle_numericGIE_node(visitor, node, *args): joinedName = args[0] @@ -366,9 +357,11 @@ def handle_templateSumExpression_node(visitor, node, *args): def handle_param_node(visitor, node): return visitor.parameterMap[node] + def handle_str_node(visitor, node): return node.replace('_', '\\_') + def handle_npv_numericGetItemExpression_node(visitor, node, *args): joinedName = args[0] @@ -382,6 +375,7 @@ def handle_npv_numericGetItemExpression_node(visitor, node, *args): pstr += '}' return pstr + def handle_npv_structuralGetItemExpression_node(visitor, node, *args): joinedName = args[0] @@ -395,12 +389,15 @@ def handle_npv_structuralGetItemExpression_node(visitor, node, *args): pstr += ']' return pstr + def handle_indexedBlock_node(visitor, node, *args): return str(node) + def handle_numericGetAttrExpression_node(visitor, node, *args): return args[0] + '.' + args[1] + class _LatexVisitor(StreamBasedExpressionVisitor): def __init__(self): super().__init__() @@ -451,7 +448,11 @@ def exitNode(self, node, data): try: return self._operator_handles[node.__class__](self, node, *data) except: - raise DeveloperError('Latex printer encountered an error when processing type %s, contact the developers'%(node.__class__)) + raise DeveloperError( + 'Latex printer encountered an error when processing type %s, contact the developers' + % (node.__class__) + ) + def analyze_variable(vr): domainMap = { @@ -493,13 +494,13 @@ def analyze_variable(vr): upperBound = '' elif domainName in ['PositiveReals', 'PositiveIntegers']: - if lowerBoundValue is not None: - if lowerBoundValue > 0: - lowerBound = str(lowerBoundValue) + ' \\leq ' - else: - lowerBound = ' 0 < ' + # if lowerBoundValue is not None: + if lowerBoundValue > 0: + lowerBound = str(lowerBoundValue) + ' \\leq ' else: lowerBound = ' 0 < ' + # else: + # lowerBound = ' 0 < ' if upperBoundValue is not None: if upperBoundValue <= 0: @@ -524,13 +525,13 @@ def analyze_variable(vr): else: lowerBound = '' - if upperBoundValue is not None: - if upperBoundValue >= 0: - upperBound = ' \\leq 0 ' - else: - upperBound = ' \\leq ' + str(upperBoundValue) - else: + # if upperBoundValue is not None: + if upperBoundValue >= 0: upperBound = ' \\leq 0 ' + else: + upperBound = ' \\leq ' + str(upperBoundValue) + # else: + # upperBound = ' \\leq 0 ' elif domainName in ['NegativeReals', 'NegativeIntegers']: if lowerBoundValue is not None: @@ -543,22 +544,22 @@ def analyze_variable(vr): else: lowerBound = '' - if upperBoundValue is not None: - if upperBoundValue >= 0: - upperBound = ' < 0 ' - else: - upperBound = ' \\leq ' + str(upperBoundValue) - else: + # if upperBoundValue is not None: + if upperBoundValue >= 0: upperBound = ' < 0 ' + else: + upperBound = ' \\leq ' + str(upperBoundValue) + # else: + # upperBound = ' < 0 ' elif domainName in ['NonNegativeReals', 'NonNegativeIntegers']: - if lowerBoundValue is not None: - if lowerBoundValue > 0: - lowerBound = str(lowerBoundValue) + ' \\leq ' - else: - lowerBound = ' 0 \\leq ' + # if lowerBoundValue is not None: + if lowerBoundValue > 0: + lowerBound = str(lowerBoundValue) + ' \\leq ' else: lowerBound = ' 0 \\leq ' + # else: + # lowerBound = ' 0 \\leq ' if upperBoundValue is not None: if upperBoundValue < 0: @@ -577,36 +578,38 @@ def analyze_variable(vr): upperBound = '' elif domainName in ['UnitInterval', 'PercentFraction']: - if lowerBoundValue is not None: - if lowerBoundValue > 0: - lowerBound = str(lowerBoundValue) + ' \\leq ' - elif lowerBoundValue > 1: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' % (vr.name) - ) - elif lowerBoundValue == 1: - lowerBound = ' = 1 ' - else: - lowerBound = ' 0 \\leq ' + # if lowerBoundValue is not None: + if lowerBoundValue > 1: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) + ) + elif lowerBoundValue == 1: + lowerBound = ' = 1 ' + elif lowerBoundValue > 0: + lowerBound = str(lowerBoundValue) + ' \\leq ' else: lowerBound = ' 0 \\leq ' + # else: + # lowerBound = ' 0 \\leq ' - if upperBoundValue is not None: - if upperBoundValue < 1: - upperBound = ' \\leq ' + str(upperBoundValue) - elif upperBoundValue < 0: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' % (vr.name) - ) - elif upperBoundValue == 0: - upperBound = ' = 0 ' - else: - upperBound = ' \\leq 1 ' + # if upperBoundValue is not None: + if upperBoundValue < 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) + ) + elif upperBoundValue == 0: + upperBound = ' = 0 ' + elif upperBoundValue < 1: + upperBound = ' \\leq ' + str(upperBoundValue) else: upperBound = ' \\leq 1 ' + # else: + # upperBound = ' \\leq 1 ' else: - raise ValueError('Domain %s not supported by the latex printer' % (domainName)) + raise DeveloperError( + 'Invalid domain somehow encountered, contact the developers' + ) varBoundData = { 'variable': vr, @@ -631,9 +634,9 @@ def latex_printer( use_equation_environment=False, split_continuous_sets=False, use_short_descriptors=False, - fontsize = None, + fontsize=None, paper_dimensions=None, - ): +): """This function produces a string that can be rendered as LaTeX As described, this function produces a string that can be rendered as LaTeX @@ -642,42 +645,42 @@ def latex_printer( ---------- pyomo_component: _BlockData or Model or Objective or Constraint or Expression The Pyomo component to be printed - + latex_component_map: pyomo.common.collections.component_map.ComponentMap - A map keyed by Pyomo component, values become the latex representation in + A map keyed by Pyomo component, values become the latex representation in the printer - + write_object: io.TextIOWrapper or io.StringIO or str - The object to print the latex string to. Can be an open file object, + The object to print the latex string to. Can be an open file object, string I/O object, or a string for a filename to write to - + use_equation_environment: bool If False, the equation/aligned construction is used to create a single - LaTeX equation. If True, then the align environment is used in LaTeX and + LaTeX equation. If True, then the align environment is used in LaTeX and each constraint and objective will be given an individual equation number - + split_continuous_sets: bool - If False, all sums will be done over 'index in set' or similar. If True, - sums will be done over 'i=1' to 'N' or similar if the set is a continuous + If False, all sums will be done over 'index in set' or similar. If True, + sums will be done over 'i=1' to 'N' or similar if the set is a continuous set - - use_short_descriptors: bool + + use_short_descriptors: bool If False, will print full 'minimize' and 'subject to' etc. If true, uses 'min' and 's.t.' instead - + fontsize: str or int - Sets the font size of the latex output when writing to a file. Can take - in any of the latex font size keywords ['tiny', 'scriptsize', - 'footnotesize', 'small', 'normalsize', 'large', 'Large', 'LARGE', huge', - 'Huge'], or an integer referenced off of 'normalsize' (ex: small is -1, + Sets the font size of the latex output when writing to a file. Can take + in any of the latex font size keywords ['tiny', 'scriptsize', + 'footnotesize', 'small', 'normalsize', 'large', 'Large', 'LARGE', huge', + 'Huge'], or an integer referenced off of 'normalsize' (ex: small is -1, Large is +2) - + paper_dimensions: dict - A dictionary that controls the paper margins and size. Keys are: - [ 'height', 'width', 'margin_left', 'margin_right', 'margin_top', - 'margin_bottom' ]. Default is standard 8.5x11 with one inch margins. - Values are in inches - + A dictionary that controls the paper margins and size. Keys are: + [ 'height', 'width', 'margin_left', 'margin_right', 'margin_top', + 'margin_bottom' ]. Default is standard 8.5x11 with one inch margins. + Values are in inches + Returns ------- @@ -700,22 +703,44 @@ def latex_printer( isSingle = False - fontSizes = ['\\tiny', '\\scriptsize', '\\footnotesize', '\\small', '\\normalsize', '\\large', '\\Large', '\\LARGE', '\\huge', '\\Huge'] - fontSizes_noSlash = ['tiny', 'scriptsize', 'footnotesize', 'small', 'normalsize', 'large', 'Large', 'LARGE', 'huge', 'Huge'] - fontsizes_ints = [ -4, -3, -2, -1, 0, 1, 2, 3, 4, 5 ] + fontSizes = [ + '\\tiny', + '\\scriptsize', + '\\footnotesize', + '\\small', + '\\normalsize', + '\\large', + '\\Large', + '\\LARGE', + '\\huge', + '\\Huge', + ] + fontSizes_noSlash = [ + 'tiny', + 'scriptsize', + 'footnotesize', + 'small', + 'normalsize', + 'large', + 'Large', + 'LARGE', + 'huge', + 'Huge', + ] + fontsizes_ints = [-4, -3, -2, -1, 0, 1, 2, 3, 4, 5] if fontsize is None: fontsize = '\\normalsize' elif fontsize in fontSizes: - #no editing needed + # no editing needed pass elif fontsize in fontSizes_noSlash: fontsize = '\\' + fontsize elif fontsize in fontsizes_ints: fontsize = fontSizes[fontsizes_ints.index(fontsize)] else: - raise ValueError('passed an invalid font size option %s'%(fontsize)) + raise ValueError('passed an invalid font size option %s' % (fontsize)) paper_dimensions_used = {} paper_dimensions_used['height'] = 11.0 @@ -726,22 +751,29 @@ def latex_printer( paper_dimensions_used['margin_bottom'] = 1.0 if paper_dimensions is not None: - for ky in [ 'height', 'width', 'margin_left', 'margin_right', 'margin_top', 'margin_bottom' ]: + for ky in [ + 'height', + 'width', + 'margin_left', + 'margin_right', + 'margin_top', + 'margin_bottom', + ]: if ky in paper_dimensions.keys(): paper_dimensions_used[ky] = paper_dimensions[ky] - else: - if paper_dimensions_used['height'] >= 225 : - raise ValueError('Paper height exceeds maximum dimension of 225') - if paper_dimensions_used['width'] >= 225 : - raise ValueError('Paper width exceeds maximum dimension of 225') - if paper_dimensions_used['margin_left'] < 0.0: - raise ValueError('Paper margin_left must be greater than or equal to zero') - if paper_dimensions_used['margin_right'] < 0.0: - raise ValueError('Paper margin_right must be greater than or equal to zero') - if paper_dimensions_used['margin_top'] < 0.0: - raise ValueError('Paper margin_top must be greater than or equal to zero') - if paper_dimensions_used['margin_bottom'] < 0.0: - raise ValueError('Paper margin_bottom must be greater than or equal to zero') + + if paper_dimensions_used['height'] >= 225: + raise ValueError('Paper height exceeds maximum dimension of 225') + if paper_dimensions_used['width'] >= 225: + raise ValueError('Paper width exceeds maximum dimension of 225') + if paper_dimensions_used['margin_left'] < 0.0: + raise ValueError('Paper margin_left must be greater than or equal to zero') + if paper_dimensions_used['margin_right'] < 0.0: + raise ValueError('Paper margin_right must be greater than or equal to zero') + if paper_dimensions_used['margin_top'] < 0.0: + raise ValueError('Paper margin_top must be greater than or equal to zero') + if paper_dimensions_used['margin_bottom'] < 0.0: + raise ValueError('Paper margin_bottom must be greater than or equal to zero') if isinstance(pyomo_component, pyo.Objective): objectives = [pyomo_component] @@ -999,7 +1031,9 @@ def latex_printer( # already detected set, do nothing pass else: - visitor.setMap[indices[0]._set] = 'SET%d'%(len(visitor.setMap.keys())+1) + visitor.setMap[indices[0]._set] = 'SET%d' % ( + len(visitor.setMap.keys()) + 1 + ) idxTag = '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( indices[0]._group, @@ -1019,7 +1053,6 @@ def latex_printer( pstr += tail - # Print bounds and sets if not isSingle: varBoundData = [] @@ -1136,21 +1169,18 @@ def latex_printer( pstr += ' \\label{%s} \n' % (pyomo_component.name) pstr += '\\end{equation} \n' - setMap = visitor.setMap setMap_inverse = {vl: ky for ky, vl in setMap.items()} - # print(setMap) - - # print('\n\n\n\n') - # print(pstr) # Handling the iterator indices defaultSetLatexNames = ComponentMap() - for ky,vl in setMap.items(): + for ky, vl in setMap.items(): st = ky defaultSetLatexNames[st] = st.name.replace('_', '\\_') if st in ComponentSet(latex_component_map.keys()): - defaultSetLatexNames[st] = latex_component_map[st][0]#.replace('_', '\\_') + defaultSetLatexNames[st] = latex_component_map[st][ + 0 + ] # .replace('_', '\\_') latexLines = pstr.split('\n') for jj in range(0, len(latexLines)): @@ -1180,7 +1210,7 @@ def latex_printer( for ky, vl in setInfo.items(): ix = int(ky[3:]) - 1 - setInfo[ky]['setObject'] = setMap_inverse[ky]#setList[ix] + setInfo[ky]['setObject'] = setMap_inverse[ky] # setList[ix] setInfo[ky][ 'setRegEx' ] = r'__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__' % (ky) @@ -1246,7 +1276,7 @@ def latex_printer( indexCounter = 0 for ky, vl in groupInfo.items(): - if vl['setObject'] in ComponentSet(latex_component_map.keys()) : + if vl['setObject'] in ComponentSet(latex_component_map.keys()): indexNames = latex_component_map[vl['setObject']][1] if len(indexNames) != 0: if len(indexNames) < len(vl['indices']): @@ -1267,7 +1297,7 @@ def latex_printer( % (vl['indices'][i], ky), alphabetStringGenerator(indexCounter, True), ) - indexCounter += 1 + indexCounter += 1 else: for i in range(0, len(vl['indices'])): ln = ln.replace( @@ -1280,8 +1310,6 @@ def latex_printer( latexLines[jj] = ln pstr = '\n'.join(latexLines) - # print('\n\n\n\n') - # print(pstr) vrIdx = 0 new_variableMap = ComponentMap() @@ -1354,26 +1382,21 @@ def latex_printer( pass else: raise ValueError( - 'The latex_component_map object has a key of invalid type: %s' % (str(ky)) + 'The latex_component_map object has a key of invalid type: %s' + % (str(ky)) ) label_rep_dict = copy.deepcopy(rep_dict) for ky, vl in label_rep_dict.items(): label_rep_dict[ky] = vl.replace('{', '').replace('}', '').replace('\\', '') - # print('\n\n\n\n') - # print(pstr) - splitLines = pstr.split('\n') for i in range(0, len(splitLines)): if use_equation_environment: splitLines[i] = multiple_replace(splitLines[i], rep_dict) else: if '\\label{' in splitLines[i]: - try: - epr, lbl = splitLines[i].split('\\label{') - except: - print(splitLines[i]) + epr, lbl = splitLines[i].split('\\label{') epr = multiple_replace(epr, rep_dict) # rep_dict[ky] = vl.replace('_', '\\_') lbl = multiple_replace(lbl, label_rep_dict) @@ -1403,10 +1426,17 @@ def latex_printer( fstr += '\\usepackage{amsmath} \n' fstr += '\\usepackage{amssymb} \n' fstr += '\\usepackage{dsfont} \n' - fstr += '\\usepackage[paperheight=%.4fin, paperwidth=%.4fin, left=%.4fin,right=%.4fin, top=%.4fin, bottom=%.4fin]{geometry} \n'%( - paper_dimensions_used['height'], paper_dimensions_used['width'], - paper_dimensions_used['margin_left'], paper_dimensions_used['margin_right'], - paper_dimensions_used['margin_top'], paper_dimensions_used['margin_bottom'] ) + fstr += ( + '\\usepackage[paperheight=%.4fin, paperwidth=%.4fin, left=%.4fin, right=%.4fin, top=%.4fin, bottom=%.4fin]{geometry} \n' + % ( + paper_dimensions_used['height'], + paper_dimensions_used['width'], + paper_dimensions_used['margin_left'], + paper_dimensions_used['margin_right'], + paper_dimensions_used['margin_top'], + paper_dimensions_used['margin_bottom'], + ) + ) fstr += '\\allowdisplaybreaks \n' fstr += '\\begin{document} \n' fstr += fontsize + ' \n' @@ -1416,12 +1446,14 @@ def latex_printer( # optional write to output file if isinstance(write_object, (io.TextIOWrapper, io.StringIO)): write_object.write(fstr) - elif isinstance(write_object,str): + elif isinstance(write_object, str): f = open(write_object, 'w') f.write(fstr) f.close() else: - raise ValueError('Invalid type %s encountered when parsing the write_object. Must be a StringIO, FileIO, or valid filename string') + raise ValueError( + 'Invalid type %s encountered when parsing the write_object. Must be a StringIO, FileIO, or valid filename string' + ) # return the latex string return pstr diff --git a/pyomo/util/tests/test_latex_printer.py b/pyomo/util/tests/test_latex_printer.py index 8649bcc462a..32381dcf36d 100644 --- a/pyomo/util/tests/test_latex_printer.py +++ b/pyomo/util/tests/test_latex_printer.py @@ -9,6 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +import io import pyomo.common.unittest as unittest from pyomo.util.latex_printer import latex_printer import pyomo.environ as pyo @@ -16,6 +17,28 @@ from pyomo.common.tempfiles import TempfileManager from pyomo.common.collections.component_map import ComponentMap +from pyomo.environ import ( + Reals, + PositiveReals, + NonPositiveReals, + NegativeReals, + NonNegativeReals, + Integers, + PositiveIntegers, + NonPositiveIntegers, + NegativeIntegers, + NonNegativeIntegers, + Boolean, + Binary, + Any, + # AnyWithNone, + EmptySet, + UnitInterval, + PercentFraction, + # RealInterval, + # IntegerInterval, +) + def generate_model(): import pyomo.environ as pyo @@ -130,7 +153,7 @@ def generate_simple_model_2(): class TestLatexPrinter(unittest.TestCase): def test_latexPrinter_simpleDocTests(self): # Ex 1 ----------------------- - m = pyo.ConcreteModel(name = 'basicFormulation') + m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var() m.y = pyo.Var() pstr = latex_printer(m.x + m.y) @@ -142,12 +165,12 @@ def test_latexPrinter_simpleDocTests(self): """ ) self.assertEqual('\n' + pstr + '\n', bstr) - + # Ex 2 ----------------------- - m = pyo.ConcreteModel(name = 'basicFormulation') + m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var() m.y = pyo.Var() - m.expression_1 = pyo.Expression(expr = m.x**2 + m.y**2) + m.expression_1 = pyo.Expression(expr=m.x**2 + m.y**2) pstr = latex_printer(m.expression_1) bstr = dedent( r""" @@ -157,12 +180,12 @@ def test_latexPrinter_simpleDocTests(self): """ ) self.assertEqual('\n' + pstr + '\n', bstr) - + # Ex 3 ----------------------- - m = pyo.ConcreteModel(name = 'basicFormulation') + m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var() m.y = pyo.Var() - m.constraint_1 = pyo.Constraint(expr = m.x**2 + m.y**2 <= 1.0) + m.constraint_1 = pyo.Constraint(expr=m.x**2 + m.y**2 <= 1.0) pstr = latex_printer(m.constraint_1) bstr = dedent( r""" @@ -172,12 +195,15 @@ def test_latexPrinter_simpleDocTests(self): """ ) self.assertEqual('\n' + pstr + '\n', bstr) - + # Ex 4 ----------------------- m = pyo.ConcreteModel(name='basicFormulation') m.I = pyo.Set(initialize=[1, 2, 3, 4, 5]) m.v = pyo.Var(m.I) - def ruleMaker(m): return sum(m.v[i] for i in m.I) <= 0 + + def ruleMaker(m): + return sum(m.v[i] for i in m.I) <= 0 + m.constraint = pyo.Constraint(rule=ruleMaker) pstr = latex_printer(m.constraint) bstr = dedent( @@ -190,13 +216,13 @@ def ruleMaker(m): return sum(m.v[i] for i in m.I) <= 0 self.assertEqual('\n' + pstr + '\n', bstr) # Ex 5 ----------------------- - m = pyo.ConcreteModel(name = 'basicFormulation') + m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var() m.y = pyo.Var() m.z = pyo.Var() m.c = pyo.Param(initialize=1.0, mutable=True) - m.objective = pyo.Objective( expr = m.x + m.y + m.z ) - m.constraint_1 = pyo.Constraint(expr = m.x**2 + m.y**2.0 - m.z**2.0 <= m.c ) + m.objective = pyo.Objective(expr=m.x + m.y + m.z) + m.constraint_1 = pyo.Constraint(expr=m.x**2 + m.y**2.0 - m.z**2.0 <= m.c) pstr = latex_printer(m) bstr = dedent( r""" @@ -214,11 +240,14 @@ def ruleMaker(m): return sum(m.v[i] for i in m.I) <= 0 m = pyo.ConcreteModel(name='basicFormulation') m.I = pyo.Set(initialize=[1, 2, 3, 4, 5]) m.v = pyo.Var(m.I) - def ruleMaker(m): return sum(m.v[i] for i in m.I) <= 0 + + def ruleMaker(m): + return sum(m.v[i] for i in m.I) <= 0 + m.constraint = pyo.Constraint(rule=ruleMaker) lcm = ComponentMap() lcm[m.v] = 'x' - lcm[m.I] = ['\\mathcal{A}',['j','k']] + lcm[m.I] = ['\\mathcal{A}', ['j', 'k']] pstr = latex_printer(m.constraint, latex_component_map=lcm) bstr = dedent( r""" @@ -231,11 +260,11 @@ def ruleMaker(m): return sum(m.v[i] for i in m.I) <= 0 def test_latexPrinter_checkAlphabetFunction(self): from pyomo.util.latex_printer import alphabetStringGenerator - self.assertEqual('z',alphabetStringGenerator(25)) - self.assertEqual('aa',alphabetStringGenerator(26)) - self.assertEqual('alm',alphabetStringGenerator(1000)) - self.assertEqual('iqni',alphabetStringGenerator(1000,True)) + self.assertEqual('z', alphabetStringGenerator(25)) + self.assertEqual('aa', alphabetStringGenerator(26)) + self.assertEqual('alm', alphabetStringGenerator(1000)) + self.assertEqual('iqni', alphabetStringGenerator(1000, True)) def test_latexPrinter_objective(self): m = generate_model() @@ -420,79 +449,6 @@ def test_latexPrinter_iteratedConstraints(self): ) self.assertEqual('\n' + pstr + '\n', bstr) - # def test_latexPrinter_model(self): - # m = generate_simple_model() - - # pstr = latex_printer(m) - # bstr = dedent( - # r""" - # \begin{equation} - # \begin{aligned} - # & \text{minimize} - # & & x + y \\ - # & \text{subject to} - # & & x^{2} + y^{2} \leq 1 \\ - # &&& 0 \leq x \\ - # &&& \left( x + y \right) \sum_{i \in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I \\ - # &&& \sum_{k \in K} p_{k} = 1 - # \end{aligned} - # \label{basicFormulation} - # \end{equation} - # """ - # ) - # self.assertEqual('\n' + pstr, bstr) - - # pstr = latex_printer(m, None, True) - # bstr = dedent( - # r""" - # \begin{align} - # & \text{minimize} - # & & x + y & \label{obj:basicFormulation_objective_1} \\ - # & \text{subject to} - # & & x^{2} + y^{2} \leq 1 & \label{con:basicFormulation_constraint_1} \\ - # &&& 0 \leq x & \label{con:basicFormulation_constraint_2} \\ - # &&& \left( x + y \right) \sum_{i \in I} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I & \label{con:basicFormulation_constraint_7} \\ - # &&& \sum_{k \in K} p_{k} = 1 & \label{con:basicFormulation_constraint_8} - # \end{align} - # """ - # ) - # self.assertEqual('\n' + pstr, bstr) - - # pstr = latex_printer(m, None, False, True) - # bstr = dedent( - # r""" - # \begin{equation} - # \begin{aligned} - # & \text{minimize} - # & & x + y \\ - # & \text{subject to} - # & & x^{2} + y^{2} \leq 1 \\ - # &&& 0 \leq x \\ - # &&& \left( x + y \right) \sum_{i = 1}^{5} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I \\ - # &&& \sum_{k \in K} p_{k} = 1 - # \end{aligned} - # \label{basicFormulation} - # \end{equation} - # """ - # ) - # self.assertEqual('\n' + pstr, bstr) - - # pstr = latex_printer(m, None, True, True) - # bstr = dedent( - # r""" - # \begin{align} - # & \text{minimize} - # & & x + y & \label{obj:basicFormulation_objective_1} \\ - # & \text{subject to} - # & & x^{2} + y^{2} \leq 1 & \label{con:basicFormulation_constraint_1} \\ - # &&& 0 \leq x & \label{con:basicFormulation_constraint_2} \\ - # &&& \left( x + y \right) \sum_{i = 1}^{5} v_{i} + u_{i,j}^{2} \leq 0 , \quad j \in I & \label{con:basicFormulation_constraint_7} \\ - # &&& \sum_{k \in K} p_{k} = 1 & \label{con:basicFormulation_constraint_8} - # \end{align} - # """ - # ) - # self.assertEqual('\n' + pstr, bstr) - def test_latexPrinter_fileWriter(self): m = generate_simple_model() @@ -505,16 +461,522 @@ def test_latexPrinter_fileWriter(self): f.close() bstr_split = bstr.split('\n') - bstr_stripped = bstr_split[3:-2] + bstr_stripped = bstr_split[8:-2] bstr = '\n'.join(bstr_stripped) + '\n' - self.assertEqual(pstr, bstr) + self.assertEqual(pstr + '\n', bstr) def test_latexPrinter_inputError(self): self.assertRaises( ValueError, latex_printer, **{'pyomo_component': 'errorString'} ) + def test_latexPrinter_fileWriter(self): + m = generate_simple_model() + + with TempfileManager.new_context() as tempfile: + fd, fname = tempfile.mkstemp() + pstr = latex_printer(m, write_object=fname) + + f = open(fname) + bstr = f.read() + f.close() + + bstr_split = bstr.split('\n') + bstr_stripped = bstr_split[8:-2] + bstr = '\n'.join(bstr_stripped) + '\n' + + self.assertEqual(pstr + '\n', bstr) + + self.assertRaises( + ValueError, latex_printer, **{'pyomo_component': m, 'write_object': 2.0} + ) + + def test_latexPrinter_fontSizes_1(self): + m = generate_simple_model() + strio = io.StringIO('') + tsh = latex_printer(m, write_object=strio, fontsize='\\normalsize') + strio.seek(0) + pstr = strio.read() + + bstr = dedent( + r""" + \documentclass{article} + \usepackage{amsmath} + \usepackage{amssymb} + \usepackage{dsfont} + \usepackage[paperheight=11.0000in, paperwidth=8.5000in, left=1.0000in, right=1.0000in, top=1.0000in, bottom=1.0000in]{geometry} + \allowdisplaybreaks + \begin{document} + \normalsize + \begin{align} + & \text{minimize} + & & x + y & \label{obj:basicFormulation_objective_1} \\ + & \text{subject to} + & & x^{2} + y^{2} \leq 1 & \label{con:basicFormulation_constraint_1} \\ + &&& 0 \leq x & \label{con:basicFormulation_constraint_2} \\ + &&& \left( x + y \right) \sum_{ i \in I } v_{i} + u_{i,j}^{2} \leq 0 & \qquad \forall j \in I \label{con:basicFormulation_constraint_7} \\ + &&& \sum_{ i \in K } p_{i} = 1 & \label{con:basicFormulation_constraint_8} + \end{align} + \end{document} + """ + ) + strio.close() + self.assertEqual('\n' + pstr, bstr) + + def test_latexPrinter_fontSizes_2(self): + m = generate_simple_model() + strio = io.StringIO('') + tsh = latex_printer(m, write_object=strio, fontsize='normalsize') + strio.seek(0) + pstr = strio.read() + + bstr = dedent( + r""" + \documentclass{article} + \usepackage{amsmath} + \usepackage{amssymb} + \usepackage{dsfont} + \usepackage[paperheight=11.0000in, paperwidth=8.5000in, left=1.0000in, right=1.0000in, top=1.0000in, bottom=1.0000in]{geometry} + \allowdisplaybreaks + \begin{document} + \normalsize + \begin{align} + & \text{minimize} + & & x + y & \label{obj:basicFormulation_objective_1} \\ + & \text{subject to} + & & x^{2} + y^{2} \leq 1 & \label{con:basicFormulation_constraint_1} \\ + &&& 0 \leq x & \label{con:basicFormulation_constraint_2} \\ + &&& \left( x + y \right) \sum_{ i \in I } v_{i} + u_{i,j}^{2} \leq 0 & \qquad \forall j \in I \label{con:basicFormulation_constraint_7} \\ + &&& \sum_{ i \in K } p_{i} = 1 & \label{con:basicFormulation_constraint_8} + \end{align} + \end{document} + """ + ) + strio.close() + self.assertEqual('\n' + pstr, bstr) + + def test_latexPrinter_fontSizes_3(self): + m = generate_simple_model() + strio = io.StringIO('') + tsh = latex_printer(m, write_object=strio, fontsize=0) + strio.seek(0) + pstr = strio.read() + + bstr = dedent( + r""" + \documentclass{article} + \usepackage{amsmath} + \usepackage{amssymb} + \usepackage{dsfont} + \usepackage[paperheight=11.0000in, paperwidth=8.5000in, left=1.0000in, right=1.0000in, top=1.0000in, bottom=1.0000in]{geometry} + \allowdisplaybreaks + \begin{document} + \normalsize + \begin{align} + & \text{minimize} + & & x + y & \label{obj:basicFormulation_objective_1} \\ + & \text{subject to} + & & x^{2} + y^{2} \leq 1 & \label{con:basicFormulation_constraint_1} \\ + &&& 0 \leq x & \label{con:basicFormulation_constraint_2} \\ + &&& \left( x + y \right) \sum_{ i \in I } v_{i} + u_{i,j}^{2} \leq 0 & \qquad \forall j \in I \label{con:basicFormulation_constraint_7} \\ + &&& \sum_{ i \in K } p_{i} = 1 & \label{con:basicFormulation_constraint_8} + \end{align} + \end{document} + """ + ) + strio.close() + self.assertEqual('\n' + pstr, bstr) + + def test_latexPrinter_fontSizes_4(self): + m = generate_simple_model() + strio = io.StringIO('') + self.assertRaises( + ValueError, + latex_printer, + **{'pyomo_component': m, 'write_object': strio, 'fontsize': -10} + ) + strio.close() + + def test_latexPrinter_paperDims(self): + m = generate_simple_model() + strio = io.StringIO('') + pdms = {} + pdms['height'] = 13.0 + pdms['width'] = 10.5 + pdms['margin_left'] = 2.0 + pdms['margin_right'] = 2.0 + pdms['margin_top'] = 2.0 + pdms['margin_bottom'] = 2.0 + tsh = latex_printer(m, write_object=strio, paper_dimensions=pdms) + strio.seek(0) + pstr = strio.read() + + bstr = dedent( + r""" + \documentclass{article} + \usepackage{amsmath} + \usepackage{amssymb} + \usepackage{dsfont} + \usepackage[paperheight=13.0000in, paperwidth=10.5000in, left=2.0000in, right=2.0000in, top=2.0000in, bottom=2.0000in]{geometry} + \allowdisplaybreaks + \begin{document} + \normalsize + \begin{align} + & \text{minimize} + & & x + y & \label{obj:basicFormulation_objective_1} \\ + & \text{subject to} + & & x^{2} + y^{2} \leq 1 & \label{con:basicFormulation_constraint_1} \\ + &&& 0 \leq x & \label{con:basicFormulation_constraint_2} \\ + &&& \left( x + y \right) \sum_{ i \in I } v_{i} + u_{i,j}^{2} \leq 0 & \qquad \forall j \in I \label{con:basicFormulation_constraint_7} \\ + &&& \sum_{ i \in K } p_{i} = 1 & \label{con:basicFormulation_constraint_8} + \end{align} + \end{document} + """ + ) + strio.close() + self.assertEqual('\n' + pstr, bstr) + + strio = io.StringIO('') + self.assertRaises( + ValueError, + latex_printer, + **{ + 'pyomo_component': m, + 'write_object': strio, + 'paper_dimensions': {'height': 230}, + } + ) + self.assertRaises( + ValueError, + latex_printer, + **{ + 'pyomo_component': m, + 'write_object': strio, + 'paper_dimensions': {'width': 230}, + } + ) + self.assertRaises( + ValueError, + latex_printer, + **{ + 'pyomo_component': m, + 'write_object': strio, + 'paper_dimensions': {'margin_left': -1}, + } + ) + self.assertRaises( + ValueError, + latex_printer, + **{ + 'pyomo_component': m, + 'write_object': strio, + 'paper_dimensions': {'margin_right': -1}, + } + ) + self.assertRaises( + ValueError, + latex_printer, + **{ + 'pyomo_component': m, + 'write_object': strio, + 'paper_dimensions': {'margin_top': -1}, + } + ) + self.assertRaises( + ValueError, + latex_printer, + **{ + 'pyomo_component': m, + 'write_object': strio, + 'paper_dimensions': {'margin_bottom': -1}, + } + ) + strio.close() + + def test_latexPrinter_overwriteError(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.I = pyo.Set(initialize=[1, 2, 3, 4, 5]) + m.v = pyo.Var(m.I) + + def ruleMaker(m): + return sum(m.v[i] for i in m.I) <= 0 + + m.constraint = pyo.Constraint(rule=ruleMaker) + lcm = ComponentMap() + lcm[m.v] = 'x' + lcm[m.I] = ['\\mathcal{A}', ['j', 'k']] + lcm['err'] = 1.0 + + self.assertRaises( + ValueError, + latex_printer, + **{'pyomo_component': m.constraint, 'latex_component_map': lcm} + ) + + def test_latexPrinter_indexedParam(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.I = pyo.Set(initialize=[1, 2, 3, 4, 5]) + m.x = pyo.Var(m.I * m.I) + m.c = pyo.Param(m.I * m.I, initialize=1.0, mutable=True) + + def ruleMaker_1(m): + return sum(m.c[i, j] * m.x[i, j] for i in m.I for j in m.I) + + def ruleMaker_2(m): + return sum(m.x[i, j] ** 2 for i in m.I for j in m.I) <= 1 + + m.objective = pyo.Objective(rule=ruleMaker_1) + m.constraint_1 = pyo.Constraint(rule=ruleMaker_2) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & \sum_{ i \in I } \sum_{ j \in I } c_{i,j} x_{i,j} & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & \sum_{ i \in I } \sum_{ j \in I } x_{i,j}^{2} \leq 1 & \label{con:basicFormulation_constraint_1} + \end{align} + """ + ) + + self.assertEqual('\n' + pstr + '\n', bstr) + + lcm = ComponentMap() + lcm[m.I] = ['\\mathcal{A}', ['j']] + self.assertRaises( + ValueError, + latex_printer, + **{'pyomo_component': m, 'latex_component_map': lcm} + ) + + def test_latexPrinter_involvedModel(self): + m = generate_model() + pstr = latex_printer(m) + print(pstr) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x + y + z & \label{obj:basicFormulation_objective_1} \\ + & \text{minimize} + & & \left( x + y \right) \sum_{ i \in J } w_{i} & \label{obj:basicFormulation_objective_2} \\ + & \text{maximize} + & & x + y + z & \label{obj:basicFormulation_objective_3} \\ + & \text{subject to} + & & x^{2} + y^{-2} - x y z + 1 = 2 & \label{con:basicFormulation_constraint_1} \\ + &&& \left| \frac{x}{z^{-2}} \right| \left( x + y \right) \leq 2 & \label{con:basicFormulation_constraint_2} \\ + &&& \sqrt { \frac{x}{z^{-2}} } \leq 2 & \label{con:basicFormulation_constraint_3} \\ + &&& 1 \leq x \leq 2 & \label{con:basicFormulation_constraint_4} \\ + &&& f_{\text{exprIf}}(x \leq 1,z,y) \leq 1 & \label{con:basicFormulation_constraint_5} \\ + &&& x + f(x,y) = 2 & \label{con:basicFormulation_constraint_6} \\ + &&& \left( x + y \right) \sum_{ i \in I } v_{i} + u_{i,j}^{2} \leq 0 & \qquad \forall j \in I \label{con:basicFormulation_constraint_7} \\ + &&& \sum_{ i \in K } p_{i} = 1 & \label{con:basicFormulation_constraint_8} + \end{align} + """ + ) + + self.assertEqual('\n' + pstr + '\n', bstr) + + def test_latexPrinter_continuousSet(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.I = pyo.Set(initialize=[1, 2, 3, 4, 5]) + m.v = pyo.Var(m.I) + + def ruleMaker(m): + return sum(m.v[i] for i in m.I) <= 0 + + m.constraint = pyo.Constraint(rule=ruleMaker) + pstr = latex_printer(m.constraint, split_continuous_sets=True) + + bstr = dedent( + r""" + \begin{equation} + \sum_{ i = 1 }^{5} v_{i} \leq 0 + \end{equation} + """ + ) + self.assertEqual('\n' + pstr + '\n', bstr) + + def test_latexPrinter_notContinuousSet(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.I = pyo.Set(initialize=[1, 3, 4, 5]) + m.v = pyo.Var(m.I) + + def ruleMaker(m): + return sum(m.v[i] for i in m.I) <= 0 + + m.constraint = pyo.Constraint(rule=ruleMaker) + pstr = latex_printer(m.constraint, split_continuous_sets=True) + + bstr = dedent( + r""" + \begin{equation} + \sum_{ i \in I } v_{i} \leq 0 + \end{equation} + """ + ) + self.assertEqual('\n' + pstr + '\n', bstr) + + def test_latexPrinter_autoIndex(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.I = pyo.Set(initialize=[1, 2, 3, 4, 5]) + m.v = pyo.Var(m.I) + + def ruleMaker(m): + return sum(m.v[i] for i in m.I) <= 0 + + m.constraint = pyo.Constraint(rule=ruleMaker) + lcm = ComponentMap() + lcm[m.v] = 'x' + lcm[m.I] = ['\\mathcal{A}', []] + pstr = latex_printer(m.constraint, latex_component_map=lcm) + bstr = dedent( + r""" + \begin{equation} + \sum_{ i \in \mathcal{A} } x_{i} \leq 0 + \end{equation} + """ + ) + self.assertEqual('\n' + pstr + '\n', bstr) + + def test_latexPrinter_equationEnvironment(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var() + m.y = pyo.Var() + m.z = pyo.Var() + m.c = pyo.Param(initialize=1.0, mutable=True) + m.objective = pyo.Objective(expr=m.x + m.y + m.z) + m.constraint_1 = pyo.Constraint(expr=m.x**2 + m.y**2.0 - m.z**2.0 <= m.c) + pstr = latex_printer(m, use_equation_environment=True) + + bstr = dedent( + r""" + \begin{equation} + \begin{aligned} + & \text{minimize} + & & x + y + z \\ + & \text{subject to} + & & x^{2} + y^{2} - z^{2} \leq c + \end{aligned} + \label{basicFormulation} + \end{equation} + """ + ) + self.assertEqual('\n' + pstr + '\n', bstr) + + def test_latexPrinter_manyVariablesWithDomains(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Integers, bounds=(-10, 10)) + m.y = pyo.Var(domain=Binary, bounds=(-10, 10)) + m.z = pyo.Var(domain=PositiveReals, bounds=(-10, 10)) + m.u = pyo.Var(domain=NonNegativeIntegers, bounds=(-10, 10)) + m.v = pyo.Var(domain=NegativeReals, bounds=(-10, 10)) + m.w = pyo.Var(domain=PercentFraction, bounds=(-10, 10)) + m.objective = pyo.Objective(expr=m.x + m.y + m.z + m.u + m.v + m.w) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x + y + z + u + v + w & \label{obj:basicFormulation_objective} \\ + & \text{with bounds} + & & -10 \leq x \leq 10 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} \\ + &&& y & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_y_bound} \\ + &&& 0 < z \leq 10 & \qquad \in \mathds{R}_{> 0} \label{con:basicFormulation_z_bound} \\ + &&& 0 \leq u \leq 10 & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_u_bound} \\ + &&& -10 \leq v < 0 & \qquad \in \mathds{R}_{< 0} \label{con:basicFormulation_v_bound} \\ + &&& 0 \leq w \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_w_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_manyVariablesWithDomains_eqn(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Integers, bounds=(-10, 10)) + m.y = pyo.Var(domain=Binary, bounds=(-10, 10)) + m.z = pyo.Var(domain=PositiveReals, bounds=(-10, 10)) + m.u = pyo.Var(domain=NonNegativeIntegers, bounds=(-10, 10)) + m.v = pyo.Var(domain=NegativeReals, bounds=(-10, 10)) + m.w = pyo.Var(domain=PercentFraction, bounds=(-10, 10)) + m.objective = pyo.Objective(expr=m.x + m.y + m.z + m.u + m.v + m.w) + pstr = latex_printer(m, use_equation_environment=True) + + bstr = dedent( + r""" + \begin{equation} + \begin{aligned} + & \text{minimize} + & & x + y + z + u + v + w \\ + & \text{with bounds} + & & -10 \leq x \leq 10 \qquad \in \mathds{Z}\\ + &&& y \qquad \in \left\{ 0 , 1 \right \}\\ + &&& 0 < z \leq 10 \qquad \in \mathds{R}_{> 0}\\ + &&& 0 \leq u \leq 10 \qquad \in \mathds{Z}_{\geq 0}\\ + &&& -10 \leq v < 0 \qquad \in \mathds{R}_{< 0}\\ + &&& 0 \leq w \leq 1 \qquad \in \mathds{R} + \end{aligned} + \label{basicFormulation} + \end{equation} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_shortDescriptors(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var() + m.y = pyo.Var() + m.z = pyo.Var() + m.c = pyo.Param(initialize=1.0, mutable=True) + m.objective = pyo.Objective(expr=m.x + m.y + m.z) + m.constraint_1 = pyo.Constraint(expr=m.x**2 + m.y**2.0 - m.z**2.0 <= m.c) + pstr = latex_printer(m, use_short_descriptors=True) + + bstr = dedent( + r""" + \begin{align} + & \min + & & x + y + z & \label{obj:basicFormulation_objective} \\ + & \text{s.t.} + & & x^{2} + y^{2} - z^{2} \leq c & \label{con:basicFormulation_constraint_1} + \end{align} + """ + ) + self.assertEqual('\n' + pstr + '\n', bstr) + + def test_latexPrinter_indexedParamSingle(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.I = pyo.Set(initialize=[1, 2, 3, 4, 5]) + m.x = pyo.Var(m.I * m.I) + m.c = pyo.Param(m.I * m.I, initialize=1.0, mutable=True) + + def ruleMaker_1(m): + return sum(m.c[i, j] * m.x[i, j] for i in m.I for j in m.I) + + def ruleMaker_2(m): + return sum(m.c[i, j] * m.x[i, j] ** 2 for i in m.I for j in m.I) <= 1 + + m.objective = pyo.Objective(rule=ruleMaker_1) + m.constraint_1 = pyo.Constraint(rule=ruleMaker_2) + pstr = latex_printer(m.constraint_1) + print(pstr) + + bstr = dedent( + r""" + \begin{equation} + \sum_{ i \in I } \sum_{ j \in I } c_{i,j} x_{i,j}^{2} \leq 1 + \end{equation} + """ + ) + + self.assertEqual('\n' + pstr + '\n', bstr) + if __name__ == '__main__': unittest.main() diff --git a/pyomo/util/tests/test_latex_printer_vartypes.py b/pyomo/util/tests/test_latex_printer_vartypes.py new file mode 100644 index 00000000000..df1641e1db1 --- /dev/null +++ b/pyomo/util/tests/test_latex_printer_vartypes.py @@ -0,0 +1,3221 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2023 +# 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.common.unittest as unittest +from pyomo.util.latex_printer import latex_printer +import pyomo.environ as pyo +from textwrap import dedent +from pyomo.common.tempfiles import TempfileManager +from pyomo.common.collections.component_map import ComponentMap + +from pyomo.environ import ( + Reals, + PositiveReals, + NonPositiveReals, + NegativeReals, + NonNegativeReals, + Integers, + PositiveIntegers, + NonPositiveIntegers, + NegativeIntegers, + NonNegativeIntegers, + Boolean, + Binary, + Any, + # AnyWithNone, + EmptySet, + UnitInterval, + PercentFraction, + # RealInterval, + # IntegerInterval, +) + + +class TestLatexPrinterVariableTypes(unittest.TestCase): + def test_latexPrinter_variableType_Reals_1(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Reals) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Reals_2(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Reals, bounds=(None, None)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Reals_3(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Reals, bounds=(-10, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x \leq 10 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Reals_4(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Reals, bounds=(-10, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x \leq 0 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Reals_5(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Reals, bounds=(-10, -2)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x \leq -2 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Reals_6(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Reals, bounds=(0, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 0 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Reals_7(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Reals, bounds=(0, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 10 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Reals_8(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Reals, bounds=(2, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 2 \leq x \leq 10 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Reals_9(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Reals, bounds=(0, 1)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Reals_10(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Reals, bounds=(1, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 1 \leq x \leq 10 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Reals_11(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Reals, bounds=(0.25, 0.75)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0.25 \leq x \leq 0.75 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PositiveReals_1(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveReals) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 < x & \qquad \in \mathds{R}_{> 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PositiveReals_2(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveReals, bounds=(None, None)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 < x & \qquad \in \mathds{R}_{> 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PositiveReals_3(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveReals, bounds=(-10, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 < x \leq 10 & \qquad \in \mathds{R}_{> 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PositiveReals_4(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveReals, bounds=(-10, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_PositiveReals_5(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveReals, bounds=(-10, -2)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_PositiveReals_6(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveReals, bounds=(0, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_PositiveReals_7(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveReals, bounds=(0, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 < x \leq 10 & \qquad \in \mathds{R}_{> 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PositiveReals_8(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveReals, bounds=(2, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 2 \leq x \leq 10 & \qquad \in \mathds{R}_{> 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PositiveReals_9(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveReals, bounds=(0, 1)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 < x \leq 1 & \qquad \in \mathds{R}_{> 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PositiveReals_10(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveReals, bounds=(1, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 1 \leq x \leq 10 & \qquad \in \mathds{R}_{> 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PositiveReals_11(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveReals, bounds=(0.25, 0.75)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0.25 \leq x \leq 0.75 & \qquad \in \mathds{R}_{> 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonPositiveReals_1(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveReals) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x \leq 0 & \qquad \in \mathds{R}_{\leq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonPositiveReals_2(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveReals, bounds=(None, None)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x \leq 0 & \qquad \in \mathds{R}_{\leq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonPositiveReals_3(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveReals, bounds=(-10, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x \leq 0 & \qquad \in \mathds{R}_{\leq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonPositiveReals_4(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveReals, bounds=(-10, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x \leq 0 & \qquad \in \mathds{R}_{\leq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonPositiveReals_5(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveReals, bounds=(-10, -2)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x \leq -2 & \qquad \in \mathds{R}_{\leq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonPositiveReals_6(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveReals, bounds=(0, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 = x \leq 0 & \qquad \in \mathds{R}_{\leq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonPositiveReals_7(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveReals, bounds=(0, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 = x \leq 0 & \qquad \in \mathds{R}_{\leq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonPositiveReals_8(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveReals, bounds=(2, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NonPositiveReals_9(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveReals, bounds=(0, 1)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 = x \leq 0 & \qquad \in \mathds{R}_{\leq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonPositiveReals_10(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveReals, bounds=(1, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NonPositiveReals_11(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveReals, bounds=(0.25, 0.75)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NegativeReals_1(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeReals) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x < 0 & \qquad \in \mathds{R}_{< 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NegativeReals_2(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeReals, bounds=(None, None)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x < 0 & \qquad \in \mathds{R}_{< 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NegativeReals_3(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeReals, bounds=(-10, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x < 0 & \qquad \in \mathds{R}_{< 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NegativeReals_4(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeReals, bounds=(-10, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x < 0 & \qquad \in \mathds{R}_{< 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NegativeReals_5(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeReals, bounds=(-10, -2)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x \leq -2 & \qquad \in \mathds{R}_{< 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NegativeReals_6(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeReals, bounds=(0, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NegativeReals_7(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeReals, bounds=(0, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NegativeReals_8(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeReals, bounds=(2, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NegativeReals_9(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeReals, bounds=(0, 1)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NegativeReals_10(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeReals, bounds=(1, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NegativeReals_11(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeReals, bounds=(0.25, 0.75)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NonNegativeReals_1(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeReals) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonNegativeReals_2(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeReals, bounds=(None, None)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonNegativeReals_3(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeReals, bounds=(-10, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 10 & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonNegativeReals_4(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeReals, bounds=(-10, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x = 0 & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonNegativeReals_5(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeReals, bounds=(-10, -2)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NonNegativeReals_6(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeReals, bounds=(0, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x = 0 & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonNegativeReals_7(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeReals, bounds=(0, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 10 & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonNegativeReals_8(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeReals, bounds=(2, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 2 \leq x \leq 10 & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonNegativeReals_9(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeReals, bounds=(0, 1)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 1 & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonNegativeReals_10(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeReals, bounds=(1, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 1 \leq x \leq 10 & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonNegativeReals_11(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeReals, bounds=(0.25, 0.75)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0.25 \leq x \leq 0.75 & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Integers_1(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Integers) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Integers_2(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Integers, bounds=(None, None)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Integers_3(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Integers, bounds=(-10, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x \leq 10 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Integers_4(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Integers, bounds=(-10, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x \leq 0 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Integers_5(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Integers, bounds=(-10, -2)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x \leq -2 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Integers_6(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Integers, bounds=(0, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 0 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Integers_7(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Integers, bounds=(0, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 10 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Integers_8(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Integers, bounds=(2, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 2 \leq x \leq 10 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Integers_9(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Integers, bounds=(0, 1)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 1 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Integers_10(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Integers, bounds=(1, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 1 \leq x \leq 10 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Integers_11(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Integers, bounds=(0.25, 0.75)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0.25 \leq x \leq 0.75 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PositiveIntegers_1(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveIntegers) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 1 \leq x & \qquad \in \mathds{Z}_{> 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PositiveIntegers_2(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveIntegers, bounds=(None, None)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 1 \leq x & \qquad \in \mathds{Z}_{> 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PositiveIntegers_3(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveIntegers, bounds=(-10, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 1 \leq x \leq 10 & \qquad \in \mathds{Z}_{> 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PositiveIntegers_4(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveIntegers, bounds=(-10, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_PositiveIntegers_5(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveIntegers, bounds=(-10, -2)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_PositiveIntegers_6(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveIntegers, bounds=(0, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_PositiveIntegers_7(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveIntegers, bounds=(0, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 1 \leq x \leq 10 & \qquad \in \mathds{Z}_{> 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PositiveIntegers_8(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveIntegers, bounds=(2, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 2 \leq x \leq 10 & \qquad \in \mathds{Z}_{> 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PositiveIntegers_9(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveIntegers, bounds=(0, 1)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 1 \leq x \leq 1 & \qquad \in \mathds{Z}_{> 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PositiveIntegers_10(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveIntegers, bounds=(1, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 1 \leq x \leq 10 & \qquad \in \mathds{Z}_{> 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PositiveIntegers_11(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PositiveIntegers, bounds=(0.25, 0.75)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 1 \leq x \leq 0.75 & \qquad \in \mathds{Z}_{> 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonPositiveIntegers_1(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveIntegers) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x \leq 0 & \qquad \in \mathds{Z}_{\leq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonPositiveIntegers_2(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveIntegers, bounds=(None, None)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x \leq 0 & \qquad \in \mathds{Z}_{\leq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonPositiveIntegers_3(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveIntegers, bounds=(-10, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x \leq 0 & \qquad \in \mathds{Z}_{\leq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonPositiveIntegers_4(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveIntegers, bounds=(-10, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x \leq 0 & \qquad \in \mathds{Z}_{\leq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonPositiveIntegers_5(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveIntegers, bounds=(-10, -2)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x \leq -2 & \qquad \in \mathds{Z}_{\leq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonPositiveIntegers_6(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveIntegers, bounds=(0, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 = x \leq 0 & \qquad \in \mathds{Z}_{\leq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonPositiveIntegers_7(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveIntegers, bounds=(0, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 = x \leq 0 & \qquad \in \mathds{Z}_{\leq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonPositiveIntegers_8(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveIntegers, bounds=(2, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NonPositiveIntegers_9(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveIntegers, bounds=(0, 1)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 = x \leq 0 & \qquad \in \mathds{Z}_{\leq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonPositiveIntegers_10(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveIntegers, bounds=(1, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NonPositiveIntegers_11(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonPositiveIntegers, bounds=(0.25, 0.75)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NegativeIntegers_1(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeIntegers) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x \leq -1 & \qquad \in \mathds{Z}_{< 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NegativeIntegers_2(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeIntegers, bounds=(None, None)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x \leq -1 & \qquad \in \mathds{Z}_{< 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NegativeIntegers_3(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeIntegers, bounds=(-10, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x \leq -1 & \qquad \in \mathds{Z}_{< 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NegativeIntegers_4(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeIntegers, bounds=(-10, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x \leq -1 & \qquad \in \mathds{Z}_{< 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NegativeIntegers_5(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeIntegers, bounds=(-10, -2)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x \leq -2 & \qquad \in \mathds{Z}_{< 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NegativeIntegers_6(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeIntegers, bounds=(0, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NegativeIntegers_7(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeIntegers, bounds=(0, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NegativeIntegers_8(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeIntegers, bounds=(2, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NegativeIntegers_9(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeIntegers, bounds=(0, 1)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NegativeIntegers_10(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeIntegers, bounds=(1, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NegativeIntegers_11(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NegativeIntegers, bounds=(0.25, 0.75)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NonNegativeIntegers_1(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeIntegers) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonNegativeIntegers_2(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeIntegers, bounds=(None, None)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonNegativeIntegers_3(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeIntegers, bounds=(-10, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 10 & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonNegativeIntegers_4(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeIntegers, bounds=(-10, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x = 0 & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonNegativeIntegers_5(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeIntegers, bounds=(-10, -2)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_NonNegativeIntegers_6(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeIntegers, bounds=(0, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x = 0 & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonNegativeIntegers_7(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeIntegers, bounds=(0, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 10 & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonNegativeIntegers_8(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeIntegers, bounds=(2, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 2 \leq x \leq 10 & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonNegativeIntegers_9(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeIntegers, bounds=(0, 1)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 1 & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonNegativeIntegers_10(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeIntegers, bounds=(1, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 1 \leq x \leq 10 & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_NonNegativeIntegers_11(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=NonNegativeIntegers, bounds=(0.25, 0.75)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0.25 \leq x \leq 0.75 & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Boolean_1(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Boolean) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Boolean_2(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Boolean, bounds=(None, None)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Boolean_3(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Boolean, bounds=(-10, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Boolean_4(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Boolean, bounds=(-10, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Boolean_5(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Boolean, bounds=(-10, -2)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Boolean_6(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Boolean, bounds=(0, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Boolean_7(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Boolean, bounds=(0, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Boolean_8(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Boolean, bounds=(2, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Boolean_9(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Boolean, bounds=(0, 1)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Boolean_10(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Boolean, bounds=(1, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Boolean_11(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Boolean, bounds=(0.25, 0.75)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Binary_1(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Binary) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Binary_2(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Binary, bounds=(None, None)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Binary_3(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Binary, bounds=(-10, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Binary_4(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Binary, bounds=(-10, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Binary_5(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Binary, bounds=(-10, -2)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Binary_6(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Binary, bounds=(0, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Binary_7(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Binary, bounds=(0, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Binary_8(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Binary, bounds=(2, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Binary_9(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Binary, bounds=(0, 1)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Binary_10(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Binary, bounds=(1, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_Binary_11(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=Binary, bounds=(0.25, 0.75)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_EmptySet_1(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=EmptySet) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_EmptySet_2(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=EmptySet, bounds=(None, None)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_EmptySet_3(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=EmptySet, bounds=(-10, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_EmptySet_4(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=EmptySet, bounds=(-10, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_EmptySet_5(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=EmptySet, bounds=(-10, -2)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_EmptySet_6(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=EmptySet, bounds=(0, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_EmptySet_7(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=EmptySet, bounds=(0, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_EmptySet_8(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=EmptySet, bounds=(2, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_EmptySet_9(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=EmptySet, bounds=(0, 1)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_EmptySet_10(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=EmptySet, bounds=(1, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_EmptySet_11(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=EmptySet, bounds=(0.25, 0.75)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_UnitInterval_1(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=UnitInterval) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_UnitInterval_2(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=UnitInterval, bounds=(None, None)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_UnitInterval_3(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=UnitInterval, bounds=(-10, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_UnitInterval_4(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=UnitInterval, bounds=(-10, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x = 0 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_UnitInterval_5(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=UnitInterval, bounds=(-10, -2)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_UnitInterval_6(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=UnitInterval, bounds=(0, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x = 0 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_UnitInterval_7(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=UnitInterval, bounds=(0, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_UnitInterval_8(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=UnitInterval, bounds=(2, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_UnitInterval_9(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=UnitInterval, bounds=(0, 1)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_UnitInterval_10(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=UnitInterval, bounds=(1, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & = 1 x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_UnitInterval_11(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=UnitInterval, bounds=(0.25, 0.75)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0.25 \leq x \leq 0.75 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PercentFraction_1(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PercentFraction) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PercentFraction_2(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PercentFraction, bounds=(None, None)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PercentFraction_3(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PercentFraction, bounds=(-10, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PercentFraction_4(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PercentFraction, bounds=(-10, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x = 0 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PercentFraction_5(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PercentFraction, bounds=(-10, -2)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_PercentFraction_6(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PercentFraction, bounds=(0, 0)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x = 0 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PercentFraction_7(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PercentFraction, bounds=(0, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PercentFraction_8(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PercentFraction, bounds=(2, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + + def test_latexPrinter_variableType_PercentFraction_9(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PercentFraction, bounds=(0, 1)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PercentFraction_10(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PercentFraction, bounds=(1, 10)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & = 1 x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + def test_latexPrinter_variableType_PercentFraction_11(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.x = pyo.Var(domain=PercentFraction, bounds=(0.25, 0.75)) + m.objective = pyo.Objective(expr=m.x) + m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) + pstr = latex_printer(m) + + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & x & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & 0.25 \leq x \leq 0.75 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual("\n" + pstr + "\n", bstr) + + +if __name__ == '__main__': + unittest.main() From 7ae81bf66023381997300d90421cd5c6ec042b9e Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Thu, 12 Oct 2023 12:09:41 -0600 Subject: [PATCH 0301/1204] removing the smart variable generator --- pyomo/util/latex_map_generator.py | 465 ------------------------------ 1 file changed, 465 deletions(-) delete mode 100644 pyomo/util/latex_map_generator.py diff --git a/pyomo/util/latex_map_generator.py b/pyomo/util/latex_map_generator.py deleted file mode 100644 index 7b5a74534d3..00000000000 --- a/pyomo/util/latex_map_generator.py +++ /dev/null @@ -1,465 +0,0 @@ -# ___________________________________________________________________________ -# -# Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2023 -# 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 math -import copy -import re -import pyomo.environ as pyo -from pyomo.core.expr.visitor import StreamBasedExpressionVisitor -from pyomo.core.expr import ( - NegationExpression, - ProductExpression, - DivisionExpression, - PowExpression, - AbsExpression, - UnaryFunctionExpression, - MonomialTermExpression, - LinearExpression, - SumExpression, - EqualityExpression, - InequalityExpression, - RangedExpression, - Expr_ifExpression, - ExternalFunctionExpression, -) - -from pyomo.core.expr.visitor import identify_components -from pyomo.core.expr.base import ExpressionBase -from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData -from pyomo.core.base.objective import ScalarObjective, _GeneralObjectiveData -import pyomo.core.kernel as kernel -from pyomo.core.expr.template_expr import ( - GetItemExpression, - GetAttrExpression, - TemplateSumExpression, - IndexTemplate, - Numeric_GetItemExpression, - templatize_constraint, - resolve_template, - templatize_rule, -) -from pyomo.core.base.var import ScalarVar, _GeneralVarData, IndexedVar -from pyomo.core.base.param import _ParamData, ScalarParam, IndexedParam -from pyomo.core.base.set import _SetData -from pyomo.core.base.constraint import ScalarConstraint, IndexedConstraint -from pyomo.common.collections.component_map import ComponentMap -from pyomo.common.collections.component_set import ComponentSet - -from pyomo.core.base.external import _PythonCallbackFunctionID - -from pyomo.core.base.block import _BlockData - -from pyomo.repn.util import ExprType - -from pyomo.common import DeveloperError - -_CONSTANT = ExprType.CONSTANT -_MONOMIAL = ExprType.MONOMIAL -_GENERAL = ExprType.GENERAL - - -def applySmartVariables(name): - splitName = name.split('_') - # print(splitName) - - filteredName = [] - - prfx = '' - psfx = '' - for i in range(0, len(splitName)): - se = splitName[i] - if se != 0: - if se == 'dot': - prfx = '\\dot{' - psfx = '}' - elif se == 'hat': - prfx = '\\hat{' - psfx = '}' - elif se == 'bar': - prfx = '\\bar{' - psfx = '}' - elif se == 'mathcal': - prfx = '\\mathcal{' - psfx = '}' - else: - filteredName.append(se) - else: - filteredName.append(se) - - joinedName = prfx + filteredName[0] + psfx - # print(joinedName) - # print(filteredName) - for i in range(1, len(filteredName)): - joinedName += '_{' + filteredName[i] - - joinedName += '}' * (len(filteredName) - 1) - # print(joinedName) - - return joinedName - - -# def multiple_replace(pstr, rep_dict): -# pattern = re.compile("|".join(rep_dict.keys()), flags=re.DOTALL) -# return pattern.sub(lambda x: rep_dict[x.group(0)], pstr) - - -def latex_component_map_generator( - pyomo_component, - use_smart_variables=False, - x_only_mode=0, - overwrite_dict=None, - # latex_component_map=None, -): - """This function produces a string that can be rendered as LaTeX - - As described, this function produces a string that can be rendered as LaTeX - - Parameters - ---------- - pyomo_component: _BlockData or Model or Constraint or Expression or Objective - The thing to be printed to LaTeX. Accepts Blocks (including models), Constraints, and Expressions - - filename: str - An optional file to write the LaTeX to. Default of None produces no file - - use_equation_environment: bool - Default behavior uses equation/aligned and produces a single LaTeX Equation (ie, ==False). - Setting this input to True will instead use the align environment, and produce equation numbers for each - objective and constraint. Each objective and constraint will be labeled with its name in the pyomo model. - This flag is only relevant for Models and Blocks. - - splitContinuous: bool - Default behavior has all sum indices be over "i \\in I" or similar. Setting this flag to - True makes the sums go from: \\sum_{i=1}^{5} if the set I is continuous and has 5 elements - - Returns - ------- - str - A LaTeX string of the pyomo_component - - """ - - # Various setup things - - # is Single implies Objective, constraint, or expression - # these objects require a slight modification of behavior - # isSingle==False means a model or block - - if overwrite_dict is None: - overwrite_dict = ComponentMap() - - isSingle = False - - if isinstance( - pyomo_component, - (pyo.Objective, pyo.Constraint, pyo.Expression, ExpressionBase, pyo.Var), - ): - isSingle = True - elif isinstance(pyomo_component, _BlockData): - # is not single, leave alone - pass - else: - raise ValueError( - "Invalid type %s passed into the latex printer" - % (str(type(pyomo_component))) - ) - - if isSingle: - temp_comp, temp_indexes = templatize_fcn(pyomo_component) - variableList = [] - for v in identify_components( - temp_comp, [ScalarVar, _GeneralVarData, IndexedVar] - ): - if isinstance(v, _GeneralVarData): - v_write = v.parent_component() - if v_write not in ComponentSet(variableList): - variableList.append(v_write) - else: - if v not in ComponentSet(variableList): - variableList.append(v) - - parameterList = [] - for p in identify_components( - temp_comp, [ScalarParam, _ParamData, IndexedParam] - ): - if isinstance(p, _ParamData): - p_write = p.parent_component() - if p_write not in ComponentSet(parameterList): - parameterList.append(p_write) - else: - if p not in ComponentSet(parameterList): - parameterList.append(p) - - # TODO: cannot extract this information, waiting on resolution of an issue - # For now, will raise an error - raise RuntimeError( - 'Printing of non-models is not currently supported, but will be added soon' - ) - # setList = identify_components(pyomo_component.expr, pyo.Set) - - else: - variableList = [ - vr - for vr in pyomo_component.component_objects( - pyo.Var, descend_into=True, active=True - ) - ] - - parameterList = [ - pm - for pm in pyomo_component.component_objects( - pyo.Param, descend_into=True, active=True - ) - ] - - setList = [ - st - for st in pyomo_component.component_objects( - pyo.Set, descend_into=True, active=True - ) - ] - - variableMap = ComponentMap() - vrIdx = 0 - for i in range(0, len(variableList)): - vr = variableList[i] - vrIdx += 1 - if isinstance(vr, ScalarVar): - variableMap[vr] = 'x_' + str(vrIdx) - elif isinstance(vr, IndexedVar): - variableMap[vr] = 'x_' + str(vrIdx) - for sd in vr.index_set().data(): - vrIdx += 1 - variableMap[vr[sd]] = 'x_' + str(vrIdx) - else: - raise DeveloperError( - 'Variable is not a variable. Should not happen. Contact developers' - ) - - parameterMap = ComponentMap() - pmIdx = 0 - for i in range(0, len(parameterList)): - vr = parameterList[i] - pmIdx += 1 - if isinstance(vr, ScalarParam): - parameterMap[vr] = 'p_' + str(pmIdx) - elif isinstance(vr, IndexedParam): - parameterMap[vr] = 'p_' + str(pmIdx) - for sd in vr.index_set().data(): - pmIdx += 1 - parameterMap[vr[sd]] = 'p_' + str(pmIdx) - else: - raise DeveloperError( - 'Parameter is not a parameter. Should not happen. Contact developers' - ) - - setMap = ComponentMap() - for i in range(0, len(setList)): - st = setList[i] - setMap[st] = 'SET' + str(i + 1) - - # # Only x modes - # # False : dont use - # # True : indexed variables become x_{ix_{subix}} - - if x_only_mode: - # Need to preserve only the set elements in the overwrite_dict - new_overwrite_dict = {} - for ky, vl in overwrite_dict.items(): - if isinstance(ky, _GeneralVarData): - pass - elif isinstance(ky, _ParamData): - pass - elif isinstance(ky, _SetData): - new_overwrite_dict[ky] = overwrite_dict[ky] - else: - raise ValueError( - 'The overwrite_dict object has a key of invalid type: %s' - % (str(ky)) - ) - overwrite_dict = new_overwrite_dict - - vrIdx = 0 - new_variableMap = ComponentMap() - for i in range(0, len(variableList)): - vr = variableList[i] - vrIdx += 1 - if isinstance(vr, ScalarVar): - new_variableMap[vr] = 'x_{' + str(vrIdx) + '}' - elif isinstance(vr, IndexedVar): - new_variableMap[vr] = 'x_{' + str(vrIdx) + '}' - for sd in vr.index_set().data(): - # vrIdx += 1 - sdString = str(sd) - if sdString[0] == '(': - sdString = sdString[1:] - if sdString[-1] == ')': - sdString = sdString[0:-1] - new_variableMap[vr[sd]] = ( - 'x_{' + str(vrIdx) + '_{' + sdString + '}' + '}' - ) - else: - raise DeveloperError( - 'Variable is not a variable. Should not happen. Contact developers' - ) - - pmIdx = 0 - new_parameterMap = ComponentMap() - for i in range(0, len(parameterList)): - pm = parameterList[i] - pmIdx += 1 - if isinstance(pm, ScalarParam): - new_parameterMap[pm] = 'p_{' + str(pmIdx) + '}' - elif isinstance(pm, IndexedParam): - new_parameterMap[pm] = 'p_{' + str(pmIdx) + '}' - for sd in pm.index_set().data(): - sdString = str(sd) - if sdString[0] == '(': - sdString = sdString[1:] - if sdString[-1] == ')': - sdString = sdString[0:-1] - new_parameterMap[pm[sd]] = ( - 'p_{' + str(pmIdx) + '_{' + sdString + '}' + '}' - ) - else: - raise DeveloperError( - 'Parameter is not a parameter. Should not happen. Contact developers' - ) - - new_overwrite_dict = ComponentMap() - for ky, vl in new_variableMap.items(): - new_overwrite_dict[ky] = vl - for ky, vl in new_parameterMap.items(): - new_overwrite_dict[ky] = vl - for ky, vl in overwrite_dict.items(): - new_overwrite_dict[ky] = vl - overwrite_dict = new_overwrite_dict - - else: - vrIdx = 0 - new_variableMap = ComponentMap() - for i in range(0, len(variableList)): - vr = variableList[i] - vrIdx += 1 - if isinstance(vr, ScalarVar): - new_variableMap[vr] = vr.name - elif isinstance(vr, IndexedVar): - new_variableMap[vr] = vr.name - for sd in vr.index_set().data(): - # vrIdx += 1 - sdString = str(sd) - if sdString[0] == '(': - sdString = sdString[1:] - if sdString[-1] == ')': - sdString = sdString[0:-1] - if use_smart_variables: - new_variableMap[vr[sd]] = applySmartVariables( - vr.name + '_' + sdString - ) - else: - new_variableMap[vr[sd]] = vr[sd].name - else: - raise DeveloperError( - 'Variable is not a variable. Should not happen. Contact developers' - ) - - pmIdx = 0 - new_parameterMap = ComponentMap() - for i in range(0, len(parameterList)): - pm = parameterList[i] - pmIdx += 1 - if isinstance(pm, ScalarParam): - new_parameterMap[pm] = pm.name - elif isinstance(pm, IndexedParam): - new_parameterMap[pm] = pm.name - for sd in pm.index_set().data(): - # pmIdx += 1 - sdString = str(sd) - if sdString[0] == '(': - sdString = sdString[1:] - if sdString[-1] == ')': - sdString = sdString[0:-1] - if use_smart_variables: - new_parameterMap[pm[sd]] = applySmartVariables( - pm.name + '_' + sdString - ) - else: - new_parameterMap[pm[sd]] = str(pm[sd]) # .name - else: - raise DeveloperError( - 'Parameter is not a parameter. Should not happen. Contact developers' - ) - - for ky, vl in new_variableMap.items(): - if ky not in overwrite_dict.keys(): - overwrite_dict[ky] = vl - for ky, vl in new_parameterMap.items(): - if ky not in overwrite_dict.keys(): - overwrite_dict[ky] = vl - - for ky in overwrite_dict.keys(): - if isinstance(ky, (pyo.Var, pyo.Param)): - if use_smart_variables and x_only_mode in [0, 3]: - overwrite_dict[ky] = applySmartVariables(overwrite_dict[ky]) - elif isinstance(ky, (_GeneralVarData, _ParamData)): - if use_smart_variables and x_only_mode in [3]: - overwrite_dict[ky] = applySmartVariables(overwrite_dict[ky]) - elif isinstance(ky, _SetData): - # already handled - pass - elif isinstance(ky, (float, int)): - # happens when immutable parameters are used, do nothing - pass - else: - raise ValueError( - 'The overwrite_dict object has a key of invalid type: %s' % (str(ky)) - ) - - for ky, vl in overwrite_dict.items(): - if use_smart_variables: - pattern = r'_{([^{]*)}_{([^{]*)}' - replacement = r'_{\1_{\2}}' - overwrite_dict[ky] = re.sub(pattern, replacement, overwrite_dict[ky]) - - pattern = r'_(.)_{([^}]*)}' - replacement = r'_{\1_{\2}}' - overwrite_dict[ky] = re.sub(pattern, replacement, overwrite_dict[ky]) - else: - overwrite_dict[ky] = vl.replace('_', '\\_') - - defaultSetLatexNames = ComponentMap() - for i in range(0, len(setList)): - st = setList[i] - if use_smart_variables: - chkName = setList[i].name - if len(chkName) == 1 and chkName.upper() == chkName: - chkName += '_mathcal' - defaultSetLatexNames[st] = applySmartVariables(chkName) - else: - defaultSetLatexNames[st] = setList[i].name.replace('_', '\\_') - - ## Could be used in the future if someone has a lot of sets - # defaultSetLatexNames[st] = 'mathcal{' + alphabetStringGenerator(i).upper() + '}' - - if st in overwrite_dict.keys(): - if use_smart_variables: - defaultSetLatexNames[st] = applySmartVariables(overwrite_dict[st][0]) - else: - defaultSetLatexNames[st] = overwrite_dict[st][0].replace('_', '\\_') - - defaultSetLatexNames[st] = defaultSetLatexNames[st].replace( - '\\mathcal', r'\\mathcal' - ) - - for ky, vl in defaultSetLatexNames.items(): - overwrite_dict[ky] = [vl, []] - - return overwrite_dict From 63aba93f3906a7d875e4558931d95d911555cbe9 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 12 Oct 2023 14:25:53 -0600 Subject: [PATCH 0302/1204] Apply black --- pyomo/common/numeric_types.py | 39 ++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/pyomo/common/numeric_types.py b/pyomo/common/numeric_types.py index f822275a907..af7eeded3cf 100644 --- a/pyomo/common/numeric_types.py +++ b/pyomo/common/numeric_types.py @@ -148,6 +148,7 @@ def RegisterBooleanType(new_type: type): native_types.add(new_type) nonpyomo_leaf_types.add(new_type) + def RegisterComplexType(new_type: type): """Register the specified type as an "complex type". @@ -243,25 +244,25 @@ def check_if_numeric_type(obj): def value(obj, exception=True): """ - A utility function that returns the value of a Pyomo object or - expression. - - Args: - obj: The argument to evaluate. If it is None, a - string, or any other primitive numeric type, - then this function simply returns the argument. - Otherwise, if the argument is a NumericValue - then the __call__ method is executed. - exception (bool): If :const:`True`, then an exception should - be raised when instances of NumericValue fail to - evaluate due to one or more objects not being - initialized to a numeric value (e.g, one or more - variables in an algebraic expression having the - value None). If :const:`False`, then the function - returns :const:`None` when an exception occurs. - Default is True. - - Returns: A numeric value or None. + A utility function that returns the value of a Pyomo object or + expression. + + Args: + obj: The argument to evaluate. If it is None, a + string, or any other primitive numeric type, + then this function simply returns the argument. + Otherwise, if the argument is a NumericValue + then the __call__ method is executed. + exception (bool): If :const:`True`, then an exception should + be raised when instances of NumericValue fail to + evaluate due to one or more objects not being + initialized to a numeric value (e.g, one or more + variables in an algebraic expression having the + value None). If :const:`False`, then the function + returns :const:`None` when an exception occurs. + Default is True. + + Returns: A numeric value or None. """ if obj.__class__ in native_types: return obj From 4c13589c08a87f62c3362f30467854447338ae21 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 18 Oct 2023 13:30:42 -0600 Subject: [PATCH 0303/1204] Track change in general expression API (use arg(0) and not expr). Fixes #2986 --- pyomo/core/expr/calculus/diff_with_pyomo.py | 2 +- pyomo/core/tests/unit/test_derivs.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/pyomo/core/expr/calculus/diff_with_pyomo.py b/pyomo/core/expr/calculus/diff_with_pyomo.py index 952e8ec6dd3..0e3ba3cc2b2 100644 --- a/pyomo/core/expr/calculus/diff_with_pyomo.py +++ b/pyomo/core/expr/calculus/diff_with_pyomo.py @@ -328,7 +328,7 @@ def _diff_GeneralExpression(node, val_dict, der_dict): val_dict: ComponentMap der_dict: ComponentMap """ - der_dict[node.expr] += der_dict[node] + der_dict[node.arg(0)] += der_dict[node] def _diff_ExternalFunctionExpression(node, val_dict, der_dict): diff --git a/pyomo/core/tests/unit/test_derivs.py b/pyomo/core/tests/unit/test_derivs.py index 9e89f2beac9..23a5a8bc7d1 100644 --- a/pyomo/core/tests/unit/test_derivs.py +++ b/pyomo/core/tests/unit/test_derivs.py @@ -230,6 +230,17 @@ def e2(m, i): symbolic = reverse_sd(m.o.expr) self.assertAlmostEqual(derivs[m.x], pyo.value(symbolic[m.x]), tol) + def test_constant_named_expressions(self): + m = pyo.ConcreteModel() + m.x = pyo.Var(initialize=3) + m.e = pyo.Expression(expr=2) + + e = m.x * m.e + derivs = reverse_ad(e) + symbolic = reverse_sd(e) + self.assertAlmostEqual(derivs[m.x], pyo.value(symbolic[m.x]), tol + 3) + self.assertAlmostEqual(derivs[m.x], approx_deriv(e, m.x), tol) + def test_multiple_named_expressions(self): m = pyo.ConcreteModel() m.x = pyo.Var() From 10edc68f71161e198c1d382be019da604c4af3d5 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 18 Oct 2023 16:02:39 -0600 Subject: [PATCH 0304/1204] Add debugging for when conda env setup fails --- .github/workflows/test_branches.yml | 17 +++++++++++++++-- .github/workflows/test_pr_and_main.yml | 15 ++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index ab91bf86ca1..540436f8781 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -318,7 +318,20 @@ jobs: fi done echo "*** Install Pyomo dependencies ***" - conda install --update-deps -q -y $CONDA_DEPENDENCIES + conda install --update-deps -q -y $CONDA_DEPENDENCIES || CONDA_INSTALL_ERR=1 + if test -n "$CONDA_INSTALL_ERR"; then + CONDAENV=$(conda list) + CONDA_INSTALL_ERR= + for PKG in $CONDA_DEPENDENCIES; do + if test `echo "$CONDAENV" | grep "^$PKG " | wc -l` -eq 0; then + echo "Conda dependency $PKG failed to install" + CONDA_INSTALL_ERR=1 + fi + done + if test -n "$CONDA_INSTALL_ERR"; then + exit 1 + fi + fi if test -z "${{matrix.slim}}"; then PYVER=$(echo "py${{matrix.python}}" | sed 's/\.//g') echo "Installing for $PYVER" @@ -334,7 +347,7 @@ jobs: echo "$_PKGLIST" _BASE=$(echo "$PKG" | sed 's/[=<>].*//') _BUILDS=$(echo "$_PKGLIST" | grep "^$_BASE " \ - | sed -r 's/\s+/ /g' | cut -d\ -f3) || echo ""x + | sed -r 's/\s+/ /g' | cut -d\ -f3) || echo "" if test -n "$_BUILDS"; then _ISPY=$(echo "$_BUILDS" | grep "^py") \ || echo "No python build detected" diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 5c27474d9d9..c6b5ef37535 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -348,7 +348,20 @@ jobs: fi done echo "*** Install Pyomo dependencies ***" - conda install --update-deps -q -y $CONDA_DEPENDENCIES + conda install --update-deps -q -y $CONDA_DEPENDENCIES || CONDA_INSTALL_ERR=1 + if test -n "$CONDA_INSTALL_ERR"; then + CONDAENV=$(conda list) + CONDA_INSTALL_ERR= + for PKG in $CONDA_DEPENDENCIES; do + if test `echo "$CONDAENV" | grep "^$PKG " | wc -l` -eq 0; then + echo "Conda dependency $PKG failed to install" + CONDA_INSTALL_ERR=1 + fi + done + if test -n "$CONDA_INSTALL_ERR"; then + exit 1 + fi + fi if test -z "${{matrix.slim}}"; then PYVER=$(echo "py${{matrix.python}}" | sed 's/\.//g') echo "Installing for $PYVER" From bbfcd680d76e6c004b3e97d4bf97cc5714c4cdcf Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 18 Oct 2023 16:09:52 -0600 Subject: [PATCH 0305/1204] Adding more debugging --- .github/workflows/test_branches.yml | 6 +++++- .github/workflows/test_pr_and_main.yml | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 540436f8781..78b4ccd0868 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -320,12 +320,16 @@ jobs: echo "*** Install Pyomo dependencies ***" conda install --update-deps -q -y $CONDA_DEPENDENCIES || CONDA_INSTALL_ERR=1 if test -n "$CONDA_INSTALL_ERR"; then + echo "WARNING: 'conda install' returned nonzero return code." + echo "Verifying packages:" CONDAENV=$(conda list) CONDA_INSTALL_ERR= for PKG in $CONDA_DEPENDENCIES; do if test `echo "$CONDAENV" | grep "^$PKG " | wc -l` -eq 0; then - echo "Conda dependency $PKG failed to install" + echo "[FAIL] $PKG" CONDA_INSTALL_ERR=1 + else + echo "[ OK ] $PKG" fi done if test -n "$CONDA_INSTALL_ERR"; then diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index c6b5ef37535..6e8db628524 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -350,12 +350,16 @@ jobs: echo "*** Install Pyomo dependencies ***" conda install --update-deps -q -y $CONDA_DEPENDENCIES || CONDA_INSTALL_ERR=1 if test -n "$CONDA_INSTALL_ERR"; then + echo "WARNING: 'conda install' returned nonzero return code." + echo "Verifying packages:" CONDAENV=$(conda list) CONDA_INSTALL_ERR= for PKG in $CONDA_DEPENDENCIES; do if test `echo "$CONDAENV" | grep "^$PKG " | wc -l` -eq 0; then - echo "Conda dependency $PKG failed to install" + echo "[FAIL] $PKG" CONDA_INSTALL_ERR=1 + else + echo "[ OK ] $PKG" fi done if test -n "$CONDA_INSTALL_ERR"; then From a86acd8c6547f7f129ec84b21b12715ac554b1b4 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 19 Oct 2023 11:37:04 -0600 Subject: [PATCH 0306/1204] Fix guards for mpi4py in tests --- .../pynumero/examples/tests/test_mpi_examples.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/pynumero/examples/tests/test_mpi_examples.py b/pyomo/contrib/pynumero/examples/tests/test_mpi_examples.py index 3c47d58754e..68fe907a8ef 100644 --- a/pyomo/contrib/pynumero/examples/tests/test_mpi_examples.py +++ b/pyomo/contrib/pynumero/examples/tests/test_mpi_examples.py @@ -7,26 +7,28 @@ ) SKIPTESTS = [] -if numpy_available and scipy_available: - pass -else: - SKIPTESTS.append("Pynumero needs scipy and numpy>=1.13.0 to run BlockMatrix tests") +if not numpy_available: + SKIPTESTS.append("Pynumero needs numpy>=1.13.0 to run BlockMatrix tests") +if not scipy_available: + SKIPTESTS.append("Pynumero needs scipy to run BlockMatrix tests") try: from mpi4py import MPI comm = MPI.COMM_WORLD if comm.Get_size() != 3: - SKIPTESTS.append("Pynumero MPI examples require exactly 3 processes") + SKIPTESTS.append( + f"Pynumero MPI examples require 3 MPI processes (got {comm.Get_size()})" + ) except ImportError: - SKIPTESTS.append("Pynumero MPI examples require exactly 3 processes") + SKIPTESTS.append("Pynumero MPI examples require mpi4py") if not SKIPTESTS: from pyomo.contrib.pynumero.examples import parallel_vector_ops, parallel_matvec @unittest.pytest.mark.mpi -@unittest.skipIf(SKIPTESTS, SKIPTESTS) +@unittest.skipIf(SKIPTESTS, "\n".join(SKIPTESTS)) class TestExamples(unittest.TestCase): def test_parallel_vector_ops(self): z1_local, z2, z3 = parallel_vector_ops.main() From aae8bb3266243d494ea395c0b6bad091326d96e5 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 19 Oct 2023 12:34:09 -0600 Subject: [PATCH 0307/1204] Remove explicit installation of openmpi --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 78b4ccd0868..67ba7b156c7 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -92,7 +92,7 @@ jobs: skip_doctest: 1 TARGET: linux PYENV: conda - PACKAGES: mpi4py openmpi + PACKAGES: mpi4py - os: ubuntu-latest python: '3.10' diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 6e8db628524..a352822c53c 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -93,7 +93,7 @@ jobs: skip_doctest: 1 TARGET: linux PYENV: conda - PACKAGES: mpi4py openmpi + PACKAGES: mpi4py - os: ubuntu-latest python: 3.11 From 67009063495fabafa0af00c00cf7a875615e1923 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 19 Oct 2023 12:52:41 -0600 Subject: [PATCH 0308/1204] Remove '--oversubscribe' from mpirun (mpich compatibility) --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 67ba7b156c7..892525a953e 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -640,7 +640,7 @@ jobs: # is fully generated by a single process before invoking MPI $PYTHON_EXE -c "from pyomo.dataportal.parse_datacmds import \ parse_data_commands; parse_data_commands(data='')" - mpirun -np ${{matrix.mpi}} --oversubscribe pytest -v \ + mpirun -np ${{matrix.mpi}} pytest -v \ --junit-xml=TEST-pyomo-mpi.xml \ -m "mpi" -W ignore::Warning \ pyomo `pwd`/pyomo-model-libraries diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index a352822c53c..18e0a1565cc 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -670,7 +670,7 @@ jobs: # is fully generated by a single process before invoking MPI $PYTHON_EXE -c "from pyomo.dataportal.parse_datacmds import \ parse_data_commands; parse_data_commands(data='')" - mpirun -np ${{matrix.mpi}} --oversubscribe pytest -v \ + mpirun -np ${{matrix.mpi}} pytest -v \ --junit-xml=TEST-pyomo-mpi.xml \ -m "mpi" -W ignore::Warning \ pyomo `pwd`/pyomo-model-libraries From 3a4399d69b621f007ba0fc0e5cffdbfb55f2fd5b Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 19 Oct 2023 13:17:54 -0600 Subject: [PATCH 0309/1204] Making inverse trig calls match the old walker, I think --- pyomo/contrib/fbbt/expression_bounds_walker.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/fbbt/expression_bounds_walker.py b/pyomo/contrib/fbbt/expression_bounds_walker.py index fb55a779017..7c84d4fc171 100644 --- a/pyomo/contrib/fbbt/expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/expression_bounds_walker.py @@ -154,15 +154,15 @@ def _handle_tan(visitor, node, arg): def _handle_asin(visitor, node, arg): - return asin(*arg) + return asin(*arg, -inf, inf, visitor.feasibility_tol) def _handle_acos(visitor, node, arg): - return acos(*arg) + return acos(*arg, -inf, inf, visitor.feasibility_tol) def _handle_atan(visitor, node, arg): - return atan(*arg) + return atan(*arg, -inf, inf) def _handle_sqrt(visitor, node, arg): From b1a1e097b4846ec5ab38069395b994b43e83b84b Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 19 Oct 2023 17:46:57 -0600 Subject: [PATCH 0310/1204] Track relocation of GamsExceptionExecution --- pyomo/solvers/plugins/solvers/GAMS.py | 5 ++++- pyomo/solvers/tests/checks/test_GAMS.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pyomo/solvers/plugins/solvers/GAMS.py b/pyomo/solvers/plugins/solvers/GAMS.py index ae0b12cdad4..c8224bbd999 100644 --- a/pyomo/solvers/plugins/solvers/GAMS.py +++ b/pyomo/solvers/plugins/solvers/GAMS.py @@ -250,7 +250,10 @@ def solve(self, *args, **kwds): self.available() from gams import GamsWorkspace, DebugLevel - from gams.workspace import GamsExceptionExecution + try: + from gams import GamsExceptionExecution + except ImportError: + from gams.workspace import GamsExceptionExecution if len(args) != 1: raise ValueError( diff --git a/pyomo/solvers/tests/checks/test_GAMS.py b/pyomo/solvers/tests/checks/test_GAMS.py index 5260f2bd195..c4913672694 100644 --- a/pyomo/solvers/tests/checks/test_GAMS.py +++ b/pyomo/solvers/tests/checks/test_GAMS.py @@ -31,7 +31,10 @@ opt_py = SolverFactory('gams', solver_io='python') gamspy_available = opt_py.available(exception_flag=False) if gamspy_available: - from gams.workspace import GamsExceptionExecution + try: + from gams import GamsExceptionExecution + except: + from gams.workspace import GamsExceptionExecution opt_gms = SolverFactory('gams', solver_io='gms') gamsgms_available = opt_gms.available(exception_flag=False) From 2c187e3f46943ecd007dac57d9d9254df07412be Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 19 Oct 2023 17:47:41 -0600 Subject: [PATCH 0311/1204] Remove special handling of conda install failures --- .github/workflows/test_branches.yml | 22 ++++------------------ .github/workflows/test_pr_and_main.yml | 22 ++++------------------ 2 files changed, 8 insertions(+), 36 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 892525a953e..e773587ec85 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -318,24 +318,9 @@ jobs: fi done echo "*** Install Pyomo dependencies ***" - conda install --update-deps -q -y $CONDA_DEPENDENCIES || CONDA_INSTALL_ERR=1 - if test -n "$CONDA_INSTALL_ERR"; then - echo "WARNING: 'conda install' returned nonzero return code." - echo "Verifying packages:" - CONDAENV=$(conda list) - CONDA_INSTALL_ERR= - for PKG in $CONDA_DEPENDENCIES; do - if test `echo "$CONDAENV" | grep "^$PKG " | wc -l` -eq 0; then - echo "[FAIL] $PKG" - CONDA_INSTALL_ERR=1 - else - echo "[ OK ] $PKG" - fi - done - if test -n "$CONDA_INSTALL_ERR"; then - exit 1 - fi - fi + # Note: this will fail the build if any installation fails (or + # possibly if it outputs messages to stderr) + conda install --update-deps -q -y $CONDA_DEPENDENCIES if test -z "${{matrix.slim}}"; then PYVER=$(echo "py${{matrix.python}}" | sed 's/\.//g') echo "Installing for $PYVER" @@ -640,6 +625,7 @@ jobs: # is fully generated by a single process before invoking MPI $PYTHON_EXE -c "from pyomo.dataportal.parse_datacmds import \ parse_data_commands; parse_data_commands(data='')" + # Note: if we are testing with openmpi, add '--oversubscribe' mpirun -np ${{matrix.mpi}} pytest -v \ --junit-xml=TEST-pyomo-mpi.xml \ -m "mpi" -W ignore::Warning \ diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 18e0a1565cc..2885fd107a8 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -348,24 +348,9 @@ jobs: fi done echo "*** Install Pyomo dependencies ***" - conda install --update-deps -q -y $CONDA_DEPENDENCIES || CONDA_INSTALL_ERR=1 - if test -n "$CONDA_INSTALL_ERR"; then - echo "WARNING: 'conda install' returned nonzero return code." - echo "Verifying packages:" - CONDAENV=$(conda list) - CONDA_INSTALL_ERR= - for PKG in $CONDA_DEPENDENCIES; do - if test `echo "$CONDAENV" | grep "^$PKG " | wc -l` -eq 0; then - echo "[FAIL] $PKG" - CONDA_INSTALL_ERR=1 - else - echo "[ OK ] $PKG" - fi - done - if test -n "$CONDA_INSTALL_ERR"; then - exit 1 - fi - fi + # Note: this will fail the build if any installation fails (or + # possibly if it outputs messages to stderr) + conda install --update-deps -q -y $CONDA_DEPENDENCIES if test -z "${{matrix.slim}}"; then PYVER=$(echo "py${{matrix.python}}" | sed 's/\.//g') echo "Installing for $PYVER" @@ -670,6 +655,7 @@ jobs: # is fully generated by a single process before invoking MPI $PYTHON_EXE -c "from pyomo.dataportal.parse_datacmds import \ parse_data_commands; parse_data_commands(data='')" + # Note: if we are testing with openmpi, add '--oversubscribe' mpirun -np ${{matrix.mpi}} pytest -v \ --junit-xml=TEST-pyomo-mpi.xml \ -m "mpi" -W ignore::Warning \ From d0a8c6e7577f409196d6370214faf347292834d3 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 19 Oct 2023 17:50:46 -0600 Subject: [PATCH 0312/1204] AApply black --- pyomo/solvers/plugins/solvers/GAMS.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/solvers/plugins/solvers/GAMS.py b/pyomo/solvers/plugins/solvers/GAMS.py index c8224bbd999..16a126b9af4 100644 --- a/pyomo/solvers/plugins/solvers/GAMS.py +++ b/pyomo/solvers/plugins/solvers/GAMS.py @@ -250,6 +250,7 @@ def solve(self, *args, **kwds): self.available() from gams import GamsWorkspace, DebugLevel + try: from gams import GamsExceptionExecution except ImportError: From e154fbc318c00503e3a4086f4874099440cb3e21 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 19 Oct 2023 21:26:48 -0600 Subject: [PATCH 0313/1204] Explicitly disallow networkx=3.2 on python3.8 --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fe73782136c..6f68eb56db5 100644 --- a/setup.py +++ b/setup.py @@ -242,7 +242,12 @@ def _print_deps(self, deplist): # Note: matplotlib 3.6.1 has bug #24127, which breaks # seaborn's histplot (triggering parmest failures) 'matplotlib!=3.6.1', - 'networkx', # network, incidence_analysis, community_detection + # network, incidence_analysis, community_detection + # Note: networkx 3.2 is Python>-3.9, but there is a broken + # 3.2 package on conda-forgethat will get implicitly + # installed on python 3.8 + 'networkx<3.2;python_version<3.9', + 'networkx;python_version>=3.9', 'numpy', 'openpyxl', # dataportals #'pathos', # requested for #963, but PR currently closed From 9cc018a85c234d9a9076d0a192687c96b0d954c5 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 19 Oct 2023 21:53:05 -0600 Subject: [PATCH 0314/1204] Improve version comparison in setup.py to handle comparisons with 3.10+ --- setup.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 6f68eb56db5..0fd6c939e39 100644 --- a/setup.py +++ b/setup.py @@ -166,9 +166,23 @@ def run(self): print(' '.join(deps)) def _print_deps(self, deplist): + class version_cmp(object): + ver = tuple(map(int, platform.python_version_tuple()[:2])) + def __lt__(self, other): + return self.ver < tuple(map(int, other.split('.'))) + def __le__(self, other): + return self.ver <= tuple(map(int, other.split('.'))) + def __gt__(self, other): + return not self.__le__(other) + def __ge__(self, other): + return not self.__lt__(other) + def __eq__(self, other): + return self.ver == tuple(map(int, other.split('.'))) + def __ne__(self, other): + return not self.__eq__(other) implementation_name = sys.implementation.name platform_system = platform.system() - python_version = '.'.join(platform.python_version_tuple()[:2]) + python_version = version_cmp() for entry in deplist: dep, _, condition = (_.strip() for _ in entry.partition(';')) if condition and not eval(condition): @@ -246,8 +260,8 @@ def _print_deps(self, deplist): # Note: networkx 3.2 is Python>-3.9, but there is a broken # 3.2 package on conda-forgethat will get implicitly # installed on python 3.8 - 'networkx<3.2;python_version<3.9', - 'networkx;python_version>=3.9', + 'networkx<3.2; python_version<"3.9"', + 'networkx; python_version>="3.9"', 'numpy', 'openpyxl', # dataportals #'pathos', # requested for #963, but PR currently closed From 0593891b1a6b46c07c0313d91b00797b1a1f15c6 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 19 Oct 2023 21:55:29 -0600 Subject: [PATCH 0315/1204] Apply black --- setup.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/setup.py b/setup.py index 0fd6c939e39..252ef2d063e 100644 --- a/setup.py +++ b/setup.py @@ -168,18 +168,25 @@ def run(self): def _print_deps(self, deplist): class version_cmp(object): ver = tuple(map(int, platform.python_version_tuple()[:2])) + def __lt__(self, other): return self.ver < tuple(map(int, other.split('.'))) + def __le__(self, other): return self.ver <= tuple(map(int, other.split('.'))) + def __gt__(self, other): return not self.__le__(other) + def __ge__(self, other): return not self.__lt__(other) + def __eq__(self, other): return self.ver == tuple(map(int, other.split('.'))) + def __ne__(self, other): return not self.__eq__(other) + implementation_name = sys.implementation.name platform_system = platform.system() python_version = version_cmp() From 393fe7fe99c544ed75ed89668b9e2b99291d258b Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 20 Oct 2023 12:51:05 -0600 Subject: [PATCH 0316/1204] Fixing a bug with absolute value --- pyomo/contrib/fbbt/expression_bounds_walker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/fbbt/expression_bounds_walker.py b/pyomo/contrib/fbbt/expression_bounds_walker.py index 7c84d4fc171..cd6fe73fd14 100644 --- a/pyomo/contrib/fbbt/expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/expression_bounds_walker.py @@ -169,7 +169,7 @@ def _handle_sqrt(visitor, node, arg): return power(*arg, 0.5, 0.5, feasibility_tol=visitor.feasibility_tol) -def _handle_abs(visitor, node, arg): +def _handle_AbsExpression(visitor, node, arg): return interval_abs(*arg) @@ -197,7 +197,6 @@ def _handle_named_expression(visitor, node, arg): 'acos': _handle_acos, 'atan': _handle_atan, 'sqrt': _handle_sqrt, - 'abs': _handle_abs, } _operator_dispatcher = defaultdict( @@ -205,6 +204,7 @@ def _handle_named_expression(visitor, node, arg): ProductExpression: _handle_ProductExpression, DivisionExpression: _handle_DivisionExpression, PowExpression: _handle_PowExpression, + AbsExpression: _handle_AbsExpression, SumExpression: _handle_SumExpression, MonomialTermExpression: _handle_ProductExpression, NegationExpression: _handle_NegationExpression, From 3bb68f17f18e13f381e94cda74ebf4edafefd10d Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 20 Oct 2023 12:51:18 -0600 Subject: [PATCH 0317/1204] Adding some comments --- pyomo/contrib/fbbt/interval.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/fbbt/interval.py b/pyomo/contrib/fbbt/interval.py index db036f50f01..61a89817fc0 100644 --- a/pyomo/contrib/fbbt/interval.py +++ b/pyomo/contrib/fbbt/interval.py @@ -147,7 +147,7 @@ def power(xl, xu, yl, yu, feasibility_tol): else: lb = xl**y ub = xu**y - else: + else: # xu is positive if y < 0: if y % 2 == 0: lb = min(xl**y, xu**y) @@ -155,8 +155,9 @@ def power(xl, xu, yl, yu, feasibility_tol): else: lb = -inf ub = inf - else: + else: # exponent is nonnegative if y % 2 == 0: + # xl is negative and xu is positive, so lb is 0 lb = 0 ub = max(xl**y, xu**y) else: @@ -321,7 +322,7 @@ def _inverse_power2(zl, zu, xl, xu, feasiblity_tol): def interval_abs(xl, xu): abs_xl = abs(xl) abs_xu = abs(xu) - if xl <= 0 <= xu: + if xl <= 0 and 0 <= xu: res_lb = 0 res_ub = max(abs_xl, abs_xu) else: From 062bae5f7f31fc2c377e6fc67d360c832b4f302d Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 20 Oct 2023 12:52:24 -0600 Subject: [PATCH 0318/1204] Blackify --- .../contrib/fbbt/expression_bounds_walker.py | 40 ++++++++++++++----- pyomo/contrib/fbbt/fbbt.py | 18 +++++---- pyomo/contrib/fbbt/interval.py | 6 +-- 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/pyomo/contrib/fbbt/expression_bounds_walker.py b/pyomo/contrib/fbbt/expression_bounds_walker.py index cd6fe73fd14..476800807fe 100644 --- a/pyomo/contrib/fbbt/expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/expression_bounds_walker.py @@ -12,8 +12,21 @@ from collections import defaultdict from pyomo.common.collections import ComponentMap from pyomo.contrib.fbbt.interval import ( - add, acos, asin, atan, cos, div, exp, interval_abs, log, - log10, mul, power, sin, sub, tan, + add, + acos, + asin, + atan, + cos, + div, + exp, + interval_abs, + log, + log10, + mul, + power, + sin, + sub, + tan, ) from pyomo.core.base.expression import _GeneralExpressionData, ScalarExpression from pyomo.core.expr.numeric_expr import ( @@ -27,7 +40,7 @@ LinearExpression, SumExpression, ExternalFunctionExpression, -) +) from pyomo.core.expr.numvalue import native_numeric_types, native_types, value from pyomo.core.expr.visitor import StreamBasedExpressionVisitor @@ -49,7 +62,8 @@ def _before_var(visitor, child): if val is None: raise ValueError( "Var '%s' is fixed to None. This value cannot be used to " - "calculate bounds." % child.name) + "calculate bounds." % child.name + ) leaf_bounds[child] = (child.value, child.value) else: lb = value(child.lb) @@ -200,7 +214,8 @@ def _handle_named_expression(visitor, node, arg): } _operator_dispatcher = defaultdict( - lambda: _handle_no_bounds, { + lambda: _handle_no_bounds, + { ProductExpression: _handle_ProductExpression, DivisionExpression: _handle_DivisionExpression, PowExpression: _handle_PowExpression, @@ -212,19 +227,24 @@ def _handle_named_expression(visitor, node, arg): LinearExpression: _handle_SumExpression, _GeneralExpressionData: _handle_named_expression, ScalarExpression: _handle_named_expression, - } + }, ) + class ExpressionBoundsVisitor(StreamBasedExpressionVisitor): """ Walker to calculate bounds on an expression, from leaf to root, with caching of terminal node bounds (Vars and Expressions) """ - def __init__(self, leaf_bounds=None, feasibility_tol=1e-8, - use_fixed_var_values_as_bounds=False): + + def __init__( + self, + leaf_bounds=None, + feasibility_tol=1e-8, + use_fixed_var_values_as_bounds=False, + ): super().__init__() - self.leaf_bounds = leaf_bounds if leaf_bounds is not None \ - else ComponentMap() + self.leaf_bounds = leaf_bounds if leaf_bounds is not None else ComponentMap() self.feasibility_tol = feasibility_tol self.use_fixed_var_values_as_bounds = use_fixed_var_values_as_bounds diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index 4fbad47c427..0fd6da8ac50 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -91,8 +91,9 @@ def _prop_bnds_leaf_to_root_ProductExpression(visitor, node, arg1, arg2): """ bnds_dict = visitor.bnds_dict if arg1 is arg2: - bnds_dict[node] = interval.power(*bnds_dict[arg1], 2, 2, - visitor.feasibility_tol) + bnds_dict[node] = interval.power( + *bnds_dict[arg1], 2, 2, visitor.feasibility_tol + ) else: bnds_dict[node] = interval.mul(*bnds_dict[arg1], *bnds_dict[arg2]) @@ -1074,8 +1075,7 @@ class _FBBTVisitorLeafToRoot(StreamBasedExpressionVisitor): the expression tree (all the way to the root node). """ - def __init__(self, integer_tol=1e-4, feasibility_tol=1e-8, - ignore_fixed=False ): + def __init__(self, integer_tol=1e-4, feasibility_tol=1e-8, ignore_fixed=False): """ Parameters ---------- @@ -1113,8 +1113,9 @@ def exitNode(self, node, data): def walk_expression(self, expr, bnds_dict=None, leaf_bnds_dict=None): try: self.bnds_dict = bnds_dict if bnds_dict is not None else ComponentMap() - self.leaf_bnds_dict = leaf_bnds_dict if leaf_bnds_dict is not None else \ - ComponentMap() + self.leaf_bnds_dict = ( + leaf_bnds_dict if leaf_bnds_dict is not None else ComponentMap() + ) super().walk_expression(expr) result = self.bnds_dict[expr] finally: @@ -1130,7 +1131,7 @@ def walk_expression(self, expr, bnds_dict=None, leaf_bnds_dict=None): # feasibility_tol=1e-8, ignore_fixed=False): # if bnds_dict is None: # bnds_dict = {} - + class _FBBTVisitorRootToLeaf(ExpressionValueVisitor): """ @@ -1553,7 +1554,8 @@ def compute_bounds_on_expr(expr, ignore_fixed=False): ub: float """ lb, ub = ExpressionBoundsVisitor( - use_fixed_var_values_as_bounds=not ignore_fixed).walk_expression(expr) + use_fixed_var_values_as_bounds=not ignore_fixed + ).walk_expression(expr) if lb == -interval.inf: lb = None if ub == interval.inf: diff --git a/pyomo/contrib/fbbt/interval.py b/pyomo/contrib/fbbt/interval.py index 61a89817fc0..fd86af4c106 100644 --- a/pyomo/contrib/fbbt/interval.py +++ b/pyomo/contrib/fbbt/interval.py @@ -33,7 +33,7 @@ def mul(xl, xu, yl, yu): lb = i if i > ub: ub = i - if i != i: # math.isnan(i) + if i != i: # math.isnan(i) return (-inf, inf) return lb, ub @@ -147,7 +147,7 @@ def power(xl, xu, yl, yu, feasibility_tol): else: lb = xl**y ub = xu**y - else: # xu is positive + else: # xu is positive if y < 0: if y % 2 == 0: lb = min(xl**y, xu**y) @@ -155,7 +155,7 @@ def power(xl, xu, yl, yu, feasibility_tol): else: lb = -inf ub = inf - else: # exponent is nonnegative + else: # exponent is nonnegative if y % 2 == 0: # xl is negative and xu is positive, so lb is 0 lb = 0 From 9219573d4c46b421f81f1ad7daa994dfa9b477e0 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 23 Oct 2023 10:08:00 -0600 Subject: [PATCH 0319/1204] NFC: fix comment typo --- pyomo/common/dependencies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 55a21916299..a0717dba883 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -816,7 +816,7 @@ def _finalize_numpy(np, available): numeric_types._native_boolean_types.add(t) _complex = [np.complex_, np.complex64, np.complex128] # complex192 and complex256 may or may not be defined in this - # particular numpy build (it depends ono platform and version). + # particular numpy build (it depends on platform and version). # Register them only if they are present if hasattr(np, 'complex192'): _complex.append(np.complex192) From c100bf917e9a31b90611041c707e5902d0cc04da Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 23 Oct 2023 14:34:46 -0600 Subject: [PATCH 0320/1204] Resolve broken import --- pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py index 6451db18087..1fb9b87de2e 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py @@ -7,7 +7,6 @@ from pyomo.common.log import LoggingIntercept from pyomo.common.tee import capture_output from pyomo.contrib.appsi.solvers.highs import Highs -from pyomo.contrib.appsi.base import TerminationCondition opt = Highs() From 332d28694e1acc8247262539e5a03b87f52596e1 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 23 Oct 2023 14:51:57 -0600 Subject: [PATCH 0321/1204] Resolving conflicts again: stream_solver -> tee --- pyomo/contrib/appsi/solvers/highs.py | 2 +- pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index da4ea8c130a..3d2104cdbfa 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -349,7 +349,7 @@ def set_instance(self, model): level=self.config.log_level, logger=self.config.solver_output_logger ) ] - if self.config.stream_solver: + if self.config.tee: ostreams.append(sys.stdout) with TeeStream(*ostreams) as t: with capture_output(output=t.STDOUT, capture_fd=True): diff --git a/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py index 1fb9b87de2e..da39a5c3d55 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py @@ -94,7 +94,7 @@ def test_capture_highs_output(self): model[-2:-1] = [ 'opt = Highs()', - 'opt.config.stream_solver = True', + 'opt.config.tee = True', 'result = opt.solve(m)', ] with LoggingIntercept() as LOG, capture_output(capture_fd=True) as OUT: From 0b348a0ac8a73fdaae9e628aefa17446c23b9584 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 23 Oct 2023 15:09:40 -0600 Subject: [PATCH 0322/1204] Resolve convergence of APPSI and new Results object --- pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py | 6 +++--- .../contrib/appsi/solvers/tests/test_persistent_solvers.py | 4 ++-- pyomo/solver/tests/test_results.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py index da39a5c3d55..25b7ae91b86 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py @@ -37,7 +37,7 @@ def test_mutable_params_with_remove_cons(self): del m.c1 m.p2.value = 2 res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, -8) + self.assertAlmostEqual(res.incumbent_objective, -8) def test_mutable_params_with_remove_vars(self): m = pe.ConcreteModel() @@ -59,14 +59,14 @@ def test_mutable_params_with_remove_vars(self): opt = Highs() res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1) + self.assertAlmostEqual(res.incumbent_objective, 1) del m.c1 del m.c2 m.p1.value = -9 m.p2.value = 9 res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, -9) + self.assertAlmostEqual(res.incumbent_objective, -9) def test_capture_highs_output(self): # tests issue #3003 diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 1b9f5c3b0a2..299a5bd5b7e 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -1354,13 +1354,13 @@ def test_bug_2(self, name: str, opt_class: Type[PersistentSolverBase], only_chil m.x.fix(1) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 2, 5) + self.assertAlmostEqual(res.incumbent_objective, 2, 5) m.x.unfix() m.x.setlb(-9) m.x.setub(9) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, -18, 5) + self.assertAlmostEqual(res.incumbent_objective, -18, 5) @unittest.skipUnless(cmodel_available, 'appsi extensions are not available') diff --git a/pyomo/solver/tests/test_results.py b/pyomo/solver/tests/test_results.py index f43b2b50ef4..5392c1135f8 100644 --- a/pyomo/solver/tests/test_results.py +++ b/pyomo/solver/tests/test_results.py @@ -35,7 +35,7 @@ def test_member_list(self): 'interrupted', 'licensingProblems', ] - self.assertEqual(member_list, expected_list) + self.assertEqual(member_list.sort(), expected_list.sort()) def test_codes(self): self.assertEqual(results.TerminationCondition.unknown.value, 42) From 457c5993dcfe73ee95064a54ac9e41a33fe9e0e8 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 23 Oct 2023 15:19:55 -0600 Subject: [PATCH 0323/1204] Resolve one more convergence error --- pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py index 25b7ae91b86..cd65783c566 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py @@ -32,7 +32,7 @@ def test_mutable_params_with_remove_cons(self): opt = Highs() res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1) + self.assertAlmostEqual(res.incumbent_objective, 1) del m.c1 m.p2.value = 2 From 11c7651d2fb761760f3742693a46300794bd9189 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 24 Oct 2023 09:46:33 -0600 Subject: [PATCH 0324/1204] Tracking change in Black rules --- pyomo/common/formatting.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/common/formatting.py b/pyomo/common/formatting.py index f76d16880df..5c2b329ce21 100644 --- a/pyomo/common/formatting.py +++ b/pyomo/common/formatting.py @@ -257,7 +257,8 @@ def writelines(self, sequence): r'|(?:\[\s*[A-Za-z0-9\.]+\s*\] +)' # [PASS]|[FAIL]|[ OK ] ) _verbatim_line_start = re.compile( - r'(\| )' r'|(\+((-{3,})|(={3,}))\+)' # line blocks # grid table + r'(\| )' # line blocks + r'|(\+((-{3,})|(={3,}))\+)' # grid table ) _verbatim_line = re.compile( r'(={3,}[ =]+)' # simple tables, ======== sections From 6d7ab0063e7a384e4e3648cb19675d641c8ddca9 Mon Sep 17 00:00:00 2001 From: robbybp Date: Wed, 25 Oct 2023 18:26:38 -0600 Subject: [PATCH 0325/1204] remove outdated comment --- pyomo/contrib/pynumero/interfaces/cyipopt_interface.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py index f277fca6231..fc9c45c6d1a 100644 --- a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py +++ b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py @@ -350,8 +350,6 @@ def objective(self, x): self._set_primals_if_necessary(x) return self._nlp.evaluate_objective() except PyNumeroEvaluationError: - # TODO: halt_on_evaluation_error option. If set, we re-raise the - # original exception. if self._halt_on_evaluation_error: raise else: From a58dc41a3d15eec8cda39cc5642b34ad39fd45f0 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 26 Oct 2023 08:39:42 -0600 Subject: [PATCH 0326/1204] Obvious bug fix --- pyomo/solver/IPOPT.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index 6501154d7ac..875f8710b10 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -84,11 +84,11 @@ def version(self): @property def config(self): - return self._config + return self.config @config.setter def config(self, val): - self._config = val + self.config = val def solve(self, model, **kwds): # Check if solver is available From 2753d4b4117ed33252b3892a8adc235d43ef4c92 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Thu, 26 Oct 2023 13:43:45 -0600 Subject: [PATCH 0327/1204] fix scip results processing --- pyomo/solvers/plugins/solvers/SCIPAMPL.py | 39 ++++++++++++----------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/pyomo/solvers/plugins/solvers/SCIPAMPL.py b/pyomo/solvers/plugins/solvers/SCIPAMPL.py index 69a24455706..0973ff38f68 100644 --- a/pyomo/solvers/plugins/solvers/SCIPAMPL.py +++ b/pyomo/solvers/plugins/solvers/SCIPAMPL.py @@ -288,7 +288,7 @@ def _postsolve(self): # UNKNOWN # unknown='unknown' # An uninitialized value - if results.solver.message == "unknown": + if "unknown" in results.solver.message: results.solver.status = SolverStatus.unknown results.solver.termination_condition = TerminationCondition.unknown if len(results.solution) > 0: @@ -296,7 +296,7 @@ def _postsolve(self): # ABORTED # userInterrupt='userInterrupt' # Interrupt signal generated by user - elif results.solver.message == "user interrupt": + elif "user interrupt" in results.solver.message: results.solver.status = SolverStatus.aborted results.solver.termination_condition = TerminationCondition.userInterrupt if len(results.solution) > 0: @@ -304,7 +304,7 @@ def _postsolve(self): # OK # maxEvaluations='maxEvaluations' # Exceeded maximum number of problem evaluations - elif results.solver.message == "node limit reached": + elif "node limit reached" in results.solver.message: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxEvaluations if len(results.solution) > 0: @@ -312,7 +312,7 @@ def _postsolve(self): # OK # maxEvaluations='maxEvaluations' # Exceeded maximum number of problem evaluations - elif results.solver.message == "total node limit reached": + elif "total node limit reached" in results.solver.message: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxEvaluations if len(results.solution) > 0: @@ -320,7 +320,7 @@ def _postsolve(self): # OK # maxEvaluations='maxEvaluations' # Exceeded maximum number of problem evaluations - elif results.solver.message == "stall node limit reached": + elif "stall node limit reached" in results.solver.message: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxEvaluations if len(results.solution) > 0: @@ -328,7 +328,7 @@ def _postsolve(self): # OK # maxTimeLimit='maxTimeLimit' # Exceeded maximum time limited allowed by user but having return a feasible solution - elif results.solver.message == "time limit reached": + elif "time limit reached" in results.solver.message: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxTimeLimit if len(results.solution) > 0: @@ -336,7 +336,7 @@ def _postsolve(self): # OK # other='other' # Other, uncategorized normal termination - elif results.solver.message == "memory limit reached": + elif "memory limit reached" in results.solver.message: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.other if len(results.solution) > 0: @@ -344,7 +344,7 @@ def _postsolve(self): # OK # other='other' # Other, uncategorized normal termination - elif results.solver.message == "gap limit reached": + elif "gap limit reached" in results.solver.message: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.other if len(results.solution) > 0: @@ -352,7 +352,7 @@ def _postsolve(self): # OK # other='other' # Other, uncategorized normal termination - elif results.solver.message == "solution limit reached": + elif "solution limit reached" in results.solver.message: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.other if len(results.solution) > 0: @@ -360,7 +360,7 @@ def _postsolve(self): # OK # other='other' # Other, uncategorized normal termination - elif results.solver.message == "solution improvement limit reached": + elif "solution improvement limit reached" in results.solver.message: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.other if len(results.solution) > 0: @@ -368,19 +368,22 @@ def _postsolve(self): # OK # optimal='optimal' # Found an optimal solution - elif results.solver.message == "optimal solution found": + elif "optimal solution" in results.solver.message: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.optimal if len(results.solution) > 0: results.solution(0).status = SolutionStatus.optimal - if results.problem.sense == ProblemSense.minimize: - results.problem.lower_bound = results.solver.primal_bound - else: - results.problem.upper_bound = results.solver.primal_bound + try: + if results.problem.sense == ProblemSense.minimize: + results.problem.lower_bound = results.solver.primal_bound + else: + results.problem.upper_bound = results.solver.primal_bound + except AttributeError: + pass # WARNING # infeasible='infeasible' # Demonstrated that the problem is infeasible - elif results.solver.message == "infeasible": + elif "infeasible" in results.solver.message: results.solver.status = SolverStatus.warning results.solver.termination_condition = TerminationCondition.infeasible if len(results.solution) > 0: @@ -388,7 +391,7 @@ def _postsolve(self): # WARNING # unbounded='unbounded' # Demonstrated that problem is unbounded - elif results.solver.message == "unbounded": + elif "unbounded" in results.solver.message: results.solver.status = SolverStatus.warning results.solver.termination_condition = TerminationCondition.unbounded if len(results.solution) > 0: @@ -396,7 +399,7 @@ def _postsolve(self): # WARNING # infeasibleOrUnbounded='infeasibleOrUnbounded' # Problem is either infeasible or unbounded - elif results.solver.message == "infeasible or unbounded": + elif "infeasible or unbounded" in results.solver.message: results.solver.status = SolverStatus.warning results.solver.termination_condition = ( TerminationCondition.infeasibleOrUnbounded From 6bcdadbfcdb9b81abe987aed665c5137c45b7c07 Mon Sep 17 00:00:00 2001 From: Zedong Peng Date: Thu, 26 Oct 2023 21:12:46 -0400 Subject: [PATCH 0328/1204] fix bug --- pyomo/contrib/mindtpy/algorithm_base_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index c254a8db72b..c0afea58a99 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -831,7 +831,7 @@ def init_rNLP(self, add_oa_cuts=True): subprob_terminate_cond = results.solver.termination_condition # Sometimes, the NLP solver might be trapped in a infeasible solution if the objective function is nonlinear and partition_obj_nonlinear_terms is True. If this happens, we will use the original objective function instead. - if subprob_terminate_cond == tc.infeasible and config.partition_obj_nonlinear_terms: + if subprob_terminate_cond == tc.infeasible and config.partition_obj_nonlinear_terms and self.rnlp.MindtPy_utils.objective_list[0].expr.polynomial_degree() not in self.mip_objective_polynomial_degree: config.logger.info( 'Initial relaxed NLP problem is infeasible. This might be related to partition_obj_nonlinear_terms. Try to solve it again without partitioning nonlinear objective function.') self.rnlp.MindtPy_utils.objective.deactivate() From 126d3d8537b7f93293cbc11ecc94822a19b8fd7f Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 27 Oct 2023 12:46:46 -0600 Subject: [PATCH 0329/1204] Moving onto BeforeChildDispatcher and ExitNodeDispatcher base classes --- .../contrib/fbbt/expression_bounds_walker.py | 146 +++++++++--------- 1 file changed, 72 insertions(+), 74 deletions(-) diff --git a/pyomo/contrib/fbbt/expression_bounds_walker.py b/pyomo/contrib/fbbt/expression_bounds_walker.py index 476800807fe..ec50385784e 100644 --- a/pyomo/contrib/fbbt/expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/expression_bounds_walker.py @@ -28,7 +28,7 @@ sub, tan, ) -from pyomo.core.base.expression import _GeneralExpressionData, ScalarExpression +from pyomo.core.base.expression import Expression from pyomo.core.expr.numeric_expr import ( NegationExpression, ProductExpression, @@ -43,83 +43,86 @@ ) from pyomo.core.expr.numvalue import native_numeric_types, native_types, value from pyomo.core.expr.visitor import StreamBasedExpressionVisitor +from pyomo.repn.util import BeforeChildDispatcher, ExitNodeDispatcher inf = float('inf') - -def _before_external_function(visitor, child): - # [ESJ 10/6/23]: If external functions ever implement callbacks to help with - # this then this should use them - return False, (-inf, inf) - - -def _before_var(visitor, child): - leaf_bounds = visitor.leaf_bounds - if child in leaf_bounds: - pass - elif child.is_fixed() and visitor.use_fixed_var_values_as_bounds: - val = child.value - if val is None: - raise ValueError( - "Var '%s' is fixed to None. This value cannot be used to " - "calculate bounds." % child.name - ) - leaf_bounds[child] = (child.value, child.value) - else: - lb = value(child.lb) - ub = value(child.ub) - if lb is None: - lb = -inf - if ub is None: - ub = inf - leaf_bounds[child] = (lb, ub) - return False, leaf_bounds[child] - - -def _before_named_expression(visitor, child): - leaf_bounds = visitor.leaf_bounds - if child in leaf_bounds: +class ExpressionBoundsBeforeChildDispatcher(BeforeChildDispatcher): + __slots__ = () + + def __init__(self): + self[ExternalFunctionExpression] = self._before_external_function + + @staticmethod + def _before_external_function(visitor, child): + # [ESJ 10/6/23]: If external functions ever implement callbacks to help with + # this then this should use them + return False, (-inf, inf) + + @staticmethod + def _before_var(visitor, child): + leaf_bounds = visitor.leaf_bounds + if child in leaf_bounds: + pass + elif child.is_fixed() and visitor.use_fixed_var_values_as_bounds: + val = child.value + if val is None: + raise ValueError( + "Var '%s' is fixed to None. This value cannot be used to " + "calculate bounds." % child.name + ) + leaf_bounds[child] = (child.value, child.value) + else: + lb = value(child.lb) + ub = value(child.ub) + if lb is None: + lb = -inf + if ub is None: + ub = inf + leaf_bounds[child] = (lb, ub) return False, leaf_bounds[child] - else: - return True, None - - -def _before_param(visitor, child): - return False, (child.value, child.value) - -def _before_constant(visitor, child): - return False, (child, child) + @staticmethod + def _before_named_expression(visitor, child): + leaf_bounds = visitor.leaf_bounds + if child in leaf_bounds: + return False, leaf_bounds[child] + else: + return True, None + @staticmethod + def _before_param(visitor, child): + return False, (child.value, child.value) -def _before_other(visitor, child): - return True, None + @staticmethod + def _before_native(visitor, child): + return False, (child, child) + @staticmethod + def _before_string(visitor, child): + raise ValueError( + f"Cannot compute bounds on expression containing {child!r} " + "of type {type(child)}, which is not a valid numeric type") -def _register_new_before_child_handler(visitor, child): - handlers = _before_child_handlers - child_type = child.__class__ - if child_type in native_numeric_types: - handlers[child_type] = _before_constant - elif child_type in native_types: - pass - # TODO: catch this, it's bad. - elif not child.is_expression_type(): - if child.is_potentially_variable(): - handlers[child_type] = _before_var - else: - handlers[child_type] = _before_param - elif issubclass(child_type, _GeneralExpressionData): - handlers[child_type] = _before_named_expression - else: - handlers[child_type] = _before_other - return handlers[child_type](visitor, child) + @staticmethod + def _before_invalid(visitor, child): + raise ValueError( + f"Cannot compute bounds on expression containing {child!r} " + "of type {type(child)}, which is not a valid numeric type") + @staticmethod + def _before_complex(visitor, child): + raise ValueError( + f"Cannot compute bounds on expression containing " + "complex numbers. Encountered when processing {child!r}") -_before_child_handlers = defaultdict(lambda: _register_new_before_child_handler) -_before_child_handlers[ExternalFunctionExpression] = _before_external_function + @staticmethod + def _before_npv(visitor, child): + return False, (value(child), value(child)) +_before_child_handlers = ExpressionBoundsBeforeChildDispatcher() + def _handle_ProductExpression(visitor, node, arg1, arg2): return mul(*arg1, *arg2) @@ -187,10 +190,6 @@ def _handle_AbsExpression(visitor, node, arg): return interval_abs(*arg) -def _handle_no_bounds(visitor, node, *args): - return (-inf, inf) - - def _handle_UnaryFunctionExpression(visitor, node, arg): return _unary_function_dispatcher[node.getname()](visitor, node, arg) @@ -213,8 +212,8 @@ def _handle_named_expression(visitor, node, arg): 'sqrt': _handle_sqrt, } -_operator_dispatcher = defaultdict( - lambda: _handle_no_bounds, + +_operator_dispatcher = ExitNodeDispatcher( { ProductExpression: _handle_ProductExpression, DivisionExpression: _handle_DivisionExpression, @@ -225,9 +224,8 @@ def _handle_named_expression(visitor, node, arg): NegationExpression: _handle_NegationExpression, UnaryFunctionExpression: _handle_UnaryFunctionExpression, LinearExpression: _handle_SumExpression, - _GeneralExpressionData: _handle_named_expression, - ScalarExpression: _handle_named_expression, - }, + Expression: _handle_named_expression, + } ) From b4b2c5b069088de3f7f876e0c8225c9351c08e7d Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 27 Oct 2023 13:49:18 -0600 Subject: [PATCH 0330/1204] Testing error messages for expression bounds walker, handling inverse trig with bounds on the range --- .../contrib/fbbt/expression_bounds_walker.py | 22 +- .../tests/test_expression_bounds_walker.py | 295 ++++++++++++++++++ 2 files changed, 308 insertions(+), 9 deletions(-) create mode 100644 pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py diff --git a/pyomo/contrib/fbbt/expression_bounds_walker.py b/pyomo/contrib/fbbt/expression_bounds_walker.py index ec50385784e..3665f5bace7 100644 --- a/pyomo/contrib/fbbt/expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/expression_bounds_walker.py @@ -10,6 +10,7 @@ # ___________________________________________________________________________ from collections import defaultdict +from math import pi from pyomo.common.collections import ComponentMap from pyomo.contrib.fbbt.interval import ( add, @@ -101,20 +102,23 @@ def _before_native(visitor, child): @staticmethod def _before_string(visitor, child): raise ValueError( - f"Cannot compute bounds on expression containing {child!r} " - "of type {type(child)}, which is not a valid numeric type") + f"{child!r} ({type(child)}) is not a valid numeric type. " + f"Cannot compute bounds on expression." + ) @staticmethod def _before_invalid(visitor, child): raise ValueError( - f"Cannot compute bounds on expression containing {child!r} " - "of type {type(child)}, which is not a valid numeric type") + f"{child!r} ({type(child)}) is not a valid numeric type. " + f"Cannot compute bounds on expression." + ) @staticmethod def _before_complex(visitor, child): raise ValueError( - f"Cannot compute bounds on expression containing " - "complex numbers. Encountered when processing {child!r}") + f"Cannot compute bounds on expressions containing " + f"complex numbers. Encountered when processing {child}" + ) @staticmethod def _before_npv(visitor, child): @@ -171,15 +175,15 @@ def _handle_tan(visitor, node, arg): def _handle_asin(visitor, node, arg): - return asin(*arg, -inf, inf, visitor.feasibility_tol) + return asin(*arg, -pi/2, pi/2, visitor.feasibility_tol) def _handle_acos(visitor, node, arg): - return acos(*arg, -inf, inf, visitor.feasibility_tol) + return acos(*arg, 0, pi, visitor.feasibility_tol) def _handle_atan(visitor, node, arg): - return atan(*arg, -inf, inf) + return atan(*arg, -pi/2, pi/2) def _handle_sqrt(visitor, node, arg): diff --git a/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py b/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py new file mode 100644 index 00000000000..adc0754d83e --- /dev/null +++ b/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py @@ -0,0 +1,295 @@ +# ___________________________________________________________________________ +# +# 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 math +from pyomo.environ import exp, log, log10, sin, cos, tan, asin, acos, atan, sqrt +import pyomo.common.unittest as unittest +from pyomo.contrib.fbbt.expression_bounds_walker import ExpressionBoundsVisitor +from pyomo.core import Any, ConcreteModel, Expression, Param, Var + + +class TestExpressionBoundsWalker(unittest.TestCase): + def make_model(self): + m = ConcreteModel() + m.x = Var(bounds=(-2, 4)) + m.y = Var(bounds=(3, 5)) + m.z = Var(bounds=(0.5, 0.75)) + return m + + def test_sum_bounds(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(m.x + m.y) + self.assertEqual(lb, 1) + self.assertEqual(ub, 9) + + self.assertEqual(len(visitor.leaf_bounds), 2) + self.assertIn(m.x, visitor.leaf_bounds) + self.assertIn(m.y, visitor.leaf_bounds) + self.assertEqual(visitor.leaf_bounds[m.x], (-2, 4)) + self.assertEqual(visitor.leaf_bounds[m.y], (3, 5)) + + def test_fixed_var(self): + m = self.make_model() + m.x.fix(3) + + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(m.x + m.y) + self.assertEqual(lb, 1) + self.assertEqual(ub, 9) + + self.assertEqual(len(visitor.leaf_bounds), 2) + self.assertIn(m.x, visitor.leaf_bounds) + self.assertIn(m.y, visitor.leaf_bounds) + self.assertEqual(visitor.leaf_bounds[m.x], (-2, 4)) + self.assertEqual(visitor.leaf_bounds[m.y], (3, 5)) + + def test_fixed_var_value_used_for_bounds(self): + m = self.make_model() + m.x.fix(3) + + visitor = ExpressionBoundsVisitor(use_fixed_var_values_as_bounds=True) + lb, ub = visitor.walk_expression(m.x + m.y) + self.assertEqual(lb, 6) + self.assertEqual(ub, 8) + + self.assertEqual(len(visitor.leaf_bounds), 2) + self.assertIn(m.x, visitor.leaf_bounds) + self.assertIn(m.y, visitor.leaf_bounds) + self.assertEqual(visitor.leaf_bounds[m.x], (3, 3)) + self.assertEqual(visitor.leaf_bounds[m.y], (3, 5)) + + def test_product_bounds(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(m.x * m.y) + self.assertEqual(lb, -10) + self.assertEqual(ub, 20) + + def test_division_bounds(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(m.x / m.y) + self.assertAlmostEqual(lb, -2 / 3) + self.assertAlmostEqual(ub, 4 / 3) + + def test_power_bounds(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(m.y**m.x) + self.assertEqual(lb, 5 ** (-2)) + self.assertEqual(ub, 5**4) + + def test_negation_bounds(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(-(m.y + 3 * m.x)) + self.assertEqual(lb, -17) + self.assertEqual(ub, 3) + + def test_exp_bounds(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(exp(m.y)) + self.assertAlmostEqual(lb, math.e**3) + self.assertAlmostEqual(ub, math.e**5) + + def test_log_bounds(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(log(m.y)) + self.assertAlmostEqual(lb, log(3)) + self.assertAlmostEqual(ub, log(5)) + + def test_log10_bounds(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(log10(m.y)) + self.assertAlmostEqual(lb, log10(3)) + self.assertAlmostEqual(ub, log10(5)) + + def test_sin_bounds(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(sin(m.y)) + self.assertAlmostEqual(lb, -1) # reaches -1 at 3*pi/2 \approx 4.712 + self.assertAlmostEqual(ub, sin(3)) # it's positive here + + def test_cos_bounds(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(cos(m.y)) + self.assertAlmostEqual(lb, -1) # reaches -1 at pi + self.assertAlmostEqual(ub, cos(5)) # it's positive here + + def test_tan_bounds(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(tan(m.y)) + self.assertEqual(lb, -float('inf')) + self.assertEqual(ub, float('inf')) + + def test_asin_bounds(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(asin(m.z)) + self.assertAlmostEqual(lb, asin(0.5)) + self.assertAlmostEqual(ub, asin(0.75)) + + def test_acos_bounds(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(acos(m.z)) + self.assertAlmostEqual(lb, acos(0.75)) + self.assertAlmostEqual(ub, acos(0.5)) + + def test_atan_bounds(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(atan(m.z)) + self.assertAlmostEqual(lb, atan(0.5)) + self.assertAlmostEqual(ub, atan(0.75)) + + def test_sqrt_bounds(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(sqrt(m.y)) + self.assertAlmostEqual(lb, sqrt(3)) + self.assertAlmostEqual(ub, sqrt(5)) + + def test_abs_bounds(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(abs(m.x)) + self.assertEqual(lb, 0) + self.assertEqual(ub, 4) + + def test_leaf_bounds_cached(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(m.x - m.y) + self.assertEqual(lb, -7) + self.assertEqual(ub, 1) + + self.assertIn(m.x, visitor.leaf_bounds) + self.assertEqual(visitor.leaf_bounds[m.x], m.x.bounds) + self.assertIn(m.y, visitor.leaf_bounds) + self.assertEqual(visitor.leaf_bounds[m.y], m.y.bounds) + + # This should exercise the code that uses the cache. + lb, ub = visitor.walk_expression(m.x**2 + 3) + self.assertEqual(lb, 3) + self.assertEqual(ub, 19) + + def test_var_fixed_to_None(self): + m = self.make_model() + m.x.fix(None) + + visitor = ExpressionBoundsVisitor(use_fixed_var_values_as_bounds=True) + with self.assertRaisesRegex( + ValueError, + "Var 'x' is fixed to None. This value cannot be " + "used to calculate bounds.", + ): + lb, ub = visitor.walk_expression(m.x - m.y) + + def test_var_with_no_lb(self): + m = self.make_model() + m.x.setlb(None) + + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(m.x - m.y) + self.assertEqual(lb, -float('inf')) + self.assertEqual(ub, 1) + + def test_var_with_no_ub(self): + m = self.make_model() + m.y.setub(None) + + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(m.x - m.y) + self.assertEqual(lb, -float('inf')) + self.assertEqual(ub, 1) + + def test_param(self): + m = self.make_model() + m.p = Param(initialize=6) + + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(m.p**m.y) + self.assertEqual(lb, 6**3) + self.assertEqual(ub, 6**5) + + def test_mutable_param(self): + m = self.make_model() + m.p = Param(initialize=6, mutable=True) + + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(m.p**m.y) + self.assertEqual(lb, 6**3) + self.assertEqual(ub, 6**5) + + def test_named_expression(self): + m = self.make_model() + m.e = Expression(expr=sqrt(m.x**2 + m.y**2)) + visitor = ExpressionBoundsVisitor() + + lb, ub = visitor.walk_expression(m.e + 4) + self.assertEqual(lb, 7) + self.assertAlmostEqual(ub, sqrt(41) + 4) + + self.assertIn(m.e, visitor.leaf_bounds) + self.assertEqual(visitor.leaf_bounds[m.e][0], 3) + self.assertAlmostEqual(visitor.leaf_bounds[m.e][1], sqrt(41)) + + # exercise the using of the cached bounds + lb, ub = visitor.walk_expression(m.e) + self.assertEqual(lb, 3) + self.assertAlmostEqual(ub, sqrt(41)) + + def test_npv_expression(self): + m = self.make_model() + m.p = Param(initialize=4, mutable=True) + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(1/m.p) + self.assertEqual(lb, 0.25) + self.assertEqual(ub, 0.25) + + def test_invalid_numeric_type(self): + m = self.make_model() + m.p = Param(initialize=True, domain=Any) + visitor = ExpressionBoundsVisitor() + with self.assertRaisesRegex( + ValueError, + r"True \(\) is not a valid numeric type. " + r"Cannot compute bounds on expression."): + lb, ub = visitor.walk_expression(m.p + m.y) + + def test_invalid_string(self): + m = self.make_model() + m.p = Param(initialize='True', domain=Any) + visitor = ExpressionBoundsVisitor() + with self.assertRaisesRegex( + ValueError, + r"'True' \(\) is not a valid numeric type. " + r"Cannot compute bounds on expression."): + lb, ub = visitor.walk_expression(m.p + m.y) + + def test_invalid_complex(self): + m = self.make_model() + m.p = Param(initialize=complex(4, 5), domain=Any) + visitor = ExpressionBoundsVisitor() + with self.assertRaisesRegex( + ValueError, + r"Cannot compute bounds on expressions containing " + r"complex numbers. Encountered when processing \(4\+5j\)" + ): + lb, ub = visitor.walk_expression(m.p + m.y) From e6e70a380742b21f03fd97c709f5c67448e4cf38 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 27 Oct 2023 13:50:08 -0600 Subject: [PATCH 0331/1204] Running black --- .../contrib/fbbt/expression_bounds_walker.py | 8 ++++--- .../tests/test_expression_bounds_walker.py | 22 ++++++++++--------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/pyomo/contrib/fbbt/expression_bounds_walker.py b/pyomo/contrib/fbbt/expression_bounds_walker.py index 3665f5bace7..ef4a398debe 100644 --- a/pyomo/contrib/fbbt/expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/expression_bounds_walker.py @@ -48,6 +48,7 @@ inf = float('inf') + class ExpressionBoundsBeforeChildDispatcher(BeforeChildDispatcher): __slots__ = () @@ -124,9 +125,10 @@ def _before_complex(visitor, child): def _before_npv(visitor, child): return False, (value(child), value(child)) + _before_child_handlers = ExpressionBoundsBeforeChildDispatcher() - + def _handle_ProductExpression(visitor, node, arg1, arg2): return mul(*arg1, *arg2) @@ -175,7 +177,7 @@ def _handle_tan(visitor, node, arg): def _handle_asin(visitor, node, arg): - return asin(*arg, -pi/2, pi/2, visitor.feasibility_tol) + return asin(*arg, -pi / 2, pi / 2, visitor.feasibility_tol) def _handle_acos(visitor, node, arg): @@ -183,7 +185,7 @@ def _handle_acos(visitor, node, arg): def _handle_atan(visitor, node, arg): - return atan(*arg, -pi/2, pi/2) + return atan(*arg, -pi / 2, pi / 2) def _handle_sqrt(visitor, node, arg): diff --git a/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py b/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py index adc0754d83e..9f991d5849a 100644 --- a/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py @@ -259,7 +259,7 @@ def test_npv_expression(self): m = self.make_model() m.p = Param(initialize=4, mutable=True) visitor = ExpressionBoundsVisitor() - lb, ub = visitor.walk_expression(1/m.p) + lb, ub = visitor.walk_expression(1 / m.p) self.assertEqual(lb, 0.25) self.assertEqual(ub, 0.25) @@ -268,9 +268,10 @@ def test_invalid_numeric_type(self): m.p = Param(initialize=True, domain=Any) visitor = ExpressionBoundsVisitor() with self.assertRaisesRegex( - ValueError, - r"True \(\) is not a valid numeric type. " - r"Cannot compute bounds on expression."): + ValueError, + r"True \(\) is not a valid numeric type. " + r"Cannot compute bounds on expression.", + ): lb, ub = visitor.walk_expression(m.p + m.y) def test_invalid_string(self): @@ -278,9 +279,10 @@ def test_invalid_string(self): m.p = Param(initialize='True', domain=Any) visitor = ExpressionBoundsVisitor() with self.assertRaisesRegex( - ValueError, - r"'True' \(\) is not a valid numeric type. " - r"Cannot compute bounds on expression."): + ValueError, + r"'True' \(\) is not a valid numeric type. " + r"Cannot compute bounds on expression.", + ): lb, ub = visitor.walk_expression(m.p + m.y) def test_invalid_complex(self): @@ -288,8 +290,8 @@ def test_invalid_complex(self): m.p = Param(initialize=complex(4, 5), domain=Any) visitor = ExpressionBoundsVisitor() with self.assertRaisesRegex( - ValueError, - r"Cannot compute bounds on expressions containing " - r"complex numbers. Encountered when processing \(4\+5j\)" + ValueError, + r"Cannot compute bounds on expressions containing " + r"complex numbers. Encountered when processing \(4\+5j\)", ): lb, ub = visitor.walk_expression(m.p + m.y) From 6590c4e6b40ba3e5307aa78516d24f6a047fc6d4 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 27 Oct 2023 13:55:34 -0600 Subject: [PATCH 0332/1204] Whoops, more black --- pyomo/gdp/plugins/bigm_mixin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyomo/gdp/plugins/bigm_mixin.py b/pyomo/gdp/plugins/bigm_mixin.py index 5209dad0860..6e8eca172d4 100644 --- a/pyomo/gdp/plugins/bigm_mixin.py +++ b/pyomo/gdp/plugins/bigm_mixin.py @@ -105,11 +105,12 @@ def _get_bigM_arg_list(self, bigm_args, block): return arg_list def _set_up_expr_bound_visitor(self): - #bnds_dict = ComponentMap() + # bnds_dict = ComponentMap() # we assume the default config arg for 'assume_fixed_vars_permanent,` # and we will change it during apply_to if we need to self._expr_bound_visitor = ExpressionBoundsVisitor( - use_fixed_var_values_as_bounds=False) + use_fixed_var_values_as_bounds=False + ) def _process_M_value( self, From b4503bb545b73d8c01442926a61a0679d2b8cec2 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 27 Oct 2023 14:19:04 -0600 Subject: [PATCH 0333/1204] Removing a lot of unnecessary changes in FBBT leaf-to-root walker --- pyomo/contrib/fbbt/fbbt.py | 80 ++++++++------------------- pyomo/contrib/fbbt/tests/test_fbbt.py | 1 - 2 files changed, 22 insertions(+), 59 deletions(-) diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index 0fd6da8ac50..b78d69547dc 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -274,7 +274,6 @@ def _prop_bnds_leaf_to_root_atan(visitor, node, arg): ---------- visitor: _FBBTVisitorLeafToRoot node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression - """ bnds_dict = visitor.bnds_dict bnds_dict[node] = interval.atan(*bnds_dict[arg], -interval.inf, interval.inf) @@ -304,18 +303,21 @@ def _prop_no_bounds(visitor, node, *args): visitor.bnds_dict[node] = (-interval.inf, interval.inf) -_unary_leaf_to_root_map = defaultdict(lambda: _prop_no_bounds) -_unary_leaf_to_root_map['exp'] = _prop_bnds_leaf_to_root_exp -_unary_leaf_to_root_map['log'] = _prop_bnds_leaf_to_root_log -_unary_leaf_to_root_map['log10'] = _prop_bnds_leaf_to_root_log10 -_unary_leaf_to_root_map['sin'] = _prop_bnds_leaf_to_root_sin -_unary_leaf_to_root_map['cos'] = _prop_bnds_leaf_to_root_cos -_unary_leaf_to_root_map['tan'] = _prop_bnds_leaf_to_root_tan -_unary_leaf_to_root_map['asin'] = _prop_bnds_leaf_to_root_asin -_unary_leaf_to_root_map['acos'] = _prop_bnds_leaf_to_root_acos -_unary_leaf_to_root_map['atan'] = _prop_bnds_leaf_to_root_atan -_unary_leaf_to_root_map['sqrt'] = _prop_bnds_leaf_to_root_sqrt -_unary_leaf_to_root_map['abs'] = _prop_bnds_leaf_to_root_abs +_unary_leaf_to_root_map = defaultdict( + lambda: _prop_no_bounds, + { + 'exp': _prop_bnds_leaf_to_root_exp, + 'log': _prop_bnds_leaf_to_root_log, + 'log10': _prop_bnds_leaf_to_root_log10, + 'sin': _prop_bnds_leaf_to_root_sin, + 'cos': _prop_bnds_leaf_to_root_cos, + 'tan': _prop_bnds_leaf_to_root_tan, + 'asin': _prop_bnds_leaf_to_root_asin, + 'acos': _prop_bnds_leaf_to_root_acos, + 'atan': _prop_bnds_leaf_to_root_atan, + 'sqrt': _prop_bnds_leaf_to_root_sqrt, + 'abs': _prop_bnds_leaf_to_root_abs, + }) def _prop_bnds_leaf_to_root_UnaryFunctionExpression(visitor, node, arg): @@ -343,16 +345,12 @@ def _prop_bnds_leaf_to_root_GeneralExpression(visitor, node, expr): bnds_dict = visitor.bnds_dict if node in bnds_dict: return - elif node in visitor.leaf_bnds_dict: - bnds_dict[node] = visitor.leaf_bnds_dict[node] - return if expr.__class__ in native_types: expr_lb = expr_ub = expr else: expr_lb, expr_ub = bnds_dict[expr] bnds_dict[node] = (expr_lb, expr_ub) - visitor.leaf_bnds_dict[node] = (expr_lb, expr_ub) _prop_bnds_leaf_to_root_map = defaultdict(lambda: _prop_no_bounds) @@ -991,20 +989,14 @@ def _check_and_reset_bounds(var, lb, ub): def _before_constant(visitor, child): if child in visitor.bnds_dict: pass - elif child in visitor.leaf_bnds_dict: - visitor.bnds_dict[child] = visitor.leaf_bnds_dict[child] else: visitor.bnds_dict[child] = (child, child) - visitor.leaf_bnds_dict[child] = (child, child) return False, None def _before_var(visitor, child): if child in visitor.bnds_dict: return False, None - elif child in visitor.leaf_bnds_dict: - visitor.bnds_dict[child] = visitor.leaf_bnds_dict[child] - return False, None elif child.is_fixed() and not visitor.ignore_fixed: lb = value(child.value) ub = lb @@ -1021,19 +1013,14 @@ def _before_var(visitor, child): 'upper bound: {0}'.format(str(child)) ) visitor.bnds_dict[child] = (lb, ub) - visitor.leaf_bnds_dict[child] = (lb, ub) return False, None def _before_NPV(visitor, child): if child in visitor.bnds_dict: return False, None - if child in visitor.leaf_bnds_dict: - visitor.bnds_dict[child] = visitor.leaf_bnds_dict[child] - return False, None val = value(child) visitor.bnds_dict[child] = (val, val) - visitor.leaf_bnds_dict[child] = (val, val) return False, None @@ -1075,12 +1062,12 @@ class _FBBTVisitorLeafToRoot(StreamBasedExpressionVisitor): the expression tree (all the way to the root node). """ - def __init__(self, integer_tol=1e-4, feasibility_tol=1e-8, ignore_fixed=False): + def __init__(self, bnds_dict, integer_tol=1e-4, feasibility_tol=1e-8, + ignore_fixed=False): """ Parameters ---------- - leaf_bnds_dict: ComponentMap, if you want to cache leaf-node bounds - bnds_dict: ComponentMap, if you want to cache non-leaf bounds + bnds_dict: ComponentMap integer_tol: float feasibility_tol: float If the bounds computed on the body of a constraint violate the bounds of @@ -1091,9 +1078,7 @@ def __init__(self, integer_tol=1e-4, feasibility_tol=1e-8, ignore_fixed=False): to prevent math domain errors (a larger value is more conservative). """ super().__init__() - # self.bnds_dict = bnds_dict if bnds_dict is not None else ComponentMap() - # self.leaf_bnds_dict = leaf_bnds_dict if leaf_bnds_dict is not None else \ - # ComponentMap() + self.bnds_dict = bnds_dict self.integer_tol = integer_tol self.feasibility_tol = feasibility_tol self.ignore_fixed = ignore_fixed @@ -1110,28 +1095,6 @@ def beforeChild(self, node, child, child_idx): def exitNode(self, node, data): _prop_bnds_leaf_to_root_map[node.__class__](self, node, *node.args) - def walk_expression(self, expr, bnds_dict=None, leaf_bnds_dict=None): - try: - self.bnds_dict = bnds_dict if bnds_dict is not None else ComponentMap() - self.leaf_bnds_dict = ( - leaf_bnds_dict if leaf_bnds_dict is not None else ComponentMap() - ) - super().walk_expression(expr) - result = self.bnds_dict[expr] - finally: - if bnds_dict is None: - self.bnds_dict.clear() - if leaf_bnds_dict is None: - self.leaf_bnds_dict.clear() - return result - - -# class FBBTVisitorLeafToRoot(_FBBTVisitorLeafToRoot): -# def __init__(self, leaf_bnds_dict, bnds_dict=None, integer_tol=1e-4, -# feasibility_tol=1e-8, ignore_fixed=False): -# if bnds_dict is None: -# bnds_dict = {} - class _FBBTVisitorRootToLeaf(ExpressionValueVisitor): """ @@ -1300,8 +1263,9 @@ def _fbbt_con(con, config): ) # a dictionary to store the bounds of every node in the tree # a walker to propagate bounds from the variables to the root - visitorA = _FBBTVisitorLeafToRoot(feasibility_tol=config.feasibility_tol) - visitorA.walk_expression(con.body, bnds_dict=bnds_dict) + visitorA = _FBBTVisitorLeafToRoot(bnds_dict=bnds_dict, + feasibility_tol=config.feasibility_tol) + visitorA.walk_expression(con.body) # Now we need to replace the bounds in bnds_dict for the root # node with the bounds on the constraint (if those bounds are diff --git a/pyomo/contrib/fbbt/tests/test_fbbt.py b/pyomo/contrib/fbbt/tests/test_fbbt.py index 7fa17bfbb9a..5e8d656eeab 100644 --- a/pyomo/contrib/fbbt/tests/test_fbbt.py +++ b/pyomo/contrib/fbbt/tests/test_fbbt.py @@ -1110,7 +1110,6 @@ def test_compute_expr_bounds(self): m.y = pyo.Var(bounds=(-1, 1)) e = m.x + m.y lb, ub = compute_bounds_on_expr(e) - print(lb, ub) self.assertAlmostEqual(lb, -2, 14) self.assertAlmostEqual(ub, 2, 14) From 417dec0d2ae1b23f620bc2d50b65b5b4528ed892 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 27 Oct 2023 14:19:15 -0600 Subject: [PATCH 0334/1204] Removing unused import --- pyomo/contrib/fbbt/expression_bounds_walker.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/contrib/fbbt/expression_bounds_walker.py b/pyomo/contrib/fbbt/expression_bounds_walker.py index ef4a398debe..b16016aa630 100644 --- a/pyomo/contrib/fbbt/expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/expression_bounds_walker.py @@ -9,7 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from collections import defaultdict from math import pi from pyomo.common.collections import ComponentMap from pyomo.contrib.fbbt.interval import ( From d77405618ca7c7a7a02ff514b6c4914164212726 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 27 Oct 2023 14:19:33 -0600 Subject: [PATCH 0335/1204] NFC: black --- pyomo/contrib/fbbt/fbbt.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index b78d69547dc..f6dd04d1c15 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -317,7 +317,8 @@ def _prop_no_bounds(visitor, node, *args): 'atan': _prop_bnds_leaf_to_root_atan, 'sqrt': _prop_bnds_leaf_to_root_sqrt, 'abs': _prop_bnds_leaf_to_root_abs, - }) + }, +) def _prop_bnds_leaf_to_root_UnaryFunctionExpression(visitor, node, arg): @@ -1062,8 +1063,9 @@ class _FBBTVisitorLeafToRoot(StreamBasedExpressionVisitor): the expression tree (all the way to the root node). """ - def __init__(self, bnds_dict, integer_tol=1e-4, feasibility_tol=1e-8, - ignore_fixed=False): + def __init__( + self, bnds_dict, integer_tol=1e-4, feasibility_tol=1e-8, ignore_fixed=False + ): """ Parameters ---------- @@ -1263,8 +1265,9 @@ def _fbbt_con(con, config): ) # a dictionary to store the bounds of every node in the tree # a walker to propagate bounds from the variables to the root - visitorA = _FBBTVisitorLeafToRoot(bnds_dict=bnds_dict, - feasibility_tol=config.feasibility_tol) + visitorA = _FBBTVisitorLeafToRoot( + bnds_dict=bnds_dict, feasibility_tol=config.feasibility_tol + ) visitorA.walk_expression(con.body) # Now we need to replace the bounds in bnds_dict for the root From 00723ab926aa6491264f7601adf53c664dfbded0 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 27 Oct 2023 14:30:43 -0600 Subject: [PATCH 0336/1204] Building the leaf-to-root handler defaultdict all at once --- pyomo/contrib/fbbt/fbbt.py | 49 +++++++++++++------------------------- 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index f6dd04d1c15..b4d11829ca7 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -354,39 +354,22 @@ def _prop_bnds_leaf_to_root_GeneralExpression(visitor, node, expr): bnds_dict[node] = (expr_lb, expr_ub) -_prop_bnds_leaf_to_root_map = defaultdict(lambda: _prop_no_bounds) -_prop_bnds_leaf_to_root_map[ - numeric_expr.ProductExpression -] = _prop_bnds_leaf_to_root_ProductExpression -_prop_bnds_leaf_to_root_map[ - numeric_expr.DivisionExpression -] = _prop_bnds_leaf_to_root_DivisionExpression -_prop_bnds_leaf_to_root_map[ - numeric_expr.PowExpression -] = _prop_bnds_leaf_to_root_PowExpression -_prop_bnds_leaf_to_root_map[ - numeric_expr.SumExpression -] = _prop_bnds_leaf_to_root_SumExpression -_prop_bnds_leaf_to_root_map[ - numeric_expr.MonomialTermExpression -] = _prop_bnds_leaf_to_root_ProductExpression -_prop_bnds_leaf_to_root_map[ - numeric_expr.NegationExpression -] = _prop_bnds_leaf_to_root_NegationExpression -_prop_bnds_leaf_to_root_map[ - numeric_expr.UnaryFunctionExpression -] = _prop_bnds_leaf_to_root_UnaryFunctionExpression -_prop_bnds_leaf_to_root_map[ - numeric_expr.LinearExpression -] = _prop_bnds_leaf_to_root_SumExpression -_prop_bnds_leaf_to_root_map[numeric_expr.AbsExpression] = _prop_bnds_leaf_to_root_abs - -_prop_bnds_leaf_to_root_map[ - _GeneralExpressionData -] = _prop_bnds_leaf_to_root_GeneralExpression -_prop_bnds_leaf_to_root_map[ - ScalarExpression -] = _prop_bnds_leaf_to_root_GeneralExpression +_prop_bnds_leaf_to_root_map = defaultdict( + lambda: _prop_no_bounds, + { + numeric_expr.ProductExpression: _prop_bnds_leaf_to_root_ProductExpression, + numeric_expr.DivisionExpression: _prop_bnds_leaf_to_root_DivisionExpression, + numeric_expr.PowExpression: _prop_bnds_leaf_to_root_PowExpression, + numeric_expr.SumExpression: _prop_bnds_leaf_to_root_SumExpression, + numeric_expr.MonomialTermExpression: _prop_bnds_leaf_to_root_ProductExpression, + numeric_expr.NegationExpression: _prop_bnds_leaf_to_root_NegationExpression, + numeric_expr.UnaryFunctionExpression: _prop_bnds_leaf_to_root_UnaryFunctionExpression, + numeric_expr.LinearExpression: _prop_bnds_leaf_to_root_SumExpression, + numeric_expr.AbsExpression: _prop_bnds_leaf_to_root_abs, + _GeneralExpressionData: _prop_bnds_leaf_to_root_GeneralExpression, + ScalarExpression: _prop_bnds_leaf_to_root_GeneralExpression, + }, +) def _prop_bnds_root_to_leaf_ProductExpression(node, bnds_dict, feasibility_tol): From ca6c4803686cdd3e7dce56055088c66432650c62 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 27 Oct 2023 14:35:23 -0600 Subject: [PATCH 0337/1204] NFC: formatting --- pyomo/contrib/fbbt/fbbt.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index b4d11829ca7..dbdd992b9c8 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -1248,9 +1248,7 @@ def _fbbt_con(con, config): ) # a dictionary to store the bounds of every node in the tree # a walker to propagate bounds from the variables to the root - visitorA = _FBBTVisitorLeafToRoot( - bnds_dict=bnds_dict, feasibility_tol=config.feasibility_tol - ) + visitorA = _FBBTVisitorLeafToRoot(bnds_dict, feasibility_tol=config.feasibility_tol) visitorA.walk_expression(con.body) # Now we need to replace the bounds in bnds_dict for the root From b67f73aa703fd53785a347363933b6ed743a756a Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 27 Oct 2023 15:00:16 -0600 Subject: [PATCH 0338/1204] Removing unused things in BigM transformations, restoring state on the bounds visitor --- pyomo/gdp/plugins/bigm.py | 3 +-- pyomo/gdp/plugins/bigm_mixin.py | 3 +-- pyomo/gdp/plugins/multiple_bigm.py | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/pyomo/gdp/plugins/bigm.py b/pyomo/gdp/plugins/bigm.py index cbc03bafb18..b960b5087ea 100644 --- a/pyomo/gdp/plugins/bigm.py +++ b/pyomo/gdp/plugins/bigm.py @@ -173,12 +173,11 @@ def _apply_to(self, instance, **kwds): # this map! with PauseGC(): try: - self._leaf_bnds_dict = ComponentMap() self._apply_to_impl(instance, **kwds) finally: self._restore_state() self.used_args.clear() - self._leaf_bnds_dict = ComponentMap() + self._expr_bound_visitor.leaf_bounds.clear() self._expr_bound_visitor.use_fixed_var_values_as_bounds = False def _apply_to_impl(self, instance, **kwds): diff --git a/pyomo/gdp/plugins/bigm_mixin.py b/pyomo/gdp/plugins/bigm_mixin.py index 6e8eca172d4..a4df641c8c6 100644 --- a/pyomo/gdp/plugins/bigm_mixin.py +++ b/pyomo/gdp/plugins/bigm_mixin.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ from pyomo.gdp import GDP_Error -from pyomo.common.collections import ComponentMap, ComponentSet +from pyomo.common.collections import ComponentSet from pyomo.contrib.fbbt.expression_bounds_walker import ExpressionBoundsVisitor import pyomo.contrib.fbbt.interval as interval from pyomo.core import Suffix @@ -105,7 +105,6 @@ def _get_bigM_arg_list(self, bigm_args, block): return arg_list def _set_up_expr_bound_visitor(self): - # bnds_dict = ComponentMap() # we assume the default config arg for 'assume_fixed_vars_permanent,` # and we will change it during apply_to if we need to self._expr_bound_visitor = ExpressionBoundsVisitor( diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index ca6a01cee52..18f159c7ca2 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -209,13 +209,12 @@ def _apply_to(self, instance, **kwds): self.used_args = ComponentMap() with PauseGC(): try: - self._leaf_bnds_dict = ComponentMap() self._apply_to_impl(instance, **kwds) finally: self._restore_state() self.used_args.clear() self._arg_list.clear() - self._leaf_bnds_dict = ComponentMap() + self._expr_bound_visitor.leaf_bounds.clear() self._expr_bound_visitor.use_fixed_var_values_as_bounds = False def _apply_to_impl(self, instance, **kwds): From f00520c977d7279254a2bc971454335cb1718490 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 27 Oct 2023 16:28:06 -0600 Subject: [PATCH 0339/1204] Fixing a bug where products didn't check if they should be squares --- pyomo/contrib/fbbt/expression_bounds_walker.py | 2 ++ pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/pyomo/contrib/fbbt/expression_bounds_walker.py b/pyomo/contrib/fbbt/expression_bounds_walker.py index b16016aa630..2d2f2701848 100644 --- a/pyomo/contrib/fbbt/expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/expression_bounds_walker.py @@ -129,6 +129,8 @@ def _before_npv(visitor, child): def _handle_ProductExpression(visitor, node, arg1, arg2): + if arg1 is arg2: + return power(*arg1, 2, 2, feasibility_tol=visitor.feasibility_tol) return mul(*arg1, *arg2) diff --git a/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py b/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py index 9f991d5849a..c51230155a7 100644 --- a/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py @@ -88,6 +88,14 @@ def test_power_bounds(self): self.assertEqual(lb, 5 ** (-2)) self.assertEqual(ub, 5**4) + def test_sums_of_squares_bounds(self): + m = ConcreteModel() + m.x = Var([1, 2], bounds=(-2, 6)) + visitor = ExpressionBoundsVisitor() + lb, ub = visitor.walk_expression(m.x[1] * m.x[1] + m.x[2] * m.x[2]) + self.assertEqual(lb, 0) + self.assertEqual(ub, 72) + def test_negation_bounds(self): m = self.make_model() visitor = ExpressionBoundsVisitor() From db0cb67696342e6aee56c2eac5264bc99cfd19aa Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 27 Oct 2023 16:35:09 -0600 Subject: [PATCH 0340/1204] NFC: Adding a doc string with a word to the wise --- pyomo/contrib/fbbt/expression_bounds_walker.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pyomo/contrib/fbbt/expression_bounds_walker.py b/pyomo/contrib/fbbt/expression_bounds_walker.py index 2d2f2701848..35cc33522ba 100644 --- a/pyomo/contrib/fbbt/expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/expression_bounds_walker.py @@ -240,6 +240,22 @@ class ExpressionBoundsVisitor(StreamBasedExpressionVisitor): """ Walker to calculate bounds on an expression, from leaf to root, with caching of terminal node bounds (Vars and Expressions) + + NOTE: If anything changes on the model (e.g., Var bounds, fixing, mutable + Param values, etc), then you need to either create a new instance of this + walker, or clear self.leaf_bounds! + + Parameters + ---------- + leaf_bounds: ComponentMap in which to cache bounds at leaves of the expression + tree + feasibility_tol: float, feasibility tolerance for interval arithmetic + calculations + use_fixed_var_values_as_bounds: bool, whether or not to use the values of + fixed Vars as the upper and lower bounds for those Vars or to instead + ignore fixed status and use the bounds. Set to 'True' if you do not + anticipate the fixed status of Variables to change for the duration that + the computed bounds should be valid. """ def __init__( From c980dac7ff62cc154e0a6baca85b79599809fb76 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 27 Oct 2023 18:40:59 -0600 Subject: [PATCH 0341/1204] cplex_direct: fix quadratic objective off-diagonal-terms --- pyomo/solvers/plugins/solvers/cplex_direct.py | 9 +- pyomo/solvers/tests/mip/test_qp.py | 194 ++++++++++++++++++ 2 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 pyomo/solvers/tests/mip/test_qp.py diff --git a/pyomo/solvers/plugins/solvers/cplex_direct.py b/pyomo/solvers/plugins/solvers/cplex_direct.py index 3ddb328ebdd..49fec1e6d09 100644 --- a/pyomo/solvers/plugins/solvers/cplex_direct.py +++ b/pyomo/solvers/plugins/solvers/cplex_direct.py @@ -596,8 +596,13 @@ def _set_objective(self, obj): cplex_expr, referenced_vars = self._get_expr_from_pyomo_expr( obj.expr, self._max_obj_degree ) - for i in range(len(cplex_expr.q_coefficients)): - cplex_expr.q_coefficients[i] *= 2 + # CPLEX actually uses x'Qx/2 in the objective, as the + # off-diagonal entries appear in both the lower triangle and the + # upper triancle (i.e., c*x1*x2 and c*x2*x1). However, since + # the diagonal entries only appear once, we need to double them. + for i, v1 in enumerate(cplex_expr.q_variables1): + if v1 == cplex_expr.q_variables2[i]: + cplex_expr.q_coefficients[i] *= 2 for var in referenced_vars: self._referenced_variables[var] += 1 diff --git a/pyomo/solvers/tests/mip/test_qp.py b/pyomo/solvers/tests/mip/test_qp.py new file mode 100644 index 00000000000..5d920b9085d --- /dev/null +++ b/pyomo/solvers/tests/mip/test_qp.py @@ -0,0 +1,194 @@ +# ___________________________________________________________________________ +# +# 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.common.unittest as unittest + +from pyomo.environ import ConcreteModel, Var, Objective, ConstraintList, SolverFactory + +gurobi_lp = SolverFactory('gurobi', solver_io='lp') +gurobi_nl = SolverFactory('gurobi', solver_io='nl') +gurobi_direct = SolverFactory('gurobi_direct') +gurobi_persistent = SolverFactory('gurobi_persistent') +gurobi_appsi = SolverFactory('appsi_gurobi') + +cplex_lp = SolverFactory('cplex', solver_io='lp') +cplex_nl = SolverFactory('cplex', solver_io='nl') +cplex_direct = SolverFactory('cplex_direct') +cplex_persistent = SolverFactory('cplex_persistent') +cplex_appsi = SolverFactory('appsi_cplex') + +xpress_lp = SolverFactory('xpress', solver_io='lp') +xpress_nl = SolverFactory('xpress', solver_io='nl') +xpress_direct = SolverFactory('xpress_direct') +xpress_persistent = SolverFactory('xpress_persistent') +xpress_appsi = SolverFactory('appsi_xpress') + + +class TestQuadraticModels(unittest.TestCase): + def _qp_model(self): + m = ConcreteModel(name="test") + m.x = Var([0, 1, 2]) + m.obj = Objective( + expr=m.x[0] + + 10 * m.x[1] + + 100 * m.x[2] + + 1000 * m.x[1] * m.x[2] + + 10000 * m.x[0] ** 2 + + 10000 * m.x[1] ** 2 + + 100000 * m.x[2] ** 2 + ) + m.c = ConstraintList() + m.c.add(m.x[0] == 1) + m.c.add(m.x[1] == 2) + m.c.add(m.x[2] == 4) + return m + + @unittest.skipUnless( + gurobi_lp.available(exception_flag=False), "needs Gurobi LP interface" + ) + def test_qp_objective_gurobi_lp(self): + m = self._qp_model() + results = gurobi_lp.solve(m) + self.assertEqual(m.obj(), results['Problem'][0]['Upper bound']) + + @unittest.skipUnless( + gurobi_nl.available(exception_flag=False), "needs Gurobi NL interface" + ) + def test_qp_objective_gurobi_nl(self): + m = self._qp_model() + results = gurobi_nl.solve(m) + # TODO: the NL interface should set either the Upper or Lower + # bound for feasible solutions! + # + # self.assertEqual(m.obj(), results['Problem'][0]['Upper bound']) + self.assertIn(str(int(m.obj())), results['Solver'][0]['Message']) + + @unittest.skipUnless( + gurobi_appsi.available(exception_flag=False), "needs Gurobi APPSI interface" + ) + def test_qp_objective_gurobi_appsi(self): + m = self._qp_model() + gurobi_appsi.set_instance(m) + results = gurobi_appsi.solve(m) + self.assertEqual(m.obj(), results['Problem'][0]['Upper bound']) + + @unittest.skipUnless( + gurobi_direct.available(exception_flag=False), "needs Gurobi Direct interface" + ) + def test_qp_objective_gurobi_direct(self): + m = self._qp_model() + results = gurobi_direct.solve(m) + self.assertEqual(m.obj(), results['Problem'][0]['Upper bound']) + + @unittest.skipUnless( + gurobi_persistent.available(exception_flag=False), + "needs Gurobi Persistent interface", + ) + def test_qp_objective_gurobi_persistent(self): + m = self._qp_model() + gurobi_persistent.set_instance(m) + results = gurobi_persistent.solve(m) + self.assertEqual(m.obj(), results['Problem'][0]['Upper bound']) + + @unittest.skipUnless( + cplex_lp.available(exception_flag=False), "needs Cplex LP interface" + ) + def test_qp_objective_cplex_lp(self): + m = self._qp_model() + results = cplex_lp.solve(m) + self.assertEqual(m.obj(), results['Problem'][0]['Upper bound']) + + @unittest.skipUnless( + cplex_nl.available(exception_flag=False), "needs Cplex NL interface" + ) + def test_qp_objective_cplex_nl(self): + m = self._qp_model() + results = cplex_nl.solve(m) + # TODO: the NL interface should set either the Upper or Lower + # bound for feasible solutions! + # + # self.assertEqual(m.obj(), results['Problem'][0]['Upper bound']) + self.assertIn(str(int(m.obj())), results['Solver'][0]['Message']) + + @unittest.skipUnless( + cplex_direct.available(exception_flag=False), "needs Cplex Direct interface" + ) + def test_qp_objective_cplex_direct(self): + m = self._qp_model() + results = cplex_direct.solve(m) + self.assertEqual(m.obj(), results['Problem'][0]['Upper bound']) + + @unittest.skipUnless( + cplex_persistent.available(exception_flag=False), + "needs Cplex Persistent interface", + ) + def test_qp_objective_cplex_persistent(self): + m = self._qp_model() + cplex_persistent.set_instance(m) + results = cplex_persistent.solve(m) + self.assertEqual(m.obj(), results['Problem'][0]['Upper bound']) + + @unittest.skipUnless( + cplex_appsi.available(exception_flag=False), "needs Cplex APPSI interface" + ) + def test_qp_objective_cplex_appsi(self): + m = self._qp_model() + cplex_appsi.set_instance(m) + results = cplex_appsi.solve(m) + self.assertEqual(m.obj(), results['Problem'][0]['Upper bound']) + + @unittest.skipUnless( + xpress_lp.available(exception_flag=False), "needs Xpress LP interface" + ) + def test_qp_objective_xpress_lp(self): + m = self._qp_model() + results = xpress_lp.solve(m) + self.assertEqual(m.obj(), results['Problem'][0]['Upper bound']) + + @unittest.skipUnless( + xpress_nl.available(exception_flag=False), "needs Xpress NL interface" + ) + def test_qp_objective_xpress_nl(self): + m = self._qp_model() + results = xpress_nl.solve(m) + # TODO: the NL interface should set either the Upper or Lower + # bound for feasible solutions! + # + # self.assertEqual(m.obj(), results['Problem'][0]['Upper bound']) + self.assertIn(str(int(m.obj())), results['Solver'][0]['Message']) + + @unittest.skipUnless( + xpress_direct.available(exception_flag=False), "needs Xpress Direct interface" + ) + def test_qp_objective_xpress_direct(self): + m = self._qp_model() + results = xpress_direct.solve(m) + self.assertEqual(m.obj(), results['Problem'][0]['Upper bound']) + + @unittest.skipUnless( + xpress_persistent.available(exception_flag=False), + "needs Xpress Persistent interface", + ) + def test_qp_objective_xpress_persistent(self): + m = self._qp_model() + xpress_persistent.set_instance(m) + results = xpress_persistent.solve(m) + self.assertEqual(m.obj(), results['Problem'][0]['Upper bound']) + + @unittest.skipUnless( + xpress_appsi.available(exception_flag=False), "needs Xpress APPSI interface" + ) + def test_qp_objective_xpress_appsi(self): + m = self._qp_model() + xpress_appsi.set_instance(m) + results = xpress_appsi.solve(m) + self.assertEqual(m.obj(), results['Problem'][0]['Upper bound']) From 43043e09b29c659f032c1ba94f287f754c5a0f42 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 27 Oct 2023 18:46:21 -0600 Subject: [PATCH 0342/1204] Fix exception due to interaction among Gurobi, Pint, and Dask --- pyomo/core/base/units_container.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pyomo/core/base/units_container.py b/pyomo/core/base/units_container.py index 1e9685188c7..dd6bb75aec9 100644 --- a/pyomo/core/base/units_container.py +++ b/pyomo/core/base/units_container.py @@ -1514,6 +1514,19 @@ def __getattribute__(self, attr): # present, at which point this instance __class__ will fall back # to PyomoUnitsContainer (where this method is not declared, OR # pint is not available and an ImportError will be raised. + # + # We need special case handling for __class__: gurobipy + # interrogates things by looking at their __class__ during + # python shutdown. Unfortunately, interrogating this + # singleton's __class__ evaluates `pint_available`, which - if + # DASK is installed - imports dask. Importing dask creates + # threading objects. Unfortunately, creating threading objects + # during interpreter shutdown generates a RuntimeError. So, our + # solution is to special-case the resolution of __class__ here + # to avoid accidentally triggering the imports. + if attr == "__class__": + return _DeferredUnitsSingleton + # if pint_available: # If the first thing that is being called is # "units.set_pint_registry(...)", then we will call __init__ From 39814d8ab700aa3a4c32f749bc6888bfa1668ab1 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 27 Oct 2023 18:54:42 -0600 Subject: [PATCH 0343/1204] NFC: fix spelling --- pyomo/solvers/plugins/solvers/cplex_direct.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/solvers/plugins/solvers/cplex_direct.py b/pyomo/solvers/plugins/solvers/cplex_direct.py index 49fec1e6d09..308d3438329 100644 --- a/pyomo/solvers/plugins/solvers/cplex_direct.py +++ b/pyomo/solvers/plugins/solvers/cplex_direct.py @@ -598,7 +598,7 @@ def _set_objective(self, obj): ) # CPLEX actually uses x'Qx/2 in the objective, as the # off-diagonal entries appear in both the lower triangle and the - # upper triancle (i.e., c*x1*x2 and c*x2*x1). However, since + # upper triangle (i.e., c*x1*x2 and c*x2*x1). However, since # the diagonal entries only appear once, we need to double them. for i, v1 in enumerate(cplex_expr.q_variables1): if v1 == cplex_expr.q_variables2[i]: From 129a39a00731234b4c88e33fe5c4ab8d7721ef1b Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 27 Oct 2023 21:47:57 -0600 Subject: [PATCH 0344/1204] Fix undefined variable --- pyomo/repn/plugins/nl_writer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 3cd8bfee4b2..d532d428be3 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1281,10 +1281,10 @@ def write(self, model): if data.prob: logger.warning("ignoring 'dual' suffix for Model") if data.con: - ostream.write(f"d{len(_data.con)}\n") + ostream.write(f"d{len(data.con)}\n") # Note: _SuffixData.compile() guarantees the value is int/float ostream.write( - ''.join(f"{_id} {_data.con[_id]!r}\n" for _id in sorted(data.con)) + ''.join(f"{_id} {data.con[_id]!r}\n" for _id in sorted(data.con)) ) # From 5908da4b0eb73d1634dff999dd11e1426b707af2 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 27 Oct 2023 21:48:07 -0600 Subject: [PATCH 0345/1204] Fix badly formatted 'v' line --- pyomo/repn/plugins/nl_writer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index d532d428be3..00ee677c348 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1586,7 +1586,7 @@ def _write_v_line(self, expr_id, k): lbl = '\t#%s' % info[0].name else: lbl = '' - self.var_id_to_nl[expr_id] = f"{self.next_V_line_id}{lbl}" + self.var_id_to_nl[expr_id] = f"v{self.next_V_line_id}{lbl}" # Do NOT write out 0 coefficients here: doing so fouls up the # ASL's logic for calculating derivatives, leading to 'nan' in # the Hessian results. From 59557ab2b439f1c91ef36d9600e4e6c2536b625a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 27 Oct 2023 22:14:56 -0600 Subject: [PATCH 0346/1204] Rename handle_constant -> check_constant, cache fixed var values --- pyomo/repn/linear.py | 20 ++++++++-------- pyomo/repn/plugins/nl_writer.py | 42 +++++++++++++++++++++++---------- pyomo/repn/tests/test_util.py | 2 +- pyomo/repn/util.py | 4 ++-- 4 files changed, 43 insertions(+), 25 deletions(-) diff --git a/pyomo/repn/linear.py b/pyomo/repn/linear.py index a0060da6e9f..f8f87795e7c 100644 --- a/pyomo/repn/linear.py +++ b/pyomo/repn/linear.py @@ -587,7 +587,7 @@ def _before_var(visitor, child): _id = id(child) if _id not in visitor.var_map: if child.fixed: - return False, (_CONSTANT, visitor.handle_constant(child.value, child)) + return False, (_CONSTANT, visitor.check_constant(child.value, child)) visitor.var_map[_id] = child visitor.var_order[_id] = len(visitor.var_order) ans = visitor.Result() @@ -603,7 +603,7 @@ def _before_monomial(visitor, child): arg1, arg2 = child._args_ if arg1.__class__ not in native_types: try: - arg1 = visitor.handle_constant(visitor.evaluate(arg1), arg1) + arg1 = visitor.check_constant(visitor.evaluate(arg1), arg1) except (ValueError, ArithmeticError): return True, None @@ -616,7 +616,7 @@ def _before_monomial(visitor, child): if arg2.fixed: return False, ( _CONSTANT, - arg1 * visitor.handle_constant(arg2.value, arg2), + arg1 * visitor.check_constant(arg2.value, arg2), ) visitor.var_map[_id] = arg2 visitor.var_order[_id] = len(visitor.var_order) @@ -624,7 +624,7 @@ def _before_monomial(visitor, child): # Trap multiplication by 0 and nan. if not arg1: if arg2.fixed: - arg2 = visitor.handle_constant(arg2.value, arg2) + arg2 = visitor.check_constant(arg2.value, arg2) if arg2 != arg2: deprecation_warning( f"Encountered {arg1}*{str(arg2.value)} in expression " @@ -652,14 +652,14 @@ def _before_linear(visitor, child): arg1, arg2 = arg._args_ if arg1.__class__ not in native_types: try: - arg1 = visitor.handle_constant(visitor.evaluate(arg1), arg1) + arg1 = visitor.check_constant(visitor.evaluate(arg1), arg1) except (ValueError, ArithmeticError): return True, None # Trap multiplication by 0 and nan. if not arg1: if arg2.fixed: - arg2 = visitor.handle_constant(arg2.value, arg2) + arg2 = visitor.check_constant(arg2.value, arg2) if arg2 != arg2: deprecation_warning( f"Encountered {arg1}*{str(arg2.value)} in expression " @@ -673,7 +673,7 @@ def _before_linear(visitor, child): _id = id(arg2) if _id not in var_map: if arg2.fixed: - const += arg1 * visitor.handle_constant(arg2.value, arg2) + const += arg1 * visitor.check_constant(arg2.value, arg2) continue var_map[_id] = arg2 var_order[_id] = next_i @@ -687,7 +687,7 @@ def _before_linear(visitor, child): const += arg else: try: - const += visitor.handle_constant(visitor.evaluate(arg), arg) + const += visitor.check_constant(visitor.evaluate(arg), arg) except (ValueError, ArithmeticError): return True, None if linear: @@ -713,7 +713,7 @@ def _before_external(visitor, child): ans = visitor.Result() if all(is_fixed(arg) for arg in child.args): try: - ans.constant = visitor.handle_constant(visitor.evaluate(child), child) + ans.constant = visitor.check_constant(visitor.evaluate(child), child) return False, (_CONSTANT, ans) except: pass @@ -752,7 +752,7 @@ def __init__(self, subexpression_cache, var_map, var_order): self._eval_expr_visitor = _EvaluationVisitor(True) self.evaluate = self._eval_expr_visitor.dfs_postorder_stack - def handle_constant(self, ans, obj): + def check_constant(self, ans, obj): if ans.__class__ not in native_numeric_types: # None can be returned from uninitialized Var/Param objects if ans is None: diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 00ee677c348..a61870b8035 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -2323,7 +2323,9 @@ def _before_var(visitor, child): _id = id(child) if _id not in visitor.var_map: if child.fixed: - return False, (_CONSTANT, visitor.handle_constant(child.value, child)) + if _id not in visitor.fixed_vars: + visitor.cache_fixed_var(_id, child) + return False, (_CONSTANT, visitor.fixed_vars[_id]) visitor.var_map[_id] = child return False, (_MONOMIAL, _id, 1) @@ -2336,14 +2338,17 @@ def _before_monomial(visitor, child): arg1, arg2 = child._args_ if arg1.__class__ not in native_types: try: - arg1 = visitor.handle_constant(visitor.evaluate(arg1), arg1) + arg1 = visitor.check_constant(visitor.evaluate(arg1), arg1) except (ValueError, ArithmeticError): return True, None # Trap multiplication by 0 and nan. if not arg1: if arg2.fixed: - arg2 = visitor.handle_constant(arg2.value, arg2) + _id = id(arg2) + if _id not in visitor.fixed_vars: + visitor.cache_fixed_var(id(arg2), arg2) + arg2 = visitor.fixed_vars[_id] if arg2 != arg2: deprecation_warning( f"Encountered {arg1}*{arg2} in expression tree. " @@ -2357,10 +2362,9 @@ def _before_monomial(visitor, child): _id = id(arg2) if _id not in visitor.var_map: if arg2.fixed: - return False, ( - _CONSTANT, - arg1 * visitor.handle_constant(arg2.value, arg2), - ) + if _id not in visitor.fixed_vars: + visitor.cache_fixed_var(_id, arg2) + return False, (_CONSTANT, arg1 * visitor.fixed_vars[_id]) visitor.var_map[_id] = arg2 return False, (_MONOMIAL, _id, arg1) @@ -2377,14 +2381,14 @@ def _before_linear(visitor, child): arg1, arg2 = arg._args_ if arg1.__class__ not in native_types: try: - arg1 = visitor.handle_constant(visitor.evaluate(arg1), arg1) + arg1 = visitor.check_constant(visitor.evaluate(arg1), arg1) except (ValueError, ArithmeticError): return True, None # Trap multiplication by 0 and nan. if not arg1: if arg2.fixed: - arg2 = visitor.handle_constant(arg2.value, arg2) + arg2 = visitor.check_constant(arg2.value, arg2) if arg2 != arg2: deprecation_warning( f"Encountered {arg1}*{str(arg2.value)} in expression " @@ -2398,7 +2402,9 @@ def _before_linear(visitor, child): _id = id(arg2) if _id not in var_map: if arg2.fixed: - const += arg1 * visitor.handle_constant(arg2.value, arg2) + if _id not in visitor.fixed_vars: + visitor.cache_fixed_var(_id, arg2) + const += arg1 * visitor.fixed_vars[_id] continue var_map[_id] = arg2 linear[_id] = arg1 @@ -2410,7 +2416,7 @@ def _before_linear(visitor, child): const += arg else: try: - const += visitor.handle_constant(visitor.evaluate(arg), arg) + const += visitor.check_constant(visitor.evaluate(arg), arg) except (ValueError, ArithmeticError): return True, None @@ -2460,10 +2466,11 @@ def __init__( self.symbolic_solver_labels = symbolic_solver_labels self.use_named_exprs = use_named_exprs self.encountered_string_arguments = False + self.fixed_vars = {} self._eval_expr_visitor = _EvaluationVisitor(True) self.evaluate = self._eval_expr_visitor.dfs_postorder_stack - def handle_constant(self, ans, obj): + def check_constant(self, ans, obj): if ans.__class__ not in native_numeric_types: # None can be returned from uninitialized Var/Param objects if ans is None: @@ -2495,6 +2502,17 @@ def handle_constant(self, ans, obj): ) return ans + def cache_fixed_var(self, _id, child): + val = self.check_constant(child.value, child) + lb, ub = child.bounds + if (lb is not None and lb - val > TOL) or (ub is not None and ub - val < -TOL): + raise InfeasibleError( + "model contains a trivially infeasible " + f"variable '{child.name}' (fixed value " + f"{val} outside bounds [lb, ub])." + ) + self.fixed_vars[_id] = self.check_constant(child.value, child) + def initializeWalker(self, expr): expr, src, src_idx, self.expression_scaling_factor = expr self.active_expression_source = (src_idx, id(src)) diff --git a/pyomo/repn/tests/test_util.py b/pyomo/repn/tests/test_util.py index 58ee09a1006..01dd1392d81 100644 --- a/pyomo/repn/tests/test_util.py +++ b/pyomo/repn/tests/test_util.py @@ -721,7 +721,7 @@ def _before_named_expression(visitor, child): return child class VisitorTester(object): - def handle_constant(self, value, node): + def check_constant(self, value, node): return value def evaluate(self, node): diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index 8c850987253..ecb8ed998d9 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -336,14 +336,14 @@ def _before_npv(visitor, child): try: return False, ( _CONSTANT, - visitor.handle_constant(visitor.evaluate(child), child), + visitor.check_constant(visitor.evaluate(child), child), ) except (ValueError, ArithmeticError): return True, None @staticmethod def _before_param(visitor, child): - return False, (_CONSTANT, visitor.handle_constant(child.value, child)) + return False, (_CONSTANT, visitor.check_constant(child.value, child)) # # The following methods must be defined by derivative classes (along From b3a0fcf6ae70ef4ea7bcb1552bdc7fa9375b83fe Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 27 Oct 2023 22:35:51 -0600 Subject: [PATCH 0347/1204] Prevent tests from bleeding into each other --- pyomo/repn/tests/ampl/test_nlv2.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 8320c37b20e..bb18363ffda 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -474,8 +474,7 @@ def test_errors_propagate_nan(self): expr = m.y**2 * m.x**2 * (((3 * m.x) / m.p) * m.x) / m.y - info = INFO() - with LoggingIntercept() as LOG: + with LoggingIntercept() as LOG, INFO() as info: repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual( LOG.getvalue(), @@ -491,7 +490,8 @@ def test_errors_propagate_nan(self): m.y.fix(None) expr = log(m.y) + 3 - repn = info.visitor.walk_expression((expr, None, None, 1)) + with INFO() as info: + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) self.assertEqual(str(repn.const), 'InvalidNumber(nan)') @@ -499,7 +499,8 @@ def test_errors_propagate_nan(self): self.assertEqual(repn.nonlinear, None) expr = 3 * m.y - repn = info.visitor.walk_expression((expr, None, None, 1)) + with INFO() as info: + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) self.assertEqual(repn.const, InvalidNumber(None)) @@ -508,7 +509,8 @@ def test_errors_propagate_nan(self): m.p.value = None expr = 5 * (m.p * m.x + 2 * m.z) - repn = info.visitor.walk_expression((expr, None, None, 1)) + with INFO() as info: + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) self.assertEqual(repn.const, 0) @@ -516,7 +518,8 @@ def test_errors_propagate_nan(self): self.assertEqual(repn.nonlinear, None) expr = m.y * m.x - repn = info.visitor.walk_expression((expr, None, None, 1)) + with INFO() as info: + repn = info.visitor.walk_expression((expr, None, None, 1)) self.assertEqual(repn.nl, None) self.assertEqual(repn.mult, 1) self.assertEqual(repn.const, 0) From 71ccb2fb1084d1aaaeda93a78bfc91773dfbae1b Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 27 Oct 2023 22:41:21 -0600 Subject: [PATCH 0348/1204] NFC: update exception message --- pyomo/repn/plugins/nl_writer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index a61870b8035..31bc186f457 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -2494,7 +2494,7 @@ def check_constant(self, ans, obj): ans = float(ans) except: return InvalidNumber( - ans, f"'{obj}' evaluated to a nonnumeric value '{ans}'" + ans, f"'{obj}' evaluated to a nonnumeric value '{ans}'" ) if ans != ans: return InvalidNumber( From a66f955500686fef09d8605571ef6f5c238101f8 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Sun, 29 Oct 2023 15:04:27 -0400 Subject: [PATCH 0349/1204] improve copy_var_list_values function --- pyomo/contrib/mindtpy/single_tree.py | 76 ++++++++++++------------- pyomo/contrib/mindtpy/util.py | 83 +++++++++++++++------------- 2 files changed, 83 insertions(+), 76 deletions(-) diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index 8b8e171c577..2174d093009 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -24,6 +24,7 @@ from pyomo.opt import TerminationCondition as tc from pyomo.core import minimize, value from pyomo.core.expr import identify_variables +import math cplex, cplex_available = attempt_import('cplex') @@ -41,7 +42,6 @@ def copy_lazy_var_list_values( config, skip_stale=False, skip_fixed=True, - ignore_integrality=False, ): """This function copies variable values from one list to another. @@ -71,43 +71,43 @@ def copy_lazy_var_list_values( if skip_fixed and v_to.is_fixed(): continue # Skip fixed variables. v_val = self.get_values(opt._pyomo_var_to_solver_var_map[v_from]) - try: - # We don't want to trigger the reset of the global stale - # indicator, so we will set this variable to be "stale", - # knowing that set_value will switch it back to "not - # stale" - v_to.stale = True - # NOTE: PEP 2180 changes the var behavior so that domain - # / bounds violations no longer generate exceptions (and - # instead log warnings). This means that the following - # will always succeed and the ValueError should never be - # raised. - v_to.set_value(v_val, skip_validation=True) - except ValueError as e: - # Snap the value to the bounds - config.logger.error(e) - if ( - v_to.has_lb() - and v_val < v_to.lb - and v_to.lb - v_val <= config.variable_tolerance - ): - v_to.set_value(v_to.lb, skip_validation=True) - elif ( - v_to.has_ub() - and v_val > v_to.ub - and v_val - v_to.ub <= config.variable_tolerance - ): - v_to.set_value(v_to.ub, skip_validation=True) - # ... or the nearest integer - elif v_to.is_integer(): - rounded_val = int(round(v_val)) - if ( - ignore_integrality - or abs(v_val - rounded_val) <= config.integer_tolerance - ) and rounded_val in v_to.domain: - v_to.set_value(rounded_val, skip_validation=True) - else: - raise + rounded_val = int(round(v_val)) + # We don't want to trigger the reset of the global stale + # indicator, so we will set this variable to be "stale", + # knowing that set_value will switch it back to "not + # stale" + v_to.stale = True + # NOTE: PEP 2180 changes the var behavior so that domain + # / bounds violations no longer generate exceptions (and + # instead log warnings). This means that the following + # will always succeed and the ValueError should never be + # raised. + if v_val in v_to.domain \ + and not ((v_to.has_lb() and v_val < v_to.lb)) \ + and not ((v_to.has_ub() and v_val > v_to.ub)): + v_to.set_value(v_val) + # Snap the value to the bounds + # TODO: check the performance of + # v_to.lb - v_val <= config.variable_tolerance + elif ( + v_to.has_lb() + and v_val < v_to.lb + # and v_to.lb - v_val <= config.variable_tolerance + ): + v_to.set_value(v_to.lb) + elif ( + v_to.has_ub() + and v_val > v_to.ub + # and v_val - v_to.ub <= config.variable_tolerance + ): + v_to.set_value(v_to.ub) + # ... or the nearest integer + elif v_to.is_integer() and math.fabs(v_val - rounded_val) <= config.integer_tolerance: # and rounded_val in v_to.domain: + v_to.set_value(rounded_val) + elif abs(v_val) <= config.zero_tolerance and 0 in v_to.domain: + v_to.set_value(0) + else: + raise ValueError('copy_lazy_var_list_values failed.') def add_lazy_oa_cuts( self, diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index 4dfb912e611..da1534b49ac 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -684,41 +684,42 @@ def copy_var_list_values_from_solution_pool( Whether to ignore the integrality of integer variables, by default False. """ for v_from, v_to in zip(from_list, to_list): - try: - if config.mip_solver == 'cplex_persistent': - var_val = solver_model.solution.pool.get_values( - solution_name, var_map[v_from] - ) - elif config.mip_solver == 'gurobi_persistent': - solver_model.setParam(gurobipy.GRB.Param.SolutionNumber, solution_name) - var_val = var_map[v_from].Xn - # We don't want to trigger the reset of the global stale - # indicator, so we will set this variable to be "stale", - # knowing that set_value will switch it back to "not - # stale" - v_to.stale = True - # NOTE: PEP 2180 changes the var behavior so that domain / - # bounds violations no longer generate exceptions (and - # instead log warnings). This means that the following will - # always succeed and the ValueError should never be raised. + if config.mip_solver == 'cplex_persistent': + var_val = solver_model.solution.pool.get_values( + solution_name, var_map[v_from] + ) + elif config.mip_solver == 'gurobi_persistent': + solver_model.setParam(gurobipy.GRB.Param.SolutionNumber, solution_name) + var_val = var_map[v_from].Xn + # We don't want to trigger the reset of the global stale + # indicator, so we will set this variable to be "stale", + # knowing that set_value will switch it back to "not + # stale" + v_to.stale = True + rounded_val = int(round(var_val)) + # NOTE: PEP 2180 changes the var behavior so that domain / + # bounds violations no longer generate exceptions (and + # instead log warnings). This means that the following will + # always succeed and the ValueError should never be raised. + if var_val in v_to.domain \ + and not ((v_to.has_lb() and var_val < v_to.lb)) \ + and not ((v_to.has_ub() and var_val > v_to.ub)): v_to.set_value(var_val, skip_validation=True) - except ValueError as e: - config.logger.error(e) - rounded_val = int(round(var_val)) - # Check to see if this is just a tolerance issue - if ignore_integrality and v_to.is_integer(): - v_to.set_value(var_val, skip_validation=True) - elif v_to.is_integer() and ( - abs(var_val - rounded_val) <= config.integer_tolerance - ): - v_to.set_value(rounded_val, skip_validation=True) - elif abs(var_val) <= config.zero_tolerance and 0 in v_to.domain: - v_to.set_value(0, skip_validation=True) - else: - config.logger.error( - 'Unknown validation domain error setting variable %s' % (v_to.name,) - ) - raise + elif v_to.has_lb() and var_val < v_to.lb: + v_to.set_value(v_to.lb) + elif v_to.has_ub() and var_val > v_to.ub: + v_to.set_value(v_to.ub) + # Check to see if this is just a tolerance issue + elif ignore_integrality and v_to.is_integer(): + v_to.set_value(var_val, skip_validation=True) + elif v_to.is_integer() and ( + abs(var_val - rounded_val) <= config.integer_tolerance + ): + v_to.set_value(rounded_val, skip_validation=True) + elif abs(var_val) <= config.zero_tolerance and 0 in v_to.domain: + v_to.set_value(0, skip_validation=True) + else: + raise ValueError("copy_var_list_values_from_solution_pool failed.") class GurobiPersistent4MindtPy(GurobiPersistent): @@ -980,14 +981,20 @@ def copy_var_list_values(from_list, to_list, config, continue # Skip fixed variables. var_val = value(v_from, exception=False) rounded_val = int(round(var_val)) - if var_val in v_to.domain: + if var_val in v_to.domain \ + and not ((v_to.has_lb() and var_val < v_to.lb)) \ + and not ((v_to.has_ub() and var_val > v_to.ub)): v_to.set_value(value(v_from, exception=False)) + elif v_to.has_lb() and var_val < v_to.lb: + v_to.set_value(v_to.lb) + elif v_to.has_ub() and var_val > v_to.ub: + v_to.set_value(v_to.ub) elif ignore_integrality and v_to.is_integer(): v_to.set_value(value(v_from, exception=False), skip_validation=True) - elif abs(var_val) <= config.zero_tolerance and 0 in v_to.domain: - v_to.set_value(0) elif v_to.is_integer() and (math.fabs(var_val - rounded_val) <= config.integer_tolerance): v_to.set_value(rounded_val) + elif abs(var_val) <= config.zero_tolerance and 0 in v_to.domain: + v_to.set_value(0) else: - raise + raise ValueError("copy_var_list_values failed.") From cefd4a66a06711e90005d20cd470efca762754a6 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Sun, 29 Oct 2023 15:12:02 -0400 Subject: [PATCH 0350/1204] fix FP bug --- pyomo/contrib/mindtpy/algorithm_base_class.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index c254a8db72b..3e53559b3df 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -2384,6 +2384,7 @@ def handle_fp_subproblem_optimal(self, fp_nlp): fp_nlp.MindtPy_utils.variable_list, self.working_model.MindtPy_utils.variable_list, self.config, + ignore_integrality=True ) add_orthogonality_cuts(self.working_model, self.mip, self.config) From c0e5eeffbca9d0b42fe160e4f1bfd32d0538e636 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 30 Oct 2023 08:50:14 -0600 Subject: [PATCH 0351/1204] add comment to scip results logic --- pyomo/solvers/plugins/solvers/SCIPAMPL.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyomo/solvers/plugins/solvers/SCIPAMPL.py b/pyomo/solvers/plugins/solvers/SCIPAMPL.py index 0973ff38f68..9898b9cdd90 100644 --- a/pyomo/solvers/plugins/solvers/SCIPAMPL.py +++ b/pyomo/solvers/plugins/solvers/SCIPAMPL.py @@ -379,6 +379,11 @@ def _postsolve(self): else: results.problem.upper_bound = results.solver.primal_bound except AttributeError: + """ + This may occur if SCIP solves the problem during presolve. In that case, + the log file may not get parsed correctly (self.read_scip_log), and + results.solver.primal_bound will not be populated. + """ pass # WARNING # infeasible='infeasible' # Demonstrated that the problem is infeasible From f6e211e67f9c8d0e4b2fdeddb0357da750da8f9e Mon Sep 17 00:00:00 2001 From: Shawn Martin Date: Tue, 31 Oct 2023 09:18:32 -0600 Subject: [PATCH 0352/1204] Fixed Pandas warning in parmest examples plus bug in semibatch parallel example. --- .../reactor_design/bootstrap_example.py | 8 +- .../reactor_design/datarec_example.py | 8 +- .../reactor_design/leaveNout_example.py | 8 +- .../likelihood_ratio_example.py | 8 +- .../multisensor_data_example.py | 14 +- .../parameter_estimation_example.py | 8 +- .../examples/reactor_design/reactor_design.py | 12 +- .../examples/semibatch/obj_at_theta.csv | 1009 +++++++++++++++++ .../parmest/examples/semibatch/semibatch.py | 10 + 9 files changed, 1055 insertions(+), 30 deletions(-) create mode 100644 pyomo/contrib/parmest/examples/semibatch/obj_at_theta.csv diff --git a/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py b/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py index cf1b8a2de23..67724644ef5 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py @@ -29,10 +29,10 @@ def main(): # Sum of squared error function def SSE(model, data): expr = ( - (float(data['ca']) - model.ca) ** 2 - + (float(data['cb']) - model.cb) ** 2 - + (float(data['cc']) - model.cc) ** 2 - + (float(data['cd']) - model.cd) ** 2 + (float(data.iloc[0]['ca']) - model.ca) ** 2 + + (float(data.iloc[0]['cb']) - model.cb) ** 2 + + (float(data.iloc[0]['cc']) - model.cc) ** 2 + + (float(data.iloc[0]['cd']) - model.cd) ** 2 ) return expr diff --git a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py index 811571e20ed..b50ee46d9b9 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py @@ -61,10 +61,10 @@ def main(): # Define sum of squared error objective function for data rec def SSE(model, data): expr = ( - ((float(data['ca']) - model.ca) / float(data_std['ca'])) ** 2 - + ((float(data['cb']) - model.cb) / float(data_std['cb'])) ** 2 - + ((float(data['cc']) - model.cc) / float(data_std['cc'])) ** 2 - + ((float(data['cd']) - model.cd) / float(data_std['cd'])) ** 2 + ((float(data.iloc[0]['ca']) - model.ca) / float(data_std['ca'])) ** 2 + + ((float(data.iloc[0]['cb']) - model.cb) / float(data_std['cb'])) ** 2 + + ((float(data.iloc[0]['cc']) - model.cc) / float(data_std['cc'])) ** 2 + + ((float(data.iloc[0]['cd']) - model.cd) / float(data_std['cd'])) ** 2 ) return expr diff --git a/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py b/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py index 95af53e63d3..1e14e1fb329 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py @@ -37,10 +37,10 @@ def main(): # Sum of squared error function def SSE(model, data): expr = ( - (float(data['ca']) - model.ca) ** 2 - + (float(data['cb']) - model.cb) ** 2 - + (float(data['cc']) - model.cc) ** 2 - + (float(data['cd']) - model.cd) ** 2 + (float(data.iloc[0]['ca']) - model.ca) ** 2 + + (float(data.iloc[0]['cb']) - model.cb) ** 2 + + (float(data.iloc[0]['cc']) - model.cc) ** 2 + + (float(data.iloc[0]['cd']) - model.cd) ** 2 ) return expr diff --git a/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py b/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py index 13a40774740..5224097c13f 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py @@ -31,10 +31,10 @@ def main(): # Sum of squared error function def SSE(model, data): expr = ( - (float(data['ca']) - model.ca) ** 2 - + (float(data['cb']) - model.cb) ** 2 - + (float(data['cc']) - model.cc) ** 2 - + (float(data['cd']) - model.cd) ** 2 + (float(data.iloc[0]['ca']) - model.ca) ** 2 + + (float(data.iloc[0]['cb']) - model.cb) ** 2 + + (float(data.iloc[0]['cc']) - model.cc) ** 2 + + (float(data.iloc[0]['cd']) - model.cd) ** 2 ) return expr diff --git a/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py b/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py index bc564cbdfd3..af7620b47b3 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py @@ -31,13 +31,13 @@ def main(): # Sum of squared error function def SSE_multisensor(model, data): expr = ( - ((float(data['ca1']) - model.ca) ** 2) * (1 / 3) - + ((float(data['ca2']) - model.ca) ** 2) * (1 / 3) - + ((float(data['ca3']) - model.ca) ** 2) * (1 / 3) - + (float(data['cb']) - model.cb) ** 2 - + ((float(data['cc1']) - model.cc) ** 2) * (1 / 2) - + ((float(data['cc2']) - model.cc) ** 2) * (1 / 2) - + (float(data['cd']) - model.cd) ** 2 + ((float(data.iloc[0]['ca1']) - model.ca) ** 2) * (1 / 3) + + ((float(data.iloc[0]['ca2']) - model.ca) ** 2) * (1 / 3) + + ((float(data.iloc[0]['ca3']) - model.ca) ** 2) * (1 / 3) + + (float(data.iloc[0]['cb']) - model.cb) ** 2 + + ((float(data.iloc[0]['cc1']) - model.cc) ** 2) * (1 / 2) + + ((float(data.iloc[0]['cc2']) - model.cc) ** 2) * (1 / 2) + + (float(data.iloc[0]['cd']) - model.cd) ** 2 ) return expr diff --git a/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py b/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py index 334dfa264a4..070c5934be5 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py @@ -29,10 +29,10 @@ def main(): # Sum of squared error function def SSE(model, data): expr = ( - (float(data['ca']) - model.ca) ** 2 - + (float(data['cb']) - model.cb) ** 2 - + (float(data['cc']) - model.cc) ** 2 - + (float(data['cd']) - model.cd) ** 2 + (float(data.iloc[0]['ca']) - model.ca) ** 2 + + (float(data.iloc[0]['cb']) - model.cb) ** 2 + + (float(data.iloc[0]['cc']) - model.cc) ** 2 + + (float(data.iloc[0]['cd']) - model.cd) ** 2 ) return expr diff --git a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py index f4cd6c8dbf5..3284a174e93 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py +++ b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py @@ -37,10 +37,16 @@ def reactor_design_model(data): ) # m^3/(gmol min) # Inlet concentration of A, gmol/m^3 - model.caf = Param(initialize=float(data['caf']), within=PositiveReals) + if isinstance(data, dict): + model.caf = Param(initialize=float(data['caf']), within=PositiveReals) + else: + model.caf = Param(initialize=float(data.iloc[0]['caf']), within=PositiveReals) # Space velocity (flowrate/volume) - model.sv = Param(initialize=float(data['sv']), within=PositiveReals) + if isinstance(data, dict): + model.sv = Param(initialize=float(data['sv']), within=PositiveReals) + else: + model.sv = Param(initialize=float(data.iloc[0]['sv']), within=PositiveReals) # Outlet concentration of each component model.ca = Var(initialize=5000.0, within=PositiveReals) @@ -81,7 +87,7 @@ def main(): sv_values = [1.0 + v * 0.05 for v in range(1, 20)] caf = 10000 for sv in sv_values: - model = reactor_design_model({'caf': caf, 'sv': sv}) + model = reactor_design_model(pd.DataFrame(data={'caf': [caf], 'sv': [sv]})) solver = SolverFactory('ipopt') solver.solve(model) results.append([sv, caf, model.ca(), model.cb(), model.cc(), model.cd()]) diff --git a/pyomo/contrib/parmest/examples/semibatch/obj_at_theta.csv b/pyomo/contrib/parmest/examples/semibatch/obj_at_theta.csv new file mode 100644 index 00000000000..79f03e07dcd --- /dev/null +++ b/pyomo/contrib/parmest/examples/semibatch/obj_at_theta.csv @@ -0,0 +1,1009 @@ +,k1,k2,E1,E2,obj +0,4,40,29000,38000,667.4023645794207 +1,4,40,29000,38500,665.8312183437167 +2,4,40,29000,39000,672.7539769993407 +3,4,40,29000,39500,684.9503752463216 +4,4,40,29000,40000,699.985589093255 +5,4,40,29000,40500,716.1241770970677 +6,4,40,29000,41000,732.2023201586336 +7,4,40,29000,41500,747.4931745925483 +8,4,40,29500,38000,907.4405527163311 +9,4,40,29500,38500,904.2229271927299 +10,4,40,29500,39000,907.6942345285257 +11,4,40,29500,39500,915.4570013614677 +12,4,40,29500,40000,925.65401444575 +13,4,40,29500,40500,936.9348578520337 +14,4,40,29500,41000,948.3759339765711 +15,4,40,29500,41500,959.386491783636 +16,4,40,30000,38000,1169.8685711377334 +17,4,40,30000,38500,1166.2211505723928 +18,4,40,30000,39000,1167.702295374574 +19,4,40,30000,39500,1172.5517020611685 +20,4,40,30000,40000,1179.3820406408263 +21,4,40,30000,40500,1187.1698633839655 +22,4,40,30000,41000,1195.2047840919602 +23,4,40,30000,41500,1203.0241101248102 +24,4,40,30500,38000,1445.9591944684807 +25,4,40,30500,38500,1442.6632745483 +26,4,40,30500,39000,1443.1982444457385 +27,4,40,30500,39500,1446.2833842279929 +28,4,40,30500,40000,1450.9012120934779 +29,4,40,30500,40500,1456.295140290636 +30,4,40,30500,41000,1461.9350767569827 +31,4,40,30500,41500,1467.4715014446226 +32,4,40,31000,38000,1726.8744994061449 +33,4,40,31000,38500,1724.2679845375048 +34,4,40,31000,39000,1724.4550886870552 +35,4,40,31000,39500,1726.5124587129135 +36,4,40,31000,40000,1729.7061680616455 +37,4,40,31000,40500,1733.48893482641 +38,4,40,31000,41000,1737.4753558920438 +39,4,40,31000,41500,1741.4093763605517 +40,4,40,31500,38000,2004.1978135112938 +41,4,40,31500,38500,2002.2807839860222 +42,4,40,31500,39000,2002.3676405166086 +43,4,40,31500,39500,2003.797808439923 +44,4,40,31500,40000,2006.048051591001 +45,4,40,31500,40500,2008.7281679153625 +46,4,40,31500,41000,2011.5626384878237 +47,4,40,31500,41500,2014.3675286347284 +48,4,80,29000,38000,845.8197358579285 +49,4,80,29000,38500,763.5039795545781 +50,4,80,29000,39000,709.8529964173656 +51,4,80,29000,39500,679.4215539491266 +52,4,80,29000,40000,666.4876088521157 +53,4,80,29000,40500,665.978271760966 +54,4,80,29000,41000,673.7240200504901 +55,4,80,29000,41500,686.4763909417914 +56,4,80,29500,38000,1042.519415429413 +57,4,80,29500,38500,982.8097210678039 +58,4,80,29500,39000,942.2990207573541 +59,4,80,29500,39500,917.9550916645245 +60,4,80,29500,40000,906.3116029967189 +61,4,80,29500,40500,904.0326666308792 +62,4,80,29500,41000,908.1964630052729 +63,4,80,29500,41500,916.4222043837499 +64,4,80,30000,38000,1271.1030403496538 +65,4,80,30000,38500,1227.7527550544085 +66,4,80,30000,39000,1197.433957624904 +67,4,80,30000,39500,1178.447676126182 +68,4,80,30000,40000,1168.645219243497 +69,4,80,30000,40500,1165.7995210546096 +70,4,80,30000,41000,1167.8586496250396 +71,4,80,30000,41500,1173.0949214020527 +72,4,80,30500,38000,1520.8220402652044 +73,4,80,30500,38500,1489.2563260709424 +74,4,80,30500,39000,1466.8099189128857 +75,4,80,30500,39500,1452.4352624958806 +76,4,80,30500,40000,1444.7074679423818 +77,4,80,30500,40500,1442.0820578624343 +78,4,80,30500,41000,1443.099006489627 +79,4,80,30500,41500,1446.5106517200784 +80,4,80,31000,38000,1781.149136032395 +81,4,80,31000,38500,1758.2414369536502 +82,4,80,31000,39000,1741.891639711003 +83,4,80,31000,39500,1731.358661496594 +84,4,80,31000,40000,1725.6231647999593 +85,4,80,31000,40500,1723.5757174297378 +86,4,80,31000,41000,1724.1680229486278 +87,4,80,31000,41500,1726.5050840601884 +88,4,80,31500,38000,2042.8335948845602 +89,4,80,31500,38500,2026.3067503042414 +90,4,80,31500,39000,2014.5720701940838 +91,4,80,31500,39500,2007.0463766643977 +92,4,80,31500,40000,2002.9647983728314 +93,4,80,31500,40500,2001.5163951989875 +94,4,80,31500,41000,2001.9474217001339 +95,4,80,31500,41500,2003.6204088755821 +96,4,120,29000,38000,1176.0713512305115 +97,4,120,29000,38500,1016.8213383282462 +98,4,120,29000,39000,886.0136231565133 +99,4,120,29000,39500,789.0101180066036 +100,4,120,29000,40000,724.5420056133441 +101,4,120,29000,40500,686.6877602625062 +102,4,120,29000,41000,668.8129085873959 +103,4,120,29000,41500,665.1167761036883 +104,4,120,29500,38000,1263.887274509128 +105,4,120,29500,38500,1155.6528408872423 +106,4,120,29500,39000,1066.393539894248 +107,4,120,29500,39500,998.9931006471243 +108,4,120,29500,40000,952.36314487701 +109,4,120,29500,40500,923.4000293372077 +110,4,120,29500,41000,908.407361383214 +111,4,120,29500,41500,903.8136176328255 +112,4,120,30000,38000,1421.1418235449091 +113,4,120,30000,38500,1347.114022652679 +114,4,120,30000,39000,1285.686103704643 +115,4,120,30000,39500,1238.2456448658272 +116,4,120,30000,40000,1204.3526810790904 +117,4,120,30000,40500,1182.4272879027071 +118,4,120,30000,41000,1170.3447810121902 +119,4,120,30000,41500,1165.8422968073423 +120,4,120,30500,38000,1625.5588911535713 +121,4,120,30500,38500,1573.5546642859429 +122,4,120,30500,39000,1530.1592840718379 +123,4,120,30500,39500,1496.2087139473604 +124,4,120,30500,40000,1471.525855239756 +125,4,120,30500,40500,1455.2084749904016 +126,4,120,30500,41000,1445.9160840082027 +127,4,120,30500,41500,1442.1255377330835 +128,4,120,31000,38000,1855.8467211183756 +129,4,120,31000,38500,1818.4368412235558 +130,4,120,31000,39000,1787.25956706785 +131,4,120,31000,39500,1762.8169908546402 +132,4,120,31000,40000,1744.9825741661596 +133,4,120,31000,40500,1733.136625016882 +134,4,120,31000,41000,1726.3352245899828 +135,4,120,31000,41500,1723.492199933745 +136,4,120,31500,38000,2096.6479813687533 +137,4,120,31500,38500,2069.3606691038876 +138,4,120,31500,39000,2046.792043575205 +139,4,120,31500,39500,2029.2128703900223 +140,4,120,31500,40000,2016.4664599897606 +141,4,120,31500,40500,2008.054814885348 +142,4,120,31500,41000,2003.2622557140814 +143,4,120,31500,41500,2001.289784483679 +144,7,40,29000,38000,149.32898706737052 +145,7,40,29000,38500,161.04814413969586 +146,7,40,29000,39000,187.87801343005242 +147,7,40,29000,39500,223.00789161520424 +148,7,40,29000,40000,261.66779887964003 +149,7,40,29000,40500,300.676316191238 +150,7,40,29000,41000,338.04021206995765 +151,7,40,29000,41500,372.6191631389286 +152,7,40,29500,38000,276.6495061185777 +153,7,40,29500,38500,282.1304583501965 +154,7,40,29500,39000,300.91417483065254 +155,7,40,29500,39500,327.24304394350395 +156,7,40,29500,40000,357.0561976596432 +157,7,40,29500,40500,387.61662064170207 +158,7,40,29500,41000,417.1836349752378 +159,7,40,29500,41500,444.73705844573243 +160,7,40,30000,38000,448.0380830353589 +161,7,40,30000,38500,448.8094536459122 +162,7,40,30000,39000,460.77530593327293 +163,7,40,30000,39500,479.342874472736 +164,7,40,30000,40000,501.20694459059405 +165,7,40,30000,40500,524.0971649678811 +166,7,40,30000,41000,546.539334134893 +167,7,40,30000,41500,567.6447156158981 +168,7,40,30500,38000,657.9909416906933 +169,7,40,30500,38500,655.7465129488842 +170,7,40,30500,39000,662.5420970804985 +171,7,40,30500,39500,674.8914651553109 +172,7,40,30500,40000,690.2111920703564 +173,7,40,30500,40500,706.6833639709198 +174,7,40,30500,41000,723.0994507096715 +175,7,40,30500,41500,738.7096013891406 +176,7,40,31000,38000,899.1769906655776 +177,7,40,31000,38500,895.4391505892945 +178,7,40,31000,39000,898.7695629120826 +179,7,40,31000,39500,906.603316771593 +180,7,40,31000,40000,916.9811481373996 +181,7,40,31000,40500,928.4913367709245 +182,7,40,31000,41000,940.1744934710283 +183,7,40,31000,41500,951.4199286075984 +184,7,40,31500,38000,1163.093373675207 +185,7,40,31500,38500,1159.0457727559028 +186,7,40,31500,39000,1160.3831770028223 +187,7,40,31500,39500,1165.2451698296604 +188,7,40,31500,40000,1172.1768190340001 +189,7,40,31500,40500,1180.1105659428963 +190,7,40,31500,41000,1188.3083929833688 +191,7,40,31500,41500,1196.29112579565 +192,7,80,29000,38000,514.0332369183081 +193,7,80,29000,38500,329.3645784712966 +194,7,80,29000,39000,215.73000998706416 +195,7,80,29000,39500,162.37338399591852 +196,7,80,29000,40000,149.8401793263549 +197,7,80,29000,40500,162.96125998112578 +198,7,80,29000,41000,191.173279165834 +199,7,80,29000,41500,227.2781971491003 +200,7,80,29500,38000,623.559246695578 +201,7,80,29500,38500,448.60620511421484 +202,7,80,29500,39000,344.21940687907573 +203,7,80,29500,39500,292.9758707105001 +204,7,80,29500,40000,277.07670134364804 +205,7,80,29500,40500,283.5158840045542 +206,7,80,29500,41000,303.33951582820265 +207,7,80,29500,41500,330.43357046741954 +208,7,80,30000,38000,732.5907387079073 +209,7,80,30000,38500,593.1926567994672 +210,7,80,30000,39000,508.5638538704666 +211,7,80,30000,39500,464.47881763522037 +212,7,80,30000,40000,448.0394620671692 +213,7,80,30000,40500,449.64309860415494 +214,7,80,30000,41000,462.4490598612332 +215,7,80,30000,41500,481.6323506247537 +216,7,80,30500,38000,871.1163930229344 +217,7,80,30500,38500,771.1320563649375 +218,7,80,30500,39000,707.8872660015606 +219,7,80,30500,39500,672.6612145133173 +220,7,80,30500,40000,657.4974157809264 +221,7,80,30500,40500,656.0835852491216 +222,7,80,30500,41000,663.6006958125331 +223,7,80,30500,41500,676.460675405631 +224,7,80,31000,38000,1053.1852617390061 +225,7,80,31000,38500,984.3647109805877 +226,7,80,31000,39000,938.6158531749268 +227,7,80,31000,39500,911.4268280093535 +228,7,80,31000,40000,898.333365348419 +229,7,80,31000,40500,895.3996527486954 +230,7,80,31000,41000,899.3556288533885 +231,7,80,31000,41500,907.6180684887955 +232,7,80,31500,38000,1274.2255948763498 +233,7,80,31500,38500,1226.5236809533717 +234,7,80,31500,39000,1193.4538731398666 +235,7,80,31500,39500,1172.8105398345213 +236,7,80,31500,40000,1162.0692230240734 +237,7,80,31500,40500,1158.7461521476607 +238,7,80,31500,41000,1160.6173577210805 +239,7,80,31500,41500,1165.840315694716 +240,7,120,29000,38000,1325.2409732290193 +241,7,120,29000,38500,900.8063148840154 +242,7,120,29000,39000,629.9300352098937 +243,7,120,29000,39500,413.81648033893424 +244,7,120,29000,40000,257.3116751690404 +245,7,120,29000,40500,177.89217179438947 +246,7,120,29000,41000,151.58366848473491 +247,7,120,29000,41500,157.56967437251706 +248,7,120,29500,38000,1211.2807882170853 +249,7,120,29500,38500,956.936161969002 +250,7,120,29500,39000,753.3050086992201 +251,7,120,29500,39500,528.2452647799327 +252,7,120,29500,40000,382.62610532894917 +253,7,120,29500,40500,308.44199089882375 +254,7,120,29500,41000,280.3893024671524 +255,7,120,29500,41500,280.4028092582749 +256,7,120,30000,38000,1266.5740351143413 +257,7,120,30000,38500,1084.3028700477778 +258,7,120,30000,39000,834.2392498526193 +259,7,120,30000,39500,650.7560171314304 +260,7,120,30000,40000,537.7846910878052 +261,7,120,30000,40500,477.3001078155485 +262,7,120,30000,41000,451.6865380286754 +263,7,120,30000,41500,448.14911508024613 +264,7,120,30500,38000,1319.6603196780936 +265,7,120,30500,38500,1102.3027489012372 +266,7,120,30500,39000,931.2523583659847 +267,7,120,30500,39500,807.0833484596384 +268,7,120,30500,40000,727.4852710400268 +269,7,120,30500,40500,682.1437030344305 +270,7,120,30500,41000,660.7859329989657 +271,7,120,30500,41500,655.6001132492668 +272,7,120,31000,38000,1330.5306924865326 +273,7,120,31000,38500,1195.9190861202942 +274,7,120,31000,39000,1086.0328080422887 +275,7,120,31000,39500,1005.4160637517409 +276,7,120,31000,40000,951.2021706290612 +277,7,120,31000,40500,918.1457644271304 +278,7,120,31000,41000,901.0511005554887 +279,7,120,31000,41500,895.4599964465793 +280,7,120,31500,38000,1447.8365822059013 +281,7,120,31500,38500,1362.3417347939844 +282,7,120,31500,39000,1292.382727215108 +283,7,120,31500,39500,1239.1826828976662 +284,7,120,31500,40000,1201.6474412465277 +285,7,120,31500,40500,1177.5235955796813 +286,7,120,31500,41000,1164.1761722345295 +287,7,120,31500,41500,1158.9997785002718 +288,10,40,29000,38000,33.437068437082054 +289,10,40,29000,38500,58.471249815534996 +290,10,40,29000,39000,101.41937628542912 +291,10,40,29000,39500,153.80690200519626 +292,10,40,29000,40000,209.66451461551316 +293,10,40,29000,40500,265.03070792175197 +294,10,40,29000,41000,317.46079310177566 +295,10,40,29000,41500,365.59950388342645 +296,10,40,29500,38000,70.26818405688635 +297,10,40,29500,38500,87.96463718548947 +298,10,40,29500,39000,122.58188233160993 +299,10,40,29500,39500,166.2478945807132 +300,10,40,29500,40000,213.48669617414316 +301,10,40,29500,40500,260.67953961944477 +302,10,40,29500,41000,305.5877041218316 +303,10,40,29500,41500,346.95612213021155 +304,10,40,30000,38000,153.67588703371362 +305,10,40,30000,38500,164.07504103479005 +306,10,40,30000,39000,190.0800160661499 +307,10,40,30000,39500,224.61382980242837 +308,10,40,30000,40000,262.79232847382445 +309,10,40,30000,40500,301.38687703450415 +310,10,40,30000,41000,338.38536686093164 +311,10,40,30000,41500,372.6399011703545 +312,10,40,30500,38000,284.2936286531718 +313,10,40,30500,38500,288.4690608277705 +314,10,40,30500,39000,306.44667517621144 +315,10,40,30500,39500,332.20122250191986 +316,10,40,30500,40000,361.5566690083291 +317,10,40,30500,40500,391.72755224929614 +318,10,40,30500,41000,420.95317535960476 +319,10,40,30500,41500,448.2049230608669 +320,10,40,31000,38000,459.03140021766137 +321,10,40,31000,38500,458.71477027519967 +322,10,40,31000,39000,469.9910751800656 +323,10,40,31000,39500,488.05850105225426 +324,10,40,31000,40000,509.5204701455629 +325,10,40,31000,40500,532.0674969691778 +326,10,40,31000,41000,554.2088430693509 +327,10,40,31000,41500,575.0485839499048 +328,10,40,31500,38000,672.2476845983564 +329,10,40,31500,38500,669.2240508488649 +330,10,40,31500,39000,675.4956226836405 +331,10,40,31500,39500,687.447764319295 +332,10,40,31500,40000,702.4395430742891 +333,10,40,31500,40500,718.6279487347668 +334,10,40,31500,41000,734.793684592168 +335,10,40,31500,41500,750.1821072409286 +336,10,80,29000,38000,387.7617282731497 +337,10,80,29000,38500,195.33642612593002 +338,10,80,29000,39000,82.7306931465102 +339,10,80,29000,39500,35.13436471793541 +340,10,80,29000,40000,33.521138659248706 +341,10,80,29000,40500,61.47395975053128 +342,10,80,29000,41000,106.71403229340167 +343,10,80,29000,41500,160.56068704487473 +344,10,80,29500,38000,459.63404601804103 +345,10,80,29500,38500,258.7453720995899 +346,10,80,29500,39000,135.96435731320256 +347,10,80,29500,39500,80.2685095017944 +348,10,80,29500,40000,70.86302366453106 +349,10,80,29500,40500,90.43203026480438 +350,10,80,29500,41000,126.7844695901737 +351,10,80,29500,41500,171.63682876805044 +352,10,80,30000,38000,564.1463320344325 +353,10,80,30000,38500,360.75718124523866 +354,10,80,30000,39000,231.70119191254307 +355,10,80,30000,39500,170.74752201483128 +356,10,80,30000,40000,154.7149036950422 +357,10,80,30000,40500,166.10596450541493 +358,10,80,30000,41000,193.3351721194443 +359,10,80,30000,41500,228.78394172417038 +360,10,80,30500,38000,689.6797223218513 +361,10,80,30500,38500,484.8023695265838 +362,10,80,30500,39000,363.5979340028588 +363,10,80,30500,39500,304.67857102688225 +364,10,80,30500,40000,285.29210000833734 +365,10,80,30500,40500,290.0135917456113 +366,10,80,30500,41000,308.8672169492536 +367,10,80,30500,41500,335.3210332569182 +368,10,80,31000,38000,789.946106942773 +369,10,80,31000,38500,625.7722360026959 +370,10,80,31000,39000,528.6063264942235 +371,10,80,31000,39500,478.6863763478618 +372,10,80,31000,40000,459.5026243189753 +373,10,80,31000,40500,459.6982093164963 +374,10,80,31000,41000,471.6790024321937 +375,10,80,31000,41500,490.3034492109124 +376,10,80,31500,38000,912.3540488244158 +377,10,80,31500,38500,798.2135101409633 +378,10,80,31500,39000,727.746684419146 +379,10,80,31500,39500,689.0119464356724 +380,10,80,31500,40000,672.0757202772029 +381,10,80,31500,40500,669.678339553036 +382,10,80,31500,41000,676.5761221409929 +383,10,80,31500,41500,688.9934449650118 +384,10,120,29000,38000,1155.1165164624408 +385,10,120,29000,38500,840.2641727088946 +386,10,120,29000,39000,506.9102636732852 +387,10,120,29000,39500,265.5278912452038 +388,10,120,29000,40000,116.39516513179322 +389,10,120,29000,40500,45.2088092745619 +390,10,120,29000,41000,30.22267557153353 +391,10,120,29000,41500,51.06063746392809 +392,10,120,29500,38000,1343.7868459826054 +393,10,120,29500,38500,977.9852373227346 +394,10,120,29500,39000,594.632756549817 +395,10,120,29500,39500,346.2478773329187 +396,10,120,29500,40000,180.23082247413407 +397,10,120,29500,40500,95.81649989178923 +398,10,120,29500,41000,71.0837801649128 +399,10,120,29500,41500,82.84289818279714 +400,10,120,30000,38000,1532.9333545384934 +401,10,120,30000,38500,1012.2223350568845 +402,10,120,30000,39000,688.4884716222766 +403,10,120,30000,39500,464.6206903113392 +404,10,120,30000,40000,283.5644748300334 +405,10,120,30000,40500,190.27593217865416 +406,10,120,30000,41000,158.0192279691727 +407,10,120,30000,41500,161.3611926772337 +408,10,120,30500,38000,1349.3785399811063 +409,10,120,30500,38500,1014.785480110738 +410,10,120,30500,39000,843.0316833766408 +411,10,120,30500,39500,589.4543896730125 +412,10,120,30500,40000,412.3358512291996 +413,10,120,30500,40500,324.11715620464133 +414,10,120,30500,41000,290.17588242984766 +415,10,120,30500,41500,287.56857384673356 +416,10,120,31000,38000,1328.0973931040146 +417,10,120,31000,38500,1216.5659656437845 +418,10,120,31000,39000,928.4831767181619 +419,10,120,31000,39500,700.3115484040329 +420,10,120,31000,40000,565.0876352458171 +421,10,120,31000,40500,494.44016026435037 +422,10,120,31000,41000,464.38005437182983 +423,10,120,31000,41500,458.7614573733091 +424,10,120,31500,38000,1473.1154650008834 +425,10,120,31500,38500,1195.943614951571 +426,10,120,31500,39000,990.2486604382486 +427,10,120,31500,39500,843.1390407497395 +428,10,120,31500,40000,751.2746391170706 +429,10,120,31500,40500,700.215375503209 +430,10,120,31500,41000,676.1585052687219 +431,10,120,31500,41500,669.5907920932743 +432,13,40,29000,38000,49.96352152045025 +433,13,40,29000,38500,83.75104994958261 +434,13,40,29000,39000,136.8176091795391 +435,13,40,29000,39500,199.91486685466407 +436,13,40,29000,40000,266.4367154860076 +437,13,40,29000,40500,331.97224579940524 +438,13,40,29000,41000,393.8001583706036 +439,13,40,29000,41500,450.42425363084493 +440,13,40,29500,38000,29.775721038786923 +441,13,40,29500,38500,57.37673742631121 +442,13,40,29500,39000,103.49161398239501 +443,13,40,29500,39500,159.3058253852367 +444,13,40,29500,40000,218.60083223764073 +445,13,40,29500,40500,277.2507278183831 +446,13,40,29500,41000,332.7141278886951 +447,13,40,29500,41500,383.58832292300576 +448,13,40,30000,38000,47.72263852005472 +449,13,40,30000,38500,68.07581028940402 +450,13,40,30000,39000,106.13974628945516 +451,13,40,30000,39500,153.58449949683063 +452,13,40,30000,40000,204.62393623358633 +453,13,40,30000,40500,255.44513025602419 +454,13,40,30000,41000,303.69954914051766 +455,13,40,30000,41500,348.0803709720354 +456,13,40,30500,38000,110.9331168284094 +457,13,40,30500,38500,123.63361262704746 +458,13,40,30500,39000,153.02654433825705 +459,13,40,30500,39500,191.40769947472756 +460,13,40,30500,40000,233.503841403055 +461,13,40,30500,40500,275.8557790922913 +462,13,40,30500,41000,316.32529882763697 +463,13,40,30500,41500,353.7060432094809 +464,13,40,31000,38000,221.90608823073939 +465,13,40,31000,38500,227.67026441593657 +466,13,40,31000,39000,248.62107049869064 +467,13,40,31000,39500,277.9507605389158 +468,13,40,31000,40000,311.0267471957685 +469,13,40,31000,40500,344.8024031161673 +470,13,40,31000,41000,377.3761144228052 +471,13,40,31000,41500,407.6529635071056 +472,13,40,31500,38000,378.8738382757093 +473,13,40,31500,38500,379.39748335944216 +474,13,40,31500,39000,393.01223361732553 +475,13,40,31500,39500,414.10238059122855 +476,13,40,31500,40000,438.8024282436204 +477,13,40,31500,40500,464.5348067190265 +478,13,40,31500,41000,489.6621039898805 +479,13,40,31500,41500,513.2163939332803 +480,13,80,29000,38000,364.387588581215 +481,13,80,29000,38500,184.2902007673634 +482,13,80,29000,39000,81.57192155036655 +483,13,80,29000,39500,42.54811210095659 +484,13,80,29000,40000,49.897338772663076 +485,13,80,29000,40500,87.84229516509882 +486,13,80,29000,41000,143.85451969447664 +487,13,80,29000,41500,208.71467984917848 +488,13,80,29500,38000,382.5794635435733 +489,13,80,29500,38500,188.38619353711718 +490,13,80,29500,39000,75.75749359688277 +491,13,80,29500,39500,29.27891251986562 +492,13,80,29500,40000,29.794874961934568 +493,13,80,29500,40500,60.654888662698205 +494,13,80,29500,41000,109.25801388824325 +495,13,80,29500,41500,166.6311093454692 +496,13,80,30000,38000,448.97795526074816 +497,13,80,30000,38500,238.44530107604737 +498,13,80,30000,39000,112.34545890264337 +499,13,80,30000,39500,56.125871791222835 +500,13,80,30000,40000,48.29987461781518 +501,13,80,30000,40500,70.7900626637678 +502,13,80,30000,41000,110.76865376691964 +503,13,80,30000,41500,159.50197316936024 +504,13,80,30500,38000,547.7818730461195 +505,13,80,30500,38500,332.92604070423494 +506,13,80,30500,39000,193.80760050280742 +507,13,80,30500,39500,128.3457644087917 +508,13,80,30500,40000,112.23915895822442 +509,13,80,30500,40500,125.96369396512564 +510,13,80,30500,41000,156.67918617660013 +511,13,80,30500,41500,196.05195109523765 +512,13,80,31000,38000,682.8591931963246 +513,13,80,31000,38500,457.56562267948556 +514,13,80,31000,39000,313.6380169123524 +515,13,80,31000,39500,245.13531819580908 +516,13,80,31000,40000,223.54473391202873 +517,13,80,31000,40500,229.60752111202834 +518,13,80,31000,41000,251.42377424735136 +519,13,80,31000,41500,281.48720903016886 +520,13,80,31500,38000,807.925638050234 +521,13,80,31500,38500,588.686585641994 +522,13,80,31500,39000,464.0488586698228 +523,13,80,31500,39500,402.69214492641095 +524,13,80,31500,40000,380.13626165363934 +525,13,80,31500,40500,380.8064948609387 +526,13,80,31500,41000,395.05186915919086 +527,13,80,31500,41500,416.70193045600774 +528,13,120,29000,38000,1068.8279454397398 +529,13,120,29000,38500,743.0012805963486 +530,13,120,29000,39000,451.2538301167544 +531,13,120,29000,39500,235.4154251166075 +532,13,120,29000,40000,104.73720814447498 +533,13,120,29000,40500,46.91983990671749 +534,13,120,29000,41000,42.81092192562316 +535,13,120,29000,41500,74.33530639171506 +536,13,120,29500,38000,1133.1178848710972 +537,13,120,29500,38500,824.0745323788527 +538,13,120,29500,39000,499.10867111401996 +539,13,120,29500,39500,256.1626809904186 +540,13,120,29500,40000,107.68599585294751 +541,13,120,29500,40500,38.18533662516749 +542,13,120,29500,41000,25.499608203619154 +543,13,120,29500,41500,49.283537699300375 +544,13,120,30000,38000,1292.409871290162 +545,13,120,30000,38500,994.669572829704 +546,13,120,30000,39000,598.9783697712826 +547,13,120,30000,39500,327.47348408537925 +548,13,120,30000,40000,156.82634841081907 +549,13,120,30000,40500,71.30833688875883 +550,13,120,30000,41000,47.72389750130817 +551,13,120,30000,41500,62.1982461882982 +552,13,120,30500,38000,1585.8797221278146 +553,13,120,30500,38500,1144.66688416451 +554,13,120,30500,39000,692.6651441690645 +555,13,120,30500,39500,441.98837639874046 +556,13,120,30500,40000,251.56311435857728 +557,13,120,30500,40500,149.79670413140468 +558,13,120,30500,41000,115.52645596043719 +559,13,120,30500,41500,120.44019473389324 +560,13,120,31000,38000,1702.7625866892163 +561,13,120,31000,38500,1071.7854750250656 +562,13,120,31000,39000,807.8943299034604 +563,13,120,31000,39500,588.672223513561 +564,13,120,31000,40000,376.44658358671404 +565,13,120,31000,40500,269.2159719426485 +566,13,120,31000,41000,229.41660529009877 +567,13,120,31000,41500,226.78274707181976 +568,13,120,31500,38000,1331.3523701291767 +569,13,120,31500,38500,1151.2055268669133 +570,13,120,31500,39000,1006.811285091974 +571,13,120,31500,39500,702.0053094629535 +572,13,120,31500,40000,515.9081891614829 +573,13,120,31500,40500,423.8652275555525 +574,13,120,31500,41000,386.4939696097151 +575,13,120,31500,41500,379.8118453367429 +576,16,40,29000,38000,106.1025746852808 +577,16,40,29000,38500,145.32590128581407 +578,16,40,29000,39000,204.74804378224422 +579,16,40,29000,39500,274.6339266648551 +580,16,40,29000,40000,347.9667393938497 +581,16,40,29000,40500,420.03753452490974 +582,16,40,29000,41000,487.9353932879741 +583,16,40,29000,41500,550.0623063219693 +584,16,40,29500,38000,54.65040870471303 +585,16,40,29500,38500,88.94089091627293 +586,16,40,29500,39000,142.72223808288405 +587,16,40,29500,39500,206.63598763907422 +588,16,40,29500,40000,273.99851593521134 +589,16,40,29500,40500,340.34861536649436 +590,16,40,29500,41000,402.935270882596 +591,16,40,29500,41500,460.2471155081633 +592,16,40,30000,38000,29.788548081995298 +593,16,40,30000,38500,57.96323252610644 +594,16,40,30000,39000,104.92815906834525 +595,16,40,30000,39500,161.71867032726158 +596,16,40,30000,40000,222.01677586338877 +597,16,40,30000,40500,281.6349465235367 +598,16,40,30000,41000,337.99683241119567 +599,16,40,30000,41500,389.68271710858414 +600,16,40,30500,38000,42.06569536892785 +601,16,40,30500,38500,62.95145274276575 +602,16,40,30500,39000,101.93860830594608 +603,16,40,30500,39500,150.47910837525734 +604,16,40,30500,40000,202.65388851823258 +605,16,40,30500,40500,254.5724108541227 +606,16,40,30500,41000,303.84403622726694 +607,16,40,30500,41500,349.1422884543064 +608,16,40,31000,38000,99.21707896667829 +609,16,40,31000,38500,112.24153596941301 +610,16,40,31000,39000,142.5186177618655 +611,16,40,31000,39500,182.02836955332134 +612,16,40,31000,40000,225.3201896575212 +613,16,40,31000,40500,268.83705389232614 +614,16,40,31000,41000,310.3895932135811 +615,16,40,31000,41500,348.7480165565453 +616,16,40,31500,38000,204.30418825821732 +617,16,40,31500,38500,210.0759235359138 +618,16,40,31500,39000,231.7643258544752 +619,16,40,31500,39500,262.1512494310348 +620,16,40,31500,40000,296.3864127264238 +621,16,40,31500,40500,331.30743171999035 +622,16,40,31500,41000,364.95322314895554 +623,16,40,31500,41500,396.20142191205844 +624,16,80,29000,38000,399.5975649320935 +625,16,80,29000,38500,225.6318269911425 +626,16,80,29000,39000,127.97354075513151 +627,16,80,29000,39500,93.73584101549991 +628,16,80,29000,40000,106.43084032022394 +629,16,80,29000,40500,150.51245762256931 +630,16,80,29000,41000,213.24213500046466 +631,16,80,29000,41500,285.0426423013882 +632,16,80,29500,38000,371.37706087096393 +633,16,80,29500,38500,189.77150413822454 +634,16,80,29500,39000,86.22375488959844 +635,16,80,29500,39500,46.98714814001572 +636,16,80,29500,40000,54.596900621760675 +637,16,80,29500,40500,93.12033833747024 +638,16,80,29500,41000,149.89341227947025 +639,16,80,29500,41500,215.5937000584367 +640,16,80,30000,38000,388.43657991253195 +641,16,80,30000,38500,190.77121362008674 +642,16,80,30000,39000,76.28535232335287 +643,16,80,30000,39500,29.152860363695716 +644,16,80,30000,40000,29.820972887404942 +645,16,80,30000,40500,61.320203047752464 +646,16,80,30000,41000,110.82086782062603 +647,16,80,30000,41500,169.197767615573 +648,16,80,30500,38000,458.8964339917103 +649,16,80,30500,38500,239.547928886725 +650,16,80,30500,39000,109.02338779317503 +651,16,80,30500,39500,50.888746196140914 +652,16,80,30500,40000,42.73606982375976 +653,16,80,30500,40500,65.75935122724029 +654,16,80,30500,41000,106.68884313872147 +655,16,80,30500,41500,156.54100549486617 +656,16,80,31000,38000,561.7385153195615 +657,16,80,31000,38500,335.5692026144635 +658,16,80,31000,39000,188.0383015831574 +659,16,80,31000,39500,118.2318539104416 +660,16,80,31000,40000,100.81000168801492 +661,16,80,31000,40500,114.72014539486217 +662,16,80,31000,41000,146.2992492326178 +663,16,80,31000,41500,186.8074429488408 +664,16,80,31500,38000,697.9937997454152 +665,16,80,31500,38500,466.42234442578484 +666,16,80,31500,39000,306.52125608515166 +667,16,80,31500,39500,230.54692639209762 +668,16,80,31500,40000,206.461121102699 +669,16,80,31500,40500,212.23429887269359 +670,16,80,31500,41000,234.70913795495554 +671,16,80,31500,41500,265.8143069252357 +672,16,120,29000,38000,1085.688903883652 +673,16,120,29000,38500,750.2887000017752 +674,16,120,29000,39000,469.92662852990964 +675,16,120,29000,39500,267.1560282754928 +676,16,120,29000,40000,146.06299930062625 +677,16,120,29000,40500,95.28836772053619 +678,16,120,29000,41000,97.41466545178946 +679,16,120,29000,41500,135.3804131941845 +680,16,120,29500,38000,1079.5576154477903 +681,16,120,29500,38500,751.2932384998761 +682,16,120,29500,39000,458.27083477307207 +683,16,120,29500,39500,240.9658024131812 +684,16,120,29500,40000,109.3801465044384 +685,16,120,29500,40500,51.274139057659724 +686,16,120,29500,41000,47.36446629605638 +687,16,120,29500,41500,79.42944320845996 +688,16,120,30000,38000,1139.3792936518537 +689,16,120,30000,38500,833.7979589668842 +690,16,120,30000,39000,507.805443202025 +691,16,120,30000,39500,259.93892964607977 +692,16,120,30000,40000,108.7341499557062 +693,16,120,30000,40500,38.152937143498605 +694,16,120,30000,41000,25.403985123518716 +695,16,120,30000,41500,49.72822589160786 +696,16,120,30500,38000,1285.0396277304772 +697,16,120,30500,38500,1025.254169031627 +698,16,120,30500,39000,622.5890550779666 +699,16,120,30500,39500,333.3353043756717 +700,16,120,30500,40000,155.70268128051293 +701,16,120,30500,40500,66.84125446522368 +702,16,120,30500,41000,42.25187049753978 +703,16,120,30500,41500,56.98314898830595 +704,16,120,31000,38000,1595.7993459811262 +705,16,120,31000,38500,1252.8886556470425 +706,16,120,31000,39000,731.4408383874198 +707,16,120,31000,39500,451.0090473423308 +708,16,120,31000,40000,251.5086563526081 +709,16,120,31000,40500,141.8915050063955 +710,16,120,31000,41000,104.67474675582574 +711,16,120,31000,41500,109.1609567535697 +712,16,120,31500,38000,1942.3896021770768 +713,16,120,31500,38500,1197.207050908449 +714,16,120,31500,39000,812.6818768064074 +715,16,120,31500,39500,611.45532452889 +716,16,120,31500,40000,380.63642711770643 +717,16,120,31500,40500,258.5514125337487 +718,16,120,31500,41000,213.48518421250665 +719,16,120,31500,41500,209.58134396574906 +720,19,40,29000,38000,169.3907733115706 +721,19,40,29000,38500,212.23331960093145 +722,19,40,29000,39000,275.9376503672959 +723,19,40,29000,39500,350.4301397081139 +724,19,40,29000,40000,428.40863665493924 +725,19,40,29000,40500,504.955113902399 +726,19,40,29000,41000,577.023450987656 +727,19,40,29000,41500,642.9410032211753 +728,19,40,29500,38000,102.40889356493292 +729,19,40,29500,38500,141.19036226103668 +730,19,40,29500,39000,200.19333708701748 +731,19,40,29500,39500,269.6750686488757 +732,19,40,29500,40000,342.6217886299377 +733,19,40,29500,40500,414.33044375626207 +734,19,40,29500,41000,481.89521316730713 +735,19,40,29500,41500,543.7211700546151 +736,19,40,30000,38000,51.95330426445395 +737,19,40,30000,38500,85.69656829127965 +738,19,40,30000,39000,138.98376466247876 +739,19,40,30000,39500,202.43251598105033 +740,19,40,30000,40000,269.3557903452929 +741,19,40,30000,40500,335.2960133312316 +742,19,40,30000,41000,397.50658847538665 +743,19,40,30000,41500,454.47903112410967 +744,19,40,30500,38000,28.864802790801026 +745,19,40,30500,38500,56.32899754732796 +746,19,40,30500,39000,102.69825523352162 +747,19,40,30500,39500,158.95118263535466 +748,19,40,30500,40000,218.75241957992617 +749,19,40,30500,40500,277.9122290233915 +750,19,40,30500,41000,333.8561815041273 +751,19,40,30500,41500,385.1662652901447 +752,19,40,31000,38000,43.72359701781447 +753,19,40,31000,38500,63.683967347844224 +754,19,40,31000,39000,101.95579433282329 +755,19,40,31000,39500,149.8826019475827 +756,19,40,31000,40000,201.50605279789198 +757,19,40,31000,40500,252.92391570754876 +758,19,40,31000,41000,301.7431453727685 +759,19,40,31000,41500,346.6368192781496 +760,19,40,31500,38000,104.05710998615942 +761,19,40,31500,38500,115.95783594434451 +762,19,40,31500,39000,145.42181873662554 +763,19,40,31500,39500,184.26373455825217 +764,19,40,31500,40000,226.97066340897095 +765,19,40,31500,40500,269.96403356902357 +766,19,40,31500,41000,311.04753558871505 +767,19,40,31500,41500,348.98866332680115 +768,19,80,29000,38000,453.1314944429312 +769,19,80,29000,38500,281.24067760117225 +770,19,80,29000,39000,185.83730378881882 +771,19,80,29000,39500,154.25726305915472 +772,19,80,29000,40000,170.2912737797755 +773,19,80,29000,40500,218.38979299191152 +774,19,80,29000,41000,285.604024444273 +775,19,80,29000,41500,362.0858325427657 +776,19,80,29500,38000,400.06299682217264 +777,19,80,29500,38500,224.41725666435008 +778,19,80,29500,39000,125.58476107530382 +779,19,80,29500,39500,90.55733834394478 +780,19,80,29500,40000,102.67519971027264 +781,19,80,29500,40500,146.27807815967392 +782,19,80,29500,41000,208.57372904155937 +783,19,80,29500,41500,279.9669583078214 +784,19,80,30000,38000,376.1594584816549 +785,19,80,30000,38500,191.30452808298463 +786,19,80,30000,39000,85.63116084217559 +787,19,80,30000,39500,45.10487847849711 +788,19,80,30000,40000,51.88389644342952 +789,19,80,30000,40500,89.78942817703852 +790,19,80,30000,41000,146.0393555385696 +791,19,80,30000,41500,211.26567367707352 +792,19,80,30500,38000,401.874315275947 +793,19,80,30500,38500,197.55305366608133 +794,19,80,30500,39000,79.00348967857379 +795,19,80,30500,39500,29.602719961568614 +796,19,80,30500,40000,28.980451378502487 +797,19,80,30500,40500,59.63541802023186 +798,19,80,30500,41000,108.48607655362268 +799,19,80,30500,41500,166.30589286399507 +800,19,80,31000,38000,484.930958445979 +801,19,80,31000,38500,254.27552635537404 +802,19,80,31000,39000,116.75543721560439 +803,19,80,31000,39500,54.77547840250418 +804,19,80,31000,40000,44.637472658824976 +805,19,80,31000,40500,66.50466903927668 +806,19,80,31000,41000,106.62737262508298 +807,19,80,31000,41500,155.8310688191254 +808,19,80,31500,38000,595.6094306603337 +809,19,80,31500,38500,359.60040819463063 +810,19,80,31500,39000,201.85328967228585 +811,19,80,31500,39500,126.24442464793601 +812,19,80,31500,40000,106.07388975142673 +813,19,80,31500,40500,118.52358345403363 +814,19,80,31500,41000,149.1597537162607 +815,19,80,31500,41500,188.94964975523197 +816,19,120,29000,38000,1133.9213841599772 +817,19,120,29000,38500,793.9759807804692 +818,19,120,29000,39000,516.5580425563733 +819,19,120,29000,39500,318.60172051726147 +820,19,120,29000,40000,201.662212274693 +821,19,120,29000,40500,154.47522945829064 +822,19,120,29000,41000,160.28049502033574 +823,19,120,29000,41500,202.35345983501588 +824,19,120,29500,38000,1091.6343400395158 +825,19,120,29500,38500,754.9332443184217 +826,19,120,29500,39000,472.1777992591152 +827,19,120,29500,39500,267.03951846894995 +828,19,120,29500,40000,144.25558152688114 +829,19,120,29500,40500,92.40384156679512 +830,19,120,29500,41000,93.81833253459942 +831,19,120,29500,41500,131.24753560710644 +832,19,120,30000,38000,1092.719296892266 +833,19,120,30000,38500,764.7065490850255 +834,19,120,30000,39000,467.2268758064373 +835,19,120,30000,39500,244.9367732985332 +836,19,120,30000,40000,110.00996333393202 +837,19,120,30000,40500,49.96381544207811 +838,19,120,30000,41000,44.9298739569088 +839,19,120,30000,41500,76.25447129089613 +840,19,120,30500,38000,1160.6160120981158 +841,19,120,30500,38500,865.5953188304933 +842,19,120,30500,39000,531.1657093741892 +843,19,120,30500,39500,271.98520008106277 +844,19,120,30500,40000,114.03616090967407 +845,19,120,30500,40500,39.74252227099571 +846,19,120,30500,41000,25.07176465285551 +847,19,120,30500,41500,48.298794094852724 +848,19,120,31000,38000,1304.8870694342509 +849,19,120,31000,38500,1089.6854636757826 +850,19,120,31000,39000,668.6632735260521 +851,19,120,31000,39500,356.7751012890747 +852,19,120,31000,40000,168.32491564142487 +853,19,120,31000,40500,72.82648063377391 +854,19,120,31000,41000,45.02326687759286 +855,19,120,31000,41500,58.13111530831655 +856,19,120,31500,38000,1645.2697164013964 +857,19,120,31500,38500,1373.859712069864 +858,19,120,31500,39000,787.3948673670299 +859,19,120,31500,39500,483.60546305948367 +860,19,120,31500,40000,273.4285373433001 +861,19,120,31500,40500,153.21079535396908 +862,19,120,31500,41000,111.21299419905313 +863,19,120,31500,41500,113.52006337929113 +864,22,40,29000,38000,229.2032513971666 +865,22,40,29000,38500,274.65023153674116 +866,22,40,29000,39000,341.4424739822062 +867,22,40,29000,39500,419.2624324130753 +868,22,40,29000,40000,500.6022690006133 +869,22,40,29000,40500,580.3923016374031 +870,22,40,29000,41000,655.4874207991389 +871,22,40,29000,41500,724.1595537770351 +872,22,40,29500,38000,155.45206306046595 +873,22,40,29500,38500,197.41588482427002 +874,22,40,29500,39000,260.1641484982308 +875,22,40,29500,39500,333.666918810689 +876,22,40,29500,40000,410.66541588422854 +877,22,40,29500,40500,486.276072112155 +878,22,40,29500,41000,557.4760464927683 +879,22,40,29500,41500,622.6057687448293 +880,22,40,30000,38000,90.70026588811803 +881,22,40,30000,38500,128.41239603755494 +882,22,40,30000,39000,186.27261386900233 +883,22,40,30000,39500,254.5802373859711 +884,22,40,30000,40000,326.3686182341553 +885,22,40,30000,40500,396.9735001502319 +886,22,40,30000,41000,463.5155278718613 +887,22,40,30000,41500,524.414569320113 +888,22,40,30500,38000,44.551475763397946 +889,22,40,30500,38500,76.95264448905411 +890,22,40,30500,39000,128.85898727872572 +891,22,40,30500,39500,190.91422001003792 +892,22,40,30500,40000,256.4755613806196 +893,22,40,30500,40500,321.125224208803 +894,22,40,30500,41000,382.14434919800453 +895,22,40,30500,41500,438.03974322333033 +896,22,40,31000,38000,28.101321546315717 +897,22,40,31000,38500,53.867829756398805 +898,22,40,31000,39000,98.57619184859544 +899,22,40,31000,39500,153.19473192134507 +900,22,40,31000,40000,211.4202434313414 +901,22,40,31000,40500,269.09905982026265 +902,22,40,31000,41000,323.68306330754416 +903,22,40,31000,41500,373.76836451736045 +904,22,40,31500,38000,51.648288279447364 +905,22,40,31500,38500,69.56074881661863 +906,22,40,31500,39000,105.91402675097291 +907,22,40,31500,39500,151.99456204656389 +908,22,40,31500,40000,201.85995274525234 +909,22,40,31500,40500,251.63807959916412 +910,22,40,31500,41000,298.9593498669657 +911,22,40,31500,41500,342.50888994628025 +912,22,80,29000,38000,507.5440336860194 +913,22,80,29000,38500,336.42019672232965 +914,22,80,29000,39000,242.21016116765423 +915,22,80,29000,39500,212.33396533224905 +916,22,80,29000,40000,230.67632355958136 +917,22,80,29000,40500,281.6224662955561 +918,22,80,29000,41000,352.0457411487133 +919,22,80,29000,41500,431.89288175778637 +920,22,80,29500,38000,443.2889283037078 +921,22,80,29500,38500,270.0648237630224 +922,22,80,29500,39000,173.57666711629645 +923,22,80,29500,39500,141.06258420240613 +924,22,80,29500,40000,156.18412870159142 +925,22,80,29500,40500,203.33105261575707 +926,22,80,29500,41000,269.5552387411201 +927,22,80,29500,41500,345.03801326123767 +928,22,80,30000,38000,395.34177505602497 +929,22,80,30000,38500,217.11094192826982 +930,22,80,30000,39000,116.38535634181476 +931,22,80,30000,39500,79.94742924888467 +932,22,80,30000,40000,90.84706550421288 +933,22,80,30000,40500,133.26308067939766 +934,22,80,30000,41000,194.36064414396228 +935,22,80,30000,41500,264.56059537656466 +936,22,80,30500,38000,382.0341866812038 +937,22,80,30500,38500,191.65621311671836 +938,22,80,30500,39000,82.3318677587146 +939,22,80,30500,39500,39.44606931321677 +940,22,80,30500,40000,44.476166488763134 +941,22,80,30500,40500,80.84561981845566 +942,22,80,30500,41000,135.62459431793735 +943,22,80,30500,41500,199.42208168600175 +944,22,80,31000,38000,425.5181957619983 +945,22,80,31000,38500,210.2667219741389 +946,22,80,31000,39000,84.97041062888985 +947,22,80,31000,39500,31.593073529038755 +948,22,80,31000,40000,28.407154164211214 +949,22,80,31000,40500,57.05446633976857 +950,22,80,31000,41000,104.10423883907688 +951,22,80,31000,41500,160.23135976433713 +952,22,80,31500,38000,527.5015417150911 +953,22,80,31500,38500,282.29650611769665 +954,22,80,31500,39000,134.62881845323489 +955,22,80,31500,39500,66.62736532046851 +956,22,80,31500,40000,52.9918858786988 +957,22,80,31500,40500,72.36913743145999 +958,22,80,31500,41000,110.38003828747726 +959,22,80,31500,41500,157.65470091455973 +960,22,120,29000,38000,1186.823326813257 +961,22,120,29000,38500,844.3317816964005 +962,22,120,29000,39000,567.7367986440256 +963,22,120,29000,39500,371.79782508970567 +964,22,120,29000,40000,256.9261857702517 +965,22,120,29000,40500,211.85466060592006 +966,22,120,29000,41000,220.09534855737033 +967,22,120,29000,41500,265.02731793490034 +968,22,120,29500,38000,1128.4568915685559 +969,22,120,29500,38500,787.7709648712951 +970,22,120,29500,39000,508.4832626962424 +971,22,120,29500,39500,308.52654841064975 +972,22,120,29500,40000,190.01030358402707 +973,22,120,29500,40500,141.62663282114926 +974,22,120,29500,41000,146.40704203984612 +975,22,120,29500,41500,187.48734389188584 +976,22,120,30000,38000,1094.7007205604846 +977,22,120,30000,38500,757.7313528729464 +978,22,120,30000,39000,471.282561364766 +979,22,120,30000,39500,262.0412520036699 +980,22,120,30000,40000,136.26956239282435 +981,22,120,30000,40500,82.4268827471484 +982,22,120,30000,41000,82.3695177584498 +983,22,120,30000,41500,118.51210034475737 +984,22,120,30500,38000,1111.0872182758205 +985,22,120,30500,38500,787.2204655558988 +986,22,120,30500,39000,481.85960605002055 +987,22,120,30500,39500,250.28740868446397 +988,22,120,30500,40000,109.21968920710272 +989,22,120,30500,40500,45.51600269221681 +990,22,120,30500,41000,38.172157811051115 +991,22,120,30500,41500,67.73748641348168 +992,22,120,31000,38000,1193.3958874354898 +993,22,120,31000,38500,923.0731791194576 +994,22,120,31000,39000,573.4457650536078 +995,22,120,31000,39500,294.2980811757103 +996,22,120,31000,40000,124.86249624679849 +997,22,120,31000,40500,43.948524347749846 +998,22,120,31000,41000,25.582084045731808 +999,22,120,31000,41500,46.36268252714472 +1000,22,120,31500,38000,1336.0993444856913 +1001,22,120,31500,38500,1194.893001664831 +1002,22,120,31500,39000,740.6584250286721 +1003,22,120,31500,39500,397.18127104230757 +1004,22,120,31500,40000,194.20390582893873 +1005,22,120,31500,40500,88.22588964369922 +1006,22,120,31500,41000,54.97797247760634 +1007,22,120,31500,41500,64.88195101638016 diff --git a/pyomo/contrib/parmest/examples/semibatch/semibatch.py b/pyomo/contrib/parmest/examples/semibatch/semibatch.py index 8cda262c019..b3da21ed993 100644 --- a/pyomo/contrib/parmest/examples/semibatch/semibatch.py +++ b/pyomo/contrib/parmest/examples/semibatch/semibatch.py @@ -34,6 +34,16 @@ def generate_model(data): + + # if data is a file name, then load file first + if isinstance(data, str): + file_name = data + try: + with open(file_name, 'r') as infile: + data = json.load(infile) + except: + raise RuntimeError(f'Could not read {file_name} as json') + # unpack and fix the data cameastemp = data['Ca_meas'] cbmeastemp = data['Cb_meas'] From 42d825ef86c74633702840e2159c49bf7531b2ab Mon Sep 17 00:00:00 2001 From: Shawn Martin Date: Tue, 31 Oct 2023 11:26:53 -0600 Subject: [PATCH 0353/1204] Fixed deprecations in parmest tests. --- .../examples/reactor_design/reactor_design.py | 14 +++++++++----- pyomo/contrib/parmest/graphics.py | 3 ++- pyomo/contrib/parmest/tests/test_parmest.py | 8 ++++---- .../contrib/parmest/tests/test_scenariocreator.py | 8 ++++---- pyomo/contrib/parmest/tests/test_utils.py | 1 + 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py index 3284a174e93..80df6fb3c12 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py +++ b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py @@ -37,17 +37,21 @@ def reactor_design_model(data): ) # m^3/(gmol min) # Inlet concentration of A, gmol/m^3 - if isinstance(data, dict): + if isinstance(data, dict) or isinstance(data, pd.Series): model.caf = Param(initialize=float(data['caf']), within=PositiveReals) - else: + elif isinstance(data, pd.DataFrame): model.caf = Param(initialize=float(data.iloc[0]['caf']), within=PositiveReals) + else: + raise ValueError('Unrecognized data type.') # Space velocity (flowrate/volume) - if isinstance(data, dict): + if isinstance(data, dict) or isinstance(data, pd.Series): model.sv = Param(initialize=float(data['sv']), within=PositiveReals) - else: + elif isinstance(data, pd.DataFrame): model.sv = Param(initialize=float(data.iloc[0]['sv']), within=PositiveReals) - + else: + raise ValueError('Unrecognized data type.') + # Outlet concentration of each component model.ca = Var(initialize=5000.0, within=PositiveReals) model.cb = Var(initialize=2000.0, within=PositiveReals) diff --git a/pyomo/contrib/parmest/graphics.py b/pyomo/contrib/parmest/graphics.py index f01622d2d17..991395a556d 100644 --- a/pyomo/contrib/parmest/graphics.py +++ b/pyomo/contrib/parmest/graphics.py @@ -178,7 +178,8 @@ def _add_obj_contour(x, y, color, columns, data, theta_star, label=None): X, Y, Z = _get_data_slice(xvar, yvar, columns, data, theta_star) triang = matplotlib.tri.Triangulation(X, Y) - cmap = plt.cm.get_cmap('Greys') + #cmap = plt.cm.get_cmap('Greys') + cmap = matplotlib.colormaps['Greys'] plt.tricontourf(triang, Z, cmap=cmap) except: diff --git a/pyomo/contrib/parmest/tests/test_parmest.py b/pyomo/contrib/parmest/tests/test_parmest.py index f26ecec2fce..b09c48e9709 100644 --- a/pyomo/contrib/parmest/tests/test_parmest.py +++ b/pyomo/contrib/parmest/tests/test_parmest.py @@ -639,10 +639,10 @@ def setUp(self): def SSE(model, data): expr = ( - (float(data['ca']) - model.ca) ** 2 - + (float(data['cb']) - model.cb) ** 2 - + (float(data['cc']) - model.cc) ** 2 - + (float(data['cd']) - model.cd) ** 2 + (float(data.iloc[0]['ca']) - model.ca) ** 2 + + (float(data.iloc[0]['cb']) - model.cb) ** 2 + + (float(data.iloc[0]['cc']) - model.cc) ** 2 + + (float(data.iloc[0]['cd']) - model.cd) ** 2 ) return expr diff --git a/pyomo/contrib/parmest/tests/test_scenariocreator.py b/pyomo/contrib/parmest/tests/test_scenariocreator.py index a2dcf4c2739..fe4528120f6 100644 --- a/pyomo/contrib/parmest/tests/test_scenariocreator.py +++ b/pyomo/contrib/parmest/tests/test_scenariocreator.py @@ -70,10 +70,10 @@ def setUp(self): def SSE(model, data): expr = ( - (float(data['ca']) - model.ca) ** 2 - + (float(data['cb']) - model.cb) ** 2 - + (float(data['cc']) - model.cc) ** 2 - + (float(data['cd']) - model.cd) ** 2 + (float(data.iloc[0]['ca']) - model.ca) ** 2 + + (float(data.iloc[0]['cb']) - model.cb) ** 2 + + (float(data.iloc[0]['cc']) - model.cc) ** 2 + + (float(data.iloc[0]['cd']) - model.cd) ** 2 ) return expr diff --git a/pyomo/contrib/parmest/tests/test_utils.py b/pyomo/contrib/parmest/tests/test_utils.py index bd0706ac38d..03b48c0326c 100644 --- a/pyomo/contrib/parmest/tests/test_utils.py +++ b/pyomo/contrib/parmest/tests/test_utils.py @@ -50,6 +50,7 @@ def test_convert_param_to_var(self): theta_names = ['k1', 'k2', 'k3'] + print(data.loc[0]) instance = reactor_design_model(data.loc[0]) solver = pyo.SolverFactory('ipopt') solver.solve(instance) From 46cf8a58174d866cf8c0db28527726d17717e08a Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 31 Oct 2023 14:25:22 -0400 Subject: [PATCH 0354/1204] fix --- pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py | 8 +++----- pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index 9fccf1e7108..19a637744a9 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -8,7 +8,7 @@ class GreyBoxModel(egb.ExternalGreyBoxModel): """Greybox model to compute the example OF.""" - def __init__(self, initial, use_exact_derivatives=True, verbose=True): + def __init__(self, initial, use_exact_derivatives=True, verbose=False): """ Parameters @@ -85,7 +85,6 @@ def set_input_values(self, input_values): def evaluate_equality_constraints(self): """Evaluate the equality constraints.""" - # Not sure what this function should return with no equality constraints return None def evaluate_outputs(self): @@ -101,9 +100,8 @@ def evaluate_outputs(self): z = x1**2 + x2**2 + y1 + 1.5 * y2 + 0.5 * y3 if self.verbose: - pass - # print("\n Consider inputs [x1,x2,y1,y2,y3] =\n",x1, x2, y1, y2, y3) - # print(" z = ",z,"\n") + print("\n Consider inputs [x1,x2,y1,y2,y3] =\n",x1, x2, y1, y2, y3) + print(" z = ",z,"\n") return np.asarray([z], dtype=np.float64) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py index 5360cfab687..95e42065122 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py @@ -15,8 +15,6 @@ from pyomo.environ import SolverFactory, value, maximize from pyomo.opt import TerminationCondition from pyomo.common.dependencies import numpy_available, scipy_available -@unittest.skipIf(not numpy_available, 'Required numpy %s is not available') -@unittest.skipIf(not scipy_available, 'Required scipy %s is not available') from pyomo.contrib.mindtpy.tests.MINLP_simple import SimpleMINLP as SimpleMINLP model_list = [SimpleMINLP(grey_box=True)] @@ -34,6 +32,8 @@ @unittest.skipIf( not differentiate_available, 'Symbolic differentiation is not available' ) +@unittest.skipIf(not numpy_available, 'Required numpy %s is not available') +@unittest.skipIf(not scipy_available, 'Required scipy %s is not available') class TestMindtPy(unittest.TestCase): """Tests for the MindtPy solver plugin.""" From ba135b631a7cf5d2d6555fded604ceebc054ee5d Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 31 Oct 2023 14:31:04 -0400 Subject: [PATCH 0355/1204] black format --- pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py | 4 ++-- pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index 19a637744a9..db37c4390c9 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -100,8 +100,8 @@ def evaluate_outputs(self): z = x1**2 + x2**2 + y1 + 1.5 * y2 + 0.5 * y3 if self.verbose: - print("\n Consider inputs [x1,x2,y1,y2,y3] =\n",x1, x2, y1, y2, y3) - print(" z = ",z,"\n") + print("\n Consider inputs [x1,x2,y1,y2,y3] =\n", x1, x2, y1, y2, y3) + print(" z = ", z, "\n") return np.asarray([z], dtype=np.float64) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py index 95e42065122..70ae881abb2 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py @@ -34,7 +34,6 @@ ) @unittest.skipIf(not numpy_available, 'Required numpy %s is not available') @unittest.skipIf(not scipy_available, 'Required scipy %s is not available') - class TestMindtPy(unittest.TestCase): """Tests for the MindtPy solver plugin.""" From fe31c4049cd20af7a97ae3a81a431a3b47de4a23 Mon Sep 17 00:00:00 2001 From: Shawn Martin Date: Tue, 31 Oct 2023 15:37:44 -0600 Subject: [PATCH 0356/1204] Removed extraneous code. --- pyomo/contrib/parmest/graphics.py | 1 - pyomo/contrib/parmest/tests/test_utils.py | 1 - 2 files changed, 2 deletions(-) diff --git a/pyomo/contrib/parmest/graphics.py b/pyomo/contrib/parmest/graphics.py index 991395a556d..99eda8aad7a 100644 --- a/pyomo/contrib/parmest/graphics.py +++ b/pyomo/contrib/parmest/graphics.py @@ -178,7 +178,6 @@ def _add_obj_contour(x, y, color, columns, data, theta_star, label=None): X, Y, Z = _get_data_slice(xvar, yvar, columns, data, theta_star) triang = matplotlib.tri.Triangulation(X, Y) - #cmap = plt.cm.get_cmap('Greys') cmap = matplotlib.colormaps['Greys'] plt.tricontourf(triang, Z, cmap=cmap) diff --git a/pyomo/contrib/parmest/tests/test_utils.py b/pyomo/contrib/parmest/tests/test_utils.py index 03b48c0326c..bd0706ac38d 100644 --- a/pyomo/contrib/parmest/tests/test_utils.py +++ b/pyomo/contrib/parmest/tests/test_utils.py @@ -50,7 +50,6 @@ def test_convert_param_to_var(self): theta_names = ['k1', 'k2', 'k3'] - print(data.loc[0]) instance = reactor_design_model(data.loc[0]) solver = pyo.SolverFactory('ipopt') solver.solve(instance) From 8eacf0b73aada09f5d187dd34b47c7d0d622a634 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 31 Oct 2023 18:50:02 -0400 Subject: [PATCH 0357/1204] fix import bug --- pyomo/contrib/mindtpy/tests/MINLP_simple.py | 18 +- .../mindtpy/tests/MINLP_simple_grey_box.py | 280 +++++++++--------- .../mindtpy/tests/test_mindtpy_grey_box.py | 3 +- 3 files changed, 156 insertions(+), 145 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple.py b/pyomo/contrib/mindtpy/tests/MINLP_simple.py index 04315f59458..7454b595986 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple.py @@ -38,16 +38,10 @@ Block, ) from pyomo.common.collections import ComponentMap -from pyomo.contrib.mindtpy.tests.MINLP_simple_grey_box import GreyBoxModel -from pyomo.common.dependencies import attempt_import - -egb = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box')[0] - - -def build_model_external(m): - ex_model = GreyBoxModel(initial={"X1": 0, "X2": 0, "Y1": 0, "Y2": 1, "Y3": 1}) - m.egb = egb.ExternalGreyBoxBlock() - m.egb.set_external_model(ex_model) +from pyomo.contrib.mindtpy.tests.MINLP_simple_grey_box import ( + GreyBoxModel, + build_model_external, +) class SimpleMINLP(ConcreteModel): @@ -56,6 +50,10 @@ class SimpleMINLP(ConcreteModel): def __init__(self, grey_box=False, *args, **kwargs): """Create the problem.""" kwargs.setdefault('name', 'SimpleMINLP') + if grey_box and GreyBoxModel is None: + m = None + return + super(SimpleMINLP, self).__init__(*args, **kwargs) m = self diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index db37c4390c9..547efc0a74c 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -2,136 +2,150 @@ import pyomo.common.dependencies.scipy.sparse as scipy_sparse from pyomo.common.dependencies import attempt_import -egb = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box')[0] - - -class GreyBoxModel(egb.ExternalGreyBoxModel): - """Greybox model to compute the example OF.""" - - def __init__(self, initial, use_exact_derivatives=True, verbose=False): - """ - Parameters - - use_exact_derivatives: bool - If True, the exact derivatives are used. If False, the finite difference - approximation is used. - verbose: bool - If True, print information about the model. - """ - self._use_exact_derivatives = use_exact_derivatives - self.verbose = verbose - self.initial = initial - - # For use with exact Hessian - self._output_con_mult_values = np.zeros(1) - - if not use_exact_derivatives: - raise NotImplementedError("use_exact_derivatives == False not supported") - - def input_names(self): - """Return the names of the inputs.""" - self.input_name_list = ["X1", "X2", "Y1", "Y2", "Y3"] - - return self.input_name_list - - def equality_constraint_names(self): - """Return the names of the equality constraints.""" - # no equality constraints - return [] - - def output_names(self): - """Return the names of the outputs.""" - return ['z'] - - def set_output_constraint_multipliers(self, output_con_multiplier_values): - """Set the values of the output constraint multipliers.""" - # because we only have one output constraint - assert len(output_con_multiplier_values) == 1 - np.copyto(self._output_con_mult_values, output_con_multiplier_values) - - def finalize_block_construction(self, pyomo_block): - """Finalize the construction of the ExternalGreyBoxBlock.""" - if self.initial is not None: - print("initialized") - pyomo_block.inputs["X1"].value = self.initial["X1"] - pyomo_block.inputs["X2"].value = self.initial["X2"] - pyomo_block.inputs["Y1"].value = self.initial["Y1"] - pyomo_block.inputs["Y2"].value = self.initial["Y2"] - pyomo_block.inputs["Y3"].value = self.initial["Y3"] - - else: - print("uninitialized") - for n in self.input_name_list: - pyomo_block.inputs[n].value = 1 - - pyomo_block.inputs["X1"].setub(4) - pyomo_block.inputs["X1"].setlb(0) - - pyomo_block.inputs["X2"].setub(4) - pyomo_block.inputs["X2"].setlb(0) - - pyomo_block.inputs["Y1"].setub(1) - pyomo_block.inputs["Y1"].setlb(0) - - pyomo_block.inputs["Y2"].setub(1) - pyomo_block.inputs["Y2"].setlb(0) - - pyomo_block.inputs["Y3"].setub(1) - pyomo_block.inputs["Y3"].setlb(0) - - def set_input_values(self, input_values): - """Set the values of the inputs.""" - self._input_values = list(input_values) - - def evaluate_equality_constraints(self): - """Evaluate the equality constraints.""" - return None - - def evaluate_outputs(self): - """Evaluate the output of the model.""" - # form matrix as a list of lists - # M = self._extract_and_assemble_fim() - x1 = self._input_values[0] - x2 = self._input_values[1] - y1 = self._input_values[2] - y2 = self._input_values[3] - y3 = self._input_values[4] - # z - z = x1**2 + x2**2 + y1 + 1.5 * y2 + 0.5 * y3 - - if self.verbose: - print("\n Consider inputs [x1,x2,y1,y2,y3] =\n", x1, x2, y1, y2, y3) - print(" z = ", z, "\n") - - return np.asarray([z], dtype=np.float64) - - def evaluate_jacobian_equality_constraints(self): - """Evaluate the Jacobian of the equality constraints.""" - return None - - ''' - def _extract_and_assemble_fim(self): - M = np.zeros((self.n_parameters, self.n_parameters)) - for i in range(self.n_parameters): - for k in range(self.n_parameters): - M[i,k] = self._input_values[self.ele_to_order[(i,k)]] - - return M - ''' - - def evaluate_jacobian_outputs(self): - """Evaluate the Jacobian of the outputs.""" - if self._use_exact_derivatives: - # compute gradient of log determinant - row = np.zeros(5) # to store row index - col = np.zeros(5) # to store column index - data = np.zeros(5) # to store data - - row[0], col[0], data[0] = (0, 0, 2 * self._input_values[0]) # x1 - row[0], col[1], data[1] = (0, 1, 2 * self._input_values[1]) # x2 - row[0], col[2], data[2] = (0, 2, 1) # y1 - row[0], col[3], data[3] = (0, 3, 1.5) # y2 - row[0], col[4], data[4] = (0, 4, 0.5) # y3 - - # sparse matrix - return scipy_sparse.coo_matrix((data, (row, col)), shape=(1, 5)) +egb, egb_available = attempt_import( + 'pyomo.contrib.pynumero.interfaces.external_grey_box' +) + +if egb_available: + + class GreyBoxModel(egb.ExternalGreyBoxModel): + """Greybox model to compute the example objective function.""" + + def __init__(self, initial, use_exact_derivatives=True, verbose=False): + """ + Parameters + + use_exact_derivatives: bool + If True, the exact derivatives are used. If False, the finite difference + approximation is used. + verbose: bool + If True, print information about the model. + """ + self._use_exact_derivatives = use_exact_derivatives + self.verbose = verbose + self.initial = initial + + # For use with exact Hessian + self._output_con_mult_values = np.zeros(1) + + if not use_exact_derivatives: + raise NotImplementedError( + "use_exact_derivatives == False not supported" + ) + + def input_names(self): + """Return the names of the inputs.""" + self.input_name_list = ["X1", "X2", "Y1", "Y2", "Y3"] + + return self.input_name_list + + def equality_constraint_names(self): + """Return the names of the equality constraints.""" + # no equality constraints + return [] + + def output_names(self): + """Return the names of the outputs.""" + return ['z'] + + def set_output_constraint_multipliers(self, output_con_multiplier_values): + """Set the values of the output constraint multipliers.""" + # because we only have one output constraint + assert len(output_con_multiplier_values) == 1 + np.copyto(self._output_con_mult_values, output_con_multiplier_values) + + def finalize_block_construction(self, pyomo_block): + """Finalize the construction of the ExternalGreyBoxBlock.""" + if self.initial is not None: + print("initialized") + pyomo_block.inputs["X1"].value = self.initial["X1"] + pyomo_block.inputs["X2"].value = self.initial["X2"] + pyomo_block.inputs["Y1"].value = self.initial["Y1"] + pyomo_block.inputs["Y2"].value = self.initial["Y2"] + pyomo_block.inputs["Y3"].value = self.initial["Y3"] + + else: + print("uninitialized") + for n in self.input_name_list: + pyomo_block.inputs[n].value = 1 + + pyomo_block.inputs["X1"].setub(4) + pyomo_block.inputs["X1"].setlb(0) + + pyomo_block.inputs["X2"].setub(4) + pyomo_block.inputs["X2"].setlb(0) + + pyomo_block.inputs["Y1"].setub(1) + pyomo_block.inputs["Y1"].setlb(0) + + pyomo_block.inputs["Y2"].setub(1) + pyomo_block.inputs["Y2"].setlb(0) + + pyomo_block.inputs["Y3"].setub(1) + pyomo_block.inputs["Y3"].setlb(0) + + def set_input_values(self, input_values): + """Set the values of the inputs.""" + self._input_values = list(input_values) + + def evaluate_equality_constraints(self): + """Evaluate the equality constraints.""" + return None + + def evaluate_outputs(self): + """Evaluate the output of the model.""" + # form matrix as a list of lists + # M = self._extract_and_assemble_fim() + x1 = self._input_values[0] + x2 = self._input_values[1] + y1 = self._input_values[2] + y2 = self._input_values[3] + y3 = self._input_values[4] + # z + z = x1**2 + x2**2 + y1 + 1.5 * y2 + 0.5 * y3 + + if self.verbose: + print("\n Consider inputs [x1,x2,y1,y2,y3] =\n", x1, x2, y1, y2, y3) + print(" z = ", z, "\n") + + return np.asarray([z], dtype=np.float64) + + def evaluate_jacobian_equality_constraints(self): + """Evaluate the Jacobian of the equality constraints.""" + return None + + ''' + def _extract_and_assemble_fim(self): + M = np.zeros((self.n_parameters, self.n_parameters)) + for i in range(self.n_parameters): + for k in range(self.n_parameters): + M[i,k] = self._input_values[self.ele_to_order[(i,k)]] + + return M + ''' + + def evaluate_jacobian_outputs(self): + """Evaluate the Jacobian of the outputs.""" + if self._use_exact_derivatives: + # compute gradient of log determinant + row = np.zeros(5) # to store row index + col = np.zeros(5) # to store column index + data = np.zeros(5) # to store data + + row[0], col[0], data[0] = (0, 0, 2 * self._input_values[0]) # x1 + row[0], col[1], data[1] = (0, 1, 2 * self._input_values[1]) # x2 + row[0], col[2], data[2] = (0, 2, 1) # y1 + row[0], col[3], data[3] = (0, 3, 1.5) # y2 + row[0], col[4], data[4] = (0, 4, 0.5) # y3 + + # sparse matrix + return scipy_sparse.coo_matrix((data, (row, col)), shape=(1, 5)) + + def build_model_external(m): + ex_model = GreyBoxModel(initial={"X1": 0, "X2": 0, "Y1": 0, "Y2": 1, "Y3": 1}) + m.egb = egb.ExternalGreyBoxBlock() + m.egb.set_external_model(ex_model) + +else: + GreyBoxModel = None + build_model_external = None diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py index 70ae881abb2..f84136ca6bf 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py @@ -25,6 +25,7 @@ subsolvers_available = False +@unittest.skipIf(model_list[0] is None, 'Unable to generate the Grey Box model.') @unittest.skipIf( not subsolvers_available, 'Required subsolvers %s are not available' % (required_solvers,), @@ -32,8 +33,6 @@ @unittest.skipIf( not differentiate_available, 'Symbolic differentiation is not available' ) -@unittest.skipIf(not numpy_available, 'Required numpy %s is not available') -@unittest.skipIf(not scipy_available, 'Required scipy %s is not available') class TestMindtPy(unittest.TestCase): """Tests for the MindtPy solver plugin.""" From bd1266445419daa6adbfaba8f98b06e42b215540 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 31 Oct 2023 20:02:24 -0400 Subject: [PATCH 0358/1204] fix import bug --- pyomo/contrib/mindtpy/algorithm_base_class.py | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index d562c924a7d..55609b60132 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -84,7 +84,7 @@ single_tree, single_tree_available = attempt_import('pyomo.contrib.mindtpy.single_tree') tabu_list, tabu_list_available = attempt_import('pyomo.contrib.mindtpy.tabu_list') -egb = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box')[0] +egb, egb_available = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box') class _MindtPyAlgorithm(object): @@ -324,11 +324,12 @@ def build_ordered_component_lists(self, model): ctype=Constraint, active=True, descend_into=(Block) ) ) - util_block.grey_box_list = list( - model.component_data_objects( - ctype=egb.ExternalGreyBoxBlock, active=True, descend_into=(Block) + if egb_available: + util_block.grey_box_list = list( + model.component_data_objects( + ctype=egb.ExternalGreyBoxBlock, active=True, descend_into=(Block) + ) ) - ) util_block.linear_constraint_list = list( c for c in util_block.constraint_list @@ -356,13 +357,22 @@ def build_ordered_component_lists(self, model): # We use component_data_objects rather than list(var_set) in order to # preserve a deterministic ordering. - util_block.variable_list = list( - v - for v in model.component_data_objects( - ctype=Var, descend_into=(Block, egb.ExternalGreyBoxBlock) + if egb_available: + util_block.variable_list = list( + v + for v in model.component_data_objects( + ctype=Var, descend_into=(Block, egb.ExternalGreyBoxBlock) + ) + if v in var_set + ) + else: + util_block.variable_list = list( + v + for v in model.component_data_objects( + ctype=Var, descend_into=(Block) + ) + if v in var_set ) - if v in var_set - ) util_block.discrete_variable_list = list( v for v in util_block.variable_list if v in var_set and v.is_integer() ) From ebe91a6b16f7a14be18dc6bc7f8f96be5e792e81 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 31 Oct 2023 20:25:52 -0400 Subject: [PATCH 0359/1204] black format --- pyomo/contrib/mindtpy/algorithm_base_class.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 55609b60132..e4dea716178 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -84,7 +84,9 @@ single_tree, single_tree_available = attempt_import('pyomo.contrib.mindtpy.single_tree') tabu_list, tabu_list_available = attempt_import('pyomo.contrib.mindtpy.tabu_list') -egb, egb_available = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box') +egb, egb_available = attempt_import( + 'pyomo.contrib.pynumero.interfaces.external_grey_box' +) class _MindtPyAlgorithm(object): @@ -368,9 +370,7 @@ def build_ordered_component_lists(self, model): else: util_block.variable_list = list( v - for v in model.component_data_objects( - ctype=Var, descend_into=(Block) - ) + for v in model.component_data_objects(ctype=Var, descend_into=(Block)) if v in var_set ) util_block.discrete_variable_list = list( From 2b4575645d73bed7d73730433e83dbdb141d27e3 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 31 Oct 2023 23:57:06 -0400 Subject: [PATCH 0360/1204] fix import bug --- pyomo/contrib/mindtpy/algorithm_base_class.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index e4dea716178..03fcc12fbac 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -332,6 +332,8 @@ def build_ordered_component_lists(self, model): ctype=egb.ExternalGreyBoxBlock, active=True, descend_into=(Block) ) ) + else: + util_block.grey_box_list = [] util_block.linear_constraint_list = list( c for c in util_block.constraint_list From 905503b13907a610a1e7f9d8620ee88127551ec5 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 1 Nov 2023 00:42:53 -0400 Subject: [PATCH 0361/1204] fix gurobi single tree termination check bug --- pyomo/contrib/mindtpy/single_tree.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index 2174d093009..9595a9fc9be 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -910,19 +910,7 @@ def LazyOACallback_gurobi(cb_m, cb_opt, cb_where, mindtpy_solver, config): if mindtpy_solver.dual_bound != mindtpy_solver.dual_bound_progress[0]: mindtpy_solver.add_regularization() - if ( - abs(mindtpy_solver.primal_bound - mindtpy_solver.dual_bound) - <= config.absolute_bound_tolerance - ): - config.logger.info( - 'MindtPy exiting on bound convergence. ' - '|Primal Bound: {} - Dual Bound: {}| <= (absolute tolerance {}) \n'.format( - mindtpy_solver.primal_bound, - mindtpy_solver.dual_bound, - config.absolute_bound_tolerance, - ) - ) - mindtpy_solver.results.solver.termination_condition = tc.optimal + if mindtpy_solver.bounds_converged() or mindtpy_solver.reached_time_limit(): cb_opt._solver_model.terminate() return From 52cb54f9418b081f44f1a3887e6dae03ebf9710f Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 1 Nov 2023 11:45:20 -0600 Subject: [PATCH 0362/1204] Fixing some places where we mix up binaries and Booleans in the hull tests --- pyomo/gdp/tests/test_hull.py | 72 ++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index 09f65765fe6..b7b5a11e28c 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -510,10 +510,10 @@ def test_disaggregatedVar_mappings(self): for i in [0, 1]: mappings = ComponentMap() mappings[m.x] = disjBlock[i].disaggregatedVars.x - if i == 1: # this disjunct as x, w, and no y + if i == 1: # this disjunct has x, w, and no y mappings[m.w] = disjBlock[i].disaggregatedVars.w mappings[m.y] = transBlock._disaggregatedVars[0] - elif i == 0: # this disjunct as x, y, and no w + elif i == 0: # this disjunct has x, y, and no w mappings[m.y] = disjBlock[i].disaggregatedVars.y mappings[m.w] = transBlock._disaggregatedVars[1] @@ -1427,16 +1427,16 @@ def test_relaxation_feasibility(self): solver = SolverFactory(linear_solvers[0]) cases = [ - (1, 1, 1, 1, None), - (0, 0, 0, 0, None), - (1, 0, 0, 0, None), - (0, 1, 0, 0, 1.1), - (0, 0, 1, 0, None), - (0, 0, 0, 1, None), - (1, 1, 0, 0, None), - (1, 0, 1, 0, 1.2), - (1, 0, 0, 1, 1.3), - (1, 0, 1, 1, None), + (True, True, True, True, None), + (False, False, False, False, None), + (True, False, False, False, None), + (False, True, False, False, 1.1), + (False, False, True, False, None), + (False, False, False, True, None), + (True, True, False, False, None), + (True, False, True, False, 1.2), + (True, False, False, True, 1.3), + (True, False, True, True, None), ] for case in cases: m.d1.indicator_var.fix(case[0]) @@ -1468,16 +1468,16 @@ def test_relaxation_feasibility_transform_inner_first(self): solver = SolverFactory(linear_solvers[0]) cases = [ - (1, 1, 1, 1, None), - (0, 0, 0, 0, None), - (1, 0, 0, 0, None), - (0, 1, 0, 0, 1.1), - (0, 0, 1, 0, None), - (0, 0, 0, 1, None), - (1, 1, 0, 0, None), - (1, 0, 1, 0, 1.2), - (1, 0, 0, 1, 1.3), - (1, 0, 1, 1, None), + (True, True, True, True, None), + (False, False, False, False, None), + (True, False, False, False, None), + (False, True, False, False, 1.1), + (False, False, True, False, None), + (False, False, False, True, None), + (True, True, False, False, None), + (True, False, True, False, 1.2), + (True, False, False, True, 1.3), + (True, False, True, True, None), ] for case in cases: m.d1.indicator_var.fix(case[0]) @@ -1722,10 +1722,10 @@ def test_disaggregated_vars_are_set_to_0_correctly(self): hull.apply_to(m) # this should be a feasible integer solution - m.d1.indicator_var.fix(0) - m.d2.indicator_var.fix(1) - m.d3.indicator_var.fix(0) - m.d4.indicator_var.fix(0) + m.d1.indicator_var.fix(False) + m.d2.indicator_var.fix(True) + m.d3.indicator_var.fix(False) + m.d4.indicator_var.fix(False) results = SolverFactory(linear_solvers[0]).solve(m) self.assertEqual( @@ -1739,10 +1739,10 @@ def test_disaggregated_vars_are_set_to_0_correctly(self): self.assertEqual(value(hull.get_disaggregated_var(m.x, m.d4)), 0) # and what if one of the inner disjuncts is true? - m.d1.indicator_var.fix(1) - m.d2.indicator_var.fix(0) - m.d3.indicator_var.fix(1) - m.d4.indicator_var.fix(0) + m.d1.indicator_var.fix(True) + m.d2.indicator_var.fix(False) + m.d3.indicator_var.fix(True) + m.d4.indicator_var.fix(False) results = SolverFactory(linear_solvers[0]).solve(m) self.assertEqual( @@ -2398,12 +2398,12 @@ def OneCentroidPerPt(m, i): TransformationFactory('gdp.hull').apply_to(m) # fix an optimal solution - m.AssignPoint[1, 1].indicator_var.fix(1) - m.AssignPoint[1, 2].indicator_var.fix(0) - m.AssignPoint[2, 1].indicator_var.fix(0) - m.AssignPoint[2, 2].indicator_var.fix(1) - m.AssignPoint[3, 1].indicator_var.fix(1) - m.AssignPoint[3, 2].indicator_var.fix(0) + m.AssignPoint[1, 1].indicator_var.fix(True) + m.AssignPoint[1, 2].indicator_var.fix(False) + m.AssignPoint[2, 1].indicator_var.fix(False) + m.AssignPoint[2, 2].indicator_var.fix(True) + m.AssignPoint[3, 1].indicator_var.fix(True) + m.AssignPoint[3, 2].indicator_var.fix(False) m.cluster_center[1].fix(0.3059) m.cluster_center[2].fix(0.8043) From 45b61d111b9d28b3862baa39bc1d89fe8007244f Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 1 Nov 2023 14:51:46 -0600 Subject: [PATCH 0363/1204] Adding some new test for edge cases with nested GDP in hull --- pyomo/gdp/tests/test_hull.py | 97 ++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index b7b5a11e28c..118ee4ca69a 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -1832,6 +1832,103 @@ def d_r(e): cons = hull.get_disaggregation_constraint(m.x, m.d_r.inner_disj) assertExpressionsEqual(self, cons.expr, x2 == x3 + x4) + def test_nested_with_var_that_does_not_appear_in_every_disjunct(self): + m = ConcreteModel() + m.x = Var(bounds=(0, 10)) + m.y = Var(bounds=(-4, 5)) + m.parent1 = Disjunct() + m.parent2 = Disjunct() + m.parent2.c = Constraint(expr=m.x == 0) + m.parent_disjunction = Disjunction(expr=[m.parent1, m.parent2]) + m.child1 = Disjunct() + m.child1.c = Constraint(expr=m.x <= 8) + m.child2 = Disjunct() + m.child2.c = Constraint(expr=m.x + m.y <= 3) + m.child3 = Disjunct() + m.child3.c = Constraint(expr=m.x <= 7) + m.parent1.disjunction = Disjunction(expr=[m.child1, m.child2, m.child3]) + + hull = TransformationFactory('gdp.hull') + hull.apply_to(m) + + y_c2 = hull.get_disaggregated_var(m.y, m.child2) + self.assertEqual(y_c2.bounds, (-4, 5)) + other_y = hull.get_disaggregated_var(m.y, m.child1) + self.assertEqual(other_y.bounds, (-4, 5)) + other_other_y = hull.get_disaggregated_var(m.y, m.child3) + self.assertIs(other_y, other_other_y) + y_p1 = hull.get_disaggregated_var(m.y, m.parent1) + self.assertEqual(y_p1.bounds, (-4, 5)) + y_p2 = hull.get_disaggregated_var(m.y, m.parent2) + self.assertEqual(y_p2.bounds, (-4, 5)) + y_cons = hull.get_disaggregation_constraint(m.y, m.parent1.disjunction) + # check that the disaggregated ys in the nested just sum to the original + assertExpressionsEqual(self, y_cons.expr, y_p1 == other_y + y_c2) + y_cons = hull.get_disaggregation_constraint(m.y, m.parent_disjunction) + assertExpressionsEqual(self, y_cons.expr, m.y == y_p1 + y_p2) + + x_c1 = hull.get_disaggregated_var(m.x, m.child1) + x_c2 = hull.get_disaggregated_var(m.x, m.child2) + x_c3 = hull.get_disaggregated_var(m.x, m.child3) + x_p1 = hull.get_disaggregated_var(m.x, m.parent1) + x_p2 = hull.get_disaggregated_var(m.x, m.parent2) + x_cons_parent = hull.get_disaggregation_constraint(m.x, m.parent_disjunction) + assertExpressionsEqual(self, x_cons_parent.expr, m.x == x_p1 + x_p2) + x_cons_child = hull.get_disaggregation_constraint(m.x, m.parent1.disjunction) + assertExpressionsEqual(self, x_cons_child.expr, x_p1 == x_c1 + x_c2 + x_c3) + + def test_nested_with_var_that_skips_a_level(self): + m = ConcreteModel() + + m.x = Var(bounds=(-2, 9)) + m.y = Var(bounds=(-3, 8)) + + m.y1 = Disjunct() + m.y1.c1 = Constraint(expr=m.x >= 4) + m.y1.z1 = Disjunct() + m.y1.z1.c1 = Constraint(expr=m.y == 0) + m.y1.z1.w1 = Disjunct() + m.y1.z1.w1.c1 = Constraint(expr=m.x == 0) + m.y1.z1.w2 = Disjunct() + m.y1.z1.w2.c1 = Constraint(expr=m.x >= 1) + m.y1.z1.disjunction = Disjunction(expr=[m.y1.z1.w1, m.y1.z1.w2]) + m.y1.z2 = Disjunct() + m.y1.z2.c1 = Constraint(expr=m.y == 1) + m.y1.disjunction = Disjunction(expr=[m.y1.z1, m.y1.z2]) + m.y2 = Disjunct() + m.y2.c1 = Constraint(expr=m.x == 0) + m.disjunction = Disjunction(expr=[m.y1, m.y2]) + + hull = TransformationFactory('gdp.hull') + hull.apply_to(m) + + x_y1 = hull.get_disaggregated_var(m.x, m.y1) + x_y2 = hull.get_disaggregated_var(m.x, m.y2) + x_z1 = hull.get_disaggregated_var(m.x, m.y1.z1) + x_z2 = hull.get_disaggregated_var(m.x, m.y1.z2) + x_w1 = hull.get_disaggregated_var(m.x, m.y1.z1.w1) + x_w2 = hull.get_disaggregated_var(m.x, m.y1.z1.w2) + + y_z1 = hull.get_disaggregated_var(m.y, m.y1.z1) + y_z2 = hull.get_disaggregated_var(m.y, m.y1.z2) + y_y1 = hull.get_disaggregated_var(m.y, m.y1) + y_y2 = hull.get_disaggregated_var(m.y, m.y2) + + cons = hull.get_disaggregation_constraint(m.x, m.y1.z1.disjunction) + assertExpressionsEqual(self, cons.expr, x_z1 == x_w1 + x_w2) + cons = hull.get_disaggregation_constraint(m.x, m.y1.disjunction) + assertExpressionsEqual(self, cons.expr, x_y1 == x_z2 + x_z1) + cons = hull.get_disaggregation_constraint(m.x, m.disjunction) + assertExpressionsEqual(self, cons.expr, m.x == x_y1 + x_y2) + + cons = hull.get_disaggregation_constraint(m.y, m.y1.z1.disjunction, + raise_exception=False) + self.assertIsNone(cons) + cons = hull.get_disaggregation_constraint(m.y, m.y1.disjunction) + assertExpressionsEqual(self, cons.expr, y_y1 == y_z1 + y_z2) + cons = hull.get_disaggregation_constraint(m.y, m.disjunction) + assertExpressionsEqual(self, cons.expr, m.y == y_y2 + y_y1) + class TestSpecialCases(unittest.TestCase): def test_local_vars(self): From b741882fa52f052c3cccf4e4c2a530cbfabfa209 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 1 Nov 2023 14:53:09 -0600 Subject: [PATCH 0364/1204] Adding option to not raise an exception when looking for disaggregated vars and constraints on transformed model --- pyomo/gdp/plugins/hull.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index b8e2b3e3699..6086bd61ad1 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -885,7 +885,7 @@ def _add_local_var_suffix(self, disjunct): % (disjunct.getname(fully_qualified=True), localSuffix.ctype) ) - def get_disaggregated_var(self, v, disjunct): + def get_disaggregated_var(self, v, disjunct, raise_exception=True): """ Returns the disaggregated variable corresponding to the Var v and the Disjunct disjunct. @@ -903,11 +903,13 @@ def get_disaggregated_var(self, v, disjunct): try: return transBlock._disaggregatedVarMap['disaggregatedVar'][disjunct][v] except: - logger.error( - "It does not appear '%s' is a " - "variable that appears in disjunct '%s'" % (v.name, disjunct.name) - ) - raise + if raise_exception: + logger.error( + "It does not appear '%s' is a " + "variable that appears in disjunct '%s'" % (v.name, disjunct.name) + ) + raise + return none def get_src_var(self, disaggregated_var): """ @@ -944,7 +946,8 @@ def get_src_var(self, disaggregated_var): # retrieves the disaggregation constraint for original_var resulting from # transforming disjunction - def get_disaggregation_constraint(self, original_var, disjunction): + def get_disaggregation_constraint(self, original_var, disjunction, + raise_exception=True): """ Returns the disaggregation (re-aggregation?) constraint (which links the disaggregated variables to their original) @@ -974,12 +977,14 @@ def get_disaggregation_constraint(self, original_var, disjunction): ._disaggregationConstraintMap[original_var][disjunction] ) except: - logger.error( - "It doesn't appear that '%s' is a variable that was " - "disaggregated by Disjunction '%s'" - % (original_var.name, disjunction.name) - ) - raise + if raise_exception: + logger.error( + "It doesn't appear that '%s' is a variable that was " + "disaggregated by Disjunction '%s'" + % (original_var.name, disjunction.name) + ) + raise + return None def get_var_bounds_constraint(self, v): """ From b1bd5d5aeead1bd1d676968cc2ff6556154f8a6b Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 1 Nov 2023 16:18:31 -0600 Subject: [PATCH 0365/1204] Putting transformed components on parent Block always, a lot of performance improvements in the variable gathering logic --- pyomo/gdp/plugins/hull.py | 50 +++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 6086bd61ad1..fcb992ed6c7 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -262,7 +262,6 @@ def _apply_to_impl(self, instance, **kwds): t, t.index(), parent_disjunct=gdp_tree.parent(t), - root_disjunct=gdp_tree.root_disjunct(t), ) # We skip disjuncts now, because we need information from the # disjunctions to transform them (which variables to disaggregate), @@ -298,9 +297,7 @@ def _add_transformation_block(self, to_block): return transBlock, True - def _transform_disjunctionData( - self, obj, index, parent_disjunct=None, root_disjunct=None - ): + def _transform_disjunctionData(self, obj, index, parent_disjunct=None): # Hull reformulation doesn't work if this is an OR constraint. So if # xor is false, give up if not obj.xor: @@ -310,8 +307,12 @@ def _transform_disjunctionData( "Must be an XOR!" % obj.name ) + # We put *all* transformed things on the parent Block of this + # disjunction. We'll mark the disaggregated Vars as local, but beyond + # that, we actually need everything to get transformed again as we go up + # the nested hierarchy (if there is one) transBlock, xorConstraint = self._setup_transform_disjunctionData( - obj, root_disjunct + obj, root_disjunct=None ) disaggregationConstraint = transBlock.disaggregationConstraints @@ -325,7 +326,8 @@ def _transform_disjunctionData( varOrder = [] varsByDisjunct = ComponentMap() localVarsByDisjunct = ComponentMap() - include_fixed_vars = not self._config.assume_fixed_vars_permanent + disjunctsVarAppearsIn = ComponentMap() + setOfDisjunctsVarAppearsIn = ComponentMap() for disjunct in obj.disjuncts: if not disjunct.active: continue @@ -338,7 +340,7 @@ def _transform_disjunctionData( Constraint, active=True, sort=SortComponents.deterministic, - descend_into=(Block, Disjunct), + descend_into=Block, ): # [ESJ 02/14/2020] By default, we disaggregate fixed variables # on the philosophy that fixing is not a promise for the future @@ -348,8 +350,8 @@ def _transform_disjunctionData( # assume_fixed_vars_permanent to True in which case we will skip # them for var in EXPR.identify_variables( - cons.body, include_fixed=include_fixed_vars - ): + cons.body, include_fixed=not + self._config.assume_fixed_vars_permanent): # Note the use of a list so that we will # eventually disaggregate the vars in a # deterministic order (the order that we found @@ -358,6 +360,12 @@ def _transform_disjunctionData( if not var in varOrder_set: varOrder.append(var) varOrder_set.add(var) + disjunctsVarAppearsIn[var] = [disjunct] + setOfDisjunctsVarAppearsIn[var] = ComponentSet([disjunct]) + else: + if disjunct not in setOfDisjunctsVarAppearsIn[var]: + disjunctsVarAppearsIn[var].append(disjunct) + setOfDisjunctsVarAppearsIn[var].add(disjunct) # check for LocalVars Suffix localVarsByDisjunct = self._get_local_var_suffixes( @@ -368,7 +376,6 @@ def _transform_disjunctionData( # being local. Since we transform from leaf to root, we are implicitly # treating our own disaggregated variables as local, so they will not be # re-disaggregated. - varSet = [] varSet = {disj: [] for disj in obj.disjuncts} # Note that variables are local with respect to a Disjunct. We deal with # them here to do some error checking (if something is obviously not @@ -379,11 +386,8 @@ def _transform_disjunctionData( # localVars of a Disjunct later) localVars = ComponentMap() varsToDisaggregate = [] - disjunctsVarAppearsIn = ComponentMap() for var in varOrder: - disjuncts = disjunctsVarAppearsIn[var] = [ - d for d in varsByDisjunct if var in varsByDisjunct[d] - ] + disjuncts = disjunctsVarAppearsIn[var] # clearly not local if used in more than one disjunct if len(disjuncts) > 1: if self._generate_debug_messages: @@ -398,8 +402,7 @@ def _transform_disjunctionData( # disjuncts is a list of length 1 elif localVarsByDisjunct.get(disjuncts[0]) is not None: if var in localVarsByDisjunct[disjuncts[0]]: - localVars_thisDisjunct = localVars.get(disjuncts[0]) - if localVars_thisDisjunct is not None: + if localVars.get(disjuncts[0]) is not None: localVars[disjuncts[0]].append(var) else: localVars[disjuncts[0]] = [var] @@ -408,7 +411,8 @@ def _transform_disjunctionData( varSet[disjuncts[0]].append(var) varsToDisaggregate.append(var) else: - # We don't even have have any local vars for this Disjunct. + # The user didn't declare any local vars for this Disjunct, so + # we know we're disaggregating it varSet[disjuncts[0]].append(var) varsToDisaggregate.append(var) @@ -497,18 +501,8 @@ def _transform_disjunctionData( ) disaggregatedExpr += disaggregatedVar - # We equate the sum of the disaggregated vars to var (the original) - # if parent_disjunct is None, else it needs to be the disaggregated - # var corresponding to var on the parent disjunct. This is the - # reason we transform from root to leaf: This constraint is now - # correct regardless of how nested something may have been. - parent_var = ( - var - if parent_disjunct is None - else self.get_disaggregated_var(var, parent_disjunct) - ) cons_idx = len(disaggregationConstraint) - disaggregationConstraint.add(cons_idx, parent_var == disaggregatedExpr) + disaggregationConstraint.add(cons_idx, var == disaggregatedExpr) # and update the map so that we can find this later. We index by # variable and the particular disjunction because there is a # different one for each disjunction From 207874428016f3e94459a148881786b05c370d32 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 1 Nov 2023 16:31:01 -0600 Subject: [PATCH 0366/1204] A few more performance things --- pyomo/gdp/plugins/hull.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index fcb992ed6c7..2469ba9c93c 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -464,14 +464,15 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct=None): (idx, 'ub'), var_free, ) - # maintain the mappings + # For every Disjunct the Var does not appear in, we want to map + # that this new variable is its disaggreggated variable. for disj in obj.disjuncts: # Because we called _transform_disjunct above, we know that # if this isn't transformed it is because it was cleanly # deactivated, and we can just skip it. if ( disj._transformation_block is not None - and disj not in disjunctsVarAppearsIn[var] + and disj not in setOfDisjunctsVarAppearsIn[var] ): relaxationBlock = disj._transformation_block().parent_block() relaxationBlock._bigMConstraintMap[ @@ -488,12 +489,7 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct=None): else: disaggregatedExpr = 0 for disjunct in disjunctsVarAppearsIn[var]: - if disjunct._transformation_block is None: - # Because we called _transform_disjunct above, we know that - # if this isn't transformed it is because it was cleanly - # deactivated, and we can just skip it. - continue - + # We know this Disjunct was active, so it has been transformed now. disaggregatedVar = ( disjunct._transformation_block() .parent_block() @@ -502,6 +498,8 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct=None): disaggregatedExpr += disaggregatedVar cons_idx = len(disaggregationConstraint) + # We always aggregate to the original var. If this is nested, this + # constraint will be transformed again. disaggregationConstraint.add(cons_idx, var == disaggregatedExpr) # and update the map so that we can find this later. We index by # variable and the particular disjunction because there is a From 563168f085d3ccb3d1291e0ad6afddca7a729a3f Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 1 Nov 2023 16:34:06 -0600 Subject: [PATCH 0367/1204] Transform from leaf to root in hull --- pyomo/gdp/plugins/hull.py | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 2469ba9c93c..25a0606dc1c 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -253,7 +253,10 @@ def _apply_to_impl(self, instance, **kwds): # Preprocess in order to find what disjunctive components need # transformation gdp_tree = self._get_gdp_tree_from_targets(instance, targets) - preprocessed_targets = gdp_tree.topological_sort() + # Transform from leaf to root: This is important for hull because for + # nested GDPs, we will introduce variables that need disaggregating into + # parent Disjuncts as we transform their child Disjunctions. + preprocessed_targets = gdp_tree.reverse_topological_sort() self._targets_set = set(preprocessed_targets) for t in preprocessed_targets: @@ -565,8 +568,8 @@ def _transform_disjunct(self, obj, transBlock, varSet, localVars, local_var_set) ) for var in localVars: - # we don't need to disaggregated, we can use this Var, but we do - # need to set up its bounds constraints. + # we don't need to disaggregate, i.e., we can use this Var, but we + # do need to set up its bounds constraints. # naming conflicts are possible here since this is a bunch # of variables from different blocks coming together, so we @@ -671,24 +674,6 @@ def _get_local_var_set(self, disjunction): return local_var_set - def _warn_for_active_disjunct( - self, innerdisjunct, outerdisjunct, var_substitute_map, zero_substitute_map - ): - # We override the base class method because in hull, it might just be - # that we haven't gotten here yet. - disjuncts = ( - innerdisjunct.values() if innerdisjunct.is_indexed() else (innerdisjunct,) - ) - for disj in disjuncts: - if disj in self._targets_set: - # We're getting to this, have some patience. - continue - else: - # But if it wasn't in the targets after preprocessing, it - # doesn't belong in an active Disjunction that we are - # transforming and we should be confused. - _warn_for_active_disjunct(innerdisjunct, outerdisjunct) - def _transform_constraint( self, obj, disjunct, var_substitute_map, zero_substitute_map ): From 3b01104f586f9a33d1386ff89c5c3e57b3ae7fc7 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 1 Nov 2023 21:24:39 -0400 Subject: [PATCH 0368/1204] fix Gurobi single tree cycle handling --- pyomo/contrib/mindtpy/algorithm_base_class.py | 3 +++ pyomo/contrib/mindtpy/single_tree.py | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index a0216b5d054..33b2f2c1d04 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -106,6 +106,8 @@ def __init__(self, **kwds): self.curr_int_sol = [] self.should_terminate = False self.integer_list = [] + # dictionary {integer solution (list): cuts index (list)} + self.int_sol_2_cuts_ind = dict() # Set up iteration counters self.nlp_iter = 0 @@ -794,6 +796,7 @@ def MindtPy_initialization(self): self.integer_list.append(self.curr_int_sol) fixed_nlp, fixed_nlp_result = self.solve_subproblem() self.handle_nlp_subproblem_tc(fixed_nlp, fixed_nlp_result) + self.int_sol_2_cuts_ind[self.curr_int_sol] = list(range(1, len(self.mip.MindtPy_utils.cuts.oa_cuts) + 1)) elif config.init_strategy == 'FP': self.init_rNLP() self.fp_loop() diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index 9595a9fc9be..dacde73a79e 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -941,15 +941,26 @@ def LazyOACallback_gurobi(cb_m, cb_opt, cb_where, mindtpy_solver, config): ) return elif config.strategy == 'OA': + # Refer to the official document of GUROBI. + # Your callback should be prepared to cut off solutions that violate any of your lazy constraints, including those that have already been added. Node solutions will usually respect previously added lazy constraints, but not always. + # https://www.gurobi.com/documentation/current/refman/cs_cb_addlazy.html + # If this happens, MindtPy will look for the index of corresponding cuts, instead of solving the fixed-NLP again. + for ind in mindtpy_solver.int_sol_2_cuts_ind[mindtpy_solver.curr_int_sol]: + cb_opt.cbLazy(mindtpy_solver.mip.MindtPy_utils.cuts.oa_cuts[ind]) return else: mindtpy_solver.integer_list.append(mindtpy_solver.curr_int_sol) + if config.strategy == 'OA': + cut_ind = len(mindtpy_solver.mip.MindtPy_utils.cuts.oa_cuts) # solve subproblem # The constraint linearization happens in the handlers fixed_nlp, fixed_nlp_result = mindtpy_solver.solve_subproblem() mindtpy_solver.handle_nlp_subproblem_tc(fixed_nlp, fixed_nlp_result, cb_opt) + if config.strategy == 'OA': + # store the cut index corresponding to current integer solution. + mindtpy_solver.int_sol_2_cuts_ind[mindtpy_solver.curr_int_sol] = list(range(cut_ind + 1, len(mindtpy_solver.mip.MindtPy_utils.cuts.oa_cuts) + 1)) def handle_lazy_main_feasible_solution_gurobi(cb_m, cb_opt, mindtpy_solver, config): From 87c4742f57f6d51052e78bd705168394baa71220 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Thu, 2 Nov 2023 12:54:00 -0600 Subject: [PATCH 0369/1204] adding a numpy float to the latex printer --- pyomo/util/latex_printer.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index 22cefd745f8..549ab358793 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -73,6 +73,10 @@ _MONOMIAL = ExprType.MONOMIAL _GENERAL = ExprType.GENERAL +from pyomo.common.dependencies import numpy, numpy_available +if numpy_available: + import numpy as np + def decoder(num, base): # Needed in the general case, but not as implemented @@ -443,6 +447,8 @@ def __init__(self): Numeric_GetAttrExpression: handle_numericGetAttrExpression_node, NPV_SumExpression: handle_sumExpression_node, } + if numpy_available: + self._operator_handles[np.float64] = handle_num_node def exitNode(self, node, data): try: From a79912c0a0c4fecc59d1c40185d5d36619088f8b Mon Sep 17 00:00:00 2001 From: Shawn Martin Date: Thu, 2 Nov 2023 12:55:30 -0600 Subject: [PATCH 0370/1204] Ran black on modified files. --- .../reactor_design/bootstrap_example.py | 18 +- .../reactor_design/datarec_example.py | 34 +-- .../reactor_design/leaveNout_example.py | 18 +- .../likelihood_ratio_example.py | 16 +- .../multisensor_data_example.py | 18 +- .../parameter_estimation_example.py | 18 +- .../examples/reactor_design/reactor_design.py | 20 +- .../parmest/examples/semibatch/semibatch.py | 47 ++-- pyomo/contrib/parmest/graphics.py | 76 +++--- pyomo/contrib/parmest/tests/test_parmest.py | 256 +++++++++--------- .../parmest/tests/test_scenariocreator.py | 20 +- pyomo/contrib/parmest/tests/test_utils.py | 8 +- 12 files changed, 274 insertions(+), 275 deletions(-) diff --git a/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py b/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py index 67724644ef5..e2d172f34f6 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py @@ -19,20 +19,20 @@ def main(): # Vars to estimate - theta_names = ['k1', 'k2', 'k3'] + theta_names = ["k1", "k2", "k3"] # Data file_dirname = dirname(abspath(str(__file__))) - file_name = abspath(join(file_dirname, 'reactor_data.csv')) + file_name = abspath(join(file_dirname, "reactor_data.csv")) data = pd.read_csv(file_name) # Sum of squared error function def SSE(model, data): expr = ( - (float(data.iloc[0]['ca']) - model.ca) ** 2 - + (float(data.iloc[0]['cb']) - model.cb) ** 2 - + (float(data.iloc[0]['cc']) - model.cc) ** 2 - + (float(data.iloc[0]['cd']) - model.cd) ** 2 + (float(data.iloc[0]["ca"]) - model.ca) ** 2 + + (float(data.iloc[0]["cb"]) - model.cb) ** 2 + + (float(data.iloc[0]["cc"]) - model.cc) ** 2 + + (float(data.iloc[0]["cd"]) - model.cd) ** 2 ) return expr @@ -46,13 +46,13 @@ def SSE(model, data): bootstrap_theta = pest.theta_est_bootstrap(50) # Plot results - parmest.graphics.pairwise_plot(bootstrap_theta, title='Bootstrap theta') + parmest.graphics.pairwise_plot(bootstrap_theta, title="Bootstrap theta") parmest.graphics.pairwise_plot( bootstrap_theta, theta, 0.8, - ['MVN', 'KDE', 'Rect'], - title='Bootstrap theta with confidence regions', + ["MVN", "KDE", "Rect"], + title="Bootstrap theta with confidence regions", ) diff --git a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py index b50ee46d9b9..cfd3891c00e 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py @@ -39,16 +39,16 @@ def generate_data(): data = pd.DataFrame() ndata = 200 # Normal distribution, mean = 3400, std = 500 - data['ca'] = 500 * np.random.randn(ndata) + 3400 + data["ca"] = 500 * np.random.randn(ndata) + 3400 # Random distribution between 500 and 1500 - data['cb'] = np.random.rand(ndata) * 1000 + 500 + data["cb"] = np.random.rand(ndata) * 1000 + 500 # Lognormal distribution - data['cc'] = np.random.lognormal(np.log(1600), 0.25, ndata) + data["cc"] = np.random.lognormal(np.log(1600), 0.25, ndata) # Triangular distribution between 1000 and 2000 - data['cd'] = np.random.triangular(1000, 1800, 3000, size=ndata) + data["cd"] = np.random.triangular(1000, 1800, 3000, size=ndata) - data['sv'] = sv_real - data['caf'] = caf_real + data["sv"] = sv_real + data["caf"] = caf_real return data @@ -61,10 +61,10 @@ def main(): # Define sum of squared error objective function for data rec def SSE(model, data): expr = ( - ((float(data.iloc[0]['ca']) - model.ca) / float(data_std['ca'])) ** 2 - + ((float(data.iloc[0]['cb']) - model.cb) / float(data_std['cb'])) ** 2 - + ((float(data.iloc[0]['cc']) - model.cc) / float(data_std['cc'])) ** 2 - + ((float(data.iloc[0]['cd']) - model.cd) / float(data_std['cd'])) ** 2 + ((float(data.iloc[0]["ca"]) - model.ca) / float(data_std["ca"])) ** 2 + + ((float(data.iloc[0]["cb"]) - model.cb) / float(data_std["cb"])) ** 2 + + ((float(data.iloc[0]["cc"]) - model.cc) / float(data_std["cc"])) ** 2 + + ((float(data.iloc[0]["cd"]) - model.cd) / float(data_std["cd"])) ** 2 ) return expr @@ -73,26 +73,26 @@ def SSE(model, data): pest = parmest.Estimator(reactor_design_model_for_datarec, data, theta_names, SSE) - obj, theta, data_rec = pest.theta_est(return_values=['ca', 'cb', 'cc', 'cd', 'caf']) + obj, theta, data_rec = pest.theta_est(return_values=["ca", "cb", "cc", "cd", "caf"]) print(obj) print(theta) parmest.graphics.grouped_boxplot( - data[['ca', 'cb', 'cc', 'cd']], - data_rec[['ca', 'cb', 'cc', 'cd']], - group_names=['Data', 'Data Rec'], + data[["ca", "cb", "cc", "cd"]], + data_rec[["ca", "cb", "cc", "cd"]], + group_names=["Data", "Data Rec"], ) ### Parameter estimation using reconciled data - theta_names = ['k1', 'k2', 'k3'] - data_rec['sv'] = data['sv'] + theta_names = ["k1", "k2", "k3"] + data_rec["sv"] = data["sv"] pest = parmest.Estimator(reactor_design_model, data_rec, theta_names, SSE) obj, theta = pest.theta_est() print(obj) print(theta) - theta_real = {'k1': 5.0 / 6.0, 'k2': 5.0 / 3.0, 'k3': 1.0 / 6000.0} + theta_real = {"k1": 5.0 / 6.0, "k2": 5.0 / 3.0, "k3": 1.0 / 6000.0} print(theta_real) diff --git a/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py b/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py index 1e14e1fb329..6952a7fc733 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py @@ -20,11 +20,11 @@ def main(): # Vars to estimate - theta_names = ['k1', 'k2', 'k3'] + theta_names = ["k1", "k2", "k3"] # Data file_dirname = dirname(abspath(str(__file__))) - file_name = abspath(join(file_dirname, 'reactor_data.csv')) + file_name = abspath(join(file_dirname, "reactor_data.csv")) data = pd.read_csv(file_name) # Create more data for the example @@ -37,10 +37,10 @@ def main(): # Sum of squared error function def SSE(model, data): expr = ( - (float(data.iloc[0]['ca']) - model.ca) ** 2 - + (float(data.iloc[0]['cb']) - model.cb) ** 2 - + (float(data.iloc[0]['cc']) - model.cc) ** 2 - + (float(data.iloc[0]['cd']) - model.cd) ** 2 + (float(data.iloc[0]["ca"]) - model.ca) ** 2 + + (float(data.iloc[0]["cb"]) - model.cb) ** 2 + + (float(data.iloc[0]["cc"]) - model.cc) ** 2 + + (float(data.iloc[0]["cd"]) - model.cd) ** 2 ) return expr @@ -68,7 +68,7 @@ def SSE(model, data): lNo = 25 lNo_samples = 5 bootstrap_samples = 20 - dist = 'MVN' + dist = "MVN" alphas = [0.7, 0.8, 0.9] results = pest.leaveNout_bootstrap_test( @@ -84,8 +84,8 @@ def SSE(model, data): bootstrap_results, theta_est_N, alpha, - ['MVN'], - title='Alpha: ' + str(alpha) + ', ' + str(theta_est_N.loc[0, alpha]), + ["MVN"], + title="Alpha: " + str(alpha) + ", " + str(theta_est_N.loc[0, alpha]), ) # Extract the percent of points that are within the alpha region diff --git a/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py b/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py index 5224097c13f..a0fe6f22305 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py @@ -21,20 +21,20 @@ def main(): # Vars to estimate - theta_names = ['k1', 'k2', 'k3'] + theta_names = ["k1", "k2", "k3"] # Data file_dirname = dirname(abspath(str(__file__))) - file_name = abspath(join(file_dirname, 'reactor_data.csv')) + file_name = abspath(join(file_dirname, "reactor_data.csv")) data = pd.read_csv(file_name) # Sum of squared error function def SSE(model, data): expr = ( - (float(data.iloc[0]['ca']) - model.ca) ** 2 - + (float(data.iloc[0]['cb']) - model.cb) ** 2 - + (float(data.iloc[0]['cc']) - model.cc) ** 2 - + (float(data.iloc[0]['cd']) - model.cd) ** 2 + (float(data.iloc[0]["ca"]) - model.ca) ** 2 + + (float(data.iloc[0]["cb"]) - model.cb) ** 2 + + (float(data.iloc[0]["cc"]) - model.cc) ** 2 + + (float(data.iloc[0]["cd"]) - model.cd) ** 2 ) return expr @@ -48,7 +48,7 @@ def SSE(model, data): k1 = [0.8, 0.85, 0.9] k2 = [1.6, 1.65, 1.7] k3 = [0.00016, 0.000165, 0.00017] - theta_vals = pd.DataFrame(list(product(k1, k2, k3)), columns=['k1', 'k2', 'k3']) + theta_vals = pd.DataFrame(list(product(k1, k2, k3)), columns=["k1", "k2", "k3"]) obj_at_theta = pest.objective_at_theta(theta_vals) # Run the likelihood ratio test @@ -56,7 +56,7 @@ def SSE(model, data): # Plot results parmest.graphics.pairwise_plot( - LR, theta, 0.9, title='LR results within 90% confidence region' + LR, theta, 0.9, title="LR results within 90% confidence region" ) diff --git a/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py b/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py index af7620b47b3..a92ac626fae 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py @@ -21,23 +21,23 @@ def main(): # Parameter estimation using multisensor data # Vars to estimate - theta_names = ['k1', 'k2', 'k3'] + theta_names = ["k1", "k2", "k3"] # Data, includes multiple sensors for ca and cc file_dirname = dirname(abspath(str(__file__))) - file_name = abspath(join(file_dirname, 'reactor_data_multisensor.csv')) + file_name = abspath(join(file_dirname, "reactor_data_multisensor.csv")) data = pd.read_csv(file_name) # Sum of squared error function def SSE_multisensor(model, data): expr = ( - ((float(data.iloc[0]['ca1']) - model.ca) ** 2) * (1 / 3) - + ((float(data.iloc[0]['ca2']) - model.ca) ** 2) * (1 / 3) - + ((float(data.iloc[0]['ca3']) - model.ca) ** 2) * (1 / 3) - + (float(data.iloc[0]['cb']) - model.cb) ** 2 - + ((float(data.iloc[0]['cc1']) - model.cc) ** 2) * (1 / 2) - + ((float(data.iloc[0]['cc2']) - model.cc) ** 2) * (1 / 2) - + (float(data.iloc[0]['cd']) - model.cd) ** 2 + ((float(data.iloc[0]["ca1"]) - model.ca) ** 2) * (1 / 3) + + ((float(data.iloc[0]["ca2"]) - model.ca) ** 2) * (1 / 3) + + ((float(data.iloc[0]["ca3"]) - model.ca) ** 2) * (1 / 3) + + (float(data.iloc[0]["cb"]) - model.cb) ** 2 + + ((float(data.iloc[0]["cc1"]) - model.cc) ** 2) * (1 / 2) + + ((float(data.iloc[0]["cc2"]) - model.cc) ** 2) * (1 / 2) + + (float(data.iloc[0]["cd"]) - model.cd) ** 2 ) return expr diff --git a/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py b/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py index 070c5934be5..581d3904c04 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py @@ -19,20 +19,20 @@ def main(): # Vars to estimate - theta_names = ['k1', 'k2', 'k3'] + theta_names = ["k1", "k2", "k3"] # Data file_dirname = dirname(abspath(str(__file__))) - file_name = abspath(join(file_dirname, 'reactor_data.csv')) + file_name = abspath(join(file_dirname, "reactor_data.csv")) data = pd.read_csv(file_name) # Sum of squared error function def SSE(model, data): expr = ( - (float(data.iloc[0]['ca']) - model.ca) ** 2 - + (float(data.iloc[0]['cb']) - model.cb) ** 2 - + (float(data.iloc[0]['cc']) - model.cc) ** 2 - + (float(data.iloc[0]['cd']) - model.cd) ** 2 + (float(data.iloc[0]["ca"]) - model.ca) ** 2 + + (float(data.iloc[0]["cb"]) - model.cb) ** 2 + + (float(data.iloc[0]["cc"]) - model.cc) ** 2 + + (float(data.iloc[0]["cd"]) - model.cd) ** 2 ) return expr @@ -46,11 +46,11 @@ def SSE(model, data): k1_expected = 5.0 / 6.0 k2_expected = 5.0 / 3.0 k3_expected = 1.0 / 6000.0 - relative_error = abs(theta['k1'] - k1_expected) / k1_expected + relative_error = abs(theta["k1"] - k1_expected) / k1_expected assert relative_error < 0.05 - relative_error = abs(theta['k2'] - k2_expected) / k2_expected + relative_error = abs(theta["k2"] - k2_expected) / k2_expected assert relative_error < 0.05 - relative_error = abs(theta['k3'] - k3_expected) / k3_expected + relative_error = abs(theta["k3"] - k3_expected) / k3_expected assert relative_error < 0.05 diff --git a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py index 80df6fb3c12..16f65e236eb 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py +++ b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py @@ -38,20 +38,20 @@ def reactor_design_model(data): # Inlet concentration of A, gmol/m^3 if isinstance(data, dict) or isinstance(data, pd.Series): - model.caf = Param(initialize=float(data['caf']), within=PositiveReals) + model.caf = Param(initialize=float(data["caf"]), within=PositiveReals) elif isinstance(data, pd.DataFrame): - model.caf = Param(initialize=float(data.iloc[0]['caf']), within=PositiveReals) + model.caf = Param(initialize=float(data.iloc[0]["caf"]), within=PositiveReals) else: - raise ValueError('Unrecognized data type.') + raise ValueError("Unrecognized data type.") # Space velocity (flowrate/volume) if isinstance(data, dict) or isinstance(data, pd.Series): - model.sv = Param(initialize=float(data['sv']), within=PositiveReals) + model.sv = Param(initialize=float(data["sv"]), within=PositiveReals) elif isinstance(data, pd.DataFrame): - model.sv = Param(initialize=float(data.iloc[0]['sv']), within=PositiveReals) + model.sv = Param(initialize=float(data.iloc[0]["sv"]), within=PositiveReals) else: - raise ValueError('Unrecognized data type.') - + raise ValueError("Unrecognized data type.") + # Outlet concentration of each component model.ca = Var(initialize=5000.0, within=PositiveReals) model.cb = Var(initialize=2000.0, within=PositiveReals) @@ -91,12 +91,12 @@ def main(): sv_values = [1.0 + v * 0.05 for v in range(1, 20)] caf = 10000 for sv in sv_values: - model = reactor_design_model(pd.DataFrame(data={'caf': [caf], 'sv': [sv]})) - solver = SolverFactory('ipopt') + model = reactor_design_model(pd.DataFrame(data={"caf": [caf], "sv": [sv]})) + solver = SolverFactory("ipopt") solver.solve(model) results.append([sv, caf, model.ca(), model.cb(), model.cc(), model.cd()]) - results = pd.DataFrame(results, columns=['sv', 'caf', 'ca', 'cb', 'cc', 'cd']) + results = pd.DataFrame(results, columns=["sv", "caf", "ca", "cb", "cc", "cd"]) print(results) diff --git a/pyomo/contrib/parmest/examples/semibatch/semibatch.py b/pyomo/contrib/parmest/examples/semibatch/semibatch.py index b3da21ed993..6762531a338 100644 --- a/pyomo/contrib/parmest/examples/semibatch/semibatch.py +++ b/pyomo/contrib/parmest/examples/semibatch/semibatch.py @@ -34,21 +34,20 @@ def generate_model(data): - # if data is a file name, then load file first if isinstance(data, str): file_name = data try: - with open(file_name, 'r') as infile: + with open(file_name, "r") as infile: data = json.load(infile) except: - raise RuntimeError(f'Could not read {file_name} as json') + raise RuntimeError(f"Could not read {file_name} as json") # unpack and fix the data - cameastemp = data['Ca_meas'] - cbmeastemp = data['Cb_meas'] - ccmeastemp = data['Cc_meas'] - trmeastemp = data['Tr_meas'] + cameastemp = data["Ca_meas"] + cbmeastemp = data["Cb_meas"] + ccmeastemp = data["Cc_meas"] + trmeastemp = data["Tr_meas"] cameas = {} cbmeas = {} @@ -89,9 +88,9 @@ def generate_model(data): m.Vc = Param(initialize=0.07) # m^3 m.rhow = Param(initialize=700.0) # kg/m^3 m.cpw = Param(initialize=3.1) # kJ/kg/K - m.Ca0 = Param(initialize=data['Ca0']) # kmol/m^3) - m.Cb0 = Param(initialize=data['Cb0']) # kmol/m^3) - m.Cc0 = Param(initialize=data['Cc0']) # kmol/m^3) + m.Ca0 = Param(initialize=data["Ca0"]) # kmol/m^3) + m.Cb0 = Param(initialize=data["Cb0"]) # kmol/m^3) + m.Cc0 = Param(initialize=data["Cc0"]) # kmol/m^3) m.Tr0 = Param(initialize=300.0) # K m.Vr0 = Param(initialize=1.0) # m^3 @@ -102,9 +101,9 @@ def generate_model(data): # def _initTc(m, t): if t < 10800: - return data['Tc1'] + return data["Tc1"] else: - return data['Tc2'] + return data["Tc2"] m.Tc = Param( m.time, initialize=_initTc, default=_initTc @@ -112,9 +111,9 @@ def _initTc(m, t): def _initFa(m, t): if t < 10800: - return data['Fa1'] + return data["Fa1"] else: - return data['Fa2'] + return data["Fa2"] m.Fa = Param( m.time, initialize=_initFa, default=_initFa @@ -240,7 +239,7 @@ def AllMeasurements(m): ) def MissingMeasurements(m): - if data['experiment'] == 1: + if data["experiment"] == 1: return sum( (m.Ca[t] - m.Ca_meas[t]) ** 2 + (m.Cb[t] - m.Cb_meas[t]) ** 2 @@ -248,7 +247,7 @@ def MissingMeasurements(m): + (m.Tr[t] - m.Tr_meas[t]) ** 2 for t in m.measT ) - elif data['experiment'] == 2: + elif data["experiment"] == 2: return sum((m.Tr[t] - m.Tr_meas[t]) ** 2 for t in m.measT) else: return sum( @@ -264,7 +263,7 @@ def total_cost_rule(model): m.Total_Cost_Objective = Objective(rule=total_cost_rule, sense=minimize) # Discretize model - disc = TransformationFactory('dae.collocation') + disc = TransformationFactory("dae.collocation") disc.apply_to(m, nfe=20, ncp=4) return m @@ -272,17 +271,17 @@ def total_cost_rule(model): def main(): # Data loaded from files file_dirname = dirname(abspath(str(__file__))) - file_name = abspath(join(file_dirname, 'exp2.out')) - with open(file_name, 'r') as infile: + file_name = abspath(join(file_dirname, "exp2.out")) + with open(file_name, "r") as infile: data = json.load(infile) - data['experiment'] = 2 + data["experiment"] = 2 model = generate_model(data) - solver = SolverFactory('ipopt') + solver = SolverFactory("ipopt") solver.solve(model) - print('k1 = ', model.k1()) - print('E1 = ', model.E1()) + print("k1 = ", model.k1()) + print("E1 = ", model.E1()) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/pyomo/contrib/parmest/graphics.py b/pyomo/contrib/parmest/graphics.py index 99eda8aad7a..b8dfa243b9a 100644 --- a/pyomo/contrib/parmest/graphics.py +++ b/pyomo/contrib/parmest/graphics.py @@ -29,7 +29,7 @@ # (e.g. python 3.5) get released that are either broken not # compatible, resulting in a SyntaxError sns, seaborn_available = attempt_import( - 'seaborn', catch_exceptions=(ImportError, SyntaxError) + "seaborn", catch_exceptions=(ImportError, SyntaxError) ) imports_available = ( @@ -93,17 +93,17 @@ def _get_data_slice(xvar, yvar, columns, data, theta_star): temp[col] = temp[col] + data[col].std() data = pd.concat([data, temp], ignore_index=True) - data_slice['obj'] = scipy.interpolate.griddata( + data_slice["obj"] = scipy.interpolate.griddata( np.array(data[columns]), - np.array(data[['obj']]), + np.array(data[["obj"]]), np.array(data_slice[columns]), - method='linear', + method="linear", rescale=True, ) X = data_slice[xvar] Y = data_slice[yvar] - Z = data_slice['obj'] + Z = data_slice["obj"] return X, Y, Z @@ -178,11 +178,11 @@ def _add_obj_contour(x, y, color, columns, data, theta_star, label=None): X, Y, Z = _get_data_slice(xvar, yvar, columns, data, theta_star) triang = matplotlib.tri.Triangulation(X, Y) - cmap = matplotlib.colormaps['Greys'] + cmap = matplotlib.colormaps["Greys"] plt.tricontourf(triang, Z, cmap=cmap) except: - print('Objective contour plot for', xvar, yvar, 'slice failed') + print("Objective contour plot for", xvar, yvar, "slice failed") def _set_axis_limits(g, axis_limits, theta_vals, theta_star): @@ -277,7 +277,7 @@ def pairwise_plot( assert isinstance(theta_star, (type(None), dict, pd.Series, pd.DataFrame)) assert isinstance(alpha, (type(None), int, float)) assert isinstance(distributions, list) - assert set(distributions).issubset(set(['MVN', 'KDE', 'Rect'])) + assert set(distributions).issubset(set(["MVN", "KDE", "Rect"])) assert isinstance(axis_limits, (type(None), dict)) assert isinstance(title, (type(None), str)) assert isinstance(add_obj_contour, bool) @@ -307,7 +307,7 @@ def pairwise_plot( theta_names = [ col for col in theta_values.columns - if (col not in ['obj']) + if (col not in ["obj"]) and (not isinstance(col, float)) and (not isinstance(col, int)) ] @@ -335,7 +335,7 @@ def pairwise_plot( g.map_diag(sns.distplot, kde=False, hist=True, norm_hist=False) # Plot filled contours using all theta values based on obj - if 'obj' in theta_values.columns and add_obj_contour: + if "obj" in theta_values.columns and add_obj_contour: g.map_offdiag( _add_obj_contour, columns=theta_names, @@ -349,10 +349,10 @@ def pairwise_plot( matplotlib.lines.Line2D( [0], [0], - marker='o', - color='w', - label='thetas', - markerfacecolor='cadetblue', + marker="o", + color="w", + label="thetas", + markerfacecolor="cadetblue", markersize=5, ) ) @@ -360,23 +360,23 @@ def pairwise_plot( # Plot theta* if theta_star is not None: g.map_offdiag( - _add_scatter, color='k', columns=theta_names, theta_star=theta_star + _add_scatter, color="k", columns=theta_names, theta_star=theta_star ) legend_elements.append( matplotlib.lines.Line2D( [0], [0], - marker='o', - color='w', - label='theta*', - markerfacecolor='k', + marker="o", + color="w", + label="theta*", + markerfacecolor="k", markersize=6, ) ) # Plot confidence regions - colors = ['r', 'mediumblue', 'darkgray'] + colors = ["r", "mediumblue", "darkgray"] if (alpha is not None) and (len(distributions) > 0): if theta_star is None: print( @@ -388,7 +388,7 @@ def pairwise_plot( mvn_dist = None kde_dist = None for i, dist in enumerate(distributions): - if dist == 'Rect': + if dist == "Rect": lb, ub = fit_rect_dist(thetas, alpha) g.map_offdiag( _add_rectangle_CI, @@ -401,7 +401,7 @@ def pairwise_plot( matplotlib.lines.Line2D([0], [0], color=colors[i], lw=1, label=dist) ) - elif dist == 'MVN': + elif dist == "MVN": mvn_dist = fit_mvn_dist(thetas) Z = mvn_dist.pdf(thetas) score = stats.scoreatpercentile(Z, (1 - alpha) * 100) @@ -418,7 +418,7 @@ def pairwise_plot( matplotlib.lines.Line2D([0], [0], color=colors[i], lw=1, label=dist) ) - elif dist == 'KDE': + elif dist == "KDE": kde_dist = fit_kde_dist(thetas) Z = kde_dist.pdf(thetas.transpose()) score = stats.scoreatpercentile(Z, (1 - alpha) * 100) @@ -438,12 +438,12 @@ def pairwise_plot( _set_axis_limits(g, axis_limits, thetas, theta_star) for ax in g.axes.flatten(): - ax.ticklabel_format(style='sci', scilimits=(-2, 2), axis='both') + ax.ticklabel_format(style="sci", scilimits=(-2, 2), axis="both") if add_legend: xvar, yvar, loc = _get_variables(ax, theta_names) if loc == (len(theta_names) - 1, 0): - ax.legend(handles=legend_elements, loc='best', prop={'size': 8}) + ax.legend(handles=legend_elements, loc="best", prop={"size": 8}) if title: g.fig.subplots_adjust(top=0.9) g.fig.suptitle(title) @@ -474,7 +474,7 @@ def pairwise_plot( ax.tick_params(reset=True) if add_legend: - ax.legend(handles=legend_elements, loc='best', prop={'size': 8}) + ax.legend(handles=legend_elements, loc="best", prop={"size": 8}) plt.close(g.fig) @@ -563,15 +563,15 @@ def _get_grouped_data(data1, data2, normalize, group_names): # Combine data1 and data2 to create a grouped histogram data = pd.concat({group_names[0]: data1, group_names[1]: data2}) data.reset_index(level=0, inplace=True) - data.rename(columns={'level_0': 'set'}, inplace=True) + data.rename(columns={"level_0": "set"}, inplace=True) - data = data.melt(id_vars='set', value_vars=data1.columns, var_name='columns') + data = data.melt(id_vars="set", value_vars=data1.columns, var_name="columns") return data def grouped_boxplot( - data1, data2, normalize=False, group_names=['data1', 'data2'], filename=None + data1, data2, normalize=False, group_names=["data1", "data2"], filename=None ): """ Plot a grouped boxplot to compare two datasets @@ -600,11 +600,11 @@ def grouped_boxplot( data = _get_grouped_data(data1, data2, normalize, group_names) plt.figure() - sns.boxplot(data=data, hue='set', y='value', x='columns', order=data1.columns) + sns.boxplot(data=data, hue="set", y="value", x="columns", order=data1.columns) - plt.gca().legend().set_title('') - plt.gca().set_xlabel('') - plt.gca().set_ylabel('') + plt.gca().legend().set_title("") + plt.gca().set_xlabel("") + plt.gca().set_ylabel("") if filename is None: plt.show() @@ -614,7 +614,7 @@ def grouped_boxplot( def grouped_violinplot( - data1, data2, normalize=False, group_names=['data1', 'data2'], filename=None + data1, data2, normalize=False, group_names=["data1", "data2"], filename=None ): """ Plot a grouped violinplot to compare two datasets @@ -644,12 +644,12 @@ def grouped_violinplot( plt.figure() sns.violinplot( - data=data, hue='set', y='value', x='columns', order=data1.columns, split=True + data=data, hue="set", y="value", x="columns", order=data1.columns, split=True ) - plt.gca().legend().set_title('') - plt.gca().set_xlabel('') - plt.gca().set_ylabel('') + plt.gca().legend().set_title("") + plt.gca().set_xlabel("") + plt.gca().set_ylabel("") if filename is None: plt.show() diff --git a/pyomo/contrib/parmest/tests/test_parmest.py b/pyomo/contrib/parmest/tests/test_parmest.py index b09c48e9709..2cc8ad36b0a 100644 --- a/pyomo/contrib/parmest/tests/test_parmest.py +++ b/pyomo/contrib/parmest/tests/test_parmest.py @@ -22,7 +22,7 @@ import platform -is_osx = platform.mac_ver()[0] != '' +is_osx = platform.mac_ver()[0] != "" import pyomo.common.unittest as unittest import sys @@ -38,11 +38,11 @@ from pyomo.opt import SolverFactory -ipopt_available = SolverFactory('ipopt').available() +ipopt_available = SolverFactory("ipopt").available() from pyomo.common.fileutils import find_library -pynumero_ASL_available = False if find_library('pynumero_ASL') is None else True +pynumero_ASL_available = False if find_library("pynumero_ASL") is None else True testdir = os.path.dirname(os.path.abspath(__file__)) @@ -61,10 +61,10 @@ def setUp(self): # Note, the data used in this test has been corrected to use data.loc[5,'hour'] = 7 (instead of 6) data = pd.DataFrame( data=[[1, 8.3], [2, 10.3], [3, 19.0], [4, 16.0], [5, 15.6], [7, 19.8]], - columns=['hour', 'y'], + columns=["hour", "y"], ) - theta_names = ['asymptote', 'rate_constant'] + theta_names = ["asymptote", "rate_constant"] def SSE(model, data): expr = sum( @@ -73,7 +73,7 @@ def SSE(model, data): ) return expr - solver_options = {'tol': 1e-8} + solver_options = {"tol": 1e-8} self.data = data self.pest = parmest.Estimator( @@ -90,10 +90,10 @@ def test_theta_est(self): self.assertAlmostEqual(objval, 4.3317112, places=2) self.assertAlmostEqual( - thetavals['asymptote'], 19.1426, places=2 + thetavals["asymptote"], 19.1426, places=2 ) # 19.1426 from the paper self.assertAlmostEqual( - thetavals['rate_constant'], 0.5311, places=2 + thetavals["rate_constant"], 0.5311, places=2 ) # 0.5311 from the paper @unittest.skipIf( @@ -105,14 +105,14 @@ def test_bootstrap(self): num_bootstraps = 10 theta_est = self.pest.theta_est_bootstrap(num_bootstraps, return_samples=True) - num_samples = theta_est['samples'].apply(len) + num_samples = theta_est["samples"].apply(len) self.assertTrue(len(theta_est.index), 10) self.assertTrue(num_samples.equals(pd.Series([6] * 10))) - del theta_est['samples'] + del theta_est["samples"] # apply confidence region test - CR = self.pest.confidence_region_test(theta_est, 'MVN', [0.5, 0.75, 1.0]) + CR = self.pest.confidence_region_test(theta_est, "MVN", [0.5, 0.75, 1.0]) self.assertTrue(set(CR.columns) >= set([0.5, 0.75, 1.0])) self.assertTrue(CR[0.5].sum() == 5) @@ -121,7 +121,7 @@ def test_bootstrap(self): graphics.pairwise_plot(theta_est) graphics.pairwise_plot(theta_est, thetavals) - graphics.pairwise_plot(theta_est, thetavals, 0.8, ['MVN', 'KDE', 'Rect']) + graphics.pairwise_plot(theta_est, thetavals, 0.8, ["MVN", "KDE", "Rect"]) @unittest.skipIf( not graphics.imports_available, "parmest.graphics imports are unavailable" @@ -151,7 +151,7 @@ def test_leaveNout(self): self.assertTrue(lNo_theta.shape == (6, 2)) results = self.pest.leaveNout_bootstrap_test( - 1, None, 3, 'Rect', [0.5, 1.0], seed=5436 + 1, None, 3, "Rect", [0.5, 1.0], seed=5436 ) self.assertTrue(len(results) == 6) # 6 lNo samples i = 1 @@ -221,10 +221,10 @@ def test_theta_est_cov(self): self.assertAlmostEqual(objval, 4.3317112, places=2) self.assertAlmostEqual( - thetavals['asymptote'], 19.1426, places=2 + thetavals["asymptote"], 19.1426, places=2 ) # 19.1426 from the paper self.assertAlmostEqual( - thetavals['rate_constant'], 0.5311, places=2 + thetavals["rate_constant"], 0.5311, places=2 ) # 0.5311 from the paper # Covariance matrix @@ -239,22 +239,22 @@ def test_theta_est_cov(self): ) # -0.4322 from paper self.assertAlmostEqual(cov.iloc[1, 1], 0.04124, places=2) # 0.04124 from paper - ''' Why does the covariance matrix from parmest not match the paper? Parmest is + """ Why does the covariance matrix from parmest not match the paper? Parmest is calculating the exact reduced Hessian. The paper (Rooney and Bielger, 2001) likely employed the first order approximation common for nonlinear regression. The paper values were verified with Scipy, which uses the same first order approximation. The formula used in parmest was verified against equations (7-5-15) and (7-5-16) in "Nonlinear Parameter Estimation", Y. Bard, 1974. - ''' + """ def test_cov_scipy_least_squares_comparison(self): - ''' + """ Scipy results differ in the 3rd decimal place from the paper. It is possible the paper used an alternative finite difference approximation for the Jacobian. - ''' + """ def model(theta, t): - ''' + """ Model to be fitted y = model(theta, t) Arguments: theta: vector of fitted parameters @@ -262,32 +262,32 @@ def model(theta, t): Returns: y: model predictions [need to check paper for units] - ''' + """ asymptote = theta[0] rate_constant = theta[1] return asymptote * (1 - np.exp(-rate_constant * t)) def residual(theta, t, y): - ''' + """ Calculate residuals Arguments: theta: vector of fitted parameters t: independent variable [hours] y: dependent variable [?] - ''' + """ return y - model(theta, t) # define data - t = self.data['hour'].to_numpy() - y = self.data['y'].to_numpy() + t = self.data["hour"].to_numpy() + y = self.data["y"].to_numpy() # define initial guess theta_guess = np.array([15, 0.5]) ## solve with optimize.least_squares sol = scipy.optimize.least_squares( - residual, theta_guess, method='trf', args=(t, y), verbose=2 + residual, theta_guess, method="trf", args=(t, y), verbose=2 ) theta_hat = sol.x @@ -313,18 +313,18 @@ def residual(theta, t, y): self.assertAlmostEqual(cov[1, 1], 0.04124, places=2) # 0.04124 from paper def test_cov_scipy_curve_fit_comparison(self): - ''' + """ Scipy results differ in the 3rd decimal place from the paper. It is possible the paper used an alternative finite difference approximation for the Jacobian. - ''' + """ ## solve with optimize.curve_fit def model(t, asymptote, rate_constant): return asymptote * (1 - np.exp(-rate_constant * t)) # define data - t = self.data['hour'].to_numpy() - y = self.data['y'].to_numpy() + t = self.data["hour"].to_numpy() + y = self.data["y"].to_numpy() # define initial guess theta_guess = np.array([15, 0.5]) @@ -351,7 +351,7 @@ class TestModelVariants(unittest.TestCase): def setUp(self): self.data = pd.DataFrame( data=[[1, 8.3], [2, 10.3], [3, 19.0], [4, 16.0], [5, 15.6], [7, 19.8]], - columns=['hour', 'y'], + columns=["hour", "y"], ) def rooney_biegler_params(data): @@ -371,16 +371,16 @@ def response_rule(m, h): def rooney_biegler_indexed_params(data): model = pyo.ConcreteModel() - model.param_names = pyo.Set(initialize=['asymptote', 'rate_constant']) + model.param_names = pyo.Set(initialize=["asymptote", "rate_constant"]) model.theta = pyo.Param( model.param_names, - initialize={'asymptote': 15, 'rate_constant': 0.5}, + initialize={"asymptote": 15, "rate_constant": 0.5}, mutable=True, ) def response_rule(m, h): - expr = m.theta['asymptote'] * ( - 1 - pyo.exp(-m.theta['rate_constant'] * h) + expr = m.theta["asymptote"] * ( + 1 - pyo.exp(-m.theta["rate_constant"] * h) ) return expr @@ -407,20 +407,20 @@ def response_rule(m, h): def rooney_biegler_indexed_vars(data): model = pyo.ConcreteModel() - model.var_names = pyo.Set(initialize=['asymptote', 'rate_constant']) + model.var_names = pyo.Set(initialize=["asymptote", "rate_constant"]) model.theta = pyo.Var( - model.var_names, initialize={'asymptote': 15, 'rate_constant': 0.5} + model.var_names, initialize={"asymptote": 15, "rate_constant": 0.5} ) model.theta[ - 'asymptote' + "asymptote" ].fixed = ( True # parmest will unfix theta variables, even when they are indexed ) - model.theta['rate_constant'].fixed = True + model.theta["rate_constant"].fixed = True def response_rule(m, h): - expr = m.theta['asymptote'] * ( - 1 - pyo.exp(-m.theta['rate_constant'] * h) + expr = m.theta["asymptote"] * ( + 1 - pyo.exp(-m.theta["rate_constant"] * h) ) return expr @@ -437,41 +437,41 @@ def SSE(model, data): self.objective_function = SSE - theta_vals = pd.DataFrame([20, 1], index=['asymptote', 'rate_constant']).T + theta_vals = pd.DataFrame([20, 1], index=["asymptote", "rate_constant"]).T theta_vals_index = pd.DataFrame( [20, 1], index=["theta['asymptote']", "theta['rate_constant']"] ).T self.input = { - 'param': { - 'model': rooney_biegler_params, - 'theta_names': ['asymptote', 'rate_constant'], - 'theta_vals': theta_vals, + "param": { + "model": rooney_biegler_params, + "theta_names": ["asymptote", "rate_constant"], + "theta_vals": theta_vals, }, - 'param_index': { - 'model': rooney_biegler_indexed_params, - 'theta_names': ['theta'], - 'theta_vals': theta_vals_index, + "param_index": { + "model": rooney_biegler_indexed_params, + "theta_names": ["theta"], + "theta_vals": theta_vals_index, }, - 'vars': { - 'model': rooney_biegler_vars, - 'theta_names': ['asymptote', 'rate_constant'], - 'theta_vals': theta_vals, + "vars": { + "model": rooney_biegler_vars, + "theta_names": ["asymptote", "rate_constant"], + "theta_vals": theta_vals, }, - 'vars_index': { - 'model': rooney_biegler_indexed_vars, - 'theta_names': ['theta'], - 'theta_vals': theta_vals_index, + "vars_index": { + "model": rooney_biegler_indexed_vars, + "theta_names": ["theta"], + "theta_vals": theta_vals_index, }, - 'vars_quoted_index': { - 'model': rooney_biegler_indexed_vars, - 'theta_names': ["theta['asymptote']", "theta['rate_constant']"], - 'theta_vals': theta_vals_index, + "vars_quoted_index": { + "model": rooney_biegler_indexed_vars, + "theta_names": ["theta['asymptote']", "theta['rate_constant']"], + "theta_vals": theta_vals_index, }, - 'vars_str_index': { - 'model': rooney_biegler_indexed_vars, - 'theta_names': ["theta[asymptote]", "theta[rate_constant]"], - 'theta_vals': theta_vals_index, + "vars_str_index": { + "model": rooney_biegler_indexed_vars, + "theta_names": ["theta[asymptote]", "theta[rate_constant]"], + "theta_vals": theta_vals_index, }, } @@ -483,9 +483,9 @@ def SSE(model, data): def test_parmest_basics(self): for model_type, parmest_input in self.input.items(): pest = parmest.Estimator( - parmest_input['model'], + parmest_input["model"], self.data, - parmest_input['theta_names'], + parmest_input["theta_names"], self.objective_function, ) @@ -505,15 +505,15 @@ def test_parmest_basics(self): cov.iloc[1, 1], 0.04193591, places=2 ) # 0.04124 from paper - obj_at_theta = pest.objective_at_theta(parmest_input['theta_vals']) - self.assertAlmostEqual(obj_at_theta['obj'][0], 16.531953, places=2) + obj_at_theta = pest.objective_at_theta(parmest_input["theta_vals"]) + self.assertAlmostEqual(obj_at_theta["obj"][0], 16.531953, places=2) def test_parmest_basics_with_initialize_parmest_model_option(self): for model_type, parmest_input in self.input.items(): pest = parmest.Estimator( - parmest_input['model'], + parmest_input["model"], self.data, - parmest_input['theta_names'], + parmest_input["theta_names"], self.objective_function, ) @@ -534,22 +534,22 @@ def test_parmest_basics_with_initialize_parmest_model_option(self): ) # 0.04124 from paper obj_at_theta = pest.objective_at_theta( - parmest_input['theta_vals'], initialize_parmest_model=True + parmest_input["theta_vals"], initialize_parmest_model=True ) - self.assertAlmostEqual(obj_at_theta['obj'][0], 16.531953, places=2) + self.assertAlmostEqual(obj_at_theta["obj"][0], 16.531953, places=2) def test_parmest_basics_with_square_problem_solve(self): for model_type, parmest_input in self.input.items(): pest = parmest.Estimator( - parmest_input['model'], + parmest_input["model"], self.data, - parmest_input['theta_names'], + parmest_input["theta_names"], self.objective_function, ) obj_at_theta = pest.objective_at_theta( - parmest_input['theta_vals'], initialize_parmest_model=True + parmest_input["theta_vals"], initialize_parmest_model=True ) objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) @@ -568,14 +568,14 @@ def test_parmest_basics_with_square_problem_solve(self): cov.iloc[1, 1], 0.04193591, places=2 ) # 0.04124 from paper - self.assertAlmostEqual(obj_at_theta['obj'][0], 16.531953, places=2) + self.assertAlmostEqual(obj_at_theta["obj"][0], 16.531953, places=2) def test_parmest_basics_with_square_problem_solve_no_theta_vals(self): for model_type, parmest_input in self.input.items(): pest = parmest.Estimator( - parmest_input['model'], + parmest_input["model"], self.data, - parmest_input['theta_names'], + parmest_input["theta_names"], self.objective_function, ) @@ -632,17 +632,17 @@ def setUp(self): [1.90, 10000, 4491.3, 1049.4, 920.5, 1769.4], [1.95, 10000, 4538.8, 1045.8, 893.9, 1760.8], ], - columns=['sv', 'caf', 'ca', 'cb', 'cc', 'cd'], + columns=["sv", "caf", "ca", "cb", "cc", "cd"], ) - theta_names = ['k1', 'k2', 'k3'] + theta_names = ["k1", "k2", "k3"] def SSE(model, data): expr = ( - (float(data.iloc[0]['ca']) - model.ca) ** 2 - + (float(data.iloc[0]['cb']) - model.cb) ** 2 - + (float(data.iloc[0]['cc']) - model.cc) ** 2 - + (float(data.iloc[0]['cd']) - model.cd) ** 2 + (float(data.iloc[0]["ca"]) - model.ca) ** 2 + + (float(data.iloc[0]["cb"]) - model.cb) ** 2 + + (float(data.iloc[0]["cc"]) - model.cc) ** 2 + + (float(data.iloc[0]["cd"]) - model.cd) ** 2 ) return expr @@ -656,13 +656,13 @@ def test_theta_est(self): # used in data reconciliation objval, thetavals = self.pest.theta_est() - self.assertAlmostEqual(thetavals['k1'], 5.0 / 6.0, places=4) - self.assertAlmostEqual(thetavals['k2'], 5.0 / 3.0, places=4) - self.assertAlmostEqual(thetavals['k3'], 1.0 / 6000.0, places=7) + self.assertAlmostEqual(thetavals["k1"], 5.0 / 6.0, places=4) + self.assertAlmostEqual(thetavals["k2"], 5.0 / 3.0, places=4) + self.assertAlmostEqual(thetavals["k3"], 1.0 / 6000.0, places=7) def test_return_values(self): objval, thetavals, data_rec = self.pest.theta_est( - return_values=['ca', 'cb', 'cc', 'cd', 'caf'] + return_values=["ca", "cb", "cc", "cd", "caf"] ) self.assertAlmostEqual(data_rec["cc"].loc[18], 893.84924, places=3) @@ -679,9 +679,9 @@ class TestReactorDesign_DAE(unittest.TestCase): def setUp(self): def ABC_model(data): - ca_meas = data['ca'] - cb_meas = data['cb'] - cc_meas = data['cc'] + ca_meas = data["ca"] + cb_meas = data["cb"] + cc_meas = data["cc"] if isinstance(data, pd.DataFrame): meas_t = data.index # time index @@ -754,7 +754,7 @@ def total_cost_rule(model): rule=total_cost_rule, sense=pyo.minimize ) - disc = pyo.TransformationFactory('dae.collocation') + disc = pyo.TransformationFactory("dae.collocation") disc.apply_to(m, nfe=20, ncp=2) return m @@ -785,15 +785,15 @@ def total_cost_rule(model): [4.737, 0.004, 0.036, 0.971], [5.000, -0.024, 0.028, 0.985], ] - data = pd.DataFrame(data, columns=['t', 'ca', 'cb', 'cc']) - data_df = data.set_index('t') + data = pd.DataFrame(data, columns=["t", "ca", "cb", "cc"]) + data_df = data.set_index("t") data_dict = { - 'ca': {k: v for (k, v) in zip(data.t, data.ca)}, - 'cb': {k: v for (k, v) in zip(data.t, data.cb)}, - 'cc': {k: v for (k, v) in zip(data.t, data.cc)}, + "ca": {k: v for (k, v) in zip(data.t, data.ca)}, + "cb": {k: v for (k, v) in zip(data.t, data.cb)}, + "cc": {k: v for (k, v) in zip(data.t, data.cc)}, } - theta_names = ['k1', 'k2'] + theta_names = ["k1", "k2"] self.pest_df = parmest.Estimator(ABC_model, [data_df], theta_names) self.pest_dict = parmest.Estimator(ABC_model, [data_dict], theta_names) @@ -815,30 +815,30 @@ def test_dataformats(self): obj2, theta2 = self.pest_dict.theta_est() self.assertAlmostEqual(obj1, obj2, places=6) - self.assertAlmostEqual(theta1['k1'], theta2['k1'], places=6) - self.assertAlmostEqual(theta1['k2'], theta2['k2'], places=6) + self.assertAlmostEqual(theta1["k1"], theta2["k1"], places=6) + self.assertAlmostEqual(theta1["k2"], theta2["k2"], places=6) def test_return_continuous_set(self): - ''' + """ test if ContinuousSet elements are returned correctly from theta_est() - ''' - obj1, theta1, return_vals1 = self.pest_df.theta_est(return_values=['time']) - obj2, theta2, return_vals2 = self.pest_dict.theta_est(return_values=['time']) - self.assertAlmostEqual(return_vals1['time'].loc[0][18], 2.368, places=3) - self.assertAlmostEqual(return_vals2['time'].loc[0][18], 2.368, places=3) + """ + obj1, theta1, return_vals1 = self.pest_df.theta_est(return_values=["time"]) + obj2, theta2, return_vals2 = self.pest_dict.theta_est(return_values=["time"]) + self.assertAlmostEqual(return_vals1["time"].loc[0][18], 2.368, places=3) + self.assertAlmostEqual(return_vals2["time"].loc[0][18], 2.368, places=3) def test_return_continuous_set_multiple_datasets(self): - ''' + """ test if ContinuousSet elements are returned correctly from theta_est() - ''' + """ obj1, theta1, return_vals1 = self.pest_df_multiple.theta_est( - return_values=['time'] + return_values=["time"] ) obj2, theta2, return_vals2 = self.pest_dict_multiple.theta_est( - return_values=['time'] + return_values=["time"] ) - self.assertAlmostEqual(return_vals1['time'].loc[1][18], 2.368, places=3) - self.assertAlmostEqual(return_vals2['time'].loc[1][18], 2.368, places=3) + self.assertAlmostEqual(return_vals1["time"].loc[1][18], 2.368, places=3) + self.assertAlmostEqual(return_vals2["time"].loc[1][18], 2.368, places=3) def test_covariance(self): from pyomo.contrib.interior_point.inverse_reduced_hessian import ( @@ -862,13 +862,13 @@ def test_covariance(self): l = len(vars_list) cov_interior_point = 2 * obj / (n - l) * inv_red_hes cov_interior_point = pd.DataFrame( - cov_interior_point, ['k1', 'k2'], ['k1', 'k2'] + cov_interior_point, ["k1", "k2"], ["k1", "k2"] ) cov_diff = (cov - cov_interior_point).abs().sum().sum() - self.assertTrue(cov.loc['k1', 'k1'] > 0) - self.assertTrue(cov.loc['k2', 'k2'] > 0) + self.assertTrue(cov.loc["k1", "k1"] > 0) + self.assertTrue(cov.loc["k2", "k2"] > 0) self.assertAlmostEqual(cov_diff, 0, places=6) @@ -886,10 +886,10 @@ def setUp(self): # Note, the data used in this test has been corrected to use data.loc[5,'hour'] = 7 (instead of 6) data = pd.DataFrame( data=[[1, 8.3], [2, 10.3], [3, 19.0], [4, 16.0], [5, 15.6], [7, 19.8]], - columns=['hour', 'y'], + columns=["hour", "y"], ) - theta_names = ['asymptote', 'rate_constant'] + theta_names = ["asymptote", "rate_constant"] def SSE(model, data): expr = sum( @@ -898,7 +898,7 @@ def SSE(model, data): ) return expr - solver_options = {'tol': 1e-8} + solver_options = {"tol": 1e-8} self.data = data self.pest = parmest.Estimator( @@ -916,15 +916,15 @@ def test_theta_est_with_square_initialization(self): self.assertAlmostEqual(objval, 4.3317112, places=2) self.assertAlmostEqual( - thetavals['asymptote'], 19.1426, places=2 + thetavals["asymptote"], 19.1426, places=2 ) # 19.1426 from the paper self.assertAlmostEqual( - thetavals['rate_constant'], 0.5311, places=2 + thetavals["rate_constant"], 0.5311, places=2 ) # 0.5311 from the paper def test_theta_est_with_square_initialization_and_custom_init_theta(self): theta_vals_init = pd.DataFrame( - data=[[19.0, 0.5]], columns=['asymptote', 'rate_constant'] + data=[[19.0, 0.5]], columns=["asymptote", "rate_constant"] ) obj_init = self.pest.objective_at_theta( theta_values=theta_vals_init, initialize_parmest_model=True @@ -932,10 +932,10 @@ def test_theta_est_with_square_initialization_and_custom_init_theta(self): objval, thetavals = self.pest.theta_est() self.assertAlmostEqual(objval, 4.3317112, places=2) self.assertAlmostEqual( - thetavals['asymptote'], 19.1426, places=2 + thetavals["asymptote"], 19.1426, places=2 ) # 19.1426 from the paper self.assertAlmostEqual( - thetavals['rate_constant'], 0.5311, places=2 + thetavals["rate_constant"], 0.5311, places=2 ) # 0.5311 from the paper def test_theta_est_with_square_initialization_diagnostic_mode_true(self): @@ -945,14 +945,14 @@ def test_theta_est_with_square_initialization_diagnostic_mode_true(self): self.assertAlmostEqual(objval, 4.3317112, places=2) self.assertAlmostEqual( - thetavals['asymptote'], 19.1426, places=2 + thetavals["asymptote"], 19.1426, places=2 ) # 19.1426 from the paper self.assertAlmostEqual( - thetavals['rate_constant'], 0.5311, places=2 + thetavals["rate_constant"], 0.5311, places=2 ) # 0.5311 from the paper self.pest.diagnostic_mode = False -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/pyomo/contrib/parmest/tests/test_scenariocreator.py b/pyomo/contrib/parmest/tests/test_scenariocreator.py index fe4528120f6..22a851ae32e 100644 --- a/pyomo/contrib/parmest/tests/test_scenariocreator.py +++ b/pyomo/contrib/parmest/tests/test_scenariocreator.py @@ -24,7 +24,7 @@ import pyomo.environ as pyo from pyomo.environ import SolverFactory -ipopt_available = SolverFactory('ipopt').available() +ipopt_available = SolverFactory("ipopt").available() testdir = os.path.dirname(os.path.abspath(__file__)) @@ -63,17 +63,17 @@ def setUp(self): [1.90, 10000, 4491.3, 1049.4, 920.5, 1769.4], [1.95, 10000, 4538.8, 1045.8, 893.9, 1760.8], ], - columns=['sv', 'caf', 'ca', 'cb', 'cc', 'cd'], + columns=["sv", "caf", "ca", "cb", "cc", "cd"], ) - theta_names = ['k1', 'k2', 'k3'] + theta_names = ["k1", "k2", "k3"] def SSE(model, data): expr = ( - (float(data.iloc[0]['ca']) - model.ca) ** 2 - + (float(data.iloc[0]['cb']) - model.cb) ** 2 - + (float(data.iloc[0]['cc']) - model.cc) ** 2 - + (float(data.iloc[0]['cd']) - model.cd) ** 2 + (float(data.iloc[0]["ca"]) - model.ca) ** 2 + + (float(data.iloc[0]["cb"]) - model.cb) ** 2 + + (float(data.iloc[0]["cc"]) - model.cc) ** 2 + + (float(data.iloc[0]["cd"]) - model.cd) ** 2 ) return expr @@ -116,7 +116,7 @@ def setUp(self): import json # Vars to estimate in parmest - theta_names = ['k1', 'k2', 'E1', 'E2'] + theta_names = ["k1", "k2", "E1", "E2"] self.fbase = os.path.join(testdir, "..", "examples", "semibatch") # Data, list of dictionaries @@ -124,7 +124,7 @@ def setUp(self): for exp_num in range(10): fname = "exp" + str(exp_num + 1) + ".out" fullname = os.path.join(self.fbase, fname) - with open(fullname, 'r') as infile: + with open(fullname, "r") as infile: d = json.load(infile) data.append(d) @@ -142,5 +142,5 @@ def test_semibatch_bootstrap(self): self.assertAlmostEqual(tval, 20.64, places=1) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/pyomo/contrib/parmest/tests/test_utils.py b/pyomo/contrib/parmest/tests/test_utils.py index bd0706ac38d..514c14b1e82 100644 --- a/pyomo/contrib/parmest/tests/test_utils.py +++ b/pyomo/contrib/parmest/tests/test_utils.py @@ -16,7 +16,7 @@ import pyomo.contrib.parmest.parmest as parmest from pyomo.opt import SolverFactory -ipopt_available = SolverFactory('ipopt').available() +ipopt_available = SolverFactory("ipopt").available() @unittest.skipIf( @@ -45,13 +45,13 @@ def test_convert_param_to_var(self): [1.10, 10000, 3535.1, 1064.8, 1613.3, 1893.4], [1.15, 10000, 3609.1, 1067.8, 1547.5, 1887.8], ], - columns=['sv', 'caf', 'ca', 'cb', 'cc', 'cd'], + columns=["sv", "caf", "ca", "cb", "cc", "cd"], ) - theta_names = ['k1', 'k2', 'k3'] + theta_names = ["k1", "k2", "k3"] instance = reactor_design_model(data.loc[0]) - solver = pyo.SolverFactory('ipopt') + solver = pyo.SolverFactory("ipopt") solver.solve(instance) instance_vars = parmest.utils.convert_params_to_vars( From 93fdf50731f79408c703d9aad10397c7a3c1f011 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Thu, 2 Nov 2023 13:27:59 -0600 Subject: [PATCH 0371/1204] improving robustness of the printer --- pyomo/util/latex_printer.py | 81 ++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index 549ab358793..d7216c00c74 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -58,7 +58,10 @@ NPV_Structural_GetItemExpression, Numeric_GetAttrExpression, ) -from pyomo.core.expr.numeric_expr import NPV_SumExpression +from pyomo.core.expr.numeric_expr import ( + NPV_SumExpression, + NPV_DivisionExpression, +) from pyomo.core.base.block import IndexedBlock from pyomo.core.base.external import _PythonCallbackFunctionID @@ -446,6 +449,7 @@ def __init__(self): str: handle_str_node, Numeric_GetAttrExpression: handle_numericGetAttrExpression_node, NPV_SumExpression: handle_sumExpression_node, + NPV_DivisionExpression: handle_division_node, } if numpy_available: self._operator_handles[np.float64] = handle_num_node @@ -966,9 +970,10 @@ def latex_printer( try: obj_template, obj_indices = templatize_fcn(obj) except: - raise RuntimeError( - "An objective has been constructed that cannot be templatized" - ) + obj_template = obj + # raise RuntimeError( + # "An objective has been constructed that cannot be templatized" + # ) if obj.sense == 1: pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['minimize']) @@ -1018,46 +1023,50 @@ def latex_printer( con = constraints[i] try: con_template, indices = templatize_fcn(con) + con_template_list = [con_template] except: - raise RuntimeError( - "A constraint has been constructed that cannot be templatized" + # con_template = con[0] + con_template_list = [c.expr for c in con.values()] + indices = [] + # raise RuntimeError( + # "A constraint has been constructed that cannot be templatized" + # ) + for con_template in con_template_list: + # Walk the constraint + conLine = ( + ' ' * tbSpc + + algn + + ' %s %s' % (visitor.walk_expression(con_template), trailingAligner) ) - # Walk the constraint - conLine = ( - ' ' * tbSpc - + algn - + ' %s %s' % (visitor.walk_expression(con_template), trailingAligner) - ) + # setMap = visitor.setMap + # Multiple constraints are generated using a set + if len(indices) > 0: + if indices[0]._set in ComponentSet(visitor.setMap.keys()): + # already detected set, do nothing + pass + else: + visitor.setMap[indices[0]._set] = 'SET%d' % ( + len(visitor.setMap.keys()) + 1 + ) - # setMap = visitor.setMap - # Multiple constraints are generated using a set - if len(indices) > 0: - if indices[0]._set in ComponentSet(visitor.setMap.keys()): - # already detected set, do nothing - pass - else: - visitor.setMap[indices[0]._set] = 'SET%d' % ( - len(visitor.setMap.keys()) + 1 + idxTag = '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( + indices[0]._group, + visitor.setMap[indices[0]._set], + ) + setTag = '__S_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( + indices[0]._group, + visitor.setMap[indices[0]._set], ) - idxTag = '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( - indices[0]._group, - visitor.setMap[indices[0]._set], - ) - setTag = '__S_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( - indices[0]._group, - visitor.setMap[indices[0]._set], - ) - - conLine += ' \\qquad \\forall %s \\in %s ' % (idxTag, setTag) - pstr += conLine + conLine += ' \\qquad \\forall %s \\in %s ' % (idxTag, setTag) + pstr += conLine - # Add labels as needed - if not use_equation_environment: - pstr += '\\label{con:' + pyomo_component.name + '_' + con.name + '} ' + # Add labels as needed + if not use_equation_environment: + pstr += '\\label{con:' + pyomo_component.name + '_' + con.name + '} ' - pstr += tail + pstr += tail # Print bounds and sets if not isSingle: From f53b63516f2f8c9831c6040313f31df2ad3b1204 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Thu, 2 Nov 2023 13:42:28 -0600 Subject: [PATCH 0372/1204] applying black --- pyomo/util/latex_printer.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index d7216c00c74..9dcbc9f912a 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -58,10 +58,7 @@ NPV_Structural_GetItemExpression, Numeric_GetAttrExpression, ) -from pyomo.core.expr.numeric_expr import ( - NPV_SumExpression, - NPV_DivisionExpression, -) +from pyomo.core.expr.numeric_expr import NPV_SumExpression, NPV_DivisionExpression from pyomo.core.base.block import IndexedBlock from pyomo.core.base.external import _PythonCallbackFunctionID @@ -77,6 +74,7 @@ _GENERAL = ExprType.GENERAL from pyomo.common.dependencies import numpy, numpy_available + if numpy_available: import numpy as np @@ -1036,7 +1034,8 @@ def latex_printer( conLine = ( ' ' * tbSpc + algn - + ' %s %s' % (visitor.walk_expression(con_template), trailingAligner) + + ' %s %s' + % (visitor.walk_expression(con_template), trailingAligner) ) # setMap = visitor.setMap @@ -1064,7 +1063,9 @@ def latex_printer( # Add labels as needed if not use_equation_environment: - pstr += '\\label{con:' + pyomo_component.name + '_' + con.name + '} ' + pstr += ( + '\\label{con:' + pyomo_component.name + '_' + con.name + '} ' + ) pstr += tail From 35c119bf90f78e05593ae75305a1fef9ec833553 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 2 Nov 2023 14:15:33 -0600 Subject: [PATCH 0373/1204] Clarifying a lot in hull's local var suffix handling, starting to update some tests, fixing a bug in GDPTree.parent_disjunct method --- pyomo/gdp/plugins/hull.py | 94 ++++++++++++++++++------------------ pyomo/gdp/tests/test_hull.py | 21 +++++++- pyomo/gdp/util.py | 5 +- 3 files changed, 71 insertions(+), 49 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 25a0606dc1c..86bd738eb09 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -204,16 +204,16 @@ def __init__(self): super().__init__(logger) self._targets = set() - def _add_local_vars(self, block, local_var_dict): + def _collect_local_vars_from_block(self, block, local_var_dict): localVars = block.component('LocalVars') - if type(localVars) is Suffix: + if localVars is not None and localVars.ctype is Suffix: for disj, var_list in localVars.items(): - if local_var_dict.get(disj) is None: - local_var_dict[disj] = ComponentSet(var_list) - else: + if disj in local_var_dict: local_var_dict[disj].update(var_list) + else: + local_var_dict[disj] = ComponentSet(var_list) - def _get_local_var_suffixes(self, block, local_var_dict): + def _get_local_vars_from_suffixes(self, block, local_var_dict): # You can specify suffixes on any block (disjuncts included). This # method starts from a Disjunct (presumably) and checks for a LocalVar # suffixes going both up and down the tree, adding them into the @@ -222,16 +222,14 @@ def _get_local_var_suffixes(self, block, local_var_dict): # first look beneath where we are (there could be Blocks on this # disjunct) for b in block.component_data_objects( - Block, descend_into=(Block), active=True, sort=SortComponents.deterministic + Block, descend_into=Block, active=True, sort=SortComponents.deterministic ): - self._add_local_vars(b, local_var_dict) + self._collect_local_vars_from_block(b, local_var_dict) # now traverse upwards and get what's above while block is not None: - self._add_local_vars(block, local_var_dict) + self._collect_local_vars_from_block(block, local_var_dict) block = block.parent_block() - return local_var_dict - def _apply_to(self, instance, **kwds): try: self._apply_to_impl(instance, **kwds) @@ -239,7 +237,6 @@ def _apply_to(self, instance, **kwds): self._restore_state() self._transformation_blocks.clear() self._algebraic_constraints.clear() - self._targets_set = set() def _apply_to_impl(self, instance, **kwds): self._process_arguments(instance, **kwds) @@ -257,14 +254,13 @@ def _apply_to_impl(self, instance, **kwds): # nested GDPs, we will introduce variables that need disaggregating into # parent Disjuncts as we transform their child Disjunctions. preprocessed_targets = gdp_tree.reverse_topological_sort() - self._targets_set = set(preprocessed_targets) for t in preprocessed_targets: if t.ctype is Disjunction: self._transform_disjunctionData( t, t.index(), - parent_disjunct=gdp_tree.parent(t), + gdp_tree.parent(t), ) # We skip disjuncts now, because we need information from the # disjunctions to transform them (which variables to disaggregate), @@ -300,7 +296,7 @@ def _add_transformation_block(self, to_block): return transBlock, True - def _transform_disjunctionData(self, obj, index, parent_disjunct=None): + def _transform_disjunctionData(self, obj, index, parent_disjunct): # Hull reformulation doesn't work if this is an OR constraint. So if # xor is false, give up if not obj.xor: @@ -371,9 +367,12 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct=None): setOfDisjunctsVarAppearsIn[var].add(disjunct) # check for LocalVars Suffix - localVarsByDisjunct = self._get_local_var_suffixes( - disjunct, localVarsByDisjunct - ) + # [ESJ 11/2/23] TODO: This could be a lot more efficient if we + # centralized it. Right now we walk up the tree to the root model + # for each Disjunct, which is pretty dumb. We could get + # user-speficied suffixes once, and then we know where we will + # create ours, or we can just track what we create. + self._get_local_vars_from_suffixes(disjunct, localVarsByDisjunct) # We will disaggregate all variables that are not explicitly declared as # being local. Since we transform from leaf to root, we are implicitly @@ -387,7 +386,7 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct=None): # transform the Disjuncts: Values of localVarsByDisjunct are # ComponentSets, so we need this for determinism (we iterate through the # localVars of a Disjunct later) - localVars = ComponentMap() + localVars = {disj: [] for disj in obj.disjuncts} varsToDisaggregate = [] for var in varOrder: disjuncts = disjunctsVarAppearsIn[var] @@ -405,10 +404,7 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct=None): # disjuncts is a list of length 1 elif localVarsByDisjunct.get(disjuncts[0]) is not None: if var in localVarsByDisjunct[disjuncts[0]]: - if localVars.get(disjuncts[0]) is not None: - localVars[disjuncts[0]].append(var) - else: - localVars[disjuncts[0]] = [var] + localVars[disjuncts[0]].append(var) else: # It's not local to this Disjunct varSet[disjuncts[0]].append(var) @@ -421,7 +417,10 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct=None): # Now that we know who we need to disaggregate, we will do it # while we also transform the disjuncts. - local_var_set = self._get_local_var_set(obj) + print("obj: %s" % obj) + print("parent disjunct: %s" % parent_disjunct) + parent_local_var_list = self._get_local_var_list(parent_disjunct) + print("parent_local_var_list: %s" % parent_local_var_list) or_expr = 0 for disjunct in obj.disjuncts: or_expr += disjunct.indicator_var.get_associated_binary() @@ -429,11 +428,10 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct=None): disjunct, transBlock, varSet[disjunct], - localVars.get(disjunct, []), - local_var_set, + localVars[disjunct], + parent_local_var_list, ) - rhs = 1 if parent_disjunct is None else parent_disjunct.binary_indicator_var - xorConstraint.add(index, (or_expr, rhs)) + 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]) @@ -452,8 +450,8 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct=None): disaggregated_var = disaggregatedVars[idx] # mark this as local because we won't re-disaggregate if this is # a nested disjunction - if local_var_set is not None: - local_var_set.append(disaggregated_var) + if parent_local_var_list is not None: + parent_local_var_list.append(disaggregated_var) var_free = 1 - sum( disj.indicator_var.get_associated_binary() for disj in disjunctsVarAppearsIn[var] @@ -518,7 +516,8 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct=None): # deactivate for the writers obj.deactivate() - def _transform_disjunct(self, obj, transBlock, varSet, localVars, local_var_set): + def _transform_disjunct(self, obj, transBlock, varSet, localVars, + parent_local_var_list): # We're not using the preprocessed list here, so this could be # inactive. We've already done the error checking in preprocessing, so # we just skip it here. @@ -535,6 +534,7 @@ def _transform_disjunct(self, obj, transBlock, varSet, localVars, local_var_set) # add the disaggregated variables and their bigm constraints # to the relaxationBlock for var in varSet: + print("disaggregating %s" % var) disaggregatedVar = Var(within=Reals, initialize=var.value) # naming conflicts are possible here since this is a bunch # of variables from different blocks coming together, so we @@ -547,8 +547,8 @@ def _transform_disjunct(self, obj, transBlock, varSet, localVars, local_var_set) ) # mark this as local because we won't re-disaggregate if this is a # nested disjunction - if local_var_set is not None: - local_var_set.append(disaggregatedVar) + if parent_local_var_list is not None: + parent_local_var_list.append(disaggregatedVar) # add the bigm constraint bigmConstraint = Constraint(transBlock.lbub) @@ -568,6 +568,7 @@ def _transform_disjunct(self, obj, transBlock, varSet, localVars, local_var_set) ) for var in localVars: + print("we knew %s was local" % var) # we don't need to disaggregate, i.e., we can use this Var, but we # do need to set up its bounds constraints. @@ -652,27 +653,23 @@ def _declare_disaggregated_var_bounds( transBlock._disaggregatedVarMap['srcVar'][disaggregatedVar] = original_var transBlock._bigMConstraintMap[disaggregatedVar] = bigmConstraint - def _get_local_var_set(self, disjunction): - # add Suffix to the relaxation block that disaggregated variables are - # local (in case this is nested in another Disjunct) - local_var_set = None - parent_disjunct = disjunction.parent_block() - while parent_disjunct is not None: - if parent_disjunct.ctype is Disjunct: - break - parent_disjunct = parent_disjunct.parent_block() + def _get_local_var_list(self, parent_disjunct): + # Add or retrieve Suffix from parent_disjunct so that, if this is + # nested, we can use it to declare that the disaggregated variables are + # local. We return the list so that we can add to it. + local_var_list = None if parent_disjunct is not None: # This limits the cases that a user is allowed to name something # (other than a Suffix) 'LocalVars' on a Disjunct. But I am assuming # that the Suffix has to be somewhere above the disjunct in the # tree, so I can't put it on a Block that I own. And if I'm coopting # something of theirs, it may as well be here. - self._add_local_var_suffix(parent_disjunct) + self._get_local_var_suffix(parent_disjunct) if parent_disjunct.LocalVars.get(parent_disjunct) is None: parent_disjunct.LocalVars[parent_disjunct] = [] - local_var_set = parent_disjunct.LocalVars[parent_disjunct] + local_var_list = parent_disjunct.LocalVars[parent_disjunct] - return local_var_set + return local_var_list def _transform_constraint( self, obj, disjunct, var_substitute_map, zero_substitute_map @@ -847,7 +844,7 @@ def _transform_constraint( # deactivate now that we have transformed obj.deactivate() - def _add_local_var_suffix(self, disjunct): + def _get_local_var_suffix(self, disjunct): # If the Suffix is there, we will borrow it. If not, we make it. If it's # something else, we complain. localSuffix = disjunct.component("LocalVars") @@ -948,7 +945,7 @@ def get_disaggregation_constraint(self, original_var, disjunction, ) try: - return ( + cons = ( transBlock() .parent_block() ._disaggregationConstraintMap[original_var][disjunction] @@ -962,6 +959,9 @@ def get_disaggregation_constraint(self, original_var, disjunction, ) raise return None + while not cons.active: + cons = self.get_transformed_constraints(cons)[0] + return cons def get_var_bounds_constraint(self, v): """ diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index 118ee4ca69a..b8aa332174b 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -41,6 +41,7 @@ import pyomo.core.expr as EXPR from pyomo.core.base import constraint from pyomo.repn import generate_standard_repn +from pyomo.repn.linear import LinearRepnVisitor from pyomo.gdp import Disjunct, Disjunction, GDP_Error import pyomo.gdp.tests.models as models @@ -1877,6 +1878,15 @@ def test_nested_with_var_that_does_not_appear_in_every_disjunct(self): x_cons_child = hull.get_disaggregation_constraint(m.x, m.parent1.disjunction) assertExpressionsEqual(self, x_cons_child.expr, x_p1 == x_c1 + x_c2 + x_c3) + def simplify_cons(self, cons): + visitor = LinearRepnVisitor({}, {}, {}) + lb = cons.lower + ub = cons.upper + self.assertEqual(cons.lb, cons.ub) + repn = visitor.walk_expression(cons.body) + self.assertIsNone(repn.nonlinear) + return repn.to_expression(visitor) == lb + def test_nested_with_var_that_skips_a_level(self): m = ConcreteModel() @@ -1915,18 +1925,27 @@ def test_nested_with_var_that_skips_a_level(self): y_y2 = hull.get_disaggregated_var(m.y, m.y2) cons = hull.get_disaggregation_constraint(m.x, m.y1.z1.disjunction) - assertExpressionsEqual(self, cons.expr, x_z1 == x_w1 + x_w2) + self.assertTrue(cons.active) + cons_expr = self.simplify_cons(cons) + print(cons_expr) + print("") + print(x_z1 - x_w2 - x_w1 == 0) + assertExpressionsEqual(self, cons_expr, x_z1 - x_w2 - x_w1 == 0) cons = hull.get_disaggregation_constraint(m.x, m.y1.disjunction) + self.assertTrue(cons.active) assertExpressionsEqual(self, cons.expr, x_y1 == x_z2 + x_z1) cons = hull.get_disaggregation_constraint(m.x, m.disjunction) + self.assertTrue(cons.active) assertExpressionsEqual(self, cons.expr, m.x == x_y1 + x_y2) cons = hull.get_disaggregation_constraint(m.y, m.y1.z1.disjunction, raise_exception=False) self.assertIsNone(cons) cons = hull.get_disaggregation_constraint(m.y, m.y1.disjunction) + self.assertTrue(cons.active) assertExpressionsEqual(self, cons.expr, y_y1 == y_z1 + y_z2) cons = hull.get_disaggregation_constraint(m.y, m.disjunction) + self.assertTrue(cons.active) assertExpressionsEqual(self, cons.expr, m.y == y_y2 + y_y1) diff --git a/pyomo/gdp/util.py b/pyomo/gdp/util.py index b460a3d691c..b5e74f73c38 100644 --- a/pyomo/gdp/util.py +++ b/pyomo/gdp/util.py @@ -169,7 +169,10 @@ def parent_disjunct(self, u): Arg: u : A node in the forest """ - return self.parent(self.parent(u)) + if isinstance(u, _DisjunctData) or u.ctype is Disjunct: + return self.parent(self.parent(u)) + else: + return self.parent(u) def root_disjunct(self, u): """Returns the highest parent Disjunct in the hierarchy, or None if From 88cae4ab976a0eda0b5ef652e9629dd13e9458b8 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 2 Nov 2023 16:45:21 -0600 Subject: [PATCH 0374/1204] Cleaning up a lot of mess using the fact that ComponentSets are ordered, simplifying how we deal with local vars significantly. --- pyomo/gdp/plugins/hull.py | 363 ++++++++++++++++++++------------------ 1 file changed, 190 insertions(+), 173 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 86bd738eb09..80ac55d45fe 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -11,6 +11,8 @@ import logging +from collections import defaultdict + import pyomo.common.config as cfg from pyomo.common import deprecated from pyomo.common.collections import ComponentMap, ComponentSet @@ -39,6 +41,7 @@ Binary, ) from pyomo.gdp import Disjunct, Disjunction, GDP_Error +from pyomo.gdp.disjunct import _DisjunctData from pyomo.gdp.plugins.gdp_to_mip_transformation import GDP_to_MIP_Transformation from pyomo.gdp.transformed_disjunct import _TransformedDisjunct from pyomo.gdp.util import ( @@ -208,27 +211,51 @@ def _collect_local_vars_from_block(self, block, local_var_dict): localVars = block.component('LocalVars') if localVars is not None and localVars.ctype is Suffix: for disj, var_list in localVars.items(): - if disj in local_var_dict: - local_var_dict[disj].update(var_list) - else: - local_var_dict[disj] = ComponentSet(var_list) - - def _get_local_vars_from_suffixes(self, block, local_var_dict): - # You can specify suffixes on any block (disjuncts included). This - # method starts from a Disjunct (presumably) and checks for a LocalVar - # suffixes going both up and down the tree, adding them into the - # dictionary that is the second argument. - - # first look beneath where we are (there could be Blocks on this - # disjunct) - for b in block.component_data_objects( - Block, descend_into=Block, active=True, sort=SortComponents.deterministic - ): - self._collect_local_vars_from_block(b, local_var_dict) - # now traverse upwards and get what's above - while block is not None: - self._collect_local_vars_from_block(block, local_var_dict) - block = block.parent_block() + local_var_dict[disj].update(var_list) + + def _get_user_defined_local_vars(self, targets): + user_defined_local_vars = defaultdict(lambda: ComponentSet()) + seen_blocks = set() + # we go through the targets looking both up and down the hierarchy, but + # we cache what Blocks/Disjuncts we've already looked on so that we + # don't duplicate effort. + for t in targets: + if t.ctype is Disjunct or isinstance(t, _DisjunctData): + # first look beneath where we are (there could be Blocks on this + # disjunct) + for b in t.component_data_objects(Block, descend_into=Block, + active=True, + sort=SortComponents.deterministic + ): + if b not in seen_blocks: + self._collect_local_vars_from_block(b, user_defined_local_vars) + seen_blocks.add(b) + # now look up in the tree + blk = t + while blk is not None: + if blk not in seen_blocks: + self._collect_local_vars_from_block(blk, + user_defined_local_vars) + seen_blocks.add(blk) + blk = blk.parent_block() + return user_defined_local_vars + + # def _get_local_vars_from_suffixes(self, block, local_var_dict): + # # You can specify suffixes on any block (disjuncts included). This + # # method starts from a Disjunct (presumably) and checks for a LocalVar + # # suffixes going both up and down the tree, adding them into the + # # dictionary that is the second argument. + + # # first look beneath where we are (there could be Blocks on this + # # disjunct) + # for b in block.component_data_objects( + # Block, descend_into=Block, active=True, sort=SortComponents.deterministic + # ): + # self._collect_local_vars_from_block(b, local_var_dict) + # # now traverse upwards and get what's above + # while block is not None: + # self._collect_local_vars_from_block(block, local_var_dict) + # block = block.parent_block() def _apply_to(self, instance, **kwds): try: @@ -254,6 +281,8 @@ def _apply_to_impl(self, instance, **kwds): # nested GDPs, we will introduce variables that need disaggregating into # parent Disjuncts as we transform their child Disjunctions. preprocessed_targets = gdp_tree.reverse_topological_sort() + local_vars_by_disjunct = self._get_user_defined_local_vars( + preprocessed_targets) for t in preprocessed_targets: if t.ctype is Disjunction: @@ -261,6 +290,7 @@ def _apply_to_impl(self, instance, **kwds): t, t.index(), gdp_tree.parent(t), + local_vars_by_disjunct ) # We skip disjuncts now, because we need information from the # disjunctions to transform them (which variables to disaggregate), @@ -296,7 +326,8 @@ def _add_transformation_block(self, to_block): return transBlock, True - def _transform_disjunctionData(self, obj, index, parent_disjunct): + def _transform_disjunctionData(self, obj, index, parent_disjunct, + local_vars_by_disjunct): # Hull reformulation doesn't work if this is an OR constraint. So if # xor is false, give up if not obj.xor: @@ -321,16 +352,11 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct): # We first go through and collect all the variables that we # are going to disaggregate. - varOrder_set = ComponentSet() - varOrder = [] - varsByDisjunct = ComponentMap() - localVarsByDisjunct = ComponentMap() - disjunctsVarAppearsIn = ComponentMap() - setOfDisjunctsVarAppearsIn = ComponentMap() + var_order = ComponentSet() + disjuncts_var_appears_in = ComponentMap() for disjunct in obj.disjuncts: if not disjunct.active: continue - disjunctVars = varsByDisjunct[disjunct] = ComponentSet() # create the key for each disjunct now transBlock._disaggregatedVarMap['disaggregatedVar'][ disjunct @@ -351,45 +377,22 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct): for var in EXPR.identify_variables( cons.body, include_fixed=not self._config.assume_fixed_vars_permanent): - # Note the use of a list so that we will - # eventually disaggregate the vars in a - # deterministic order (the order that we found - # them) - disjunctVars.add(var) - if not var in varOrder_set: - varOrder.append(var) - varOrder_set.add(var) - disjunctsVarAppearsIn[var] = [disjunct] - setOfDisjunctsVarAppearsIn[var] = ComponentSet([disjunct]) + # Note that, because ComponentSets are ordered, we will + # eventually disaggregate the vars in a deterministic order + # (the order that we found them) + if var not in var_order: + var_order.add(var) + disjuncts_var_appears_in[var] = ComponentSet([disjunct]) else: - if disjunct not in setOfDisjunctsVarAppearsIn[var]: - disjunctsVarAppearsIn[var].append(disjunct) - setOfDisjunctsVarAppearsIn[var].add(disjunct) - - # check for LocalVars Suffix - # [ESJ 11/2/23] TODO: This could be a lot more efficient if we - # centralized it. Right now we walk up the tree to the root model - # for each Disjunct, which is pretty dumb. We could get - # user-speficied suffixes once, and then we know where we will - # create ours, or we can just track what we create. - self._get_local_vars_from_suffixes(disjunct, localVarsByDisjunct) + disjuncts_var_appears_in[var].add(disjunct) # We will disaggregate all variables that are not explicitly declared as # being local. Since we transform from leaf to root, we are implicitly # treating our own disaggregated variables as local, so they will not be # re-disaggregated. - varSet = {disj: [] for disj in obj.disjuncts} - # Note that variables are local with respect to a Disjunct. We deal with - # them here to do some error checking (if something is obviously not - # local since it is used in multiple Disjuncts in this Disjunction) and - # also to get a deterministic order in which to process them when we - # transform the Disjuncts: Values of localVarsByDisjunct are - # ComponentSets, so we need this for determinism (we iterate through the - # localVars of a Disjunct later) - localVars = {disj: [] for disj in obj.disjuncts} - varsToDisaggregate = [] - for var in varOrder: - disjuncts = disjunctsVarAppearsIn[var] + vars_to_disaggregate = {disj: ComponentSet() for disj in obj.disjuncts} + for var in var_order: + disjuncts = disjuncts_var_appears_in[var] # clearly not local if used in more than one disjunct if len(disjuncts) > 1: if self._generate_debug_messages: @@ -399,21 +402,18 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct): % var.getname(fully_qualified=True) ) for disj in disjuncts: - varSet[disj].append(var) - varsToDisaggregate.append(var) - # disjuncts is a list of length 1 - elif localVarsByDisjunct.get(disjuncts[0]) is not None: - if var in localVarsByDisjunct[disjuncts[0]]: - localVars[disjuncts[0]].append(var) + vars_to_disaggregate[disj].add(var) + else: # disjuncts is a set of length 1 + disjunct = next(iter(disjuncts)) + if disjunct in local_vars_by_disjunct: + if var not in local_vars_by_disjunct[disjunct]: + # It's not declared local to this Disjunct, so we + # disaggregate + vars_to_disaggregate[disjunct].add(var) else: - # It's not local to this Disjunct - varSet[disjuncts[0]].append(var) - varsToDisaggregate.append(var) - else: - # The user didn't declare any local vars for this Disjunct, so - # we know we're disaggregating it - varSet[disjuncts[0]].append(var) - varsToDisaggregate.append(var) + # The user didn't declare any local vars for this + # Disjunct, so we know we're disaggregating it + vars_to_disaggregate[disjunct].add(var) # Now that we know who we need to disaggregate, we will do it # while we also transform the disjuncts. @@ -424,106 +424,111 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct): or_expr = 0 for disjunct in obj.disjuncts: or_expr += disjunct.indicator_var.get_associated_binary() - self._transform_disjunct( - disjunct, - transBlock, - varSet[disjunct], - localVars[disjunct], - parent_local_var_list, - ) + if obj.active: + self._transform_disjunct( + disjunct, + transBlock, + vars_to_disaggregate[disjunct], + local_vars_by_disjunct.get(disjunct, []), + parent_local_var_list, + local_vars_by_disjunct[parent_disjunct] + ) 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]) # add the reaggregation constraints - for i, var in enumerate(varsToDisaggregate): - # There are two cases here: Either the var appeared in every - # disjunct in the disjunction, or it didn't. If it did, there's - # nothing special to do: All of the disaggregated variables have - # been created, and we can just proceed and make this constraint. If - # it didn't, we need one more disaggregated variable, correctly - # defined. And then we can make the constraint. - if len(disjunctsVarAppearsIn[var]) < len(obj.disjuncts): - # create one more disaggregated var - idx = len(disaggregatedVars) - disaggregated_var = disaggregatedVars[idx] - # mark this as local because we won't re-disaggregate if this is - # a nested disjunction - if parent_local_var_list is not None: - parent_local_var_list.append(disaggregated_var) - var_free = 1 - sum( - disj.indicator_var.get_associated_binary() - for disj in disjunctsVarAppearsIn[var] - ) - self._declare_disaggregated_var_bounds( - var, - disaggregated_var, - obj, - disaggregated_var_bounds, - (idx, 'lb'), - (idx, 'ub'), - var_free, - ) - # For every Disjunct the Var does not appear in, we want to map - # that this new variable is its disaggreggated variable. - for disj in obj.disjuncts: - # Because we called _transform_disjunct above, we know that - # if this isn't transformed it is because it was cleanly - # deactivated, and we can just skip it. - if ( - disj._transformation_block is not None - and disj not in setOfDisjunctsVarAppearsIn[var] - ): - relaxationBlock = disj._transformation_block().parent_block() - relaxationBlock._bigMConstraintMap[ - disaggregated_var - ] = Reference(disaggregated_var_bounds[idx, :]) - relaxationBlock._disaggregatedVarMap['srcVar'][ - disaggregated_var - ] = var - relaxationBlock._disaggregatedVarMap['disaggregatedVar'][disj][ - var - ] = disaggregated_var - - disaggregatedExpr = disaggregated_var - else: - disaggregatedExpr = 0 - for disjunct in disjunctsVarAppearsIn[var]: - # We know this Disjunct was active, so it has been transformed now. - disaggregatedVar = ( - disjunct._transformation_block() - .parent_block() - ._disaggregatedVarMap['disaggregatedVar'][disjunct][var] - ) - disaggregatedExpr += disaggregatedVar - - cons_idx = len(disaggregationConstraint) - # We always aggregate to the original var. If this is nested, this - # constraint will be transformed again. - disaggregationConstraint.add(cons_idx, var == disaggregatedExpr) - # and update the map so that we can find this later. We index by - # variable and the particular disjunction because there is a - # different one for each disjunction - if disaggregationConstraintMap.get(var) is not None: - disaggregationConstraintMap[var][obj] = disaggregationConstraint[ - cons_idx - ] - else: - thismap = disaggregationConstraintMap[var] = ComponentMap() - thismap[obj] = disaggregationConstraint[cons_idx] + i = 0 + for disj in obj.disjuncts: + if not disj.active: + continue + for var in vars_to_disaggregate[disj]: + # There are two cases here: Either the var appeared in every + # disjunct in the disjunction, or it didn't. If it did, there's + # nothing special to do: All of the disaggregated variables have + # been created, and we can just proceed and make this constraint. If + # it didn't, we need one more disaggregated variable, correctly + # defined. And then we can make the constraint. + if len(disjuncts_var_appears_in[var]) < len(obj.disjuncts): + # create one more disaggregated var + idx = len(disaggregatedVars) + disaggregated_var = disaggregatedVars[idx] + # mark this as local because we won't re-disaggregate if this is + # a nested disjunction + if parent_local_var_list is not None: + 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() + for disj in disjuncts_var_appears_in[var] + ) + self._declare_disaggregated_var_bounds( + var, + disaggregated_var, + obj, + disaggregated_var_bounds, + (idx, 'lb'), + (idx, 'ub'), + var_free, + ) + # For every Disjunct the Var does not appear in, we want to map + # that this new variable is its disaggreggated variable. + for disj in obj.disjuncts: + # Because we called _transform_disjunct above, we know that + # if this isn't transformed it is because it was cleanly + # deactivated, and we can just skip it. + if ( + disj._transformation_block is not None + and disj not in disjuncts_var_appears_in[var] + ): + relaxationBlock = disj._transformation_block().\ + parent_block() + relaxationBlock._bigMConstraintMap[ + disaggregated_var + ] = Reference(disaggregated_var_bounds[idx, :]) + relaxationBlock._disaggregatedVarMap['srcVar'][ + disaggregated_var + ] = var + relaxationBlock._disaggregatedVarMap[ + 'disaggregatedVar'][disj][ + var + ] = disaggregated_var + + disaggregatedExpr = disaggregated_var + else: + disaggregatedExpr = 0 + for disjunct in disjuncts_var_appears_in[var]: + # We know this Disjunct was active, so it has been transformed now. + disaggregatedVar = ( + disjunct._transformation_block() + .parent_block() + ._disaggregatedVarMap['disaggregatedVar'][disjunct][var] + ) + disaggregatedExpr += disaggregatedVar + + cons_idx = len(disaggregationConstraint) + # We always aggregate to the original var. If this is nested, this + # constraint will be transformed again. + disaggregationConstraint.add(cons_idx, var == disaggregatedExpr) + # and update the map so that we can find this later. We index by + # variable and the particular disjunction because there is a + # different one for each disjunction + if disaggregationConstraintMap.get(var) is not None: + disaggregationConstraintMap[var][obj] = disaggregationConstraint[ + cons_idx + ] + else: + thismap = disaggregationConstraintMap[var] = ComponentMap() + thismap[obj] = disaggregationConstraint[cons_idx] + + i += 1 # deactivate for the writers obj.deactivate() - def _transform_disjunct(self, obj, transBlock, varSet, localVars, - parent_local_var_list): - # We're not using the preprocessed list here, so this could be - # inactive. We've already done the error checking in preprocessing, so - # we just skip it here. - if not obj.active: - return - + def _transform_disjunct(self, obj, transBlock, vars_to_disaggregate, local_vars, + parent_local_var_suffix, parent_disjunct_local_vars): relaxationBlock = self._get_disjunct_transformation_block(obj, transBlock) # Put the disaggregated variables all on their own block so that we can @@ -533,7 +538,7 @@ def _transform_disjunct(self, obj, transBlock, varSet, localVars, # add the disaggregated variables and their bigm constraints # to the relaxationBlock - for var in varSet: + for var in vars_to_disaggregate: print("disaggregating %s" % var) disaggregatedVar = Var(within=Reals, initialize=var.value) # naming conflicts are possible here since this is a bunch @@ -545,10 +550,13 @@ def _transform_disjunct(self, obj, transBlock, varSet, localVars, relaxationBlock.disaggregatedVars.add_component( disaggregatedVarName, disaggregatedVar ) - # mark this as local because we won't re-disaggregate if this is a - # nested disjunction - if parent_local_var_list is not None: - parent_local_var_list.append(disaggregatedVar) + # mark this as local via the Suffix in case this is a partial + # transformation: + if parent_local_var_suffix is not None: + parent_local_var_suffix.append(disaggregatedVar) + # Record that it's local for our own bookkeeping in case we're in a + # nested situation in *this* transformation + parent_disjunct_local_vars.add(disaggregatedVar) # add the bigm constraint bigmConstraint = Constraint(transBlock.lbub) @@ -567,7 +575,13 @@ def _transform_disjunct(self, obj, transBlock, varSet, localVars, transBlock, ) - for var in localVars: + for var in local_vars: + if var in vars_to_disaggregate: + logger.warning( + "Var '%s' was declared as a local Var for Disjunct '%s', " + "but it appeared in multiple Disjuncts, so it will be " + "disaggregated." % (var.name, obj.name)) + continue print("we knew %s was local" % var) # we don't need to disaggregate, i.e., we can use this Var, but we # do need to set up its bounds constraints. @@ -604,13 +618,16 @@ def _transform_disjunct(self, obj, transBlock, varSet, localVars, obj ].items() ) - zero_substitute_map.update((id(v), ZeroConstant) for v in localVars) + zero_substitute_map.update((id(v), ZeroConstant) for v in local_vars) # Transform each component within this disjunct self._transform_block_components( obj, obj, var_substitute_map, zero_substitute_map ) + # Anything that was local to this Disjunct is also local to the parent, + # and just got "promoted" up there, so to speak. + parent_disjunct_local_vars.update(local_vars) # deactivate disjunct so writers can be happy obj._deactivate_without_fixing_indicator() From 6cf7e68a50016850426f7d1ce477c9a3fcf63807 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 3 Nov 2023 00:44:01 -0400 Subject: [PATCH 0375/1204] Update version info retrieval logic --- doc/OnlineDocs/contributed_packages/pyros.rst | 9 +-- pyomo/contrib/pyros/pyros.py | 56 ++++++++++--------- pyomo/contrib/pyros/tests/test_grcs.py | 4 +- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/doc/OnlineDocs/contributed_packages/pyros.rst b/doc/OnlineDocs/contributed_packages/pyros.rst index 0bf8fa93be6..23ec60f2e20 100644 --- a/doc/OnlineDocs/contributed_packages/pyros.rst +++ b/doc/OnlineDocs/contributed_packages/pyros.rst @@ -632,7 +632,7 @@ In this example, we select affine decision rules by setting ... decision_rule_order=1, ... ) ============================================================================== - PyROS: The Pyomo Robust Optimization Solver. + PyROS: The Pyomo Robust Optimization Solver... ... ------------------------------------------------------------------------------ Robust optimal solution identified. @@ -854,9 +854,10 @@ Observe that the log contains the following information: :linenos: ============================================================================== - PyROS: The Pyomo Robust Optimization Solver. - Version 1.2.8 | Git branch: unknown, commit hash: unknown - Invoked at UTC 2023-10-12T15:36:19.035916 + PyROS: The Pyomo Robust Optimization Solver, v1.2.8. + Pyomo version: 6.7.0 + Commit hash: unknown + Invoked at UTC 2023-11-03T04:27:42.954101 Developed by: Natalie M. Isenberg (1), Jason A. F. Sherman (1), John D. Siirola (2), Chrysanthos E. Gounaris (1) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index e48690da5d6..5b37b114722 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -55,29 +55,34 @@ default_pyros_solver_logger = setup_pyros_logger() -def _get_pyomo_git_info(): +def _get_pyomo_version_info(): """ - Get Pyomo git commit hash. + Get Pyomo version information. """ import os import subprocess + from pyomo.version import version - pyros_dir = os.path.join(*os.path.split(__file__)[:-1]) - - git_info_dict = {} - commands_dict = { - "branch": ["git", "-C", f"{pyros_dir}", "rev-parse", "--abbrev-ref", "HEAD"], - "commit hash": ["git", "-C", f"{pyros_dir}", "rev-parse", "--short", "HEAD"], - } - for field, command in commands_dict.items(): - try: - field_val = subprocess.check_output(command).decode("ascii").strip() - except subprocess.CalledProcessError: - field_val = "unknown" + pyomo_version = version + commit_hash = "unknown" - git_info_dict[field] = field_val + pyros_dir = os.path.join(*os.path.split(__file__)[:-1]) + commit_hash_command_args = [ + "git", + "-C", + f"{pyros_dir}", + "rev-parse", + "--short", + "HEAD", + ] + try: + commit_hash = ( + subprocess.check_output(commit_hash_command_args).decode("ascii").strip() + ) + except subprocess.CalledProcessError: + commit_hash = "unknown" - return git_info_dict + return {"Pyomo version": pyomo_version, "Commit hash": commit_hash} def NonNegIntOrMinusOne(obj): @@ -712,18 +717,19 @@ def _log_intro(self, logger, **log_kwargs): Should not include `msg`. """ logger.log(msg="=" * self._LOG_LINE_LENGTH, **log_kwargs) - logger.log(msg="PyROS: The Pyomo Robust Optimization Solver.", **log_kwargs) - - git_info_str = ", ".join( - f"{field}: {val}" for field, val in _get_pyomo_git_info().items() - ) logger.log( - msg=( - f"{' ' * len('PyROS:')} Version {self.version()} | " - f"Git {git_info_str}" - ), + msg=f"PyROS: The Pyomo Robust Optimization Solver, v{self.version()}.", **log_kwargs, ) + + # git_info_str = ", ".join( + # f"{field}: {val}" for field, val in _get_pyomo_git_info().items() + # ) + version_info = _get_pyomo_version_info() + version_info_str = ' ' * len("PyROS: ") + ("\n" + ' ' * len("PyROS: ")).join( + f"{key}: {val}" for key, val in version_info.items() + ) + logger.log(msg=version_info_str, **log_kwargs) logger.log( msg=( f"{' ' * len('PyROS:')} " diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index a76e531d666..2be73826f61 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -6058,7 +6058,7 @@ def test_log_intro(self): # check number of lines is as expected self.assertEqual( len(intro_msg_lines), - 13, + 14, msg=( "PyROS solver introductory message does not contain" "the expected number of lines." @@ -6072,7 +6072,7 @@ def test_log_intro(self): # check regex main text self.assertRegex( " ".join(intro_msg_lines[1:-1]), - r"PyROS: The Pyomo Robust Optimization Solver\..* \(IDAES\)\.", + r"PyROS: The Pyomo Robust Optimization Solver, v.* \(IDAES\)\.", ) def test_log_disclaimer(self): From 3e9f156d4e896e9e8be736a33cabecb9147ee191 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 3 Nov 2023 00:46:58 -0400 Subject: [PATCH 0376/1204] Fix coefficient matching failure message grammar --- pyomo/contrib/pyros/pyros_algorithm_methods.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index 5b234b150c8..df0d539a70d 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -222,7 +222,7 @@ def ROSolver_iterative_solve(model_data, config): config.progress_logger.error( f"Equality constraint {c.name!r} cannot be guaranteed to " "be robustly feasible, given the current partitioning " - "between first-stage, second-stage, and state variables. " + "among first-stage, second-stage, and state variables. " "Consider editing this constraint to reference some " "second-stage and/or state variable(s)." ) From a54bb122afff2c187ef93e88ee34f53c28e010ce Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 3 Nov 2023 10:43:45 -0600 Subject: [PATCH 0377/1204] Fixing a bug where we use Disjunct active status after we've transformed them, which is useless becuase we've deactivated them --- pyomo/gdp/plugins/hull.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 80ac55d45fe..35656b2ff0a 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -54,6 +54,7 @@ logger = logging.getLogger('pyomo.gdp.hull') +from pytest import set_trace @TransformationFactory.register( 'gdp.hull', doc="Relax disjunctive model by forming the hull reformulation." @@ -336,6 +337,10 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, "Disjunction '%s' with OR constraint. " "Must be an XOR!" % obj.name ) + # collect the Disjuncts we are going to transform now because we will + # change their active status when we transform them, but still need this + # list after the fact. + active_disjuncts = [disj for disj in obj.disjuncts if disj.active] # We put *all* transformed things on the parent Block of this # disjunction. We'll mark the disaggregated Vars as local, but beyond @@ -354,9 +359,7 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, # are going to disaggregate. var_order = ComponentSet() disjuncts_var_appears_in = ComponentMap() - for disjunct in obj.disjuncts: - if not disjunct.active: - continue + for disjunct in active_disjuncts: # create the key for each disjunct now transBlock._disaggregatedVarMap['disaggregatedVar'][ disjunct @@ -440,9 +443,7 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, # add the reaggregation constraints i = 0 - for disj in obj.disjuncts: - if not disj.active: - continue + for disj in active_disjuncts: for var in vars_to_disaggregate[disj]: # There are two cases here: Either the var appeared in every # disjunct in the disjunction, or it didn't. If it did, there's @@ -510,6 +511,9 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, cons_idx = len(disaggregationConstraint) # We always aggregate to the original var. If this is nested, this # constraint will be transformed again. + print("Adding disaggregation constraint for '%s' on Disjunction '%s' " + "to Block '%s'" % + (var, obj, disaggregationConstraint.parent_block())) disaggregationConstraint.add(cons_idx, var == disaggregatedExpr) # and update the map so that we can find this later. We index by # variable and the particular disjunction because there is a @@ -951,7 +955,7 @@ def get_disaggregation_constraint(self, original_var, disjunction, disjunction: a transformed Disjunction containing original_var """ for disjunct in disjunction.disjuncts: - transBlock = disjunct._transformation_block + transBlock = disjunct.transformation_block if transBlock is not None: break if transBlock is None: @@ -963,7 +967,7 @@ def get_disaggregation_constraint(self, original_var, disjunction, try: cons = ( - transBlock() + transBlock .parent_block() ._disaggregationConstraintMap[original_var][disjunction] ) From da7ee79045bfec48f4ba4b85b46827cb5b0c9f14 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 3 Nov 2023 12:58:00 -0600 Subject: [PATCH 0378/1204] Modifying APIs for getting transformed from original to account for the fact that constraints might get transformed multiple times. --- pyomo/gdp/plugins/hull.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 35656b2ff0a..b6e8065ba67 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -1011,10 +1011,30 @@ def get_var_bounds_constraint(self, v): logger.error(msg) raise try: - return transBlock._bigMConstraintMap[v] + cons = transBlock._bigMConstraintMap[v] except: logger.error(msg) raise + transformed_cons = {key: con for key, con in cons.items()} + def is_active(cons): + return all(c.active for c in cons.values()) + while not is_active(transformed_cons): + if 'lb' in transformed_cons: + transformed_cons['lb'] = self.get_transformed_constraints( + transformed_cons['lb'])[0] + if 'ub' in transformed_cons: + transformed_cons['ub'] = self.get_transformed_constraints( + transformed_cons['ub'])[0] + return transformed_cons + + def get_transformed_constraints(self, cons): + cons = super().get_transformed_constraints(cons) + while not cons[0].active: + transformed_cons = [] + for con in cons: + transformed_cons += super().get_transformed_constraints(con) + cons = transformed_cons + return cons @TransformationFactory.register( From 7fc03e1ce63c7888aa547f86f8c678e722869693 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 3 Nov 2023 12:58:16 -0600 Subject: [PATCH 0379/1204] Rewriting simple nested test --- pyomo/gdp/tests/test_hull.py | 276 ++++++++++++++++++++--------------- 1 file changed, 157 insertions(+), 119 deletions(-) diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index b8aa332174b..3ef57c73274 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -1551,154 +1551,184 @@ def check_transformed_constraint(self, cons, dis, lb, ind_var): def test_transformed_model_nestedDisjuncts(self): # This test tests *everything* for a simple nested disjunction case. m = models.makeNestedDisjunctions_NestedDisjuncts() - + m.LocalVars = Suffix(direction=Suffix.LOCAL) + m.LocalVars[m.d1] = [ + m.d1.binary_indicator_var, + m.d1.d3.binary_indicator_var, + m.d1.d4.binary_indicator_var + ] + hull = TransformationFactory('gdp.hull') hull.apply_to(m) transBlock = m._pyomo_gdp_hull_reformulation self.assertTrue(transBlock.active) - # outer xor should be on this block + # check outer xor xor = transBlock.disj_xor self.assertIsInstance(xor, Constraint) - self.assertTrue(xor.active) - self.assertEqual(xor.lower, 1) - self.assertEqual(xor.upper, 1) - repn = generate_standard_repn(xor.body) - self.assertTrue(repn.is_linear()) - self.assertEqual(repn.constant, 0) - ct.check_linear_coef(self, repn, m.d1.binary_indicator_var, 1) - ct.check_linear_coef(self, repn, m.d2.binary_indicator_var, 1) + ct.check_obj_in_active_tree(self, xor) + assertExpressionsEqual( + self, + xor.expr, + m.d1.binary_indicator_var + m.d2.binary_indicator_var == 1 + ) self.assertIs(xor, m.disj.algebraic_constraint) self.assertIs(m.disj, hull.get_src_disjunction(xor)) - # inner xor should be on this block + # check inner xor xor = m.d1.disj2.algebraic_constraint - self.assertIs(xor.parent_block(), transBlock) - self.assertIsInstance(xor, Constraint) - self.assertTrue(xor.active) - self.assertEqual(xor.lower, 0) - self.assertEqual(xor.upper, 0) - repn = generate_standard_repn(xor.body) - self.assertTrue(repn.is_linear()) - self.assertEqual(repn.constant, 0) - ct.check_linear_coef(self, repn, m.d1.d3.binary_indicator_var, 1) - ct.check_linear_coef(self, repn, m.d1.d4.binary_indicator_var, 1) - ct.check_linear_coef(self, repn, m.d1.binary_indicator_var, -1) self.assertIs(m.d1.disj2, hull.get_src_disjunction(xor)) - - # so should both disaggregation constraints - dis = transBlock.disaggregationConstraints - self.assertIsInstance(dis, Constraint) - self.assertTrue(dis.active) - self.assertEqual(len(dis), 2) - self.check_outer_disaggregation_constraint(dis[0], m.x, m.d1, m.d2) - self.assertIs(hull.get_disaggregation_constraint(m.x, m.disj), dis[0]) - self.check_outer_disaggregation_constraint( - dis[1], m.x, m.d1.d3, m.d1.d4, rhs=hull.get_disaggregated_var(m.x, m.d1) - ) - self.assertIs(hull.get_disaggregation_constraint(m.x, m.d1.disj2), dis[1]) - - # we should have four disjunct transformation blocks - disjBlocks = transBlock.relaxedDisjuncts - self.assertTrue(disjBlocks.active) - self.assertEqual(len(disjBlocks), 4) - - ## d1's transformation block - - disj1 = disjBlocks[0] - self.assertTrue(disj1.active) - self.assertIs(disj1, m.d1.transformation_block) - self.assertIs(m.d1, hull.get_src_disjunct(disj1)) - # check the disaggregated x is here - self.assertIsInstance(disj1.disaggregatedVars.x, Var) - self.assertEqual(disj1.disaggregatedVars.x.lb, 0) - self.assertEqual(disj1.disaggregatedVars.x.ub, 2) - self.assertIs(disj1.disaggregatedVars.x, hull.get_disaggregated_var(m.x, m.d1)) - self.assertIs(m.x, hull.get_src_var(disj1.disaggregatedVars.x)) - # check the bounds constraints - self.check_bounds_constraint_ub( - disj1.x_bounds, 2, disj1.disaggregatedVars.x, m.d1.indicator_var - ) - # transformed constraint x >= 1 - cons = hull.get_transformed_constraints(m.d1.c) - self.check_transformed_constraint( - cons, disj1.disaggregatedVars.x, 1, m.d1.indicator_var + xor = hull.get_transformed_constraints(xor) + self.assertEqual(len(xor), 1) + xor = xor[0] + ct.check_obj_in_active_tree(self, xor) + xor_expr = self.simplify_cons(xor) + assertExpressionsEqual( + self, + xor_expr, + m.d1.d3.binary_indicator_var + + m.d1.d4.binary_indicator_var - + m.d1.binary_indicator_var == 0.0 + ) + + # check disaggregation constraints + x_d3 = hull.get_disaggregated_var(m.x, m.d1.d3) + x_d4 = hull.get_disaggregated_var(m.x, m.d1.d4) + x_d1 = hull.get_disaggregated_var(m.x, m.d1) + x_d2 = hull.get_disaggregated_var(m.x, m.d2) + for x in [x_d1, x_d2, x_d3, x_d4]: + self.assertEqual(x.lb, 0) + self.assertEqual(x.ub, 2) + # Inner disjunction + cons = hull.get_disaggregation_constraint(m.x, m.d1.disj2) + ct.check_obj_in_active_tree(self, cons) + cons_expr = self.simplify_cons(cons) + assertExpressionsEqual( + self, + cons_expr, + x_d1 - x_d3 - x_d4 == 0.0 + ) + # Outer disjunction + cons = hull.get_disaggregation_constraint(m.x, m.disj) + ct.check_obj_in_active_tree(self, cons) + cons_expr = self.simplify_cons(cons) + assertExpressionsEqual( + self, + cons_expr, + m.x - x_d1 - x_d2 == 0.0 ) - ## d2's transformation block + ## Bound constraints - disj2 = disjBlocks[1] - self.assertTrue(disj2.active) - self.assertIs(disj2, m.d2.transformation_block) - self.assertIs(m.d2, hull.get_src_disjunct(disj2)) - # disaggregated var - x2 = disj2.disaggregatedVars.x - self.assertIsInstance(x2, Var) - self.assertEqual(x2.lb, 0) - self.assertEqual(x2.ub, 2) - self.assertIs(hull.get_disaggregated_var(m.x, m.d2), x2) - self.assertIs(hull.get_src_var(x2), m.x) - # bounds constraint - x_bounds = disj2.x_bounds - self.check_bounds_constraint_ub(x_bounds, 2, x2, m.d2.binary_indicator_var) - # transformed constraint x >= 1.1 - cons = hull.get_transformed_constraints(m.d2.c) - self.check_transformed_constraint(cons, x2, 1.1, m.d2.binary_indicator_var) - - ## d1.d3's transformation block - - disj3 = disjBlocks[2] - self.assertTrue(disj3.active) - self.assertIs(disj3, m.d1.d3.transformation_block) - self.assertIs(m.d1.d3, hull.get_src_disjunct(disj3)) - # disaggregated var - x3 = disj3.disaggregatedVars.x - self.assertIsInstance(x3, Var) - self.assertEqual(x3.lb, 0) - self.assertEqual(x3.ub, 2) - self.assertIs(hull.get_disaggregated_var(m.x, m.d1.d3), x3) - self.assertIs(hull.get_src_var(x3), m.x) - # bounds constraints - self.check_bounds_constraint_ub( - disj3.x_bounds, 2, x3, m.d1.d3.binary_indicator_var - ) - # transformed x >= 1.2 + ## Transformed constraints cons = hull.get_transformed_constraints(m.d1.d3.c) - self.check_transformed_constraint(cons, x3, 1.2, m.d1.d3.binary_indicator_var) - - ## d1.d4's transformation block - - disj4 = disjBlocks[3] - self.assertTrue(disj4.active) - self.assertIs(disj4, m.d1.d4.transformation_block) - self.assertIs(m.d1.d4, hull.get_src_disjunct(disj4)) - # disaggregated var - x4 = disj4.disaggregatedVars.x - self.assertIsInstance(x4, Var) - self.assertEqual(x4.lb, 0) - self.assertEqual(x4.ub, 2) - self.assertIs(hull.get_disaggregated_var(m.x, m.d1.d4), x4) - self.assertIs(hull.get_src_var(x4), m.x) - # bounds constraints - self.check_bounds_constraint_ub( - disj4.x_bounds, 2, x4, m.d1.d4.binary_indicator_var - ) - # transformed x >= 1.3 + self.assertEqual(len(cons), 1) + cons = cons[0] + ct.check_obj_in_active_tree(self, cons) + cons_expr = self.simplify_leq_cons(cons) + assertExpressionsEqual( + self, + cons_expr, + 1.2*m.d1.d3.binary_indicator_var - x_d3 <= 0.0 + ) + cons = hull.get_transformed_constraints(m.d1.d4.c) - self.check_transformed_constraint(cons, x4, 1.3, m.d1.d4.binary_indicator_var) + self.assertEqual(len(cons), 1) + cons = cons[0] + ct.check_obj_in_active_tree(self, cons) + cons_expr = self.simplify_leq_cons(cons) + assertExpressionsEqual( + self, + cons_expr, + 1.3*m.d1.d4.binary_indicator_var - x_d4 <= 0.0 + ) + + cons = hull.get_transformed_constraints(m.d1.c) + self.assertEqual(len(cons), 1) + cons = cons[0] + ct.check_obj_in_active_tree(self, cons) + cons_expr = self.simplify_leq_cons(cons) + assertExpressionsEqual( + self, + cons_expr, + 1.0*m.d1.binary_indicator_var - x_d1 <= 0.0 + ) + + cons = hull.get_transformed_constraints(m.d2.c) + self.assertEqual(len(cons), 1) + cons = cons[0] + ct.check_obj_in_active_tree(self, cons) + cons_expr = self.simplify_leq_cons(cons) + assertExpressionsEqual( + self, + cons_expr, + 1.1*m.d2.binary_indicator_var - x_d2 <= 0.0 + ) + + ## Bounds constraints + cons = hull.get_var_bounds_constraint(x_d1) + # the lb is trivial in this case, so we just have 1 + self.assertEqual(len(cons), 1) + ct.check_obj_in_active_tree(self, cons['ub']) + cons_expr = self.simplify_leq_cons(cons['ub']) + assertExpressionsEqual( + self, + cons_expr, + x_d1 - 2*m.d1.binary_indicator_var <= 0.0 + ) + cons = hull.get_var_bounds_constraint(x_d2) + # the lb is trivial in this case, so we just have 1 + self.assertEqual(len(cons), 1) + ct.check_obj_in_active_tree(self, cons['ub']) + cons_expr = self.simplify_leq_cons(cons['ub']) + assertExpressionsEqual( + self, + cons_expr, + x_d2 - 2*m.d2.binary_indicator_var <= 0.0 + ) + cons = hull.get_var_bounds_constraint(x_d3) + # the lb is trivial in this case, so we just have 1 + self.assertEqual(len(cons), 1) + ct.check_obj_in_active_tree(self, cons['ub']) + cons_expr = self.simplify_leq_cons(cons['ub']) + assertExpressionsEqual( + self, + cons_expr, + x_d3 - 2*m.d1.d3.binary_indicator_var <= 0.0 + ) + cons = hull.get_var_bounds_constraint(x_d4) + # the lb is trivial in this case, so we just have 1 + self.assertEqual(len(cons), 1) + ct.check_obj_in_active_tree(self, cons['ub']) + cons_expr = self.simplify_leq_cons(cons['ub']) + assertExpressionsEqual( + self, + cons_expr, + x_d4 - 2*m.d1.d4.binary_indicator_var <= 0.0 + ) @unittest.skipIf(not linear_solvers, "No linear solver available") def test_solve_nested_model(self): # This is really a test that our variable references have all been moved # up correctly. m = models.makeNestedDisjunctions_NestedDisjuncts() - + m.LocalVars = Suffix(direction=Suffix.LOCAL) + m.LocalVars[m.d1] = [ + m.d1.binary_indicator_var, + m.d1.d3.binary_indicator_var, + m.d1.d4.binary_indicator_var + ] hull = TransformationFactory('gdp.hull') m_hull = hull.create_using(m) SolverFactory(linear_solvers[0]).solve(m_hull) + print("MODEL") + for cons in m_hull.component_data_objects(Constraint, active=True, + descend_into=Block): + print(cons.expr) + # check solution self.assertEqual(value(m_hull.d1.binary_indicator_var), 0) self.assertEqual(value(m_hull.d2.binary_indicator_var), 1) @@ -1887,6 +1917,14 @@ def simplify_cons(self, cons): self.assertIsNone(repn.nonlinear) return repn.to_expression(visitor) == lb + def simplify_leq_cons(self, cons): + visitor = LinearRepnVisitor({}, {}, {}) + self.assertIsNone(cons.lower) + ub = cons.upper + repn = visitor.walk_expression(cons.body) + self.assertIsNone(repn.nonlinear) + return repn.to_expression(visitor) <= ub + def test_nested_with_var_that_skips_a_level(self): m = ConcreteModel() From 6dfe1917772df0a51cd060ff486d770ad1b9e298 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 3 Nov 2023 16:47:42 -0600 Subject: [PATCH 0380/1204] Removing a lot of unnecessary calls to value --- pyomo/contrib/fbbt/expression_bounds_walker.py | 7 ++++--- pyomo/contrib/fbbt/fbbt.py | 12 ++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/pyomo/contrib/fbbt/expression_bounds_walker.py b/pyomo/contrib/fbbt/expression_bounds_walker.py index 35cc33522ba..426d30f0ee6 100644 --- a/pyomo/contrib/fbbt/expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/expression_bounds_walker.py @@ -74,8 +74,8 @@ def _before_var(visitor, child): ) leaf_bounds[child] = (child.value, child.value) else: - lb = value(child.lb) - ub = value(child.ub) + lb = child.lb + ub = child.ub if lb is None: lb = -inf if ub is None: @@ -122,7 +122,8 @@ def _before_complex(visitor, child): @staticmethod def _before_npv(visitor, child): - return False, (value(child), value(child)) + val = value(child) + return False, (val, val) _before_child_handlers = ExpressionBoundsBeforeChildDispatcher() diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index dbdd992b9c8..db33c27dd96 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -957,8 +957,8 @@ def _check_and_reset_bounds(var, lb, ub): """ This function ensures that lb is not less than var.lb and that ub is not greater than var.ub. """ - orig_lb = value(var.lb) - orig_ub = value(var.ub) + orig_lb = var.lb + orig_ub = var.ub if orig_lb is None: orig_lb = -interval.inf if orig_ub is None: @@ -985,8 +985,8 @@ def _before_var(visitor, child): lb = value(child.value) ub = lb else: - lb = value(child.lb) - ub = value(child.ub) + lb = child.lb + ub = child.ub if lb is None: lb = -interval.inf if ub is None: @@ -1339,11 +1339,11 @@ def _fbbt_block(m, config): if v.lb is None: var_lbs[v] = -interval.inf else: - var_lbs[v] = value(v.lb) + var_lbs[v] = v.lb if v.ub is None: var_ubs[v] = interval.inf else: - var_ubs[v] = value(v.ub) + var_ubs[v] = v.ub var_to_con_map[v].append(c) n_cons += 1 From a3fa19b24334bdf713cf984a4778e97a7a9ae15f Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 5 Nov 2023 08:30:41 -0700 Subject: [PATCH 0381/1204] Adding linear presolve to NL writer --- pyomo/repn/plugins/nl_writer.py | 485 +++++++++++++++++++---------- pyomo/repn/tests/ampl/test_nlv2.py | 126 ++++++++ 2 files changed, 451 insertions(+), 160 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 31bc186f457..2ff83f5e20b 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -12,7 +12,7 @@ import itertools import logging import os -from collections import deque +from collections import deque, defaultdict from operator import itemgetter, attrgetter, setitem from contextlib import nullcontext @@ -24,7 +24,7 @@ document_kwargs_from_configdict, ) from pyomo.common.deprecation import deprecation_warning -from pyomo.common.errors import DeveloperError +from pyomo.common.errors import DeveloperError, InfeasibleConstraintException from pyomo.common.gc_manager import PauseGC from pyomo.common.numeric_types import ( native_complex_types, @@ -156,15 +156,27 @@ class NLWriterInfo(object): file in the same order as the :py:attr:`variables` and generated .col file. + eliminated_vars: List[Tuple[_VarData, NumericExpression]] + + The list of variables in the model that were eliminated by the + presolve. each entry is a 2-tuple of (:py:class:`_VarData`, + :py:class`NumericExpression`|`float`). the list is ordered in + the necessary order for correct evaluation (i.e., all variables + appearing in the expression must either have been sent to the + solver, or appear *earlier* in this list. + """ - def __init__(self, var, con, obj, extlib, row_lbl, col_lbl): + def __init__( + self, var, con, obj, external_libs, row_labels, col_labels, eliminated_vars + ): self.variables = var self.constraints = con self.objectives = obj - self.external_function_libraries = extlib - self.row_labels = row_lbl - self.column_labels = col_lbl + self.external_function_libraries = external_libs + self.row_labels = row_labels + self.column_labels = col_labels + self.eliminated_vars = eliminated_vars @WriterFactory.register('nl_v2', 'Generate the corresponding AMPL NL file (version 2).') @@ -273,6 +285,17 @@ class NLWriter(object): variables'.""", ), ) + CONFIG.declare( + 'linear_presolve', + ConfigValue( + default=False, + domain=bool, + description='Perform linear presolve', + doc=""" + If True, we will perform a basic linear presolve by performing + variable elimination (without fill-in).""", + ), + ) def __init__(self): self.config = self.CONFIG() @@ -354,20 +377,6 @@ def _generate_symbol_map(self, info): return symbol_map -def _RANGE_TYPE(lb, ub): - if lb == ub: - if lb is None: - return 3 # -inf <= c <= inf - else: - return 4 # L == c == U - elif lb is None: - return 1 # c <= U - elif ub is None: - return 2 # L <= c - else: - return 0 # L <= c <= U - - class _SuffixData(object): def __init__(self, name): self.name = name @@ -545,6 +554,7 @@ def write(self, model): symbolic_solver_labels = self.symbolic_solver_labels visitor = self.visitor ostream = self.ostream + linear_presolve = self.config.linear_presolve var_map = self.var_map initialize_var_map_from_column_order(model, self.config, var_map) @@ -574,6 +584,17 @@ def write(self, model): else: scaling_factor = _NoScalingFactor() + # + # Data structures to support presolve + # + # con_by_linear_nnz stores all constraints grouped by the NNZ + # in the linear portion of the expression. The value is another + # dict mapping id(con) to constraint info + con_by_linear_nnz = defaultdict(dict) + # con_by_linear_var maps id(var) to lists of constraint infos + # that have that var in the linear portion of the expression + con_by_linear_var = defaultdict(list) + # # Tabulate the model expressions # @@ -584,13 +605,17 @@ def write(self, model): if with_debug_timing and obj.parent_component() is not last_parent: timer.toc('Objective %s', last_parent, level=logging.DEBUG) last_parent = obj.parent_component() - expr = visitor.walk_expression((obj.expr, obj, 1, scaling_factor(obj))) - if expr.named_exprs: - self._record_named_expression_usage(expr.named_exprs, obj, 1) - if expr.nonlinear: - objectives.append((obj, expr)) + expr_info = visitor.walk_expression((obj.expr, obj, 1, scaling_factor(obj))) + if expr_info.named_exprs: + self._record_named_expression_usage(expr_info.named_exprs, obj, 1) + if expr_info.nonlinear: + objectives.append((obj, expr_info)) else: - linear_objs.append((obj, expr)) + linear_objs.append((obj, expr_info)) + if linear_presolve: + obj_id = id(obj) + for _id in expr_info.linear: + con_by_linear_var[_id].append((obj_id, expr_info)) if with_debug_timing: # report the last objective timer.toc('Objective %s', last_parent, level=logging.DEBUG) @@ -612,91 +637,192 @@ def write(self, model): # required for solvers like PATH. n_complementarity_range = 0 n_complementarity_nz_var_lb = 0 + # for con in ordered_active_constraints(model, self.config): if with_debug_timing and con.parent_component() is not last_parent: timer.toc('Constraint %s', last_parent, level=logging.DEBUG) last_parent = con.parent_component() scale = scaling_factor(con) - expr = visitor.walk_expression((con.body, con, 0, scale)) - if expr.named_exprs: - self._record_named_expression_usage(expr.named_exprs, con, 0) + expr_info = visitor.walk_expression((con.body, con, 0, scale)) + if expr_info.named_exprs: + self._record_named_expression_usage(expr_info.named_exprs, con, 0) # Note: Constraint.lb/ub guarantee a return value that is # either a (finite) native_numeric_type, or None - const = expr.const lb = con.lb ub = con.ub + if lb is None and ub is None: # and self.config.skip_trivial_constraints: + continue if scale != 1: if lb is not None: - lb = repr(lb * scale - const) + lb = lb * scale if ub is not None: - ub = repr(ub * scale - const) + ub = ub * scale if scale < 0: lb, ub = ub, lb - else: - if lb is not None: - lb = repr(lb - const) - if ub is not None: - ub = repr(ub - const) - _type = _RANGE_TYPE(lb, ub) - if _type == 4: - n_equality += 1 - elif _type == 0: - n_ranges += 1 - elif _type == 3: # and self.config.skip_trivial_constraints: - continue - # FIXME: this is a HACK to be compatible with the NLv1 - # writer. In the future, this writer should be expanded to - # look for and process Complementarity components (assuming - # that they are in an acceptable form). - if hasattr(con, '_complementarity'): - _type = 5 - # we are going to pass the complementarity type and the - # corresponding variable id() as the "lb" and "ub" for - # the range. - lb = con._complementarity - ub = con._vid - if expr.nonlinear: - n_complementarity_nonlin += 1 - else: - n_complementarity_lin += 1 - if expr.nonlinear: - constraints.append((con, expr, _type, lb, ub)) - elif expr.linear: - linear_cons.append((con, expr, _type, lb, ub)) + if expr_info.nonlinear: + constraints.append((con, expr_info, lb, ub)) + elif expr_info.linear: + linear_cons.append((con, expr_info, lb, ub)) elif not self.config.skip_trivial_constraints: - linear_cons.append((con, expr, _type, lb, ub)) - else: - # constant constraint and skip_trivial_constraints - # - # TODO: skip_trivial_constraints should be an - # enum that also accepts "Exception" so that - # solvers can be (easily) notified of infeasible - # trivial constraints. - if (lb is not None and float(lb) > TOL) or ( - ub is not None and float(ub) < -TOL + linear_cons.append((con, expr_info, lb, ub)) + else: # constant constraint and skip_trivial_constraints + c = expr_info.const + if (lb is not None and lb - c > TOL) or ( + ub is not None and ub - c < -TOL ): - logger.warning( + raise InfeasibleConstraintException( "model contains a trivially infeasible " - f"constraint {con.name}, but " - "skip_trivial_constraints==True and the " - "constraint is being omitted from the NL " - "file. Solving the model may incorrectly " - "report a feasible solution." + f"constraint '{con.name}' (fixed body value " + f"{c} outside bounds [{lb}, {ub}])." ) + if linear_presolve: + con_id = id(con) + if not expr_info.nonlinear and lb == ub and lb is not None: + con_by_linear_nnz[len(expr_info.linear)][con_id] = expr_info, lb + for _id in expr_info.linear: + con_by_linear_var[_id].append((con_id, expr_info)) if with_debug_timing: # report the last constraint timer.toc('Constraint %s', last_parent, level=logging.DEBUG) + # This may fetch more bounds than needed, but only in the cases + # where variables were completely eliminated while walking the + # expressions, and when users provide superfluous variables in + # the column ordering. + var_bounds = {_id: v.bounds for _id, v in var_map.items()} + + substitutions_by_linear_var = defaultdict(set) + eliminated_vars = {} + eliminated_cons = set() + if linear_presolve: + template = self.template + one_var = con_by_linear_nnz[1] + two_var = con_by_linear_nnz[2] + while 1: + if one_var: + con_id, info = one_var.popitem() + expr_info, lb = info + _id, coef = expr_info.linear.popitem() + # replacing _id with a*x + b + a = x = None + b = expr_info.const = (lb - expr_info.const) / coef + print(f"PRESOLVE: {var_map[_id]} := {expr_info.const}") + eliminated_vars[_id] = expr_info # , nl=(template.const % b, ()) + lb, ub = var_bounds[_id] + if (lb is not None and lb - b > TOL) or ( + ub is not None and ub - b < -TOL + ): + raise InfeasibleConstraintException( + "model contains a trivially infeasible variable " + f"'{var_map[_id].name}' (presolved to a value of " + f"{b} outside bounds [{lb}, {ub}])." + ) + elif two_var: + con_id, info = two_var.popitem() + expr_info, lb = info + _id, coef = expr_info.linear.popitem() + id2, coef2 = expr_info.linear.popitem() + # For no particularly good reason, we will solve for + # (and substitute out) the variable with the smaller + # magnitude) + if abs(coef2) < abs(coef): + _id, id2 = id2, _id + coef, coef2 = coef2, coef + # replacing _id with a*x + b + a = -coef2 / coef + x = id2 + b = expr_info.const = (lb - expr_info.const) / coef + expr_info.linear[x] = a + substitutions_by_linear_var[x].add(_id) + eliminated_vars[_id] = expr_info + print( + f"PRESOLVE: {var_map[_id]} := {expr_info.const} + {a}*{var_map[x]}" + ) + # repn=expr_info, + # nl=( + # template.binary_sum + # + template.product + # + (template.const % a) + # + template.var + # + (template.const % b), + # (x,), + # ) + # ) + # Tighten variable bounds + x_lb, x_ub = var_bounds[x] + lb, ub = var_bounds[_id] + if lb is not None: + lb = (lb - b) / a + if ub is not None: + ub = (ub - b) / a + if a < 0: + lb, ub = ub, lb + if x_lb is None or lb > x_lb: + x_lb = lb + if x_ub is None or ub < x_ub: + x_ub = ub + var_bounds[x] = x_lb, x_ub + else: + del con_by_linear_nnz + del con_by_linear_var + break + eliminated_cons.add(con_id) + for con_id, expr_info in con_by_linear_var[_id]: + # Note that if we were aggregating (i.e., _id was + # from two_var), then one of these info's will be + # for the constraint we just eliminated. In this + # case, _id will no longer be in expr_info.linear - so c + # will be 0 - thereby preventing us from re-updating + # the expression. We still want it to persist so + # that if later substitutions replace x with + # something else, then the expr_info gets updated + # appropriately (that expr_info is persisting in the + # eliminated_vars dict - and we will use that to + # update other linear expressions later.) + c = expr_info.linear.pop(_id, 0) + expr_info.const += c * b + if x in expr_info.linear: + expr_info.linear[x] += c * a + elif a: + expr_info.linear[x] = c * a + # replacing _id with x... NNZ is not changing, + # but we need to remember that x is now part of + # this constraint + con_by_linear_var[x].append((con_id, expr_info)) + continue + # NNZ has been reduced by 1 + nnz = len(expr_info.linear) + _old = con_by_linear_nnz[nnz + 1] + if con_id in _old: + con_by_linear_nnz[nnz][con_id] = _old.pop(con_id) + # If variables were replaced by the variable that + # we are currently eliminating, then we need to update + # the representation of those variables + for resubst in substitutions_by_linear_var.pop(_id, ()): + expr_info = eliminated_vars[resubst] + c = expr_info.linear.pop(_id, 0) + expr_info.const += c * b + if x in expr_info.linear: + expr_info.linear[x] += c * a + elif a: + expr_info.linear[x] = c * a + # Order the constraints, moving all nonlinear constraints to # the beginning n_nonlinear_cons = len(constraints) - constraints.extend(linear_cons) + if eliminated_cons: + _removed = eliminated_cons.__contains__ + constraints.extend( + itertools.filterfalse(lambda c: _removed(id(c[0])), linear_cons) + ) + else: + constraints.extend(linear_cons) n_cons = len(constraints) # - # Collect constraints and objectives into the groupings - # necessary for AMPL + # Collect variables from constraints and objectives into the + # groupings necessary for AMPL # # For efficiency, we will do everything with ids (and not the # var objects themselves) @@ -739,6 +865,7 @@ def write(self, model): _id = id(_v) if _id not in var_map: var_map[_id] = _v + var_bounds[_id] = _v.bounds con_vars_nonlinear.add(_id) con_nnz = sum(con_nnz_by_var.values()) @@ -862,7 +989,7 @@ def write(self, model): level=logging.DEBUG, ) - # Fill in the variable list and update the new column order. + # Update the column order. # # Note that as we allow var_map to contain "known" variables # that are not needed in the NL file (and column_order was @@ -870,26 +997,6 @@ def write(self, model): # column_order to *just* contain the variables that we are # sending to the NL. self.column_order = column_order = {_id: i for i, _id in enumerate(variables)} - for idx, _id in enumerate(variables): - v = var_map[_id] - # Note: Var.bounds guarantees the values are either (finite) - # native_numeric_types or None - lb, ub = v.bounds - scale = scaling_factor(v) - if scale != 1: - if lb is not None: - lb = repr(lb * scale) - if ub is not None: - ub = repr(ub * scale) - if scale < 0: - lb, ub = ub, lb - else: - if lb is not None: - lb = repr(lb) - if ub is not None: - ub = repr(ub) - variables[idx] = (v, _id, _RANGE_TYPE(lb, ub), lb, ub) - timer.toc("Computed variable bounds", level=logging.DEBUG) # Collect all defined SOSConstraints on the model if component_map[SOSConstraint]: @@ -953,11 +1060,11 @@ def write(self, model): labeler(info[0]) for info in objectives ] row_comments = [f'\t#{lbl}' for lbl in row_labels] - col_labels = [labeler(info[0]) for info in variables] + col_labels = [labeler(var_map[_id]) for _id in variables] col_comments = [f'\t#{lbl}' for lbl in col_labels] self.var_id_to_nl = { - info[1]: f'v{var_idx}{col_comments[var_idx]}' - for var_idx, info in enumerate(variables) + _id: f'v{var_idx}{col_comments[var_idx]}' + for var_idx, _id in enumerate(variables) } # Write out the .row and .col data if self.rowstream is not None: @@ -970,19 +1077,59 @@ def write(self, model): row_labels = row_comments = [''] * (n_cons + n_objs) col_labels = col_comments = [''] * len(variables) self.var_id_to_nl = { - info[1]: f"v{var_idx}" for var_idx, info in enumerate(variables) + _id: f"v{var_idx}" for var_idx, _id in enumerate(variables) } + _vmap = self.var_id_to_nl if scaling_factor.scale: - _vmap = self.var_id_to_nl - for var_idx, info in enumerate(variables): - _id = info[1] + template = self.template + for var_idx, _id in enumerate(variables): scale = scaling_cache[_id] if scale != 1: _vmap[_id] = ( template.division + _vmap[_id] + '\n' + template.const % scale ).rstrip() + for _id, expr_info in eliminated_vars.items(): + nl, args, _ = expr_info.compile_repn(visitor) + _vmap[_id] = nl % args + + r_lines = [None] * n_cons + for idx, (con, expr_info, lb, ub) in enumerate(constraints): + if lb == ub: # TBD: should this be within tolerance? + if lb is None: # and self.config.skip_trivial_constraints: + # type = 3 # -inf <= c <= inf + r_lines[idx] = "3" + else: + # _type = 4 # L == c == U + r_lines[idx] = f"4 {lb - expr_info.const!r}" + n_equality += 1 + elif lb is None: + # _type = 1 # c <= U + r_lines[idx] = f"1 {ub - expr_info.const!r}" + elif ub is None: + # _type = 2 # L <= c + r_lines[idx] = f"2 {lb - expr_info.const!r}" + else: + # _type = 0 # L <= c <= U + r_lines[idx] = f"0 {lb - expr_info.const!r} {ub - expr_info.const!r}" + n_ranges += 1 + expr_info.const = 0 + # FIXME: this is a HACK to be compatible with the NLv1 + # writer. In the future, this writer should be expanded to + # look for and process Complementarity components (assuming + # that they are in an acceptable form). + if hasattr(con, '_complementarity'): + # _type = 5 + r_lines[idx] = f"5 {con._complementarity} {1+column_order[con._vid]}" + if expr_info.nonlinear: + n_complementarity_nonlin += 1 + else: + n_complementarity_lin += 1 + if symbolic_solver_labels: + for idx in range(len(constraints)): + r_lines[idx] += row_comments[idx] + timer.toc("Generated row/col labels & comments", level=logging.DEBUG) # @@ -1223,12 +1370,18 @@ def write(self, model): # constraints at the end (as their nonlinear expressions # are the constant 0). _expr = self.template.const % 0 - ostream.write( - _expr.join( - f'C{i}{row_comments[i]}\n' - for i in range(row_idx, len(constraints)) + if symbolic_solver_labels: + ostream.write( + _expr.join( + f'C{i}{row_comments[i]}\n' + for i in range(row_idx, len(constraints)) + ) ) - ) + else: + ostream.write( + _expr.join(f'C{i}\n' for i in range(row_idx, len(constraints))) + ) + # We know that there is at least one linear expression # (row_idx), so we can unconditionally emit the last "0 # expression": @@ -1292,12 +1445,12 @@ def write(self, model): # _init_lines = [ (var_idx, val if val.__class__ in int_float else float(val)) - for var_idx, val in enumerate(v[0].value for v in variables) + for var_idx, val in enumerate(var_map[_id].value for _id in variables) if val is not None ] if scaling_factor.scale: - for i, (var_idx, val) in _init_lines: - _init_lines[i] = (var_idx, val * scaling_cache[variables[var_idx][1]]) + for i, (var_idx, val) in enumerate(_init_lines): + _init_lines[i] = (var_idx, val * scaling_cache[variables[var_idx]]) ostream.write( 'x%d%s\n' % (len(_init_lines), "\t# initial guess" if symbolic_solver_labels else '') @@ -1320,23 +1473,9 @@ def write(self, model): else '', ) ) - for row_idx, info in enumerate(constraints): - i = info[2] - if i == 4: # == - ostream.write(f"4 {info[3]}{row_comments[row_idx]}\n") - elif i == 1: # body <= ub - ostream.write(f"1 {info[4]}{row_comments[row_idx]}\n") - elif i == 2: # lb <= body - ostream.write(f"2 {info[3]}{row_comments[row_idx]}\n") - elif i == 0: # lb <= body <= ub - ostream.write(f"0 {info[3]} {info[4]}{row_comments[row_idx]}\n") - elif i == 5: # complementarity - ostream.write( - f"5 {info[3]} {1+column_order[info[4]]}" - f"{row_comments[row_idx]}\n" - ) - else: # i == 3; unbounded - ostream.write(f"3{row_comments[row_idx]}\n") + ostream.write("\n".join(r_lines)) + if r_lines: + ostream.write("\n") # # "b" lines (variable bounds) @@ -1349,20 +1488,29 @@ def write(self, model): else '', ) ) - for var_idx, info in enumerate(variables): - # _bound_writer[info[2]](info, col_comments[var_idx]) - ### - i = info[2] - if i == 0: # lb <= body <= ub - ostream.write(f"0 {info[3]} {info[4]}{col_comments[var_idx]}\n") - elif i == 2: # lb <= body - ostream.write(f"2 {info[3]}{col_comments[var_idx]}\n") - elif i == 1: # body <= ub - ostream.write(f"1 {info[4]}{col_comments[var_idx]}\n") - elif i == 4: # == - ostream.write(f"4 {info[3]}{col_comments[var_idx]}\n") - else: # i == 3; unbounded - ostream.write(f"3{col_comments[var_idx]}\n") + for var_idx, _id in enumerate(variables): + lb, ub = var_bounds[_id] + if lb == ub: + if lb is None: # unbounded + ostream.write(f"3{col_comments[var_idx]}\n") + else: # == + if scaling_factor.scale: + lb *= scaling_factor(var_map[_id]) + ostream.write(f"4 {lb!r}{col_comments[var_idx]}\n") + elif lb is None: # var <= ub + if scaling_factor.scale: + ub *= scaling_factor(var_map[_id]) + ostream.write(f"1 {ub!r}{col_comments[var_idx]}\n") + elif ub is None: # lb <= body + if scaling_factor.scale: + lb *= scaling_factor(var_map[_id]) + ostream.write(f"2 {lb!r}{col_comments[var_idx]}\n") + else: # lb <= body <= ub + if scaling_factor.scale: + _sf = scaling_factor(var_map[_id]) + lb *= _sf + ub *= _sf + ostream.write(f"0 {lb!r} {ub!r}{col_comments[var_idx]}\n") # # "k" lines (column offsets in Jacobian NNZ) @@ -1377,8 +1525,8 @@ def write(self, model): ) ) ktot = 0 - for var_idx, info in enumerate(variables[:-1]): - ktot += con_nnz_by_var.get(info[1], 0) + for var_idx, _id in enumerate(variables[:-1]): + ktot += con_nnz_by_var.get(_id, 0) ostream.write(f"{ktot}\n") # @@ -1414,13 +1562,18 @@ def write(self, model): ostream.write(f'{column_order[_id]} {linear[_id]!r}\n') # Generate the return information + eliminated_vars = [ + (var_map[_id], expr_info) for _id, expr_info in eliminated_vars.items() + ] + eliminated_vars.reverse() info = NLWriterInfo( - [info[0] for info in variables], - [info[0] for info in constraints], - [info[0] for info in objectives], - sorted(amplfunc_libraries), - row_labels, - col_labels, + var=[var_map[_id] for _id in variables], + con=[info[0] for info in constraints], + obj=[info[0] for info in objectives], + external_libs=sorted(amplfunc_libraries), + row_labels=row_labels, + col_labels=col_labels, + eliminated_vars=eliminated_vars, ) timer.toc("Wrote NL stream", level=logging.DEBUG) timer.toc("Generated NL representation", delta=False) @@ -1486,8 +1639,10 @@ def _categorize_vars(self, comp_list, linear_by_comp): if expr_info.nonlinear: nonlinear_vars = set() for _id in expr_info.nonlinear[1]: + if _id in nonlinear_vars: + continue if _id in linear_by_comp: - nonlinear_vars.update(linear_by_comp[_id].keys()) + nonlinear_vars.update(linear_by_comp[_id]) else: nonlinear_vars.add(_id) # Recreate nz if this component has both linear and @@ -1645,6 +1800,16 @@ def __str__(self): def __repr__(self): return str(self) + def __eq__(self, other): + return other.__class__ is AMPLRepn and ( + self.nl == other.nl + and self.mult == other.mult + and self.const == other.const + and self.linear == other.linear + and self.nonlinear == other.nonlinear + and self.named_exprs == other.named_exprs + ) + def duplicate(self): ans = self.__class__.__new__(self.__class__) ans.nl = self.nl @@ -2506,10 +2671,10 @@ def cache_fixed_var(self, _id, child): val = self.check_constant(child.value, child) lb, ub = child.bounds if (lb is not None and lb - val > TOL) or (ub is not None and ub - val < -TOL): - raise InfeasibleError( + raise InfeasibleConstraintException( "model contains a trivially infeasible " f"variable '{child.name}' (fixed value " - f"{val} outside bounds [lb, ub])." + f"{val} outside bounds [{lb}, {ub}])." ) self.fixed_vars[_id] = self.check_constant(child.value, child) diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index bb18363ffda..dbc33f2ce4d 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1175,3 +1175,129 @@ def test_nonfloat_constants(self): OUT.getvalue(), ) ) + + def test_presolve_lower_triangular(self): + # This tests the example from issue #2827 + m = pyo.ConcreteModel() + m.x = pyo.Var(range(5), bounds=(-10, 10)) + m.obj = Objective(expr=m.x[3] + m.x[4]) + m.c = pyo.ConstraintList() + m.c.add(m.x[0] == 5) + m.c.add(2 * m.x[0] + 3 * m.x[2] == 19) + m.c.add(m.x[0] + 2 * m.x[2] - 2 * m.x[1] == 3) + m.c.add(-2 * m.x[0] + m.x[2] + m.x[1] - m.x[3] == 1) + + OUT = io.StringIO() + with LoggingIntercept() as LOG: + nlinfo = nl_writer.NLWriter().write(m, OUT, linear_presolve=True) + self.assertEqual(LOG.getvalue(), "") + + self.assertEqual( + nlinfo.eliminated_vars, + [ + (m.x[3], nl_writer.AMPLRepn(-4.0, {}, None)), + (m.x[1], nl_writer.AMPLRepn(4.0, {}, None)), + (m.x[2], nl_writer.AMPLRepn(3.0, {}, None)), + (m.x[0], nl_writer.AMPLRepn(5.0, {}, None)), + ], + ) + self.assertEqual( + *nl_diff( + """g3 1 1 0 # problem unknown + 1 0 1 0 0 # vars, constraints, objectives, ranges, eqns + 0 0 0 0 0 0 # nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb + 0 0 # network constraints: nonlinear, linear + 0 0 0 # nonlinear vars in constraints, objectives, both + 0 0 0 1 # linear network variables; functions; arith, flags + 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) + 0 1 # nonzeros in Jacobian, obj. gradient + 0 0 # max name lengths: constraints, variables + 0 0 0 0 0 # common exprs: b,c,o,c1,o1 +O0 0 +n-4.0 +x0 +r +b +0 -10 10 +k0 +G0 1 +0 1 +""", + OUT.getvalue(), + ) + ) + + def test_presolve_almost_lower_triangular(self): + # This tests the example from issue #2827 + m = pyo.ConcreteModel() + m.x = pyo.Var(range(5), bounds=(-10, 10)) + m.obj = Objective(expr=m.x[3] + m.x[4]) + m.c = pyo.ConstraintList() + m.c.add(m.x[0] + 2 * m.x[4] == 5) + m.c.add(2 * m.x[0] + 3 * m.x[2] == 19) + m.c.add(m.x[0] + 2 * m.x[2] - 2 * m.x[1] == 3) + m.c.add(-2 * m.x[0] + m.x[2] + m.x[1] - m.x[3] == 1) + + OUT = io.StringIO() + with LoggingIntercept() as LOG: + nlinfo = nl_writer.NLWriter().write(m, OUT, linear_presolve=True) + self.assertEqual(LOG.getvalue(), "") + + self.assertEqual( + nlinfo.eliminated_vars, + [ + (m.x[4], nl_writer.AMPLRepn(-12, {id(m.x[1]): 3}, None)), + (m.x[3], nl_writer.AMPLRepn(-72, {id(m.x[1]): 17}, None)), + (m.x[2], nl_writer.AMPLRepn(-13, {id(m.x[1]): 4}, None)), + (m.x[0], nl_writer.AMPLRepn(29, {id(m.x[1]): -6}, None)), + ], + ) + # Note: bounds on x[1] are: + # min(22/3, 82/17, 23/4, -39/-6) == 4.823529411764706 + # max(2/3, 62/17, 3/4, -19/-6) == 3.6470588235294117 + self.assertEqual( + *nl_diff( + """g3 1 1 0 # problem unknown + 1 0 1 0 0 # vars, constraints, objectives, ranges, eqns + 0 0 0 0 0 0 # nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb + 0 0 # network constraints: nonlinear, linear + 0 0 0 # nonlinear vars in constraints, objectives, both + 0 0 0 1 # linear network variables; functions; arith, flags + 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) + 0 1 # nonzeros in Jacobian, obj. gradient + 0 0 # max name lengths: constraints, variables + 0 0 0 0 0 # common exprs: b,c,o,c1,o1 +O0 0 +n-84.0 +x0 +r +b +0 3.6470588235294117 4.823529411764706 +k0 +G0 1 +0 20 +""", + OUT.getvalue(), + ) + ) + + def test_presolve_lower_triangular_out_of_bounds(self): + # This tests the example from issue #2827 + m = pyo.ConcreteModel() + m.x = pyo.Var(range(5), domain=pyo.NonNegativeReals) + m.obj = Objective(expr=m.x[3] + m.x[4]) + m.c = pyo.ConstraintList() + m.c.add(m.x[0] == 5) + m.c.add(2 * m.x[0] + 3 * m.x[2] == 19) + m.c.add(m.x[0] + 2 * m.x[2] - 2 * m.x[1] == 3) + m.c.add(-2 * m.x[0] + m.x[2] + m.x[1] - m.x[3] == 1) + + OUT = io.StringIO() + with self.assertRaisesRegex( + nl_writer.InfeasibleConstraintException, + r"model contains a trivially infeasible variable 'x\[3\]' " + r"\(presolved to a value of -4.0 outside bounds \[0, None\]\).", + ): + with LoggingIntercept() as LOG: + nlinfo = nl_writer.NLWriter().write(m, OUT, linear_presolve=True) + self.assertEqual(LOG.getvalue(), "") From 59235394b15409d0a5bd0003c2b495ce9dd93a7e Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 5 Nov 2023 15:22:13 -0700 Subject: [PATCH 0382/1204] Minor code cleanup [mostly NFC] --- pyomo/repn/plugins/nl_writer.py | 35 ++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 2ff83f5e20b..3405d8bf0bb 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -9,10 +9,10 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -import itertools import logging import os from collections import deque, defaultdict +from itertools import filterfalse, product from operator import itemgetter, attrgetter, setitem from contextlib import nullcontext @@ -419,11 +419,8 @@ def compile(self, column_order, row_order, obj_order, model_id): # component data is not in the original dictionary # of values that we extracted from the Suffixes queue.append( - itertools.product( - itertools.filterfalse( - self.values.__contains__, obj.values() - ), - (val,), + product( + filterfalse(self.values.__contains__, obj.values()), (val,) ) ) else: @@ -688,7 +685,7 @@ def write(self, model): # This may fetch more bounds than needed, but only in the cases # where variables were completely eliminated while walking the - # expressions, and when users provide superfluous variables in + # expressions, or when users provide superfluous variables in # the column ordering. var_bounds = {_id: v.bounds for _id, v in var_map.items()} @@ -704,10 +701,14 @@ def write(self, model): con_id, info = one_var.popitem() expr_info, lb = info _id, coef = expr_info.linear.popitem() - # replacing _id with a*x + b + # substituting _id with a*x + b a = x = None b = expr_info.const = (lb - expr_info.const) / coef - print(f"PRESOLVE: {var_map[_id]} := {expr_info.const}") + logger.debug( + "NL presolve: substituting %s := %s", + var_map[_id], + expr_info.const, + ) eliminated_vars[_id] = expr_info # , nl=(template.const % b, ()) lb, ub = var_bounds[_id] if (lb is not None and lb - b > TOL) or ( @@ -729,15 +730,19 @@ def write(self, model): if abs(coef2) < abs(coef): _id, id2 = id2, _id coef, coef2 = coef2, coef - # replacing _id with a*x + b + # substituting _id with a*x + b a = -coef2 / coef x = id2 b = expr_info.const = (lb - expr_info.const) / coef expr_info.linear[x] = a substitutions_by_linear_var[x].add(_id) eliminated_vars[_id] = expr_info - print( - f"PRESOLVE: {var_map[_id]} := {expr_info.const} + {a}*{var_map[x]}" + logger.debug( + "NL presolve: substituting %s := %s*%s + %s", + var_map[_id], + a, + var_map[x], + b, ) # repn=expr_info, # nl=( @@ -813,9 +818,7 @@ def write(self, model): n_nonlinear_cons = len(constraints) if eliminated_cons: _removed = eliminated_cons.__contains__ - constraints.extend( - itertools.filterfalse(lambda c: _removed(id(c[0])), linear_cons) - ) + constraints.extend(filterfalse(lambda c: _removed(id(c[0])), linear_cons)) else: constraints.extend(linear_cons) n_cons = len(constraints) @@ -1650,7 +1653,7 @@ def _categorize_vars(self, comp_list, linear_by_comp): if expr_info.linear: # Ensure any variables that only appear nonlinearly # in the expression have 0's in the linear dict - for i in nonlinear_vars - linear_vars: + for i in filterfalse(linear_vars.__contains__, nonlinear_vars): expr_info.linear[i] = 0 else: # All variables are nonlinear; generate the linear From 07171a481e6fe70493dbab6e1bc551781d41b2fb Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 5 Nov 2023 15:22:36 -0700 Subject: [PATCH 0383/1204] Fix presolving nonlinear expressions; add test --- pyomo/repn/plugins/nl_writer.py | 15 ++++-- pyomo/repn/tests/ampl/test_nlv2.py | 86 ++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 3405d8bf0bb..9d1f1905143 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -836,12 +836,21 @@ def write(self, model): filter(self.used_named_expressions.__contains__, self.subexpression_order) ) - # linear contribution by (constraint, objective) component. + # linear contribution by (constraint, objective, variable) component. # Keys are component id(), Values are dicts mapping variable # id() to linear coefficient. All nonzeros in the component # (variables appearing in the linear and/or nonlinear # subexpressions) will appear in the dict. - linear_by_comp = {} + # + # We initialize the map with any variables eliminated from + # (presolved out of) the model (necessary so that + # _categorize_vars will map eliminated vars to the current + # vars). Note that at the moment, we only consider linear + # equality constraints in the presolve. If that ever changes + # (e.g., to support eliminating variables appearing linearly in + # nonlinear equality constraints), then this logic will need to + # be revisited. + linear_by_comp = {_id: info.linear for _id, info in eliminated_vars.items()} # We need to categorize the named subexpressions first so that # we know their linear / nonlinear vars when we encounter them @@ -1095,7 +1104,7 @@ def write(self, model): for _id, expr_info in eliminated_vars.items(): nl, args, _ = expr_info.compile_repn(visitor) - _vmap[_id] = nl % args + _vmap[_id] = nl.rstrip() % tuple(_vmap[_id] for _id in args) r_lines = [None] * n_cons for idx, (con, expr_info, lb, ub) in enumerate(constraints): diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index dbc33f2ce4d..8721a5057ce 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1276,6 +1276,92 @@ def test_presolve_almost_lower_triangular(self): k0 G0 1 0 20 +""", + OUT.getvalue(), + ) + ) + + def test_presolve_almost_lower_triangular_nonlinear(self): + # This tests the example from issue #2827 + m = pyo.ConcreteModel() + m.x = pyo.Var(range(5), bounds=(-10, 10)) + m.obj = Objective(expr=m.x[3] + m.x[4] + pyo.log(m.x[0])) + m.c = pyo.ConstraintList() + m.c.add(m.x[0] + 2 * m.x[4] == 5) + m.c.add(2 * m.x[0] + 3 * m.x[2] == 19) + m.c.add(m.x[0] + 2 * m.x[2] - 2 * m.x[1] == 3) + m.c.add(-2 * m.x[0] + m.x[2] + m.x[1] - m.x[3] == 1) + m.c.add(2 * (m.x[0] ** 2) + m.x[0] + m.x[2] + 3 * (m.x[3] ** 3) == 10) + + OUT = io.StringIO() + with LoggingIntercept() as LOG: + nlinfo = nl_writer.NLWriter().write(m, OUT, linear_presolve=True) + self.assertEqual(LOG.getvalue(), "") + + self.assertEqual( + nlinfo.eliminated_vars, + [ + (m.x[4], nl_writer.AMPLRepn(-12, {id(m.x[1]): 3}, None)), + (m.x[3], nl_writer.AMPLRepn(-72, {id(m.x[1]): 17}, None)), + (m.x[2], nl_writer.AMPLRepn(-13, {id(m.x[1]): 4}, None)), + (m.x[0], nl_writer.AMPLRepn(29, {id(m.x[1]): -6}, None)), + ], + ) + # Note: bounds on x[1] are: + # min(22/3, 82/17, 23/4, -39/-6) == 4.823529411764706 + # max(2/3, 62/17, 3/4, -19/-6) == 3.6470588235294117 + print(OUT.getvalue()) + self.assertEqual( + *nl_diff( + """g3 1 1 0 # problem unknown + 1 1 1 0 1 # vars, constraints, objectives, ranges, eqns + 1 1 0 0 0 0 # nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb + 0 0 # network constraints: nonlinear, linear + 1 1 1 # nonlinear vars in constraints, objectives, both + 0 0 0 1 # linear network variables; functions; arith, flags + 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) + 1 1 # nonzeros in Jacobian, obj. gradient + 0 0 # max name lengths: constraints, variables + 0 0 0 0 0 # common exprs: b,c,o,c1,o1 +C0 +o0 +o2 +n2 +o5 +o0 +o2 +n-6.0 +v0 +n29.0 +n2 +o2 +n3 +o5 +o0 +o2 +n17.0 +v0 +n-72.0 +n3 +O0 0 +o0 +o43 +o0 +o2 +n-6.0 +v0 +n29.0 +n-84.0 +x0 +r +4 -6.0 +b +0 3.6470588235294117 4.823529411764706 +k0 +J0 1 +0 -2.0 +G0 1 +0 20.0 """, OUT.getvalue(), ) From 4a310b04bdf8a48ec3707d5c12ff44501eade406 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 5 Nov 2023 16:19:19 -0700 Subject: [PATCH 0384/1204] Moving presolve to a separate method --- pyomo/repn/plugins/nl_writer.py | 274 +++++++++++++++++--------------- 1 file changed, 142 insertions(+), 132 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 9d1f1905143..fd3058bbf40 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -574,6 +574,9 @@ def write(self, model): suffix_data[name].update(suffix) timer.toc("Collected suffixes", level=logging.DEBUG) + # + # Data structures to support variable/constraint scaling + # if self.config.scale_model and 'scaling_factor' in suffix_data: scaling_factor = CachingNumericSuffixFinder('scaling_factor', 1) scaling_cache = scaling_factor.scaling_cache @@ -584,13 +587,14 @@ def write(self, model): # # Data structures to support presolve # - # con_by_linear_nnz stores all constraints grouped by the NNZ + # lcon_by_linear_nnz stores all linear constraints grouped by the NNZ # in the linear portion of the expression. The value is another # dict mapping id(con) to constraint info - con_by_linear_nnz = defaultdict(dict) - # con_by_linear_var maps id(var) to lists of constraint infos - # that have that var in the linear portion of the expression - con_by_linear_var = defaultdict(list) + lcon_by_linear_nnz = defaultdict(dict) + # comp_by_linear_var maps id(var) to lists of constraint / + # object infos that have that var in the linear portion of the + # expression + comp_by_linear_var = defaultdict(list) # # Tabulate the model expressions @@ -612,7 +616,7 @@ def write(self, model): if linear_presolve: obj_id = id(obj) for _id in expr_info.linear: - con_by_linear_var[_id].append((obj_id, expr_info)) + comp_by_linear_var[_id].append((obj_id, expr_info)) if with_debug_timing: # report the last objective timer.toc('Objective %s', last_parent, level=logging.DEBUG) @@ -676,9 +680,9 @@ def write(self, model): if linear_presolve: con_id = id(con) if not expr_info.nonlinear and lb == ub and lb is not None: - con_by_linear_nnz[len(expr_info.linear)][con_id] = expr_info, lb + lcon_by_linear_nnz[len(expr_info.linear)][con_id] = expr_info, lb for _id in expr_info.linear: - con_by_linear_var[_id].append((con_id, expr_info)) + comp_by_linear_var[_id].append((con_id, expr_info)) if with_debug_timing: # report the last constraint timer.toc('Constraint %s', last_parent, level=logging.DEBUG) @@ -689,129 +693,11 @@ def write(self, model): # the column ordering. var_bounds = {_id: v.bounds for _id, v in var_map.items()} - substitutions_by_linear_var = defaultdict(set) - eliminated_vars = {} - eliminated_cons = set() - if linear_presolve: - template = self.template - one_var = con_by_linear_nnz[1] - two_var = con_by_linear_nnz[2] - while 1: - if one_var: - con_id, info = one_var.popitem() - expr_info, lb = info - _id, coef = expr_info.linear.popitem() - # substituting _id with a*x + b - a = x = None - b = expr_info.const = (lb - expr_info.const) / coef - logger.debug( - "NL presolve: substituting %s := %s", - var_map[_id], - expr_info.const, - ) - eliminated_vars[_id] = expr_info # , nl=(template.const % b, ()) - lb, ub = var_bounds[_id] - if (lb is not None and lb - b > TOL) or ( - ub is not None and ub - b < -TOL - ): - raise InfeasibleConstraintException( - "model contains a trivially infeasible variable " - f"'{var_map[_id].name}' (presolved to a value of " - f"{b} outside bounds [{lb}, {ub}])." - ) - elif two_var: - con_id, info = two_var.popitem() - expr_info, lb = info - _id, coef = expr_info.linear.popitem() - id2, coef2 = expr_info.linear.popitem() - # For no particularly good reason, we will solve for - # (and substitute out) the variable with the smaller - # magnitude) - if abs(coef2) < abs(coef): - _id, id2 = id2, _id - coef, coef2 = coef2, coef - # substituting _id with a*x + b - a = -coef2 / coef - x = id2 - b = expr_info.const = (lb - expr_info.const) / coef - expr_info.linear[x] = a - substitutions_by_linear_var[x].add(_id) - eliminated_vars[_id] = expr_info - logger.debug( - "NL presolve: substituting %s := %s*%s + %s", - var_map[_id], - a, - var_map[x], - b, - ) - # repn=expr_info, - # nl=( - # template.binary_sum - # + template.product - # + (template.const % a) - # + template.var - # + (template.const % b), - # (x,), - # ) - # ) - # Tighten variable bounds - x_lb, x_ub = var_bounds[x] - lb, ub = var_bounds[_id] - if lb is not None: - lb = (lb - b) / a - if ub is not None: - ub = (ub - b) / a - if a < 0: - lb, ub = ub, lb - if x_lb is None or lb > x_lb: - x_lb = lb - if x_ub is None or ub < x_ub: - x_ub = ub - var_bounds[x] = x_lb, x_ub - else: - del con_by_linear_nnz - del con_by_linear_var - break - eliminated_cons.add(con_id) - for con_id, expr_info in con_by_linear_var[_id]: - # Note that if we were aggregating (i.e., _id was - # from two_var), then one of these info's will be - # for the constraint we just eliminated. In this - # case, _id will no longer be in expr_info.linear - so c - # will be 0 - thereby preventing us from re-updating - # the expression. We still want it to persist so - # that if later substitutions replace x with - # something else, then the expr_info gets updated - # appropriately (that expr_info is persisting in the - # eliminated_vars dict - and we will use that to - # update other linear expressions later.) - c = expr_info.linear.pop(_id, 0) - expr_info.const += c * b - if x in expr_info.linear: - expr_info.linear[x] += c * a - elif a: - expr_info.linear[x] = c * a - # replacing _id with x... NNZ is not changing, - # but we need to remember that x is now part of - # this constraint - con_by_linear_var[x].append((con_id, expr_info)) - continue - # NNZ has been reduced by 1 - nnz = len(expr_info.linear) - _old = con_by_linear_nnz[nnz + 1] - if con_id in _old: - con_by_linear_nnz[nnz][con_id] = _old.pop(con_id) - # If variables were replaced by the variable that - # we are currently eliminating, then we need to update - # the representation of those variables - for resubst in substitutions_by_linear_var.pop(_id, ()): - expr_info = eliminated_vars[resubst] - c = expr_info.linear.pop(_id, 0) - expr_info.const += c * b - if x in expr_info.linear: - expr_info.linear[x] += c * a - elif a: - expr_info.linear[x] = c * a + eliminated_cons, eliminated_vars = self._linear_presolve( + comp_by_linear_var, lcon_by_linear_nnz, var_bounds + ) + del comp_by_linear_var + del lcon_by_linear_nnz # Order the constraints, moving all nonlinear constraints to # the beginning @@ -1109,7 +995,7 @@ def write(self, model): r_lines = [None] * n_cons for idx, (con, expr_info, lb, ub) in enumerate(constraints): if lb == ub: # TBD: should this be within tolerance? - if lb is None: # and self.config.skip_trivial_constraints: + if lb is None: # type = 3 # -inf <= c <= inf r_lines[idx] = "3" else: @@ -1715,6 +1601,130 @@ def _count_subexpression_occurrences(self): n_subexpressions[0] += 1 return n_subexpressions + def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): + eliminated_vars = {} + eliminated_cons = set() + if not self.config.linear_presolve: + return eliminated_cons, eliminated_vars + + var_map = self.var_map + substitutions_by_linear_var = defaultdict(set) + template = self.template + one_var = lcon_by_linear_nnz[1] + two_var = lcon_by_linear_nnz[2] + while 1: + if one_var: + con_id, info = one_var.popitem() + expr_info, lb = info + _id, coef = expr_info.linear.popitem() + # substituting _id with a*x + b + a = x = None + b = expr_info.const = (lb - expr_info.const) / coef + logger.debug( + "NL presolve: substituting %s := %s", var_map[_id], expr_info.const + ) + eliminated_vars[_id] = expr_info # , nl=(template.const % b, ()) + lb, ub = var_bounds[_id] + if (lb is not None and lb - b > TOL) or ( + ub is not None and ub - b < -TOL + ): + raise InfeasibleConstraintException( + "model contains a trivially infeasible variable " + f"'{var_map[_id].name}' (presolved to a value of " + f"{b} outside bounds [{lb}, {ub}])." + ) + elif two_var: + con_id, info = two_var.popitem() + expr_info, lb = info + _id, coef = expr_info.linear.popitem() + id2, coef2 = expr_info.linear.popitem() + # For no particularly good reason, we will solve for + # (and substitute out) the variable with the smaller + # magnitude) + if abs(coef2) < abs(coef): + _id, id2 = id2, _id + coef, coef2 = coef2, coef + # substituting _id with a*x + b + a = -coef2 / coef + x = id2 + b = expr_info.const = (lb - expr_info.const) / coef + expr_info.linear[x] = a + substitutions_by_linear_var[x].add(_id) + eliminated_vars[_id] = expr_info + logger.debug( + "NL presolve: substituting %s := %s*%s + %s", + var_map[_id], + a, + var_map[x], + b, + ) + # repn=expr_info, + # nl=( + # template.binary_sum + # + template.product + # + (template.const % a) + # + template.var + # + (template.const % b), + # (x,), + # ) + # ) + # Tighten variable bounds + x_lb, x_ub = var_bounds[x] + lb, ub = var_bounds[_id] + if lb is not None: + lb = (lb - b) / a + if ub is not None: + ub = (ub - b) / a + if a < 0: + lb, ub = ub, lb + if x_lb is None or lb > x_lb: + x_lb = lb + if x_ub is None or ub < x_ub: + x_ub = ub + var_bounds[x] = x_lb, x_ub + else: + return eliminated_cons, eliminated_vars + eliminated_cons.add(con_id) + for con_id, expr_info in comp_by_linear_var[_id]: + # Note that if we were aggregating (i.e., _id was + # from two_var), then one of these info's will be + # for the constraint we just eliminated. In this + # case, _id will no longer be in expr_info.linear - so c + # will be 0 - thereby preventing us from re-updating + # the expression. We still want it to persist so + # that if later substitutions replace x with + # something else, then the expr_info gets updated + # appropriately (that expr_info is persisting in the + # eliminated_vars dict - and we will use that to + # update other linear expressions later.) + c = expr_info.linear.pop(_id, 0) + expr_info.const += c * b + if x in expr_info.linear: + expr_info.linear[x] += c * a + elif a: + expr_info.linear[x] = c * a + # replacing _id with x... NNZ is not changing, + # but we need to remember that x is now part of + # this constraint + comp_by_linear_var[x].append((con_id, expr_info)) + continue + # NNZ has been reduced by 1 + nnz = len(expr_info.linear) + _old = lcon_by_linear_nnz[nnz + 1] + if con_id in _old: + lcon_by_linear_nnz[nnz][con_id] = _old.pop(con_id) + # If variables were replaced by the variable that + # we are currently eliminating, then we need to update + # the representation of those variables + for resubst in substitutions_by_linear_var.pop(_id, ()): + expr_info = eliminated_vars[resubst] + c = expr_info.linear.pop(_id, 0) + expr_info.const += c * b + if x in expr_info.linear: + expr_info.linear[x] += c * a + elif a: + expr_info.linear[x] = c * a + def _record_named_expression_usage(self, named_exprs, src, comp_type): self.used_named_expressions.update(named_exprs) src = id(src) From 8501cc27e84ec0800a667263400320ca0e8d3ef8 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 6 Nov 2023 18:57:21 -0700 Subject: [PATCH 0385/1204] presolve out variables fixed by bounds --- pyomo/repn/plugins/nl_writer.py | 13 ++++++-- pyomo/repn/tests/ampl/test_nlv2.py | 52 ++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index fd3058bbf40..12fabd391aa 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1607,13 +1607,21 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): if not self.config.linear_presolve: return eliminated_cons, eliminated_vars + fixed_vars = [ + _id for _id, (lb, ub) in var_bounds.items() if lb == ub and lb is not None + ] var_map = self.var_map substitutions_by_linear_var = defaultdict(set) template = self.template one_var = lcon_by_linear_nnz[1] two_var = lcon_by_linear_nnz[2] while 1: - if one_var: + if fixed_vars: + _id = fixed_vars.pop() + a = x = None + b, _ = var_bounds[_id] + eliminated_vars[_id] = AMPLRepn(b, {}, None) + elif one_var: con_id, info = one_var.popitem() expr_info, lb = info _id, coef = expr_info.linear.popitem() @@ -1633,6 +1641,7 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): f"'{var_map[_id].name}' (presolved to a value of " f"{b} outside bounds [{lb}, {ub}])." ) + eliminated_cons.add(con_id) elif two_var: con_id, info = two_var.popitem() expr_info, lb = info @@ -1682,9 +1691,9 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): if x_ub is None or ub < x_ub: x_ub = ub var_bounds[x] = x_lb, x_ub + eliminated_cons.add(con_id) else: return eliminated_cons, eliminated_vars - eliminated_cons.add(con_id) for con_id, expr_info in comp_by_linear_var[_id]: # Note that if we were aggregating (i.e., _id was # from two_var), then one of these info's will be diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 8721a5057ce..26db894f2f6 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1222,6 +1222,58 @@ def test_presolve_lower_triangular(self): k0 G0 1 0 1 +""", + OUT.getvalue(), + ) + ) + + def test_presolve_lower_triangular_fixed(self): + # This tests the example from issue #2827 + m = pyo.ConcreteModel() + m.x = pyo.Var(range(5), bounds=(-10, 10)) + m.obj = Objective(expr=m.x[3] + m.x[4]) + m.c = pyo.ConstraintList() + # m.c.add(m.x[0] == 5) + m.x[0].bounds = (5, 5) + m.c.add(2 * m.x[0] + 3 * m.x[2] == 19) + m.c.add(m.x[0] + 2 * m.x[2] - 2 * m.x[1] == 3) + m.c.add(-2 * m.x[0] + m.x[2] + m.x[1] - m.x[3] == 1) + + OUT = io.StringIO() + with LoggingIntercept() as LOG: + nlinfo = nl_writer.NLWriter().write(m, OUT, linear_presolve=True) + self.assertEqual(LOG.getvalue(), "") + + self.assertEqual( + nlinfo.eliminated_vars, + [ + (m.x[3], nl_writer.AMPLRepn(-4.0, {}, None)), + (m.x[1], nl_writer.AMPLRepn(4.0, {}, None)), + (m.x[2], nl_writer.AMPLRepn(3.0, {}, None)), + (m.x[0], nl_writer.AMPLRepn(5.0, {}, None)), + ], + ) + self.assertEqual( + *nl_diff( + """g3 1 1 0 # problem unknown + 1 0 1 0 0 # vars, constraints, objectives, ranges, eqns + 0 0 0 0 0 0 # nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb + 0 0 # network constraints: nonlinear, linear + 0 0 0 # nonlinear vars in constraints, objectives, both + 0 0 0 1 # linear network variables; functions; arith, flags + 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) + 0 1 # nonzeros in Jacobian, obj. gradient + 0 0 # max name lengths: constraints, variables + 0 0 0 0 0 # common exprs: b,c,o,c1,o1 +O0 0 +n-4.0 +x0 +r +b +0 -10 10 +k0 +G0 1 +0 1 """, OUT.getvalue(), ) From 3c462e1c8efac5780c60f6988d39de95b1c0a712 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 6 Nov 2023 18:57:49 -0700 Subject: [PATCH 0386/1204] bugfixes for scaling --- pyomo/repn/plugins/nl_writer.py | 10 +-- pyomo/repn/tests/ampl/test_nlv2.py | 140 +++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 5 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 12fabd391aa..af12e992acd 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -579,7 +579,7 @@ def write(self, model): # if self.config.scale_model and 'scaling_factor' in suffix_data: scaling_factor = CachingNumericSuffixFinder('scaling_factor', 1) - scaling_cache = scaling_factor.scaling_cache + scaling_cache = scaling_factor.suffix_cache del suffix_data['scaling_factor'] else: scaling_factor = _NoScalingFactor() @@ -982,7 +982,7 @@ def write(self, model): if scaling_factor.scale: template = self.template for var_idx, _id in enumerate(variables): - scale = scaling_cache[_id] + scale = scaling_factor(var_map[_id]) if scale != 1: _vmap[_id] = ( template.division + _vmap[_id] + '\n' + template.const % scale @@ -1320,11 +1320,11 @@ def write(self, model): "objectives. Assuming that the duals are computed " "against the first objective." ) - _obj_scale = scaling_cache[objectives[0][1]] + _obj_scale = scaling_cache[id(objectives[0][0])] else: _obj_scale = 1 - for _id in _data.con: - _data.con[_id] *= _obj_scale / scaling_cache[constraints[_id][1]] + for _id in data.con: + data.con[_id] *= _obj_scale / scaling_cache[id(constraints[_id][0])] if data.var: logger.warning("ignoring 'dual' suffix for Var types") if data.obj: diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 26db894f2f6..68135acde59 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1439,3 +1439,143 @@ def test_presolve_lower_triangular_out_of_bounds(self): with LoggingIntercept() as LOG: nlinfo = nl_writer.NLWriter().write(m, OUT, linear_presolve=True) self.assertEqual(LOG.getvalue(), "") + + def test_scaling(self): + m = pyo.ConcreteModel() + m.x = pyo.Var(initialize=0) + m.y = pyo.Var(initialize=0, bounds=(-1e5, 1e5)) + m.z = pyo.Var(initialize=0, bounds=(1e3, None)) + m.obj = pyo.Objective(expr=m.x**2 + (m.y - 50000) ** 2 + m.z) + m.c = pyo.ConstraintList() + m.c.add(100 * m.x + m.y / 100 >= 600) + m.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT_EXPORT) + + m.dual[m.c[1]] = 0.02 + + OUT = io.StringIO() + with LoggingIntercept() as LOG: + nlinfo = nl_writer.NLWriter().write(m, OUT, scale_model=False) + self.assertEqual(LOG.getvalue(), "") + + nl1 = OUT.getvalue() + self.assertEqual( + *nl_diff( + nl1, + """g3 1 1 0 # problem unknown + 3 1 1 0 0 # vars, constraints, objectives, ranges, eqns + 0 1 0 0 0 0 # nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb + 0 0 # network constraints: nonlinear, linear + 0 2 0 # nonlinear vars in constraints, objectives, both + 0 0 0 1 # linear network variables; functions; arith, flags + 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) + 2 3 # nonzeros in Jacobian, obj. gradient + 0 0 # max name lengths: constraints, variables + 0 0 0 0 0 # common exprs: b,c,o,c1,o1 +C0 +n0 +O0 0 +o0 +o5 +v0 +n2 +o5 +o0 +v1 +n-50000 +n2 +d1 +0 0.02 +x3 +0 0 +1 0 +2 0 +r +2 600 +b +3 +0 -100000.0 100000.0 +2 1000.0 +k2 +1 +2 +J0 2 +0 100 +1 0.01 +G0 3 +0 0 +1 0 +2 1 +""", + ) + ) + + m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) + m.scaling_factor[m.x] = 1 + m.scaling_factor[m.y] = 1 / 50000 + m.scaling_factor[m.z] = 1 / 1000 + m.scaling_factor[m.c[1]] = 1 / 10 + m.scaling_factor[m.obj] = 1 / 100 + + OUT = io.StringIO() + with LoggingIntercept() as LOG: + nlinfo = nl_writer.NLWriter().write(m, OUT, scale_model=True) + self.assertEqual(LOG.getvalue(), "") + + nl2 = OUT.getvalue() + self.assertEqual( + *nl_diff( + nl2, + """g3 1 1 0 # problem unknown + 3 1 1 0 0 # vars, constraints, objectives, ranges, eqns + 0 1 0 0 0 0 # nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb + 0 0 # network constraints: nonlinear, linear + 0 2 0 # nonlinear vars in constraints, objectives, both + 0 0 0 1 # linear network variables; functions; arith, flags + 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) + 2 3 # nonzeros in Jacobian, obj. gradient + 0 0 # max name lengths: constraints, variables + 0 0 0 0 0 # common exprs: b,c,o,c1,o1 +C0 +n0 +O0 0 +o2 +n0.01 +o0 +o5 +v0 +n2 +o5 +o0 +o3 +v1 +n2e-05 +n-50000 +n2 +d1 +0 0.002 +x3 +0 0 +1 0.0 +2 0.0 +r +2 60.0 +b +3 +0 -2.0 2.0 +2 1.0 +k2 +1 +2 +J0 2 +0 10.0 +1 50.0 +G0 3 +0 0.0 +1 0.0 +2 10.0 +""", + ) + ) + + # Debugging: this diffs the unscaled & scaled models + # self.assertEqual(*nl_diff(nl1, nl2)) From 95df631f57a6724984f0453a8784897fa811923f Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 6 Nov 2023 21:37:18 -0700 Subject: [PATCH 0387/1204] fix suffix usage from ExternalGreyBox --- pyomo/repn/plugins/nl_writer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index af12e992acd..0eddc54561a 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -401,6 +401,9 @@ def compile(self, column_order, row_order, obj_order, model_id): while queue: for obj, val in queue.pop(0): if val.__class__ not in int_float: + if isinstance(val, dict): + queue.append(val.items()) + continue val = float(val) _id = id(obj) if _id in column_order: From b596bd42fb2e96229922383214496e7ecf8f9f57 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 6 Nov 2023 21:40:39 -0700 Subject: [PATCH 0388/1204] NL presolve: resolve bugs with implicitly fixed variables --- pyomo/repn/plugins/nl_writer.py | 11 +++++-- pyomo/repn/tests/ampl/test_nlv2.py | 53 ++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 0eddc54561a..e485d3cc569 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1623,6 +1623,9 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): _id = fixed_vars.pop() a = x = None b, _ = var_bounds[_id] + logger.debug( + "NL presolve: bounds fixed %s := %s", var_map[_id], b + ) eliminated_vars[_id] = AMPLRepn(b, {}, None) elif one_var: con_id, info = one_var.popitem() @@ -1632,7 +1635,7 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): a = x = None b = expr_info.const = (lb - expr_info.const) / coef logger.debug( - "NL presolve: substituting %s := %s", var_map[_id], expr_info.const + "NL presolve: substituting %s := %s", var_map[_id], b ) eliminated_vars[_id] = expr_info # , nl=(template.const % b, ()) lb, ub = var_bounds[_id] @@ -1689,11 +1692,13 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): ub = (ub - b) / a if a < 0: lb, ub = ub, lb - if x_lb is None or lb > x_lb: + if x_lb is None or (lb is not None and lb > x_lb): x_lb = lb - if x_ub is None or ub < x_ub: + if x_ub is None or (ub is not None and ub < x_ub): x_ub = ub var_bounds[x] = x_lb, x_ub + if x_lb == x_ub: + fixed_vars.append(x) eliminated_cons.add(con_id) else: return eliminated_cons, eliminated_vars diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 68135acde59..d80008e569a 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1274,6 +1274,59 @@ def test_presolve_lower_triangular_fixed(self): k0 G0 1 0 1 +""", + OUT.getvalue(), + ) + ) + + def test_presolve_lower_triangular_implied(self): + m = pyo.ConcreteModel() + m.x = pyo.Var(range(6), bounds=(-10, 10)) + m.obj = Objective(expr=m.x[3] + m.x[4]) + m.c = pyo.ConstraintList() + m.c.add(m.x[0] == m.x[5]) + m.x[0].bounds = (None, 5) + m.x[5].bounds = (5, None) + m.c.add(2 * m.x[0] + 3 * m.x[2] == 19) + m.c.add(m.x[0] + 2 * m.x[2] - 2 * m.x[1] == 3) + m.c.add(-2 * m.x[0] + m.x[2] + m.x[1] - m.x[3] == 1) + + OUT = io.StringIO() + with LoggingIntercept() as LOG: + nlinfo = nl_writer.NLWriter().write(m, OUT, linear_presolve=True) + self.assertEqual(LOG.getvalue(), "") + + self.assertEqual( + nlinfo.eliminated_vars, + [ + (m.x[1], nl_writer.AMPLRepn(4.0, {}, None)), + (m.x[5], nl_writer.AMPLRepn(5.0, {}, None)), + (m.x[3], nl_writer.AMPLRepn(-4.0, {}, None)), + (m.x[2], nl_writer.AMPLRepn(3.0, {}, None)), + (m.x[0], nl_writer.AMPLRepn(5.0, {}, None)), + ], + ) + self.assertEqual( + *nl_diff( + """g3 1 1 0 # problem unknown + 1 0 1 0 0 # vars, constraints, objectives, ranges, eqns + 0 0 0 0 0 0 # nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb + 0 0 # network constraints: nonlinear, linear + 0 0 0 # nonlinear vars in constraints, objectives, both + 0 0 0 1 # linear network variables; functions; arith, flags + 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) + 0 1 # nonzeros in Jacobian, obj. gradient + 0 0 # max name lengths: constraints, variables + 0 0 0 0 0 # common exprs: b,c,o,c1,o1 +O0 0 +n-4.0 +x0 +r +b +0 -10 10 +k0 +G0 1 +0 1 """, OUT.getvalue(), ) From 2353d972f62c7a8d1a4a7af0468b43db065bf8d5 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 6 Nov 2023 21:41:12 -0700 Subject: [PATCH 0389/1204] NFC: remove outdated comments --- pyomo/repn/plugins/nl_writer.py | 12 +----------- pyomo/repn/tests/ampl/test_nlv2.py | 5 ----- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index e485d3cc569..4f5bc8f6176 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1637,7 +1637,7 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): logger.debug( "NL presolve: substituting %s := %s", var_map[_id], b ) - eliminated_vars[_id] = expr_info # , nl=(template.const % b, ()) + eliminated_vars[_id] = expr_info lb, ub = var_bounds[_id] if (lb is not None and lb - b > TOL) or ( ub is not None and ub - b < -TOL @@ -1673,16 +1673,6 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): var_map[x], b, ) - # repn=expr_info, - # nl=( - # template.binary_sum - # + template.product - # + (template.const % a) - # + template.var - # + (template.const % b), - # (x,), - # ) - # ) # Tighten variable bounds x_lb, x_ub = var_bounds[x] lb, ub = var_bounds[_id] diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index d80008e569a..b7caaa3d87a 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1177,7 +1177,6 @@ def test_nonfloat_constants(self): ) def test_presolve_lower_triangular(self): - # This tests the example from issue #2827 m = pyo.ConcreteModel() m.x = pyo.Var(range(5), bounds=(-10, 10)) m.obj = Objective(expr=m.x[3] + m.x[4]) @@ -1228,7 +1227,6 @@ def test_presolve_lower_triangular(self): ) def test_presolve_lower_triangular_fixed(self): - # This tests the example from issue #2827 m = pyo.ConcreteModel() m.x = pyo.Var(range(5), bounds=(-10, 10)) m.obj = Objective(expr=m.x[3] + m.x[4]) @@ -1333,7 +1331,6 @@ def test_presolve_lower_triangular_implied(self): ) def test_presolve_almost_lower_triangular(self): - # This tests the example from issue #2827 m = pyo.ConcreteModel() m.x = pyo.Var(range(5), bounds=(-10, 10)) m.obj = Objective(expr=m.x[3] + m.x[4]) @@ -1387,7 +1384,6 @@ def test_presolve_almost_lower_triangular(self): ) def test_presolve_almost_lower_triangular_nonlinear(self): - # This tests the example from issue #2827 m = pyo.ConcreteModel() m.x = pyo.Var(range(5), bounds=(-10, 10)) m.obj = Objective(expr=m.x[3] + m.x[4] + pyo.log(m.x[0])) @@ -1473,7 +1469,6 @@ def test_presolve_almost_lower_triangular_nonlinear(self): ) def test_presolve_lower_triangular_out_of_bounds(self): - # This tests the example from issue #2827 m = pyo.ConcreteModel() m.x = pyo.Var(range(5), domain=pyo.NonNegativeReals) m.obj = Objective(expr=m.x[3] + m.x[4]) From b60bfcbe223f6b743b31a5ec2051e25ced1a5e2e Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 7 Nov 2023 06:31:31 -0700 Subject: [PATCH 0390/1204] NFC: apply black --- pyomo/repn/plugins/nl_writer.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 4f5bc8f6176..80f46bce279 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1623,9 +1623,7 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): _id = fixed_vars.pop() a = x = None b, _ = var_bounds[_id] - logger.debug( - "NL presolve: bounds fixed %s := %s", var_map[_id], b - ) + logger.debug("NL presolve: bounds fixed %s := %s", var_map[_id], b) eliminated_vars[_id] = AMPLRepn(b, {}, None) elif one_var: con_id, info = one_var.popitem() @@ -1634,9 +1632,7 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): # substituting _id with a*x + b a = x = None b = expr_info.const = (lb - expr_info.const) / coef - logger.debug( - "NL presolve: substituting %s := %s", var_map[_id], b - ) + logger.debug("NL presolve: substituting %s := %s", var_map[_id], b) eliminated_vars[_id] = expr_info lb, ub = var_bounds[_id] if (lb is not None and lb - b > TOL) or ( From 3981f2f0cfd1ce76061cc40ad7336e8f2eca3b95 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Tue, 7 Nov 2023 14:53:01 -0600 Subject: [PATCH 0391/1204] updating the default behavior of failed templatization --- .../model_debugging/latex_printing.rst | 2 + pyomo/util/latex_printer.py | 29 ++++++++----- pyomo/util/tests/test_latex_printer.py | 41 +++++++++++++++++++ 3 files changed, 62 insertions(+), 10 deletions(-) diff --git a/doc/OnlineDocs/model_debugging/latex_printing.rst b/doc/OnlineDocs/model_debugging/latex_printing.rst index 63ecd09f950..99e66b2688c 100644 --- a/doc/OnlineDocs/model_debugging/latex_printing.rst +++ b/doc/OnlineDocs/model_debugging/latex_printing.rst @@ -23,6 +23,8 @@ Pyomo models can be printed to a LaTeX compatible format using the ``pyomo.util. :type fontsize: str or int :param paper_dimensions: A dictionary that controls the paper margins and size. Keys are: [ 'height', 'width', 'margin_left', 'margin_right', 'margin_top', 'margin_bottom' ]. Default is standard 8.5x11 with one inch margins. Values are in inches :type paper_dimensions: dict + :param throw_templatization_error: Option to throw an error on templatization failure rather than printing each constraint individually, useful for very large models + :type throw_templatization_error: bool :return: A LaTeX style string that represents the passed in pyomoElement diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index 9dcbc9f912a..750caf36b60 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -644,6 +644,7 @@ def latex_printer( use_short_descriptors=False, fontsize=None, paper_dimensions=None, + throw_templatization_error=False, ): """This function produces a string that can be rendered as LaTeX @@ -689,6 +690,10 @@ def latex_printer( 'margin_bottom' ]. Default is standard 8.5x11 with one inch margins. Values are in inches + throw_templatization_error: bool + Option to throw an error on templatization failure rather than + printing each constraint individually, useful for very large models + Returns ------- @@ -968,10 +973,12 @@ def latex_printer( try: obj_template, obj_indices = templatize_fcn(obj) except: - obj_template = obj - # raise RuntimeError( - # "An objective has been constructed that cannot be templatized" - # ) + if throw_templatization_error: + raise RuntimeError( + "An objective has been constructed that cannot be templatized" + ) + else: + obj_template = obj if obj.sense == 1: pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['minimize']) @@ -1023,12 +1030,14 @@ def latex_printer( con_template, indices = templatize_fcn(con) con_template_list = [con_template] except: - # con_template = con[0] - con_template_list = [c.expr for c in con.values()] - indices = [] - # raise RuntimeError( - # "A constraint has been constructed that cannot be templatized" - # ) + if throw_templatization_error: + raise RuntimeError( + "A constraint has been constructed that cannot be templatized" + ) + else: + con_template_list = [c.expr for c in con.values()] + indices = [] + for con_template in con_template_list: # Walk the constraint conLine = ( diff --git a/pyomo/util/tests/test_latex_printer.py b/pyomo/util/tests/test_latex_printer.py index 32381dcf36d..685e7e2df38 100644 --- a/pyomo/util/tests/test_latex_printer.py +++ b/pyomo/util/tests/test_latex_printer.py @@ -977,6 +977,47 @@ def ruleMaker_2(m): self.assertEqual('\n' + pstr + '\n', bstr) + def test_latexPrinter_throwTemplatizeError(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.I = pyo.Set(initialize=[1, 2, 3, 4, 5]) + m.x = pyo.Var(m.I, bounds=[-10, 10]) + m.c = pyo.Param(m.I, initialize=1.0, mutable=True) + + def ruleMaker_1(m): + return sum(m.c[i] * m.x[i] for i in m.I) + + def ruleMaker_2(m, i): + if i >= 2: + return m.x[i] <= 1 + else: + return pyo.Constraint.Skip + + m.objective = pyo.Objective(rule=ruleMaker_1) + m.constraint_1 = pyo.Constraint(m.I, rule=ruleMaker_2) + self.assertRaises( + RuntimeError, + latex_printer, + **{'pyomo_component': m, 'throw_templatization_error': True} + ) + pstr = latex_printer(m) + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & \sum_{ i \in I } c_{i} x_{i} & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x[2] \leq 1 & \label{con:basicFormulation_constraint_1} \\ + & & x[3] \leq 1 & \label{con:basicFormulation_constraint_1} \\ + & & x[4] \leq 1 & \label{con:basicFormulation_constraint_1} \\ + & & x[5] \leq 1 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x \leq 10 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual('\n' + pstr + '\n', bstr) + if __name__ == '__main__': unittest.main() From b2a6a3e7b228637f0ea0e5d81d6804696b1ce5a3 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Wed, 8 Nov 2023 11:34:10 -0600 Subject: [PATCH 0392/1204] fixing the doc examples pe->pyo --- .../model_debugging/latex_printing.rst | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/doc/OnlineDocs/model_debugging/latex_printing.rst b/doc/OnlineDocs/model_debugging/latex_printing.rst index 99e66b2688c..0654344ca2f 100644 --- a/doc/OnlineDocs/model_debugging/latex_printing.rst +++ b/doc/OnlineDocs/model_debugging/latex_printing.rst @@ -46,16 +46,16 @@ A Model .. doctest:: - >>> import pyomo.environ as pe + >>> import pyomo.environ as pyo >>> from pyomo.util.latex_printer import latex_printer - >>> m = pe.ConcreteModel(name = 'basicFormulation') - >>> m.x = pe.Var() - >>> m.y = pe.Var() - >>> m.z = pe.Var() - >>> m.c = pe.Param(initialize=1.0, mutable=True) - >>> m.objective = pe.Objective( expr = m.x + m.y + m.z ) - >>> m.constraint_1 = pe.Constraint(expr = m.x**2 + m.y**2.0 - m.z**2.0 <= m.c ) + >>> m = pyo.ConcreteModel(name = 'basicFormulation') + >>> m.x = pyo.Var() + >>> m.y = pyo.Var() + >>> m.z = pyo.Var() + >>> m.c = pyo.Param(initialize=1.0, mutable=True) + >>> m.objective = pyo.Objective( expr = m.x + m.y + m.z ) + >>> m.constraint_1 = pyo.Constraint(expr = m.x**2 + m.y**2.0 - m.z**2.0 <= m.c ) >>> pstr = latex_printer(m) @@ -65,14 +65,14 @@ A Constraint .. doctest:: - >>> import pyomo.environ as pe + >>> import pyomo.environ as pyo >>> from pyomo.util.latex_printer import latex_printer - >>> m = pe.ConcreteModel(name = 'basicFormulation') - >>> m.x = pe.Var() - >>> m.y = pe.Var() + >>> m = pyo.ConcreteModel(name = 'basicFormulation') + >>> m.x = pyo.Var() + >>> m.y = pyo.Var() - >>> m.constraint_1 = pe.Constraint(expr = m.x**2 + m.y**2 <= 1.0) + >>> m.constraint_1 = pyo.Constraint(expr = m.x**2 + m.y**2 <= 1.0) >>> pstr = latex_printer(m.constraint_1) @@ -81,15 +81,15 @@ A Constraint with a Set .. doctest:: - >>> import pyomo.environ as pe + >>> import pyomo.environ as pyo >>> from pyomo.util.latex_printer import latex_printer - >>> m = pe.ConcreteModel(name='basicFormulation') - >>> m.I = pe.Set(initialize=[1, 2, 3, 4, 5]) - >>> m.v = pe.Var(m.I) + >>> m = pyo.ConcreteModel(name='basicFormulation') + >>> m.I = pyo.Set(initialize=[1, 2, 3, 4, 5]) + >>> m.v = pyo.Var(m.I) >>> def ruleMaker(m): return sum(m.v[i] for i in m.I) <= 0 - >>> m.constraint = pe.Constraint(rule=ruleMaker) + >>> m.constraint = pyo.Constraint(rule=ruleMaker) >>> pstr = latex_printer(m.constraint) @@ -98,17 +98,17 @@ Using a ComponentMap .. doctest:: - >>> import pyomo.environ as pe + >>> import pyomo.environ as pyo >>> from pyomo.util.latex_printer import latex_printer >>> from pyomo.common.collections.component_map import ComponentMap - >>> m = pe.ConcreteModel(name='basicFormulation') - >>> m.I = pe.Set(initialize=[1, 2, 3, 4, 5]) - >>> m.v = pe.Var(m.I) + >>> m = pyo.ConcreteModel(name='basicFormulation') + >>> m.I = pyo.Set(initialize=[1, 2, 3, 4, 5]) + >>> m.v = pyo.Var(m.I) >>> def ruleMaker(m): return sum(m.v[i] for i in m.I) <= 0 - >>> m.constraint = pe.Constraint(rule=ruleMaker) + >>> m.constraint = pyo.Constraint(rule=ruleMaker) >>> lcm = ComponentMap() >>> lcm[m.v] = 'x' @@ -122,14 +122,14 @@ An Expression .. doctest:: - >>> import pyomo.environ as pe + >>> import pyomo.environ as pyo >>> from pyomo.util.latex_printer import latex_printer - >>> m = pe.ConcreteModel(name = 'basicFormulation') - >>> m.x = pe.Var() - >>> m.y = pe.Var() + >>> m = pyo.ConcreteModel(name = 'basicFormulation') + >>> m.x = pyo.Var() + >>> m.y = pyo.Var() - >>> m.expression_1 = pe.Expression(expr = m.x**2 + m.y**2) + >>> m.expression_1 = pyo.Expression(expr = m.x**2 + m.y**2) >>> pstr = latex_printer(m.expression_1) @@ -139,12 +139,12 @@ A Simple Expression .. doctest:: - >>> import pyomo.environ as pe + >>> import pyomo.environ as pyo >>> from pyomo.util.latex_printer import latex_printer - >>> m = pe.ConcreteModel(name = 'basicFormulation') - >>> m.x = pe.Var() - >>> m.y = pe.Var() + >>> m = pyo.ConcreteModel(name = 'basicFormulation') + >>> m.x = pyo.Var() + >>> m.y = pyo.Var() >>> pstr = latex_printer(m.x + m.y) From d43765c5bf9609c5f74e1a58b2ab1b32d595f101 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 8 Nov 2023 15:11:29 -0700 Subject: [PATCH 0393/1204] SAVE STATE --- pyomo/solver/results.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index 8e4b6cf21a7..d7505a7ed95 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -289,11 +289,13 @@ def parse_sol_file(file, results): while i < number_of_cons: line = file.readline() constraints.append(float(line)) + i += 1 # Parse through the variable lines and capture the variables i = 0 while i < number_of_vars: line = file.readline() variables.append(float(line)) + i += 1 # Parse the exit code line and capture it exit_code = [0, 0] line = file.readline() @@ -315,30 +317,29 @@ def parse_sol_file(file, results): exit_code_message = "Optimal solution indicated, but ERROR LIKELY!" results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied results.solution_status = SolutionStatus.optimal - if results.extra_info.solver_message: - results.extra_info.solver_message += '; ' + exit_code_message - else: - results.extra_info.solver_message = exit_code_message elif (exit_code[1] >= 200) and (exit_code[1] <= 299): + exit_code_message = "INFEASIBLE SOLUTION: constraints cannot be satisfied!" results.termination_condition = TerminationCondition.locallyInfeasible results.solution_status = SolutionStatus.infeasible elif (exit_code[1] >= 300) and (exit_code[1] <= 399): + exit_code_message = "UNBOUNDED PROBLEM: the objective can be improved without limit!" results.termination_condition = TerminationCondition.unbounded results.solution_status = SolutionStatus.infeasible elif (exit_code[1] >= 400) and (exit_code[1] <= 499): + exit_code_message = ("EXCEEDED MAXIMUM NUMBER OF ITERATIONS: the solver " + "was stopped by a limit that you set!") results.solver.termination_condition = TerminationCondition.iterationLimit elif (exit_code[1] >= 500) and (exit_code[1] <= 599): exit_code_message = ( "FAILURE: the solver stopped by an error condition " "in the solver routines!" ) - if results.extra_info.solver_message: - results.extra_info.solver_message += '; ' + exit_code_message - else: - results.extra_info.solver_message = exit_code_message results.solver.termination_condition = TerminationCondition.error - return results - + + if results.extra_info.solver_message: + results.extra_info.solver_message += '; ' + exit_code_message + else: + results.extra_info.solver_message = exit_code_message return results def parse_yaml(): From b5c58322bfc208f5f0bfac50baf73700bfa34812 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 8 Nov 2023 15:13:19 -0700 Subject: [PATCH 0394/1204] Update Performance Plot URL --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e544d854c71..42923a0339d 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ subproblems using Python parallel communication libraries. * [About Pyomo](http://www.pyomo.org/about) * [Download](http://www.pyomo.org/installation/) * [Documentation](http://www.pyomo.org/documentation/) -* [Performance Plots](https://software.sandia.gov/downloads/pub/pyomo/performance/index.html) +* [Performance Plots](https://pyomo.github.io/performance/) Pyomo was formerly released as the Coopr software library. From 3d47029b9cea14660fd6612092eff1333bfb29cc Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 9 Nov 2023 18:12:26 -0700 Subject: [PATCH 0395/1204] Fixing a bug with adding multiple identical reaggregation constraints --- pyomo/gdp/plugins/hull.py | 177 ++++++++++++++++++-------------------- 1 file changed, 84 insertions(+), 93 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index b6e8065ba67..7a5d752bdbb 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -355,8 +355,9 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, disaggregatedVars = transBlock._disaggregatedVars disaggregated_var_bounds = transBlock._boundsConstraints - # We first go through and collect all the variables that we - # are going to disaggregate. + # We first go through and collect all the variables that we are going to + # disaggregate. We do this in its own pass because we want to know all + # the Disjuncts that each Var appears in. var_order = ComponentSet() disjuncts_var_appears_in = ComponentMap() for disjunct in active_disjuncts: @@ -390,9 +391,8 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, disjuncts_var_appears_in[var].add(disjunct) # We will disaggregate all variables that are not explicitly declared as - # being local. Since we transform from leaf to root, we are implicitly - # treating our own disaggregated variables as local, so they will not be - # re-disaggregated. + # being local. We have marked our own disaggregated variables as local, + # so they will not be re-disaggregated. vars_to_disaggregate = {disj: ComponentSet() for disj in obj.disjuncts} for var in var_order: disjuncts = disjuncts_var_appears_in[var] @@ -420,10 +420,7 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, # Now that we know who we need to disaggregate, we will do it # while we also transform the disjuncts. - print("obj: %s" % obj) - print("parent disjunct: %s" % parent_disjunct) parent_local_var_list = self._get_local_var_list(parent_disjunct) - print("parent_local_var_list: %s" % parent_local_var_list) or_expr = 0 for disjunct in obj.disjuncts: or_expr += disjunct.indicator_var.get_associated_binary() @@ -443,90 +440,86 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, # add the reaggregation constraints i = 0 - for disj in active_disjuncts: - for var in vars_to_disaggregate[disj]: - # There are two cases here: Either the var appeared in every - # disjunct in the disjunction, or it didn't. If it did, there's - # nothing special to do: All of the disaggregated variables have - # been created, and we can just proceed and make this constraint. If - # it didn't, we need one more disaggregated variable, correctly - # defined. And then we can make the constraint. - if len(disjuncts_var_appears_in[var]) < len(obj.disjuncts): - # create one more disaggregated var - idx = len(disaggregatedVars) - disaggregated_var = disaggregatedVars[idx] - # mark this as local because we won't re-disaggregate if this is - # a nested disjunction - if parent_local_var_list is not None: - 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() - for disj in disjuncts_var_appears_in[var] - ) - self._declare_disaggregated_var_bounds( - var, - disaggregated_var, - obj, - disaggregated_var_bounds, - (idx, 'lb'), - (idx, 'ub'), - var_free, - ) - # For every Disjunct the Var does not appear in, we want to map - # that this new variable is its disaggreggated variable. - for disj in obj.disjuncts: - # Because we called _transform_disjunct above, we know that - # if this isn't transformed it is because it was cleanly - # deactivated, and we can just skip it. - if ( - disj._transformation_block is not None - and disj not in disjuncts_var_appears_in[var] - ): - relaxationBlock = disj._transformation_block().\ - parent_block() - relaxationBlock._bigMConstraintMap[ - disaggregated_var - ] = Reference(disaggregated_var_bounds[idx, :]) - relaxationBlock._disaggregatedVarMap['srcVar'][ - disaggregated_var - ] = var - relaxationBlock._disaggregatedVarMap[ - 'disaggregatedVar'][disj][ - var - ] = disaggregated_var - - disaggregatedExpr = disaggregated_var - else: - disaggregatedExpr = 0 - for disjunct in disjuncts_var_appears_in[var]: - # We know this Disjunct was active, so it has been transformed now. - disaggregatedVar = ( - disjunct._transformation_block() - .parent_block() - ._disaggregatedVarMap['disaggregatedVar'][disjunct][var] - ) - disaggregatedExpr += disaggregatedVar - - cons_idx = len(disaggregationConstraint) - # We always aggregate to the original var. If this is nested, this - # constraint will be transformed again. - print("Adding disaggregation constraint for '%s' on Disjunction '%s' " - "to Block '%s'" % - (var, obj, disaggregationConstraint.parent_block())) - disaggregationConstraint.add(cons_idx, var == disaggregatedExpr) - # and update the map so that we can find this later. We index by - # variable and the particular disjunction because there is a - # different one for each disjunction - if disaggregationConstraintMap.get(var) is not None: - disaggregationConstraintMap[var][obj] = disaggregationConstraint[ - cons_idx - ] - else: - thismap = disaggregationConstraintMap[var] = ComponentMap() - thismap[obj] = disaggregationConstraint[cons_idx] + for var in var_order: + # There are two cases here: Either the var appeared in every + # disjunct in the disjunction, or it didn't. If it did, there's + # nothing special to do: All of the disaggregated variables have + # been created, and we can just proceed and make this constraint. If + # it didn't, we need one more disaggregated variable, correctly + # defined. And then we can make the constraint. + if len(disjuncts_var_appears_in[var]) < len(active_disjuncts): + # create one more disaggregated var + idx = len(disaggregatedVars) + disaggregated_var = disaggregatedVars[idx] + # mark this as local because we won't re-disaggregate if this is + # a nested disjunction + if parent_local_var_list is not None: + 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() + for disj in disjuncts_var_appears_in[var] + ) + self._declare_disaggregated_var_bounds( + var, + disaggregated_var, + obj, + disaggregated_var_bounds, + (idx, 'lb'), + (idx, 'ub'), + var_free, + ) + # For every Disjunct the Var does not appear in, we want to map + # that this new variable is its disaggreggated variable. + for disj in active_disjuncts: + # Because we called _transform_disjunct above, we know that + # if this isn't transformed it is because it was cleanly + # deactivated, and we can just skip it. + if ( + disj._transformation_block is not None + and disj not in disjuncts_var_appears_in[var] + ): + relaxationBlock = disj._transformation_block().\ + parent_block() + relaxationBlock._bigMConstraintMap[ + disaggregated_var + ] = Reference(disaggregated_var_bounds[idx, :]) + relaxationBlock._disaggregatedVarMap['srcVar'][ + disaggregated_var + ] = var + relaxationBlock._disaggregatedVarMap[ + 'disaggregatedVar'][disj][ + var + ] = disaggregated_var + + disaggregatedExpr = disaggregated_var + else: + disaggregatedExpr = 0 + for disjunct in disjuncts_var_appears_in[var]: + # We know this Disjunct was active, so it has been transformed now. + disaggregatedVar = ( + disjunct._transformation_block() + .parent_block() + ._disaggregatedVarMap['disaggregatedVar'][disjunct][var] + ) + disaggregatedExpr += disaggregatedVar + + cons_idx = len(disaggregationConstraint) + # We always aggregate to the original var. If this is nested, this + # constraint will be transformed again. + disaggregationConstraint.add(cons_idx, var == disaggregatedExpr) + # and update the map so that we can find this later. We index by + # variable and the particular disjunction because there is a + # different one for each disjunction + if disaggregationConstraintMap.get(var) is not None: + disaggregationConstraintMap[var][obj] = disaggregationConstraint[ + cons_idx + ] + else: + thismap = disaggregationConstraintMap[var] = ComponentMap() + thismap[obj] = disaggregationConstraint[cons_idx] - i += 1 + i += 1 # deactivate for the writers obj.deactivate() @@ -543,7 +536,6 @@ def _transform_disjunct(self, obj, transBlock, vars_to_disaggregate, local_vars, # add the disaggregated variables and their bigm constraints # to the relaxationBlock for var in vars_to_disaggregate: - print("disaggregating %s" % var) disaggregatedVar = Var(within=Reals, initialize=var.value) # naming conflicts are possible here since this is a bunch # of variables from different blocks coming together, so we @@ -586,7 +578,6 @@ def _transform_disjunct(self, obj, transBlock, vars_to_disaggregate, local_vars, "but it appeared in multiple Disjuncts, so it will be " "disaggregated." % (var.name, obj.name)) continue - print("we knew %s was local" % var) # we don't need to disaggregate, i.e., we can use this Var, but we # do need to set up its bounds constraints. From bb464908051c4580360098350a1828405eb5f434 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 9 Nov 2023 18:12:50 -0700 Subject: [PATCH 0396/1204] Fixing a couple nested GDP tests --- pyomo/gdp/tests/test_hull.py | 43 +++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index 3ef57c73274..b224385bec0 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -52,6 +52,9 @@ import os from os.path import abspath, dirname, join +##DEBUG +from pytest import set_trace + currdir = dirname(abspath(__file__)) from filecmp import cmp @@ -1724,11 +1727,6 @@ def test_solve_nested_model(self): SolverFactory(linear_solvers[0]).solve(m_hull) - print("MODEL") - for cons in m_hull.component_data_objects(Constraint, active=True, - descend_into=Block): - print(cons.expr) - # check solution self.assertEqual(value(m_hull.d1.binary_indicator_var), 0) self.assertEqual(value(m_hull.d2.binary_indicator_var), 1) @@ -1892,11 +1890,14 @@ def test_nested_with_var_that_does_not_appear_in_every_disjunct(self): self.assertEqual(y_p1.bounds, (-4, 5)) y_p2 = hull.get_disaggregated_var(m.y, m.parent2) self.assertEqual(y_p2.bounds, (-4, 5)) + y_cons = hull.get_disaggregation_constraint(m.y, m.parent1.disjunction) # check that the disaggregated ys in the nested just sum to the original - assertExpressionsEqual(self, y_cons.expr, y_p1 == other_y + y_c2) + y_cons_expr = self.simplify_cons(y_cons) + assertExpressionsEqual(self, y_cons_expr, y_p1 - other_y - y_c2 == 0.0) y_cons = hull.get_disaggregation_constraint(m.y, m.parent_disjunction) - assertExpressionsEqual(self, y_cons.expr, m.y == y_p1 + y_p2) + y_cons_expr = self.simplify_cons(y_cons) + assertExpressionsEqual(self, y_cons_expr, m.y - y_p2 - y_p1 == 0.0) x_c1 = hull.get_disaggregated_var(m.x, m.child1) x_c2 = hull.get_disaggregated_var(m.x, m.child2) @@ -1906,7 +1907,9 @@ def test_nested_with_var_that_does_not_appear_in_every_disjunct(self): x_cons_parent = hull.get_disaggregation_constraint(m.x, m.parent_disjunction) assertExpressionsEqual(self, x_cons_parent.expr, m.x == x_p1 + x_p2) x_cons_child = hull.get_disaggregation_constraint(m.x, m.parent1.disjunction) - assertExpressionsEqual(self, x_cons_child.expr, x_p1 == x_c1 + x_c2 + x_c3) + x_cons_child_expr = self.simplify_cons(x_cons_child) + assertExpressionsEqual(self, x_cons_child_expr, x_p1 - x_c1 - x_c2 - + x_c3 == 0.0) def simplify_cons(self, cons): visitor = LinearRepnVisitor({}, {}, {}) @@ -1934,9 +1937,9 @@ def test_nested_with_var_that_skips_a_level(self): m.y1 = Disjunct() m.y1.c1 = Constraint(expr=m.x >= 4) m.y1.z1 = Disjunct() - m.y1.z1.c1 = Constraint(expr=m.y == 0) + m.y1.z1.c1 = Constraint(expr=m.y == 2) m.y1.z1.w1 = Disjunct() - m.y1.z1.w1.c1 = Constraint(expr=m.x == 0) + m.y1.z1.w1.c1 = Constraint(expr=m.x == 3) m.y1.z1.w2 = Disjunct() m.y1.z1.w2.c1 = Constraint(expr=m.x >= 1) m.y1.z1.disjunction = Disjunction(expr=[m.y1.z1.w1, m.y1.z1.w2]) @@ -1944,7 +1947,7 @@ def test_nested_with_var_that_skips_a_level(self): m.y1.z2.c1 = Constraint(expr=m.y == 1) m.y1.disjunction = Disjunction(expr=[m.y1.z1, m.y1.z2]) m.y2 = Disjunct() - m.y2.c1 = Constraint(expr=m.x == 0) + m.y2.c1 = Constraint(expr=m.x == 4) m.disjunction = Disjunction(expr=[m.y1, m.y2]) hull = TransformationFactory('gdp.hull') @@ -1965,26 +1968,26 @@ def test_nested_with_var_that_skips_a_level(self): cons = hull.get_disaggregation_constraint(m.x, m.y1.z1.disjunction) self.assertTrue(cons.active) cons_expr = self.simplify_cons(cons) - print(cons_expr) - print("") - print(x_z1 - x_w2 - x_w1 == 0) - assertExpressionsEqual(self, cons_expr, x_z1 - x_w2 - x_w1 == 0) + assertExpressionsEqual(self, cons_expr, x_z1 - x_w1 - x_w2 == 0.0) cons = hull.get_disaggregation_constraint(m.x, m.y1.disjunction) self.assertTrue(cons.active) - assertExpressionsEqual(self, cons.expr, x_y1 == x_z2 + x_z1) + cons_expr = self.simplify_cons(cons) + assertExpressionsEqual(self, cons_expr, x_y1 - x_z2 - x_z1 == 0.0) cons = hull.get_disaggregation_constraint(m.x, m.disjunction) self.assertTrue(cons.active) - assertExpressionsEqual(self, cons.expr, m.x == x_y1 + x_y2) - + cons_expr = self.simplify_cons(cons) + assertExpressionsEqual(self, cons_expr, m.x - x_y1 - x_y2 == 0.0) cons = hull.get_disaggregation_constraint(m.y, m.y1.z1.disjunction, raise_exception=False) self.assertIsNone(cons) cons = hull.get_disaggregation_constraint(m.y, m.y1.disjunction) self.assertTrue(cons.active) - assertExpressionsEqual(self, cons.expr, y_y1 == y_z1 + y_z2) + cons_expr = self.simplify_cons(cons) + assertExpressionsEqual(self, cons_expr, y_y1 - y_z1 - y_z2 == 0.0) cons = hull.get_disaggregation_constraint(m.y, m.disjunction) self.assertTrue(cons.active) - assertExpressionsEqual(self, cons.expr, m.y == y_y2 + y_y1) + cons_expr = self.simplify_cons(cons) + assertExpressionsEqual(self, cons_expr, m.y - y_y2 - y_y1 == 0.0) class TestSpecialCases(unittest.TestCase): From a5289185d6e118bc117737988a51132615d436f6 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 9 Nov 2023 18:25:05 -0700 Subject: [PATCH 0397/1204] Disable scaling in NLv2 call API --- pyomo/repn/plugins/nl_writer.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 80f46bce279..32bc8296108 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -308,7 +308,15 @@ def __call__(self, model, filename, solver_capability, io_options): col_fname = filename_base + '.col' config = self.config(io_options) + + # There is no (convenient) way to pass the scaling factors or + # information about presolved variables back to the solver + # through the old "call" interface (since solvers that used that + # interface predated scaling / presolve). We will play it safe + # and disable scaling / presolve when called through this API config.scale_model = False + config.linear_presolve = False + if config.symbolic_solver_labels: _open = lambda fname: open(fname, 'w') else: From 0e2b4cb5ec981ba0d00cd7920c90f5d360466aa8 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 9 Nov 2023 18:25:46 -0700 Subject: [PATCH 0398/1204] Minor performance improvement for scaling in NLv2 --- pyomo/repn/plugins/nl_writer.py | 46 +++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 32bc8296108..1622188ef7a 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -594,6 +594,7 @@ def write(self, model): del suffix_data['scaling_factor'] else: scaling_factor = _NoScalingFactor() + scale_model = scaling_factor.scale # # Data structures to support presolve @@ -990,15 +991,30 @@ def write(self, model): } _vmap = self.var_id_to_nl - if scaling_factor.scale: + if scale_model: template = self.template for var_idx, _id in enumerate(variables): scale = scaling_factor(var_map[_id]) - if scale != 1: - _vmap[_id] = ( - template.division + _vmap[_id] + '\n' + template.const % scale - ).rstrip() - + if scale == 1: + continue + # Update var_bounds to be scaled bounds + if scale < 0: + # Note: reverse bounds for negative scaling factors + ub, lb = var_bounds[_id] + else: + lb, ub = var_bounds[_id] + if lb is not None: + lb *= scale + if ub is not None: + ub *= scale + var_bounds[_id] = lb, ub + # Update _vmap to output scaled variables in NL expressions + _vmap[_id] = ( + template.division + _vmap[_id] + '\n' + template.const % scale + ).rstrip() + + # Update any eliminated variables to point to the (potentially + # scaled) substituted variables for _id, expr_info in eliminated_vars.items(): nl, args, _ = expr_info.compile_repn(visitor) _vmap[_id] = nl.rstrip() % tuple(_vmap[_id] for _id in args) @@ -1323,7 +1339,7 @@ def write(self, model): if 'dual' in suffix_data: data = suffix_data['dual'] data.compile(column_order, row_order, obj_order, model_id) - if scaling_factor.scale: + if scale_model: if objectives: if len(objectives) > 1: logger.warning( @@ -1357,7 +1373,7 @@ def write(self, model): for var_idx, val in enumerate(var_map[_id].value for _id in variables) if val is not None ] - if scaling_factor.scale: + if scale_model: for i, (var_idx, val) in enumerate(_init_lines): _init_lines[i] = (var_idx, val * scaling_cache[variables[var_idx]]) ostream.write( @@ -1403,22 +1419,12 @@ def write(self, model): if lb is None: # unbounded ostream.write(f"3{col_comments[var_idx]}\n") else: # == - if scaling_factor.scale: - lb *= scaling_factor(var_map[_id]) ostream.write(f"4 {lb!r}{col_comments[var_idx]}\n") elif lb is None: # var <= ub - if scaling_factor.scale: - ub *= scaling_factor(var_map[_id]) ostream.write(f"1 {ub!r}{col_comments[var_idx]}\n") elif ub is None: # lb <= body - if scaling_factor.scale: - lb *= scaling_factor(var_map[_id]) ostream.write(f"2 {lb!r}{col_comments[var_idx]}\n") else: # lb <= body <= ub - if scaling_factor.scale: - _sf = scaling_factor(var_map[_id]) - lb *= _sf - ub *= _sf ostream.write(f"0 {lb!r} {ub!r}{col_comments[var_idx]}\n") # @@ -1447,7 +1453,7 @@ def write(self, model): # (e.g., a nonlinear-only constraint), then skip this entry if not linear: continue - if scaling_factor.scale: + if scale_model: for _id, val in linear.items(): linear[_id] /= scaling_cache[_id] ostream.write(f'J{row_idx} {len(linear)}{row_comments[row_idx]}\n') @@ -1463,7 +1469,7 @@ def write(self, model): # (e.g., a constant objective), then skip this entry if not linear: continue - if scaling_factor.scale: + if scale_model: for _id, val in linear.items(): linear[_id] /= scaling_cache[_id] ostream.write(f'G{obj_idx} {len(linear)}{row_comments[obj_idx + n_cons]}\n') From 07b16ce59e9c8d87c824c4710699672a8f0ffdfc Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 9 Nov 2023 18:26:08 -0700 Subject: [PATCH 0399/1204] NFC: update comments --- pyomo/repn/plugins/nl_writer.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 1622188ef7a..97569784aa6 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -409,6 +409,12 @@ def compile(self, column_order, row_order, obj_order, model_id): while queue: for obj, val in queue.pop(0): if val.__class__ not in int_float: + # [JDS] I am not entirely sure why, but we have + # historically supported suffix values that hold + # dictionaries that map arbirtary component data + # objects to values. We will preserve that behavior + # here. This behavior is exercised by a + # ExternalGreyBox test. if isinstance(val, dict): queue.append(val.items()) continue From 91f07bc31e82884e8193e3857c5f8eaee8f3aade Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 9 Nov 2023 18:26:19 -0700 Subject: [PATCH 0400/1204] Update scaling tests to cover more edge cases --- pyomo/repn/tests/ampl/test_nlv2.py | 109 ++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 34 deletions(-) diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index b7caaa3d87a..003c36aa528 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1491,11 +1491,14 @@ def test_presolve_lower_triangular_out_of_bounds(self): def test_scaling(self): m = pyo.ConcreteModel() m.x = pyo.Var(initialize=0) - m.y = pyo.Var(initialize=0, bounds=(-1e5, 1e5)) + m.y = pyo.Var(initialize=0, bounds=(-2e5, 1e5)) m.z = pyo.Var(initialize=0, bounds=(1e3, None)) + m.v = pyo.Var(initialize=0, bounds=(1e3, 1e3)) + m.w = pyo.Var(initialize=0, bounds=(None, 1e3)) m.obj = pyo.Objective(expr=m.x**2 + (m.y - 50000) ** 2 + m.z) m.c = pyo.ConstraintList() m.c.add(100 * m.x + m.y / 100 >= 600) + m.c.add(1000*m.w + m.v * m.x <= 100) m.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT_EXPORT) m.dual[m.c[1]] = 0.02 @@ -1508,18 +1511,21 @@ def test_scaling(self): nl1 = OUT.getvalue() self.assertEqual( *nl_diff( - nl1, """g3 1 1 0 # problem unknown - 3 1 1 0 0 # vars, constraints, objectives, ranges, eqns - 0 1 0 0 0 0 # nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb + 5 2 1 0 0 # vars, constraints, objectives, ranges, eqns + 1 1 0 0 0 0 # nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb 0 0 # network constraints: nonlinear, linear - 0 2 0 # nonlinear vars in constraints, objectives, both + 2 3 1 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) - 2 3 # nonzeros in Jacobian, obj. gradient + 5 3 # nonzeros in Jacobian, obj. gradient 0 0 # max name lengths: constraints, variables 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 +o2 +v1 +v0 +C1 n0 O0 0 o0 @@ -1528,40 +1534,55 @@ def test_scaling(self): n2 o5 o0 -v1 +v2 n-50000 n2 d1 -0 0.02 -x3 +1 0.02 +x5 0 0 1 0 2 0 +3 0 +4 0 r +1 100 2 600 b 3 -0 -100000.0 100000.0 +4 1000.0 +0 -200000.0 100000.0 2 1000.0 -k2 -1 +1 1000.0 +k4 2 -J0 2 +3 +4 +4 +J0 3 +0 0 +1 0 +4 1000 +J1 2 0 100 -1 0.01 +2 0.01 G0 3 0 0 -1 0 -2 1 +2 0 +3 1 """, + nl1, ) ) m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) - m.scaling_factor[m.x] = 1 - m.scaling_factor[m.y] = 1 / 50000 + m.scaling_factor[m.v] = 1 / 250 + m.scaling_factor[m.w] = 1 / 500 + #m.scaling_factor[m.x] = 1 + m.scaling_factor[m.y] = -1 / 50000 m.scaling_factor[m.z] = 1 / 1000 m.scaling_factor[m.c[1]] = 1 / 10 + m.scaling_factor[m.c[2]] = -1 / 100 m.scaling_factor[m.obj] = 1 / 100 OUT = io.StringIO() @@ -1570,20 +1591,28 @@ def test_scaling(self): self.assertEqual(LOG.getvalue(), "") nl2 = OUT.getvalue() + print(nl2) self.assertEqual( *nl_diff( - nl2, """g3 1 1 0 # problem unknown - 3 1 1 0 0 # vars, constraints, objectives, ranges, eqns - 0 1 0 0 0 0 # nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb + 5 2 1 0 0 # vars, constraints, objectives, ranges, eqns + 1 1 0 0 0 0 # nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb 0 0 # network constraints: nonlinear, linear - 0 2 0 # nonlinear vars in constraints, objectives, both + 2 3 1 # nonlinear vars in constraints, objectives, both 0 0 0 1 # linear network variables; functions; arith, flags 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) - 2 3 # nonzeros in Jacobian, obj. gradient + 5 3 # nonzeros in Jacobian, obj. gradient 0 0 # max name lengths: constraints, variables 0 0 0 0 0 # common exprs: b,c,o,c1,o1 C0 +o2 +n-0.01 +o2 +o3 +v1 +n0.004 +v0 +C1 n0 O0 0 o2 @@ -1595,33 +1624,45 @@ def test_scaling(self): o5 o0 o3 -v1 -n2e-05 +v2 +n-2e-05 n-50000 n2 d1 -0 0.002 -x3 +1 0.002 +x5 0 0 1 0.0 2 0.0 +3 0.0 +4 0.0 r +2 -1.0 2 60.0 b 3 -0 -2.0 2.0 +4 4.0 +0 -2.0 4.0 2 1.0 -k2 -1 +1 2.0 +k4 2 -J0 2 +3 +4 +4 +J0 3 +0 0.0 +1 0.0 +4 -5000.0 +J1 2 0 10.0 -1 50.0 +2 -50.0 G0 3 0 0.0 -1 0.0 -2 10.0 +2 0.0 +3 10.0 """, + nl2, ) ) From 9b0162aa217c4743232a41ab474887ab1c698ac8 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 9 Nov 2023 20:35:26 -0700 Subject: [PATCH 0401/1204] Additional NLv2 testing --- pyomo/repn/tests/ampl/test_nlv2.py | 111 ++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 3 deletions(-) diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 003c36aa528..45e6806fc87 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1126,9 +1126,13 @@ def test_nonfloat_constants(self): m.weight = pyo.Constraint(expr=pyo.sum_product(m.w, m.x) <= m.limit) OUT = io.StringIO() + ROW = io.StringIO() + COL = io.StringIO() with LoggingIntercept() as LOG: - nl_writer.NLWriter().write(m, OUT, symbolic_solver_labels=True) + nl_writer.NLWriter().write(m, OUT, ROW, COL, symbolic_solver_labels=True) self.assertEqual(LOG.getvalue(), "") + self.assertEqual(ROW.getvalue(), "weight\nvalue\n") + self.assertEqual(COL.getvalue(), "x[0]\nx[1]\nx[2]\nx[3]\n") self.assertEqual( *nl_diff( """g3 1 1 0 #problem unknown @@ -1498,7 +1502,7 @@ def test_scaling(self): m.obj = pyo.Objective(expr=m.x**2 + (m.y - 50000) ** 2 + m.z) m.c = pyo.ConstraintList() m.c.add(100 * m.x + m.y / 100 >= 600) - m.c.add(1000*m.w + m.v * m.x <= 100) + m.c.add(1000 * m.w + m.v * m.x <= 100) m.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT_EXPORT) m.dual[m.c[1]] = 0.02 @@ -1578,7 +1582,7 @@ def test_scaling(self): m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) m.scaling_factor[m.v] = 1 / 250 m.scaling_factor[m.w] = 1 / 500 - #m.scaling_factor[m.x] = 1 + # m.scaling_factor[m.x] = 1 m.scaling_factor[m.y] = -1 / 50000 m.scaling_factor[m.z] = 1 / 1000 m.scaling_factor[m.c[1]] = 1 / 10 @@ -1668,3 +1672,104 @@ def test_scaling(self): # Debugging: this diffs the unscaled & scaled models # self.assertEqual(*nl_diff(nl1, nl2)) + + def test_named_expressions(self): + # This tests an error possibly reported by #2810 + m = ConcreteModel() + m.x = Var() + m.y = Var() + m.z = Var() + m.E1 = Expression(expr=3*(m.x*m.y + m.z)) + m.E2 = Expression(expr=m.z*m.y) + m.E3 = Expression(expr=m.x*m.z + m.y) + m.o1 = Objective(expr=m.E1 + m.E2) + m.o2 = Objective(expr=m.E1**2) + m.c1 = Constraint(expr=m.E2 + 2*m.E3 >= 0) + m.c2 = Constraint(expr=pyo.inequality(0, m.E3**2, 10)) + + OUT = io.StringIO() + nl_writer.NLWriter().write(m, OUT, symbolic_solver_labels=True) + print(OUT.getvalue()) + self.assertEqual( + *nl_diff( + """g3 1 1 0 # problem unknown + 3 2 2 1 0 # vars, constraints, objectives, ranges, eqns + 2 2 0 0 0 0 # nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb + 0 0 # network constraints: nonlinear, linear + 3 3 3 # nonlinear vars in constraints, objectives, both + 0 0 0 1 # linear network variables; functions; arith, flags + 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) + 6 6 # nonzeros in Jacobian, obj. gradient + 2 1 # max name lengths: constraints, variables + 1 1 1 1 1 # common exprs: b,c,o,c1,o1 +V3 0 0 #nl(E1) +o2 #* +v0 #x +v1 #y +V4 0 0 #E2 +o2 #* +v2 #z +v1 #y +V5 0 0 #nl(E3) +o2 #* +v0 #x +v2 #z +C0 #c1 +o0 #+ +v4 #E2 +o2 #* +n2 +v5 #nl(E3) +V6 1 2 #E3 +1 1 +v5 #nl(E3) +C1 #c2 +o5 #^ +v6 #E3 +n2 +O0 0 #o1 +o0 #+ +o2 #* +n3 +v3 #nl(E1) +v4 #E2 +V7 1 4 #E1 +2 3 +o2 #* +n3 +v3 #nl(E1) +O1 0 #o2 +o5 #^ +v7 #E1 +n2 +x0 # initial guess +r #2 ranges (rhs's) +2 0 #c1 +0 0 10 #c2 +b #3 bounds (on variables) +3 #x +3 #y +3 #z +k2 #intermediate Jacobian column lengths +2 +4 +J0 3 #c1 +0 0 +1 2 +2 0 +J1 3 #c2 +0 0 +1 0 +2 0 +G0 3 #o1 +0 0 +1 0 +2 3 +G1 3 #o2 +0 0 +1 0 +2 0 +""", + OUT.getvalue(), + ) + ) From f34571682ef7b451edcc201991ca6e53c3342572 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 9 Nov 2023 21:32:09 -0700 Subject: [PATCH 0402/1204] NFC: Apply black --- pyomo/repn/tests/ampl/test_nlv2.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 45e6806fc87..663e795b661 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1679,12 +1679,12 @@ def test_named_expressions(self): m.x = Var() m.y = Var() m.z = Var() - m.E1 = Expression(expr=3*(m.x*m.y + m.z)) - m.E2 = Expression(expr=m.z*m.y) - m.E3 = Expression(expr=m.x*m.z + m.y) + m.E1 = Expression(expr=3 * (m.x * m.y + m.z)) + m.E2 = Expression(expr=m.z * m.y) + m.E3 = Expression(expr=m.x * m.z + m.y) m.o1 = Objective(expr=m.E1 + m.E2) m.o2 = Objective(expr=m.E1**2) - m.c1 = Constraint(expr=m.E2 + 2*m.E3 >= 0) + m.c1 = Constraint(expr=m.E2 + 2 * m.E3 >= 0) m.c2 = Constraint(expr=pyo.inequality(0, m.E3**2, 10)) OUT = io.StringIO() From 906fff7d18e9cc98c6a7d7e0a1b9d4657aaa9be9 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 13 Nov 2023 10:59:38 -0500 Subject: [PATCH 0403/1204] black format --- pyomo/contrib/mindtpy/algorithm_base_class.py | 26 ++++++++----- pyomo/contrib/mindtpy/single_tree.py | 38 +++++++++--------- pyomo/contrib/mindtpy/util.py | 39 ++++++++++++------- 3 files changed, 62 insertions(+), 41 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 33b2f2c1d04..9771c04fc62 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -80,7 +80,7 @@ set_solver_mipgap, set_solver_constraint_violation_tolerance, update_solver_timelimit, - copy_var_list_values + copy_var_list_values, ) single_tree, single_tree_available = attempt_import('pyomo.contrib.mindtpy.single_tree') @@ -796,7 +796,9 @@ def MindtPy_initialization(self): self.integer_list.append(self.curr_int_sol) fixed_nlp, fixed_nlp_result = self.solve_subproblem() self.handle_nlp_subproblem_tc(fixed_nlp, fixed_nlp_result) - self.int_sol_2_cuts_ind[self.curr_int_sol] = list(range(1, len(self.mip.MindtPy_utils.cuts.oa_cuts) + 1)) + self.int_sol_2_cuts_ind[self.curr_int_sol] = list( + range(1, len(self.mip.MindtPy_utils.cuts.oa_cuts) + 1) + ) elif config.init_strategy == 'FP': self.init_rNLP() self.fp_loop() @@ -834,9 +836,15 @@ def init_rNLP(self, add_oa_cuts=True): subprob_terminate_cond = results.solver.termination_condition # Sometimes, the NLP solver might be trapped in a infeasible solution if the objective function is nonlinear and partition_obj_nonlinear_terms is True. If this happens, we will use the original objective function instead. - if subprob_terminate_cond == tc.infeasible and config.partition_obj_nonlinear_terms and self.rnlp.MindtPy_utils.objective_list[0].expr.polynomial_degree() not in self.mip_objective_polynomial_degree: + if ( + subprob_terminate_cond == tc.infeasible + and config.partition_obj_nonlinear_terms + and self.rnlp.MindtPy_utils.objective_list[0].expr.polynomial_degree() + not in self.mip_objective_polynomial_degree + ): config.logger.info( - 'Initial relaxed NLP problem is infeasible. This might be related to partition_obj_nonlinear_terms. Try to solve it again without partitioning nonlinear objective function.') + 'Initial relaxed NLP problem is infeasible. This might be related to partition_obj_nonlinear_terms. Try to solve it again without partitioning nonlinear objective function.' + ) self.rnlp.MindtPy_utils.objective.deactivate() self.rnlp.MindtPy_utils.objective_list[0].activate() results = self.nlp_opt.solve( @@ -889,14 +897,14 @@ def init_rNLP(self, add_oa_cuts=True): self.rnlp.MindtPy_utils.variable_list, self.mip.MindtPy_utils.variable_list, config, - ignore_integrality=True + ignore_integrality=True, ) if config.init_strategy == 'FP': copy_var_list_values( self.rnlp.MindtPy_utils.variable_list, self.working_model.MindtPy_utils.variable_list, config, - ignore_integrality=True + ignore_integrality=True, ) self.add_cuts( dual_values=dual_values, @@ -1700,9 +1708,7 @@ def solve_fp_main(self): config = self.config self.setup_fp_main() mip_args = self.set_up_mip_solver() - update_solver_timelimit( - self.mip_opt, config.mip_solver, self.timing, config - ) + update_solver_timelimit(self.mip_opt, config.mip_solver, self.timing, config) main_mip_results = self.mip_opt.solve( self.mip, @@ -2387,7 +2393,7 @@ def handle_fp_subproblem_optimal(self, fp_nlp): fp_nlp.MindtPy_utils.variable_list, self.working_model.MindtPy_utils.variable_list, self.config, - ignore_integrality=True + ignore_integrality=True, ) add_orthogonality_cuts(self.working_model, self.mip, self.config) diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index dacde73a79e..66435c2587f 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -17,10 +17,7 @@ import pyomo.core.expr as EXPR from math import copysign from pyomo.contrib.mindtpy.util import get_integer_solution, copy_var_list_values -from pyomo.contrib.gdpopt.util import ( - get_main_elapsed_time, - time_code, -) +from pyomo.contrib.gdpopt.util import get_main_elapsed_time, time_code from pyomo.opt import TerminationCondition as tc from pyomo.core import minimize, value from pyomo.core.expr import identify_variables @@ -35,13 +32,7 @@ class LazyOACallback_cplex( """Inherent class in CPLEX to call Lazy callback.""" def copy_lazy_var_list_values( - self, - opt, - from_list, - to_list, - config, - skip_stale=False, - skip_fixed=True, + self, opt, from_list, to_list, config, skip_stale=False, skip_fixed=True ): """This function copies variable values from one list to another. @@ -82,12 +73,14 @@ def copy_lazy_var_list_values( # instead log warnings). This means that the following # will always succeed and the ValueError should never be # raised. - if v_val in v_to.domain \ - and not ((v_to.has_lb() and v_val < v_to.lb)) \ - and not ((v_to.has_ub() and v_val > v_to.ub)): + if ( + v_val in v_to.domain + and not ((v_to.has_lb() and v_val < v_to.lb)) + and not ((v_to.has_ub() and v_val > v_to.ub)) + ): v_to.set_value(v_val) # Snap the value to the bounds - # TODO: check the performance of + # TODO: check the performance of # v_to.lb - v_val <= config.variable_tolerance elif ( v_to.has_lb() @@ -102,7 +95,10 @@ def copy_lazy_var_list_values( ): v_to.set_value(v_to.ub) # ... or the nearest integer - elif v_to.is_integer() and math.fabs(v_val - rounded_val) <= config.integer_tolerance: # and rounded_val in v_to.domain: + elif ( + v_to.is_integer() + and math.fabs(v_val - rounded_val) <= config.integer_tolerance + ): # and rounded_val in v_to.domain: v_to.set_value(rounded_val) elif abs(v_val) <= config.zero_tolerance and 0 in v_to.domain: v_to.set_value(0) @@ -945,7 +941,9 @@ def LazyOACallback_gurobi(cb_m, cb_opt, cb_where, mindtpy_solver, config): # Your callback should be prepared to cut off solutions that violate any of your lazy constraints, including those that have already been added. Node solutions will usually respect previously added lazy constraints, but not always. # https://www.gurobi.com/documentation/current/refman/cs_cb_addlazy.html # If this happens, MindtPy will look for the index of corresponding cuts, instead of solving the fixed-NLP again. - for ind in mindtpy_solver.int_sol_2_cuts_ind[mindtpy_solver.curr_int_sol]: + for ind in mindtpy_solver.int_sol_2_cuts_ind[ + mindtpy_solver.curr_int_sol + ]: cb_opt.cbLazy(mindtpy_solver.mip.MindtPy_utils.cuts.oa_cuts[ind]) return else: @@ -960,7 +958,11 @@ def LazyOACallback_gurobi(cb_m, cb_opt, cb_where, mindtpy_solver, config): mindtpy_solver.handle_nlp_subproblem_tc(fixed_nlp, fixed_nlp_result, cb_opt) if config.strategy == 'OA': # store the cut index corresponding to current integer solution. - mindtpy_solver.int_sol_2_cuts_ind[mindtpy_solver.curr_int_sol] = list(range(cut_ind + 1, len(mindtpy_solver.mip.MindtPy_utils.cuts.oa_cuts) + 1)) + mindtpy_solver.int_sol_2_cuts_ind[mindtpy_solver.curr_int_sol] = list( + range( + cut_ind + 1, len(mindtpy_solver.mip.MindtPy_utils.cuts.oa_cuts) + 1 + ) + ) def handle_lazy_main_feasible_solution_gurobi(cb_m, cb_opt, mindtpy_solver, config): diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index da1534b49ac..48c8aab31c4 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -23,7 +23,7 @@ RangeSet, ConstraintList, TransformationFactory, - value + value, ) from pyomo.repn import generate_standard_repn from pyomo.contrib.mcpp.pyomo_mcpp import mcpp_available, McCormick @@ -567,7 +567,9 @@ def set_solver_mipgap(opt, solver_name, config): opt.options['add_options'].append('option optcr=%s;' % config.mip_solver_mipgap) -def set_solver_constraint_violation_tolerance(opt, solver_name, config, warm_start=True): +def set_solver_constraint_violation_tolerance( + opt, solver_name, config, warm_start=True +): """Set constraint violation tolerance for solvers. Parameters @@ -701,9 +703,11 @@ def copy_var_list_values_from_solution_pool( # bounds violations no longer generate exceptions (and # instead log warnings). This means that the following will # always succeed and the ValueError should never be raised. - if var_val in v_to.domain \ - and not ((v_to.has_lb() and var_val < v_to.lb)) \ - and not ((v_to.has_ub() and var_val > v_to.ub)): + if ( + var_val in v_to.domain + and not ((v_to.has_lb() and var_val < v_to.lb)) + and not ((v_to.has_ub() and var_val > v_to.ub)) + ): v_to.set_value(var_val, skip_validation=True) elif v_to.has_lb() and var_val < v_to.lb: v_to.set_value(v_to.lb) @@ -967,9 +971,15 @@ def generate_norm_constraint(fp_nlp_model, mip_model, config): ): fp_nlp_model.norm_constraint.add(nlp_var - mip_var.value <= rhs) -def copy_var_list_values(from_list, to_list, config, - skip_stale=False, skip_fixed=True, - ignore_integrality=False): + +def copy_var_list_values( + from_list, + to_list, + config, + skip_stale=False, + skip_fixed=True, + ignore_integrality=False, +): """Copy variable values from one list to another. Rounds to Binary/Integer if necessary Sets to zero for NonNegativeReals if necessary @@ -981,9 +991,11 @@ def copy_var_list_values(from_list, to_list, config, continue # Skip fixed variables. var_val = value(v_from, exception=False) rounded_val = int(round(var_val)) - if var_val in v_to.domain \ - and not ((v_to.has_lb() and var_val < v_to.lb)) \ - and not ((v_to.has_ub() and var_val > v_to.ub)): + if ( + var_val in v_to.domain + and not ((v_to.has_lb() and var_val < v_to.lb)) + and not ((v_to.has_ub() and var_val > v_to.ub)) + ): v_to.set_value(value(v_from, exception=False)) elif v_to.has_lb() and var_val < v_to.lb: v_to.set_value(v_to.lb) @@ -991,8 +1003,9 @@ def copy_var_list_values(from_list, to_list, config, v_to.set_value(v_to.ub) elif ignore_integrality and v_to.is_integer(): v_to.set_value(value(v_from, exception=False), skip_validation=True) - elif v_to.is_integer() and (math.fabs(var_val - rounded_val) <= - config.integer_tolerance): + elif v_to.is_integer() and ( + math.fabs(var_val - rounded_val) <= config.integer_tolerance + ): v_to.set_value(rounded_val) elif abs(var_val) <= config.zero_tolerance and 0 in v_to.domain: v_to.set_value(0) From cd58fe68d961b5d2e8382e28c3a650019fc81f38 Mon Sep 17 00:00:00 2001 From: jasherma Date: Mon, 13 Nov 2023 13:45:55 -0500 Subject: [PATCH 0404/1204] Report scaled master variable shifts --- .../contrib/pyros/pyros_algorithm_methods.py | 128 +++++++++++++++--- pyomo/contrib/pyros/tests/test_grcs.py | 18 ++- pyomo/contrib/pyros/util.py | 24 +++- 3 files changed, 140 insertions(+), 30 deletions(-) diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index df0d539a70d..06666692146 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -191,6 +191,68 @@ def evaluate_and_log_component_stats(model_data, separation_model, config): ) +def evaluate_first_stage_var_shift( + current_master_fsv_vals, + previous_master_fsv_vals, + first_iter_master_fsv_vals, + ): + """ + Evaluate first-stage variable "shift". + """ + if not current_master_fsv_vals: + # there are no first-stage variables + return None + else: + return max( + abs(current_master_fsv_vals[var] - previous_master_fsv_vals[var]) + / max((abs(first_iter_master_fsv_vals[var]), 1)) + for var in previous_master_fsv_vals + ) + + +def evalaute_second_stage_var_shift( + current_master_nom_ssv_vals, + previous_master_nom_ssv_vals, + first_iter_master_nom_ssv_vals, + ): + """ + Evaluate second-stage variable "shift". + """ + if not current_master_nom_ssv_vals: + return None + else: + return max( + abs( + current_master_nom_ssv_vals[ssv] + - previous_master_nom_ssv_vals[ssv] + ) + / max((abs(first_iter_master_nom_ssv_vals[ssv]), 1)) + for ssv in previous_master_nom_ssv_vals + ) + + +def evaluate_dr_var_shift( + current_master_dr_var_vals, + previous_master_dr_var_vals, + first_iter_master_nom_ssv_vals, + dr_var_to_ssv_map, + ): + """ + Evalaute decision rule variable "shift". + """ + if not current_master_dr_var_vals: + return None + else: + return max( + abs( + current_master_dr_var_vals[drvar] + - previous_master_dr_var_vals[drvar] + ) + / max((1, abs(first_iter_master_nom_ssv_vals[dr_var_to_ssv_map[drvar]]))) + for drvar in previous_master_dr_var_vals + ) + + def ROSolver_iterative_solve(model_data, config): ''' GRCS algorithm implementation @@ -371,9 +433,20 @@ def ROSolver_iterative_solve(model_data, config): for var in master_data.master_model.scenarios[0, 0].util.first_stage_variables if var not in master_dr_var_set ) + master_nom_ssv_set = ComponentSet( + master_data.master_model.scenarios[0, 0].util.second_stage_variables + ) previous_master_fsv_vals = ComponentMap((var, None) for var in master_fsv_set) previous_master_dr_var_vals = ComponentMap((var, None) for var in master_dr_var_set) + previous_master_nom_ssv_vals = ComponentMap( + (var, None) for var in master_nom_ssv_set + ) + first_iter_master_fsv_vals = ComponentMap((var, None) for var in master_fsv_set) + first_iter_master_nom_ssv_vals = ComponentMap( + (var, None) for var in master_nom_ssv_set + ) + first_iter_dr_var_vals = ComponentMap((var, None) for var in master_dr_var_set) nom_master_util_blk = master_data.master_model.scenarios[0, 0].util dr_var_scaled_expr_map = get_dr_var_to_scaled_expr_map( decision_rule_vars=nom_master_util_blk.decision_rule_vars, @@ -381,6 +454,14 @@ def ROSolver_iterative_solve(model_data, config): second_stage_vars=nom_master_util_blk.second_stage_variables, uncertain_params=nom_master_util_blk.uncertain_params, ) + dr_var_to_ssv_map = ComponentMap() + dr_ssv_zip = zip( + nom_master_util_blk.decision_rule_vars, + nom_master_util_blk.second_stage_variables, + ) + for indexed_dr_var, ssv in dr_ssv_zip: + for drvar in indexed_dr_var.values(): + dr_var_to_ssv_map[drvar] = ssv IterationLogRecord.log_header(config.progress_logger.info) k = 0 @@ -439,6 +520,7 @@ def ROSolver_iterative_solve(model_data, config): iteration=k, objective=None, first_stage_var_shift=None, + second_stage_var_shift=None, dr_var_shift=None, num_violated_cons=None, max_violation=None, @@ -509,31 +591,38 @@ def ROSolver_iterative_solve(model_data, config): current_master_fsv_vals = ComponentMap( (var, value(var)) for var in master_fsv_set ) + current_master_nom_ssv_vals = ComponentMap( + (var, value(var)) for var in master_nom_ssv_set + ) current_master_dr_var_vals = ComponentMap( - (var, value(var)) for var, expr in dr_var_scaled_expr_map.items() + (var, value(expr)) for var, expr in dr_var_scaled_expr_map.items() ) if k > 0: - first_stage_var_shift = ( - max( - abs(current_master_fsv_vals[var] - previous_master_fsv_vals[var]) - for var in previous_master_fsv_vals - ) - if current_master_fsv_vals - else None + first_stage_var_shift = evaluate_first_stage_var_shift( + current_master_fsv_vals=current_master_fsv_vals, + previous_master_fsv_vals=previous_master_fsv_vals, + first_iter_master_fsv_vals=first_iter_master_fsv_vals, ) - dr_var_shift = ( - max( - abs( - current_master_dr_var_vals[var] - - previous_master_dr_var_vals[var] - ) - for var in previous_master_dr_var_vals - ) - if current_master_dr_var_vals - else None + second_stage_var_shift = evalaute_second_stage_var_shift( + current_master_nom_ssv_vals=current_master_nom_ssv_vals, + previous_master_nom_ssv_vals=previous_master_nom_ssv_vals, + first_iter_master_nom_ssv_vals=first_iter_master_nom_ssv_vals, + ) + dr_var_shift = evaluate_dr_var_shift( + current_master_dr_var_vals=current_master_dr_var_vals, + previous_master_dr_var_vals=previous_master_dr_var_vals, + first_iter_master_nom_ssv_vals=first_iter_master_nom_ssv_vals, + dr_var_to_ssv_map=dr_var_to_ssv_map, ) else: + for fsv in first_iter_master_fsv_vals: + first_iter_master_fsv_vals[fsv] = value(fsv) + for ssv in first_iter_master_nom_ssv_vals: + first_iter_master_nom_ssv_vals[ssv] = value(ssv) + for drvar in first_iter_dr_var_vals: + first_iter_dr_var_vals[drvar] = value(dr_var_scaled_expr_map[drvar]) first_stage_var_shift = None + second_stage_var_shift = None dr_var_shift = None # === Check if time limit reached after polishing @@ -544,6 +633,7 @@ def ROSolver_iterative_solve(model_data, config): iteration=k, objective=value(master_data.master_model.obj), first_stage_var_shift=first_stage_var_shift, + second_stage_var_shift=second_stage_var_shift, dr_var_shift=dr_var_shift, num_violated_cons=None, max_violation=None, @@ -637,6 +727,7 @@ def ROSolver_iterative_solve(model_data, config): iteration=k, objective=value(master_data.master_model.obj), first_stage_var_shift=first_stage_var_shift, + second_stage_var_shift=second_stage_var_shift, dr_var_shift=dr_var_shift, num_violated_cons=num_violated_cons, max_violation=max_sep_con_violation, @@ -725,6 +816,7 @@ def ROSolver_iterative_solve(model_data, config): iter_log_record.log(config.progress_logger.info) previous_master_fsv_vals = current_master_fsv_vals + previous_master_nom_ssv_vals = current_master_nom_ssv_vals previous_master_dr_var_vals = current_master_dr_var_vals # Iteration limit reached diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 2be73826f61..46af1277ba5 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -5704,7 +5704,7 @@ def test_log_header(self): """Test method for logging iteration log table header.""" ans = ( "------------------------------------------------------------------------------\n" - "Itn Objective 1-Stg Shift DR Shift #CViol Max Viol Wall Time (s)\n" + "Itn Objective 1-Stg Shift 2-Stg Shift #CViol Max Viol Wall Time (s)\n" "------------------------------------------------------------------------------\n" ) with LoggingIntercept(level=logging.INFO) as LOG: @@ -5725,7 +5725,8 @@ def test_log_standard_iter_record(self): iteration=4, objective=1.234567, first_stage_var_shift=2.3456789e-8, - dr_var_shift=3.456789e-7, + second_stage_var_shift=3.456789e-7, + dr_var_shift=1.234567e-7, num_violated_cons=10, max_violation=7.654321e-3, elapsed_time=21.2, @@ -5757,7 +5758,8 @@ def test_log_iter_record_polishing_failed(self): iteration=4, objective=1.234567, first_stage_var_shift=2.3456789e-8, - dr_var_shift=3.456789e-7, + second_stage_var_shift=3.456789e-7, + dr_var_shift=1.234567e-7, num_violated_cons=10, max_violation=7.654321e-3, elapsed_time=21.2, @@ -5794,7 +5796,8 @@ def test_log_iter_record_global_separation(self): iteration=4, objective=1.234567, first_stage_var_shift=2.3456789e-8, - dr_var_shift=3.456789e-7, + second_stage_var_shift=3.456789e-7, + dr_var_shift=1.234567e-7, num_violated_cons=10, max_violation=7.654321e-3, elapsed_time=21.2, @@ -5834,7 +5837,8 @@ def test_log_iter_record_not_all_sep_solved(self): iteration=4, objective=1.234567, first_stage_var_shift=2.3456789e-8, - dr_var_shift=3.456789e-7, + second_stage_var_shift=3.456789e-7, + dr_var_shift=1.234567e-7, num_violated_cons=10, max_violation=7.654321e-3, elapsed_time=21.2, @@ -5869,7 +5873,8 @@ def test_log_iter_record_all_special(self): iteration=4, objective=1.234567, first_stage_var_shift=2.3456789e-8, - dr_var_shift=3.456789e-7, + second_stage_var_shift=3.456789e-7, + dr_var_shift=1.234567e-7, num_violated_cons=10, max_violation=7.654321e-3, elapsed_time=21.2, @@ -5907,6 +5912,7 @@ def test_log_iter_record_attrs_none(self): iteration=0, objective=-1.234567, first_stage_var_shift=None, + second_stage_var_shift=None, dr_var_shift=None, num_violated_cons=10, max_violation=7.654321e-3, diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index a956e17b089..c1a429c0ba9 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -1648,7 +1648,7 @@ class IterationLogRecord: first_stage_var_shift : float or None, optional Infinity norm of the difference between first-stage variable vectors for the current and previous iterations. - dr_var_shift : float or None, optional + second_stage_var_shift : float or None, optional Infinity norm of the difference between decision rule variable vectors for the current and previous iterations. dr_polishing_success : bool or None, optional @@ -1680,13 +1680,18 @@ class IterationLogRecord: then this is the negative of the objective value of the original model. first_stage_var_shift : float or None - Infinity norm of the difference between first-stage + Infinity norm of the relative difference between first-stage variable vectors for the current and previous iterations. + second_stage_var_shift : float or None + Infinity norm of the relative difference between second-stage + variable vectors (evaluated subject to the nominal uncertain + parameter realization) for the current and previous iterations. dr_var_shift : float or None - Infinity norm of the difference between decision rule + Infinity norm of the relative difference between decision rule variable vectors for the current and previous iterations. + NOTE: This value is not reported in log messages. dr_polishing_success : bool or None - True if DR polishing solved successfully, False otherwise. + True if DR polishing was solved successfully, False otherwise. num_violated_cons : int or None Number of performance constraints found to be violated during separation step. @@ -1710,6 +1715,7 @@ class IterationLogRecord: "iteration": 5, "objective": 13, "first_stage_var_shift": 13, + "second_stage_var_shift": 13, "dr_var_shift": 13, "num_violated_cons": 8, "max_violation": 13, @@ -1719,6 +1725,7 @@ class IterationLogRecord: "iteration": "Itn", "objective": "Objective", "first_stage_var_shift": "1-Stg Shift", + "second_stage_var_shift": "2-Stg Shift", "dr_var_shift": "DR Shift", "num_violated_cons": "#CViol", "max_violation": "Max Viol", @@ -1730,6 +1737,7 @@ def __init__( iteration, objective, first_stage_var_shift, + second_stage_var_shift, dr_var_shift, dr_polishing_success, num_violated_cons, @@ -1742,6 +1750,7 @@ def __init__( self.iteration = iteration self.objective = objective self.first_stage_var_shift = first_stage_var_shift + self.second_stage_var_shift = second_stage_var_shift self.dr_var_shift = dr_var_shift self.dr_polishing_success = dr_polishing_success self.num_violated_cons = num_violated_cons @@ -1756,7 +1765,8 @@ def get_log_str(self): "iteration", "objective", "first_stage_var_shift", - "dr_var_shift", + "second_stage_var_shift", + # "dr_var_shift", "num_violated_cons", "max_violation", "elapsed_time", @@ -1774,6 +1784,7 @@ def _format_record_attr(self, attr_name): "iteration": "f'{attr_val:d}'", "objective": "f'{attr_val: .4e}'", "first_stage_var_shift": "f'{attr_val:.4e}'", + "second_stage_var_shift": "f'{attr_val:.4e}'", "dr_var_shift": "f'{attr_val:.4e}'", "num_violated_cons": "f'{attr_val:d}'", "max_violation": "f'{attr_val:.4e}'", @@ -1781,7 +1792,7 @@ def _format_record_attr(self, attr_name): } # qualifier for DR polishing and separation columns - if attr_name == "dr_var_shift": + if attr_name in ["second_stage_var_shift", "dr_var_shift"]: qual = "*" if not self.dr_polishing_success else "" elif attr_name == "num_violated_cons": qual = "+" if not self.all_sep_problems_solved else "" @@ -1807,6 +1818,7 @@ def get_log_header_str(): return "".join( f"{header_names_dict[attr]:<{fmt_lengths_dict[attr]}s}" for attr in fmt_lengths_dict + if attr != "dr_var_shift" ) @staticmethod From dbfcc8bc0ae6c9bfb41df508715c6728f7b3d35c Mon Sep 17 00:00:00 2001 From: jasherma Date: Mon, 13 Nov 2023 13:46:33 -0500 Subject: [PATCH 0405/1204] Update solver logs documentation --- doc/OnlineDocs/contributed_packages/pyros.rst | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/doc/OnlineDocs/contributed_packages/pyros.rst b/doc/OnlineDocs/contributed_packages/pyros.rst index 23ec60f2e20..133258fb9b8 100644 --- a/doc/OnlineDocs/contributed_packages/pyros.rst +++ b/doc/OnlineDocs/contributed_packages/pyros.rst @@ -912,16 +912,16 @@ Observe that the log contains the following information: First-stage inequalities (incl. certain var bounds) : 10 Performance constraints (incl. var bounds) : 47 ------------------------------------------------------------------------------ - Itn Objective 1-Stg Shift DR Shift #CViol Max Viol Wall Time (s) + Itn Objective 1-Stg Shift 2-Stg Shift #CViol Max Viol Wall Time (s) ------------------------------------------------------------------------------ - 0 3.5838e+07 - - 5 1.8832e+04 1.198 - 1 3.5838e+07 7.4506e-09 1.6105e+03 7 3.7766e+04 2.893 - 2 3.6116e+07 2.7803e+05 1.2918e+03 8 1.3466e+06 4.732 - 3 3.6285e+07 1.6957e+05 5.8386e+03 6 4.8734e+03 6.740 - 4 3.6285e+07 1.4901e-08 3.3097e+03 1 3.5036e+01 9.099 - 5 3.6285e+07 2.9786e-10 3.3597e+03 6 2.9103e+00 11.588 - 6 3.6285e+07 7.4506e-07 8.7228e+02 5 4.1726e-01 14.360 - 7 3.6285e+07 7.4506e-07 8.1995e+02 0 9.3279e-10g 21.597 + 0 3.5838e+07 - - 5 1.8832e+04 1.555 + 1 3.5838e+07 2.2045e-12 2.7854e-12 7 3.7766e+04 2.991 + 2 3.6116e+07 1.2324e-01 3.9256e-01 8 1.3466e+06 4.881 + 3 3.6285e+07 5.1968e-01 4.5604e-01 6 4.8734e+03 6.908 + 4 3.6285e+07 2.6524e-13 1.3909e-13 1 3.5036e+01 9.176 + 5 3.6285e+07 2.0167e-13 5.4357e-02 6 2.9103e+00 11.457 + 6 3.6285e+07 2.2335e-12 1.2150e-01 5 4.1726e-01 13.868 + 7 3.6285e+07 2.2340e-12 1.1422e-01 0 9.3279e-10g 20.917 ------------------------------------------------------------------------------ Robust optimal solution identified. ------------------------------------------------------------------------------ @@ -929,22 +929,22 @@ Observe that the log contains the following information: Identifier ncalls cumtime percall % ----------------------------------------------------------- - main 1 21.598 21.598 100.0 + main 1 20.918 20.918 100.0 ------------------------------------------------------ - dr_polishing 7 1.502 0.215 7.0 - global_separation 47 1.300 0.028 6.0 - local_separation 376 9.779 0.026 45.3 - master 8 5.385 0.673 24.9 - master_feasibility 7 0.531 0.076 2.5 - preprocessing 1 0.175 0.175 0.8 - other n/a 2.926 n/a 13.5 + dr_polishing 7 1.472 0.210 7.0 + global_separation 47 1.239 0.026 5.9 + local_separation 376 9.244 0.025 44.2 + master 8 5.259 0.657 25.1 + master_feasibility 7 0.486 0.069 2.3 + preprocessing 1 0.403 0.403 1.9 + other n/a 2.815 n/a 13.5 ====================================================== =========================================================== ------------------------------------------------------------------------------ Termination stats: Iterations : 8 - Solve time (wall s) : 21.598 + Solve time (wall s) : 20.918 Final objective value : 3.6285e+07 Termination condition : pyrosTerminationCondition.robust_optimal ------------------------------------------------------------------------------ @@ -983,7 +983,7 @@ The constituent columns are defined in the A dash ("-") is produced in lieu of a value if the master problem of the current iteration is not solved successfully. * - 1-Stg Shift - - Infinity norm of the difference between the first-stage + - Infinity norm of the relative difference between the first-stage variable vectors of the master solutions of the current and previous iterations. Expect this value to trend downward as the iteration number increases. @@ -991,16 +991,15 @@ The constituent columns are defined in the if the current iteration number is 0, there are no first-stage variables, or the master problem of the current iteration is not solved successfully. - * - DR Shift - - Infinity norm of the difference between the decision rule - variable vectors of the master solutions of the current - and previous iterations. - Expect this value to trend downward as the iteration number increases. - An asterisk ("*") is appended to this value if the decision rules are - not successfully polished. + * - 2-Stg Shift + - Infinity norm of the relative difference between the second-stage + variable vectors (evaluated subject to the nominal uncertain + parameter realization) of the master solutions of the current + and previous iterations. Expect this value to trend + downward as the iteration number increases. A dash ("-") is produced in lieu of a value if the current iteration number is 0, - there are no decision rule variables, + there are no second-stage variables, or the master problem of the current iteration is not solved successfully. * - #CViol - Number of performance constraints found to be violated during From 7226cfeaa2bbd16f0f9465e8017f8a1dc1225080 Mon Sep 17 00:00:00 2001 From: jasherma Date: Mon, 13 Nov 2023 14:29:37 -0500 Subject: [PATCH 0406/1204] Improve docs for variable update evalaution functions --- .../contrib/pyros/pyros_algorithm_methods.py | 88 ++++++++++++++++++- 1 file changed, 85 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index 06666692146..4473d85e060 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -197,7 +197,31 @@ def evaluate_first_stage_var_shift( first_iter_master_fsv_vals, ): """ - Evaluate first-stage variable "shift". + Evaluate first-stage variable "shift": the maximum relative + difference between first-stage variable values from the current + and previous master iterations. + + Parameters + ---------- + current_master_fsv_vals : ComponentMap + First-stage variable values from the current master + iteration. + previous_master_fsv_vals : ComponentMap + First-stage variable values from the previous master + iteration. + first_iter_master_fsv_vals : ComponentMap + First-stage variable values from the first master + iteration. + + Returns + ------- + None + Returned only if `current_master_fsv_vals` is empty, + which should occur only if the problem has no first-stage + variables. + float + The maximum relative difference + Returned only if `current_master_fsv_vals` is not empty. """ if not current_master_fsv_vals: # there are no first-stage variables @@ -216,7 +240,35 @@ def evalaute_second_stage_var_shift( first_iter_master_nom_ssv_vals, ): """ - Evaluate second-stage variable "shift". + Evaluate second-stage variable "shift": the maximum relative + difference between second-stage variable values from the current + and previous master iterations as evaluated subject to the + nominal uncertain parameter realization. + + Parameters + ---------- + current_master_nom_ssv_vals : ComponentMap + Second-stage variable values from the current master + iteration, evaluated subject to the nominal uncertain + parameter realization. + previous_master_nom_ssv_vals : ComponentMap + Second-stage variable values from the previous master + iteration, evaluated subject to the nominal uncertain + parameter realization. + first_iter_master_nom_ssv_vals : ComponentMap + Second-stage variable values from the first master + iteration, evaluated subject to the nominal uncertain + parameter realization. + + Returns + ------- + None + Returned only if `current_master_nom_ssv_vals` is empty, + which should occur only if the problem has no second-stage + variables. + float + The maximum relative difference. + Returned only if `current_master_nom_ssv_vals` is not empty. """ if not current_master_nom_ssv_vals: return None @@ -238,7 +290,37 @@ def evaluate_dr_var_shift( dr_var_to_ssv_map, ): """ - Evalaute decision rule variable "shift". + Evaluate decision rule variable "shift": the maximum relative + difference between scaled decision rule (DR) variable expressions + (terms in the DR equations) from the current + and previous master iterations. + + Parameters + ---------- + current_master_dr_var_vals : ComponentMap + DR variable values from the current master + iteration. + previous_master_dr_var_vals : ComponentMap + DR variable values from the previous master + iteration. + first_iter_master_nom_ssv_vals : ComponentMap + Second-stage variable values (evalauted subject to the + nominal uncertain parameter realization) + from the first master iteration. + dr_var_to_ssv_map : ComponentMap + Mapping from each DR variable to the + second-stage variable whose value is a function of the + DR variable. + + Returns + ------- + None + Returned only if `current_master_dr_var_vals` is empty, + which should occur only if the problem has no decision rule + (or equivalently, second-stage) variables. + float + The maximum relative difference. + Returned only if `current_master_dr_var_vals` is not empty. """ if not current_master_dr_var_vals: return None From 9edbc21040264655c2267d4354d29e0d54a7a6e9 Mon Sep 17 00:00:00 2001 From: jasherma Date: Mon, 13 Nov 2023 14:30:21 -0500 Subject: [PATCH 0407/1204] Apply black --- .../contrib/pyros/pyros_algorithm_methods.py | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index 4473d85e060..e7c27ff5815 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -192,10 +192,8 @@ def evaluate_and_log_component_stats(model_data, separation_model, config): def evaluate_first_stage_var_shift( - current_master_fsv_vals, - previous_master_fsv_vals, - first_iter_master_fsv_vals, - ): + current_master_fsv_vals, previous_master_fsv_vals, first_iter_master_fsv_vals +): """ Evaluate first-stage variable "shift": the maximum relative difference between first-stage variable values from the current @@ -235,10 +233,10 @@ def evaluate_first_stage_var_shift( def evalaute_second_stage_var_shift( - current_master_nom_ssv_vals, - previous_master_nom_ssv_vals, - first_iter_master_nom_ssv_vals, - ): + current_master_nom_ssv_vals, + previous_master_nom_ssv_vals, + first_iter_master_nom_ssv_vals, +): """ Evaluate second-stage variable "shift": the maximum relative difference between second-stage variable values from the current @@ -274,21 +272,18 @@ def evalaute_second_stage_var_shift( return None else: return max( - abs( - current_master_nom_ssv_vals[ssv] - - previous_master_nom_ssv_vals[ssv] - ) + abs(current_master_nom_ssv_vals[ssv] - previous_master_nom_ssv_vals[ssv]) / max((abs(first_iter_master_nom_ssv_vals[ssv]), 1)) for ssv in previous_master_nom_ssv_vals ) def evaluate_dr_var_shift( - current_master_dr_var_vals, - previous_master_dr_var_vals, - first_iter_master_nom_ssv_vals, - dr_var_to_ssv_map, - ): + current_master_dr_var_vals, + previous_master_dr_var_vals, + first_iter_master_nom_ssv_vals, + dr_var_to_ssv_map, +): """ Evaluate decision rule variable "shift": the maximum relative difference between scaled decision rule (DR) variable expressions @@ -326,10 +321,7 @@ def evaluate_dr_var_shift( return None else: return max( - abs( - current_master_dr_var_vals[drvar] - - previous_master_dr_var_vals[drvar] - ) + abs(current_master_dr_var_vals[drvar] - previous_master_dr_var_vals[drvar]) / max((1, abs(first_iter_master_nom_ssv_vals[dr_var_to_ssv_map[drvar]]))) for drvar in previous_master_dr_var_vals ) From 60fee49b98e630baa3a1829e08bbff75e6b657ea Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 13 Nov 2023 13:57:36 -0700 Subject: [PATCH 0408/1204] Push changes from pair-programming --- pyomo/opt/plugins/sol.py | 1 + pyomo/solver/IPOPT.py | 4 +++- pyomo/solver/config.py | 1 + pyomo/solver/results.py | 36 ++++++++++++++---------------------- 4 files changed, 19 insertions(+), 23 deletions(-) diff --git a/pyomo/opt/plugins/sol.py b/pyomo/opt/plugins/sol.py index 6e1ca666633..255df117399 100644 --- a/pyomo/opt/plugins/sol.py +++ b/pyomo/opt/plugins/sol.py @@ -189,6 +189,7 @@ def _load(self, fin, res, soln, suffixes): if line == "": continue line = line.split() + # Some sort of garbage we tag onto the solver message, assuming we are past the suffixes if line[0] != 'suffix': # We assume this is the start of a # section like kestrel_option, which diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index 875f8710b10..90c8a6d1bce 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -147,7 +147,9 @@ def solve(self, model, **kwds): results = Results() results.termination_condition = TerminationCondition.error else: - results = self._parse_solution() + # TODO: Make a context manager out of this and open the file + # to pass to the results, instead of doing this thing. + results = self._parse_solution(os.path.join(dname, model.name + '.sol'), self.info) def _parse_solution(self): # STOPPING POINT: The suggestion here is to look at the original diff --git a/pyomo/solver/config.py b/pyomo/solver/config.py index ed9008b7e1f..3f4424a8806 100644 --- a/pyomo/solver/config.py +++ b/pyomo/solver/config.py @@ -61,6 +61,7 @@ def __init__( self.load_solution: bool = self.declare( 'load_solution', ConfigValue(domain=bool, default=True) ) + self.raise_exception_on_nonoptimal_result: bool = self.declare('raise_exception_on_nonoptimal_result', ConfigValue(domain=bool, default=True)) self.symbolic_solver_labels: bool = self.declare( 'symbolic_solver_labels', ConfigValue(domain=bool, default=False) ) diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index d7505a7ed95..9aa2869b414 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -241,20 +241,20 @@ class ResultsReader: pass -def parse_sol_file(file, results): +def parse_sol_file(sol_file, nl_info): # The original reader for sol files is in pyomo.opt.plugins.sol. # Per my original complaint, it has "magic numbers" that I just don't # know how to test. It's apparently less fragile than that in APPSI. # NOTE: The Results object now also holds the solution loader, so we do # not need pass in a solution like we did previously. - if results is None: - results = Results() + # nl_info is an NLWriterInfo object that has vars, cons, etc. + results = Results() # For backwards compatibility and general safety, we will parse all # lines until "Options" appears. Anything before "Options" we will # consider to be the solver message. message = [] - for line in file: + for line in sol_file: if not line: break line = line.strip() @@ -265,40 +265,32 @@ def parse_sol_file(file, results): # Once "Options" appears, we must now read the content under it. model_objects = [] if "Options" in line: - line = file.readline() + line = sol_file.readline() number_of_options = int(line) need_tolerance = False if number_of_options > 4: # MRM: Entirely unclear why this is necessary, or if it even is number_of_options -= 2 need_tolerance = True for i in range(number_of_options + 4): - line = file.readline() + line = sol_file.readline() model_objects.append(int(line)) if need_tolerance: # MRM: Entirely unclear why this is necessary, or if it even is - line = file.readline() + line = sol_file.readline() model_objects.append(float(line)) else: raise SolverSystemError("ERROR READING `sol` FILE. No 'Options' line found.") # Identify the total number of variables and constraints number_of_cons = model_objects[number_of_options + 1] number_of_vars = model_objects[number_of_options + 3] - constraints = [] - variables = [] - # Parse through the constraint lines and capture the constraints - i = 0 - while i < number_of_cons: - line = file.readline() - constraints.append(float(line)) - i += 1 - # Parse through the variable lines and capture the variables - i = 0 - while i < number_of_vars: - line = file.readline() - variables.append(float(line)) - i += 1 + assert number_of_cons == len(nl_info.constraints) + assert number_of_vars == len(nl_info.variables) + + duals = [float(sol_file.readline()) for i in range(number_of_cons)] + variable_vals = [float(sol_file.readline()) for i in range(number_of_vars)] + # Parse the exit code line and capture it exit_code = [0, 0] - line = file.readline() + line = sol_file.readline() if line and ('objno' in line): exit_code_line = line.split() if (len(exit_code_line) != 3): From 45ae79f9646ae75a505fbff82a8da1c422e3b891 Mon Sep 17 00:00:00 2001 From: jasherma Date: Mon, 13 Nov 2023 16:44:30 -0500 Subject: [PATCH 0409/1204] Fix typos evalaute -> evaluate --- pyomo/contrib/pyros/pyros_algorithm_methods.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index e7c27ff5815..af7a91d21a4 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -232,7 +232,7 @@ def evaluate_first_stage_var_shift( ) -def evalaute_second_stage_var_shift( +def evaluate_second_stage_var_shift( current_master_nom_ssv_vals, previous_master_nom_ssv_vals, first_iter_master_nom_ssv_vals, @@ -299,7 +299,7 @@ def evaluate_dr_var_shift( DR variable values from the previous master iteration. first_iter_master_nom_ssv_vals : ComponentMap - Second-stage variable values (evalauted subject to the + Second-stage variable values (evaluated subject to the nominal uncertain parameter realization) from the first master iteration. dr_var_to_ssv_map : ComponentMap @@ -677,7 +677,7 @@ def ROSolver_iterative_solve(model_data, config): previous_master_fsv_vals=previous_master_fsv_vals, first_iter_master_fsv_vals=first_iter_master_fsv_vals, ) - second_stage_var_shift = evalaute_second_stage_var_shift( + second_stage_var_shift = evaluate_second_stage_var_shift( current_master_nom_ssv_vals=current_master_nom_ssv_vals, previous_master_nom_ssv_vals=previous_master_nom_ssv_vals, first_iter_master_nom_ssv_vals=first_iter_master_nom_ssv_vals, From 124cdf41e8c98a233483e8578016658fec41501b Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Mon, 13 Nov 2023 16:06:20 -0700 Subject: [PATCH 0410/1204] Fixing a bug where we were accidentally ignoring local vars and disaggregating them anyway --- pyomo/gdp/plugins/hull.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 7a5d752bdbb..1a76f08ff60 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -394,6 +394,7 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, # being local. We have marked our own disaggregated variables as local, # so they will not be re-disaggregated. vars_to_disaggregate = {disj: ComponentSet() for disj in obj.disjuncts} + all_vars_to_disaggregate = ComponentSet() for var in var_order: disjuncts = disjuncts_var_appears_in[var] # clearly not local if used in more than one disjunct @@ -406,6 +407,7 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, ) for disj in disjuncts: vars_to_disaggregate[disj].add(var) + all_vars_to_disaggregate.add(var) else: # disjuncts is a set of length 1 disjunct = next(iter(disjuncts)) if disjunct in local_vars_by_disjunct: @@ -413,10 +415,12 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, # It's not declared local to this Disjunct, so we # disaggregate vars_to_disaggregate[disjunct].add(var) + all_vars_to_disaggregate.add(var) else: # The user didn't declare any local vars for this # Disjunct, so we know we're disaggregating it vars_to_disaggregate[disjunct].add(var) + all_vars_to_disaggregate.add(var) # Now that we know who we need to disaggregate, we will do it # while we also transform the disjuncts. @@ -440,7 +444,7 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, # add the reaggregation constraints i = 0 - for var in var_order: + for var in all_vars_to_disaggregate: # There are two cases here: Either the var appeared in every # disjunct in the disjunction, or it didn't. If it did, there's # nothing special to do: All of the disaggregated variables have @@ -526,6 +530,7 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, def _transform_disjunct(self, obj, transBlock, vars_to_disaggregate, local_vars, parent_local_var_suffix, parent_disjunct_local_vars): + print("\nTransforming '%s'" % obj.name) relaxationBlock = self._get_disjunct_transformation_block(obj, transBlock) # Put the disaggregated variables all on their own block so that we can @@ -560,6 +565,7 @@ def _transform_disjunct(self, obj, transBlock, vars_to_disaggregate, local_vars, disaggregatedVarName + "_bounds", bigmConstraint ) + print("Adding bounds constraints for '%s'" % var) self._declare_disaggregated_var_bounds( var, disaggregatedVar, @@ -590,6 +596,9 @@ def _transform_disjunct(self, obj, transBlock, vars_to_disaggregate, local_vars, bigmConstraint = Constraint(transBlock.lbub) relaxationBlock.add_component(conName, bigmConstraint) + print("Adding bounds constraints for local var '%s'" % var) + # TODO: This gets mapped in a place where we can't find it if we ask + # for it from the local var itself. self._declare_disaggregated_var_bounds( var, var, @@ -984,7 +993,7 @@ def get_var_bounds_constraint(self, v): Parameters ---------- - v: a Var which was created by the hull transformation as a + v: a Var that was created by the hull transformation as a disaggregated variable (and so appears on a transformation block of some Disjunct) """ From 688a3b17cfba8e8aeb93b4d03323fc0af84e4b1d Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Mon, 13 Nov 2023 16:06:59 -0700 Subject: [PATCH 0411/1204] Generalizing the nested test, starting to test for not having more constraints than we expect (which currently we do) --- pyomo/gdp/tests/common_tests.py | 38 +++++++++++++++++++--- pyomo/gdp/tests/test_hull.py | 56 +++++++++++++++++++++++++++++---- 2 files changed, 83 insertions(+), 11 deletions(-) diff --git a/pyomo/gdp/tests/common_tests.py b/pyomo/gdp/tests/common_tests.py index b475334981b..2eff67a8826 100644 --- a/pyomo/gdp/tests/common_tests.py +++ b/pyomo/gdp/tests/common_tests.py @@ -1703,17 +1703,45 @@ def check_all_components_transformed(self, m): def check_transformation_blocks_nestedDisjunctions(self, m, transformation): disjunctionTransBlock = m.disj.algebraic_constraint.parent_block() transBlocks = disjunctionTransBlock.relaxedDisjuncts - self.assertEqual(len(transBlocks), 4) if transformation == 'bigm': + self.assertEqual(len(transBlocks), 4) self.assertIs(transBlocks[0], m.d1.d3.transformation_block) self.assertIs(transBlocks[1], m.d1.d4.transformation_block) self.assertIs(transBlocks[2], m.d1.transformation_block) self.assertIs(transBlocks[3], m.d2.transformation_block) if transformation == 'hull': - self.assertIs(transBlocks[2], m.d1.d3.transformation_block) - self.assertIs(transBlocks[3], m.d1.d4.transformation_block) - self.assertIs(transBlocks[0], m.d1.transformation_block) - self.assertIs(transBlocks[1], m.d2.transformation_block) + # This is a much more comprehensive test that doesn't depend on + # transformation Block structure, so just reuse it: + hull = TransformationFactory('gdp.hull') + d3 = hull.get_disaggregated_var(m.d1.d3.indicator_var, m.d1) + d4 = hull.get_disaggregated_var(m.d1.d4.indicator_var, m.d1) + self.check_transformed_model_nestedDisjuncts(m, d3, d4) + + # check the disaggregated indicator var bound constraints too + cons = hull.get_var_bounds_constraint(d3) + self.assertEqual(len(cons), 1) + check_obj_in_active_tree(self, cons['ub']) + cons_expr = self.simplify_leq_cons(cons['ub']) + assertExpressionsEqual( + self, + cons_expr, + d3 - m.d1.binary_indicator_var <= 0.0 + ) + + cons = hull.get_var_bounds_constraint(d4) + self.assertEqual(len(cons), 1) + check_obj_in_active_tree(self, cons['ub']) + cons_expr = self.simplify_leq_cons(cons['ub']) + assertExpressionsEqual( + self, + cons_expr, + d4 - m.d1.binary_indicator_var <= 0.0 + ) + + num_cons = len(m.component_data_objects(Constraint, + active=True, + descend_into=Block)) + self.assertEqual(num_cons, 10) def check_nested_disjunction_target(self, transformation): diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index b224385bec0..cfd0feac2b2 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -1564,6 +1564,36 @@ def test_transformed_model_nestedDisjuncts(self): hull = TransformationFactory('gdp.hull') hull.apply_to(m) + self.check_transformed_model_nestedDisjuncts(m, m.d1.d3.binary_indicator_var, + m.d1.d4.binary_indicator_var) + + # Last, check that there aren't things we weren't expecting + + all_cons = list(m.component_data_objects(Constraint, active=True, + descend_into=Block)) + num_cons = len(all_cons) + # TODO: I shouldn't have d1.binary_indicator_var in the local list + # above, but I think if I do it should be ignored when it doesn't appear + # in any Disjuncts... + + # TODO: We get duplicate bounds constraints for inner disaggregated Vars + # because we declare bounds constraints for local vars every time. We + # should actually track them separately so that we don't duplicate + # bounds constraints over and over again. + for idx, cons in enumerate(all_cons): + print(idx) + print(cons.name) + print(cons.expr) + print("") + # 2 disaggregation constraints for x 0,3 + # + 4 bounds constraints for x 6,8,9,13, These are dumb: 10,14,16 + # + 2 bounds constraints for inner indicator vars 11, 12 + # + 2 exactly-one constraints 1,4 + # + 4 transformed constraints 2,5,7,15 + self.assertEqual(num_cons, 14) + + def check_transformed_model_nestedDisjuncts(self, m, d3, d4): + hull = TransformationFactory('gdp.hull') transBlock = m._pyomo_gdp_hull_reformulation self.assertTrue(transBlock.active) @@ -1590,8 +1620,8 @@ def test_transformed_model_nestedDisjuncts(self): assertExpressionsEqual( self, xor_expr, - m.d1.d3.binary_indicator_var + - m.d1.d4.binary_indicator_var - + d3 + + d4 - m.d1.binary_indicator_var == 0.0 ) @@ -1622,8 +1652,6 @@ def test_transformed_model_nestedDisjuncts(self): m.x - x_d1 - x_d2 == 0.0 ) - ## Bound constraints - ## Transformed constraints cons = hull.get_transformed_constraints(m.d1.d3.c) self.assertEqual(len(cons), 1) @@ -1698,7 +1726,7 @@ def test_transformed_model_nestedDisjuncts(self): assertExpressionsEqual( self, cons_expr, - x_d3 - 2*m.d1.d3.binary_indicator_var <= 0.0 + x_d3 - 2*d3 <= 0.0 ) cons = hull.get_var_bounds_constraint(x_d4) # the lb is trivial in this case, so we just have 1 @@ -1708,7 +1736,23 @@ def test_transformed_model_nestedDisjuncts(self): assertExpressionsEqual( self, cons_expr, - x_d4 - 2*m.d1.d4.binary_indicator_var <= 0.0 + x_d4 - 2*d4 <= 0.0 + ) + + # Bounds constraints for local vars + cons = hull.get_var_bounds_constraint(m.d1.d3.binary_indicator_var) + ct.check_obj_in_active_tree(self, cons['ub']) + assertExpressionsEqual( + self, + cons['ub'].expr, + m.d1.d3.binary_indicator_var <= m.d1.binary_indicator_var + ) + cons = hull.get_var_bounds_constraint(m.d1.d4.binary_indicator_var) + ct.check_obj_in_active_tree(self, cons['ub']) + assertExpressionsEqual( + self, + cons['ub'].expr, + m.d1.d4.binary_indicator_var <= m.d1.binary_indicator_var ) @unittest.skipIf(not linear_solvers, "No linear solver available") From 73f2e16ce76bfb6e84bcb0183b523d2340ff477c Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 14 Nov 2023 15:23:58 -0500 Subject: [PATCH 0412/1204] change load_solutions to attribute --- pyomo/contrib/mindtpy/algorithm_base_class.py | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 03fcc12fbac..5f7bc438fd0 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -143,6 +143,8 @@ def __init__(self, **kwds): self.last_iter_cuts = False # Store the OA cuts generated in the mip_start_process. self.mip_start_lazy_oa_cuts = [] + # Whether to load solutions in solve() function + self.load_solutions = True # Support use as a context manager under current solver API def __enter__(self): @@ -292,7 +294,7 @@ def model_is_valid(self): results = self.mip_opt.solve( self.original_model, tee=config.mip_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **config.mip_solver_args, ) if len(results.solution) > 0: @@ -832,7 +834,7 @@ def init_rNLP(self, add_oa_cuts=True): results = self.nlp_opt.solve( self.rnlp, tee=config.nlp_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **nlp_args, ) if len(results.solution) > 0: @@ -962,7 +964,7 @@ def init_max_binaries(self): results = self.mip_opt.solve( m, tee=config.mip_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **mip_args, ) if len(results.solution) > 0: @@ -1082,7 +1084,7 @@ def solve_subproblem(self): results = self.nlp_opt.solve( self.fixed_nlp, tee=config.nlp_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **nlp_args, ) if len(results.solution) > 0: @@ -1520,7 +1522,7 @@ def fix_dual_bound(self, last_iter_cuts): main_mip_results = self.mip_opt.solve( self.mip, tee=config.mip_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **mip_args, ) if len(main_mip_results.solution) > 0: @@ -1607,7 +1609,7 @@ def solve_main(self): main_mip_results = self.mip_opt.solve( self.mip, tee=config.mip_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **mip_args, ) # update_attributes should be before load_from(main_mip_results), since load_from(main_mip_results) may fail. @@ -1663,7 +1665,7 @@ def solve_fp_main(self): main_mip_results = self.mip_opt.solve( self.mip, tee=config.mip_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **mip_args, ) # update_attributes should be before load_from(main_mip_results), since load_from(main_mip_results) may fail. @@ -1706,7 +1708,7 @@ def solve_regularization_main(self): main_mip_results = self.regularization_mip_opt.solve( self.mip, tee=config.mip_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **dict(config.mip_solver_args), ) if len(main_mip_results.solution) > 0: @@ -1918,7 +1920,7 @@ def handle_main_unbounded(self, main_mip): main_mip_results = self.mip_opt.solve( main_mip, tee=config.mip_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **config.mip_solver_args, ) if len(main_mip_results.solution) > 0: @@ -2256,7 +2258,7 @@ def check_config(self): and 'appsi' in config.mip_regularization_solver ) ): - config.load_solutions = False + self.load_solutions = False ################################################################################################################################ # Feasibility Pump @@ -2323,7 +2325,7 @@ def solve_fp_subproblem(self): results = self.nlp_opt.solve( fp_nlp, tee=config.nlp_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **nlp_args, ) if len(results.solution) > 0: From a08fd36825f4fb3383668d74b0d3a18c513b7237 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 14 Nov 2023 15:48:35 -0500 Subject: [PATCH 0413/1204] black format --- pyomo/contrib/mindtpy/algorithm_base_class.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 5f7bc438fd0..c9169ab8c62 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -962,10 +962,7 @@ def init_max_binaries(self): mip_args = dict(config.mip_solver_args) update_solver_timelimit(self.mip_opt, config.mip_solver, self.timing, config) results = self.mip_opt.solve( - m, - tee=config.mip_solver_tee, - load_solutions=self.load_solutions, - **mip_args, + m, tee=config.mip_solver_tee, load_solutions=self.load_solutions, **mip_args ) if len(results.solution) > 0: m.solutions.load_from(results) From d15f499c5507438fbac7192c23621541b9673135 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 14 Nov 2023 13:57:08 -0700 Subject: [PATCH 0414/1204] Return scaling information from the NL writer --- pyomo/repn/plugins/nl_writer.py | 42 +++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 97569784aa6..8b5db8dc320 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -11,7 +11,7 @@ import logging import os -from collections import deque, defaultdict +from collections import deque, defaultdict, namedtuple from itertools import filterfalse, product from operator import itemgetter, attrgetter, setitem from contextlib import nullcontext @@ -116,6 +116,9 @@ _MONOMIAL = ExprType.MONOMIAL _GENERAL = ExprType.GENERAL +ScalingFactors = namedtuple( + 'ScalingFactors', ['variables', 'constraints', 'objectives'] +) # TODO: make a proper base class class NLWriterInfo(object): @@ -165,10 +168,16 @@ class NLWriterInfo(object): appearing in the expression must either have been sent to the solver, or appear *earlier* in this list. + scaling: ScalingFactors or None + + namedtuple holding 3 lists of (variables, constraints, objectives) + scaling factors in the same order (and size) as the `variables`, + `constraints`, and `objectives` attributes above. + """ def __init__( - self, var, con, obj, external_libs, row_labels, col_labels, eliminated_vars + self, var, con, obj, external_libs, row_labels, col_labels, eliminated_vars, scaling ): self.variables = var self.constraints = con @@ -177,6 +186,7 @@ def __init__( self.row_labels = row_labels self.column_labels = col_labels self.eliminated_vars = eliminated_vars + self.scaling = scaling @WriterFactory.register('nl_v2', 'Generate the corresponding AMPL NL file (version 2).') @@ -905,7 +915,7 @@ def write(self, model): level=logging.DEBUG, ) - # Update the column order. + # Update the column order (based on our reordering of the variables above). # # Note that as we allow var_map to contain "known" variables # that are not needed in the NL file (and column_order was @@ -999,8 +1009,10 @@ def write(self, model): _vmap = self.var_id_to_nl if scale_model: template = self.template - for var_idx, _id in enumerate(variables): - scale = scaling_factor(var_map[_id]) + objective_scaling = [scaling_cache[id(info[0])] for info in objectives] + constraint_scaling = [scaling_cache[id(info[0])] for info in constraints] + variable_scaling = [scaling_factor(var_map[_id]) for _id in variables] + for _id, scale in zip(variables, variable_scaling): if scale == 1: continue # Update var_bounds to be scaled bounds @@ -1353,11 +1365,11 @@ def write(self, model): "objectives. Assuming that the duals are computed " "against the first objective." ) - _obj_scale = scaling_cache[id(objectives[0][0])] + _obj_scale = objective_scaling[0] else: _obj_scale = 1 - for _id in data.con: - data.con[_id] *= _obj_scale / scaling_cache[id(constraints[_id][0])] + for i in data.con: + data.con[i] *= _obj_scale / constraint_scaling[i] if data.var: logger.warning("ignoring 'dual' suffix for Var types") if data.obj: @@ -1380,8 +1392,9 @@ def write(self, model): if val is not None ] if scale_model: - for i, (var_idx, val) in enumerate(_init_lines): - _init_lines[i] = (var_idx, val * scaling_cache[variables[var_idx]]) + _init_lines = [ + (var_idx, val * variable_scaling[var_idx]) for var_idx, val in _init_lines + ] ostream.write( 'x%d%s\n' % (len(_init_lines), "\t# initial guess" if symbolic_solver_labels else '') @@ -1487,6 +1500,14 @@ def write(self, model): (var_map[_id], expr_info) for _id, expr_info in eliminated_vars.items() ] eliminated_vars.reverse() + if scale_model: + scaling = ScalingFactors( + variables=variable_scaling, + constraints=constraint_scaling, + objectives=objective_scaling, + ) + else: + scaling = None info = NLWriterInfo( var=[var_map[_id] for _id in variables], con=[info[0] for info in constraints], @@ -1495,6 +1516,7 @@ def write(self, model): row_labels=row_labels, col_labels=col_labels, eliminated_vars=eliminated_vars, + scaling=scaling, ) timer.toc("Wrote NL stream", level=logging.DEBUG) timer.toc("Generated NL representation", delta=False) From b3341b077f8af6666c2af24f6167339985849ee4 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 14 Nov 2023 13:59:41 -0700 Subject: [PATCH 0415/1204] NFC: apply black, fix typos --- pyomo/repn/plugins/nl_writer.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 8b5db8dc320..82a8175f16b 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -120,6 +120,7 @@ 'ScalingFactors', ['variables', 'constraints', 'objectives'] ) + # TODO: make a proper base class class NLWriterInfo(object): """Return type for NLWriter.write() @@ -177,7 +178,15 @@ class NLWriterInfo(object): """ def __init__( - self, var, con, obj, external_libs, row_labels, col_labels, eliminated_vars, scaling + self, + var, + con, + obj, + external_libs, + row_labels, + col_labels, + eliminated_vars, + scaling, ): self.variables = var self.constraints = con @@ -421,7 +430,7 @@ def compile(self, column_order, row_order, obj_order, model_id): if val.__class__ not in int_float: # [JDS] I am not entirely sure why, but we have # historically supported suffix values that hold - # dictionaries that map arbirtary component data + # dictionaries that map arbitrary component data # objects to values. We will preserve that behavior # here. This behavior is exercised by a # ExternalGreyBox test. @@ -1393,7 +1402,8 @@ def write(self, model): ] if scale_model: _init_lines = [ - (var_idx, val * variable_scaling[var_idx]) for var_idx, val in _init_lines + (var_idx, val * variable_scaling[var_idx]) + for var_idx, val in _init_lines ] ostream.write( 'x%d%s\n' From 973f12eeff8a42ab483462d780da77c1becff0de Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 15 Nov 2023 01:17:09 -0700 Subject: [PATCH 0416/1204] Allow report_timing() to be used as a context manager --- pyomo/common/timing.py | 75 +++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/pyomo/common/timing.py b/pyomo/common/timing.py index 96360c61a1b..17f508804e4 100644 --- a/pyomo/common/timing.py +++ b/pyomo/common/timing.py @@ -44,30 +44,59 @@ _transform_logger = logging.getLogger('pyomo.common.timing.transformation') -def report_timing(stream=True, level=logging.INFO): - """Set reporting of Pyomo timing information. +class report_timing(object): + def __init__(self, stream=True, level=logging.INFO): + """Set reporting of Pyomo timing information. - Parameters - ---------- - stream: bool, TextIOBase - The destination stream to emit timing information. If ``True``, - defaults to ``sys.stdout``. If ``False`` or ``None``, disables - reporting of timing information. - level: int - The logging level for the timing logger - """ - if stream: - _logger.setLevel(level) - if stream is True: - stream = sys.stdout - handler = logging.StreamHandler(stream) - handler.setFormatter(logging.Formatter(" %(message)s")) - _logger.addHandler(handler) - return handler - else: - _logger.setLevel(logging.WARNING) - for h in _logger.handlers: - _logger.removeHandler(h) + For historical reasons, this class may be used as a function + (the reporting logger is configured as part of the instance + initializer). However, the preferred usage is as a context + manager (thereby ensuring that the timing logger is restored + upon exit). + + Parameters + ---------- + stream: bool, TextIOBase + + The destination stream to emit timing information. If + ``True``, defaults to ``sys.stdout``. If ``False`` or + ``None``, disables reporting of timing information. + + level: int + + The logging level for the timing logger + + """ + self._old_level = _logger.level + # For historical reasons (because report_timing() used to be a + # function), we will do what you think should be done in + # __enter__ here in __init__. + if stream: + _logger.setLevel(level) + if stream is True: + stream = sys.stdout + self._handler = logging.StreamHandler(stream) + self._handler.setFormatter(logging.Formatter(" %(message)s")) + _logger.addHandler(self._handler) + else: + self._handler = list(_logger.handlers) + _logger.setLevel(logging.WARNING) + for h in list(_logger.handlers): + _logger.removeHandler(h) + + def reset(self): + _logger.setLevel(self._old_level) + if type(self._handler) is list: + for h in self._handler: + _logger.addHandler(h) + else: + _logger.removeHandler(self._handler) + + def __enter__(self): + return self + + def __exit__(self, et, ev, tb): + self.reset() class GeneralTimer(object): From 3799e6fb811ead3562eb668fcbfc851dfdf9df3b Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 15 Nov 2023 01:17:49 -0700 Subject: [PATCH 0417/1204] create_instance(): use report_timing as a context manager --- pyomo/core/base/PyomoModel.py | 68 +++++++++++++++++------------------ 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/pyomo/core/base/PyomoModel.py b/pyomo/core/base/PyomoModel.py index f8b2710b9f2..6aacabeb183 100644 --- a/pyomo/core/base/PyomoModel.py +++ b/pyomo/core/base/PyomoModel.py @@ -40,6 +40,7 @@ from pyomo.opt.results import SolverResults, Solution, SolverStatus, UndefinedData +from contextlib import nullcontext from io import StringIO logger = logging.getLogger('pyomo.core') @@ -691,9 +692,6 @@ def create_instance( if self.is_constructed(): return self.clone() - if report_timing: - timing.report_timing() - if name is None: # Preserve only the local name (not the FQ name, as that may # have been quoted or otherwise escaped) @@ -709,42 +707,44 @@ def create_instance( if data is None: data = {} - # - # Clone the model and load the data - # - instance = self.clone() + reporting_context = timing.report_timing if report_timing else nullcontext + with reporting_context(): + # + # Clone the model and load the data + # + instance = self.clone() - if name is not None: - instance._name = name + if name is not None: + instance._name = name - # If someone passed a rule for creating the instance, fire the - # rule before constructing the components. - if instance._rule is not None: - instance._rule(instance, next(iter(self.index_set()))) + # If someone passed a rule for creating the instance, fire the + # rule before constructing the components. + if instance._rule is not None: + instance._rule(instance, next(iter(self.index_set()))) - if namespaces: - _namespaces = list(namespaces) - else: - _namespaces = [] - if namespace is not None: - _namespaces.append(namespace) - if None not in _namespaces: - _namespaces.append(None) + if namespaces: + _namespaces = list(namespaces) + else: + _namespaces = [] + if namespace is not None: + _namespaces.append(namespace) + if None not in _namespaces: + _namespaces.append(None) - instance.load(data, namespaces=_namespaces, profile_memory=profile_memory) + instance.load(data, namespaces=_namespaces, profile_memory=profile_memory) - # - # Indicate that the model is concrete/constructed - # - instance._constructed = True - # - # Change this class from "Abstract" to "Concrete". It is - # absolutely crazy that this is allowed in Python, but since the - # AbstractModel and ConcreteModel are basically identical, we - # can "reassign" the new concrete instance to be an instance of - # ConcreteModel - # - instance.__class__ = ConcreteModel + # + # Indicate that the model is concrete/constructed + # + instance._constructed = True + # + # Change this class from "Abstract" to "Concrete". It is + # absolutely crazy that this is allowed in Python, but since the + # AbstractModel and ConcreteModel are basically identical, we + # can "reassign" the new concrete instance to be an instance of + # ConcreteModel + # + instance.__class__ = ConcreteModel return instance @deprecated( From 3808be15a156a8755c2322b4c7bc69d7009b850b Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 15 Nov 2023 01:18:24 -0700 Subject: [PATCH 0418/1204] Clean up / test NLv2 timing logger --- pyomo/repn/plugins/nl_writer.py | 23 +++++++++++++----- pyomo/repn/tests/ampl/test_nlv2.py | 38 ++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 82a8175f16b..154bbbe17bc 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -608,8 +608,6 @@ def write(self, model): if name not in suffix_data: suffix_data[name] = _SuffixData(name) suffix_data[name].update(suffix) - timer.toc("Collected suffixes", level=logging.DEBUG) - # # Data structures to support variable/constraint scaling # @@ -621,6 +619,8 @@ def write(self, model): scaling_factor = _NoScalingFactor() scale_model = scaling_factor.scale + timer.toc("Collected suffixes", level=logging.DEBUG) + # # Data structures to support presolve # @@ -641,7 +641,10 @@ def write(self, model): last_parent = None for obj in model.component_data_objects(Objective, active=True, sort=sorter): if with_debug_timing and obj.parent_component() is not last_parent: - timer.toc('Objective %s', last_parent, level=logging.DEBUG) + if last_parent is None: + timer.toc(None) + else: + timer.toc('Objective %s', last_parent, level=logging.DEBUG) last_parent = obj.parent_component() expr_info = visitor.walk_expression((obj.expr, obj, 1, scaling_factor(obj))) if expr_info.named_exprs: @@ -657,6 +660,8 @@ def write(self, model): if with_debug_timing: # report the last objective timer.toc('Objective %s', last_parent, level=logging.DEBUG) + else: + timer.toc('Processed %s objectives', len(objectives)) # Order the objectives, moving all nonlinear objectives to # the beginning @@ -676,9 +681,13 @@ def write(self, model): n_complementarity_range = 0 n_complementarity_nz_var_lb = 0 # + last_parent = None for con in ordered_active_constraints(model, self.config): if with_debug_timing and con.parent_component() is not last_parent: - timer.toc('Constraint %s', last_parent, level=logging.DEBUG) + if last_parent is None: + timer.toc(None) + else: + timer.toc('Constraint %s', last_parent, level=logging.DEBUG) last_parent = con.parent_component() scale = scaling_factor(con) expr_info = visitor.walk_expression((con.body, con, 0, scale)) @@ -723,6 +732,8 @@ def write(self, model): if with_debug_timing: # report the last constraint timer.toc('Constraint %s', last_parent, level=logging.DEBUG) + else: + timer.toc('Processed %s constraints', len(constraints)) # This may fetch more bounds than needed, but only in the cases # where variables were completely eliminated while walking the @@ -912,8 +923,8 @@ def write(self, model): linear_binary_vars = linear_integer_vars = set() assert len(variables) == n_vars timer.toc( - 'Set row / column ordering: %s variables [%s, %s, %s R/B/Z], ' - '%s constraints [%s, %s L/NL]', + 'Set row / column ordering: %s var [%s, %s, %s R/B/Z], ' + '%s con [%s, %s L/NL]', n_vars, len(continuous_vars), len(binary_vars), diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 663e795b661..7189d4d89e9 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -13,8 +13,10 @@ import pyomo.common.unittest as unittest import io +import logging import math import os +import re import pyomo.repn.util as repn_util import pyomo.repn.plugins.nl_writer as nl_writer @@ -23,7 +25,9 @@ from pyomo.common.dependencies import numpy, numpy_available from pyomo.common.log import LoggingIntercept +from pyomo.common.tee import capture_output from pyomo.common.tempfiles import TempfileManager +from pyomo.common.timing import report_timing from pyomo.core.expr import Expr_if, inequality, LinearExpression from pyomo.core.base.expression import ScalarExpression from pyomo.environ import ( @@ -989,6 +993,40 @@ def d(m, i): LOG.getvalue(), ) + def test_log_timing(self): + # This tests an error possibly reported by #2810 + m = ConcreteModel() + m.x = Var(range(6)) + m.x[0].domain = pyo.Binary + m.x[1].domain = pyo.Integers + m.x[2].domain = pyo.Integers + m.p = Param(initialize=5, mutable=True) + m.o1 = Objective([1, 2], rule=lambda m, i: 1) + m.o2 = Objective(expr=m.x[1] * m.x[2]) + m.c1 = Constraint([1, 2], rule=lambda m, i: sum(m.x.values()) == 1) + m.c2 = Constraint(expr=m.p * m.x[1] ** 2 + m.x[2] ** 3 <= 100) + + self.maxDiff = None + OUT = io.StringIO() + with capture_output() as LOG: + with report_timing(level=logging.DEBUG): + nl_writer.NLWriter().write(m, OUT) + self.assertEqual( + """ [+ #.##] Initialized column order + [+ #.##] Collected suffixes + [+ #.##] Objective o1 + [+ #.##] Objective o2 + [+ #.##] Constraint c1 + [+ #.##] Constraint c2 + [+ #.##] Categorized model variables: 14 nnz + [+ #.##] Set row / column ordering: 6 var [3, 1, 2 R/B/Z], 3 con [2, 1 L/NL] + [+ #.##] Generated row/col labels & comments + [+ #.##] Wrote NL stream + [ #.##] Generated NL representation +""", + re.sub(r'\d\.\d\d\]', '#.##]', LOG.getvalue()), + ) + def test_linear_constraint_npv_const(self): # This tests an error possibly reported by #2810 m = ConcreteModel() From e920c6c235c65fef8003365366c1b364e36e0cc1 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 15 Nov 2023 02:29:31 -0700 Subject: [PATCH 0419/1204] Remove unnecessary use of ComponentMap --- pyomo/core/base/suffix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index 130da451b1c..46a87523001 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -543,7 +543,7 @@ def __init__(self, name, default=None): self.name = name self.default = default self.all_suffixes = [] - self._suffixes_by_block = ComponentMap({None: []}) + self._suffixes_by_block = {None: []} def find(self, component_data): """Find suffix value for a given component data object in model tree From 216eb816347a124c3c6439c6b81b7bcbb14ec655 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 15 Nov 2023 02:30:37 -0700 Subject: [PATCH 0420/1204] Eliminate variables with coefficients closer to 1 --- pyomo/repn/plugins/nl_writer.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 82a8175f16b..0685b05de6f 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -12,9 +12,10 @@ import logging import os from collections import deque, defaultdict, namedtuple +from contextlib import nullcontext from itertools import filterfalse, product +from math import log10 as _log10 from operator import itemgetter, attrgetter, setitem -from contextlib import nullcontext from pyomo.common.collections import ComponentMap, ComponentSet from pyomo.common.config import ( @@ -1701,10 +1702,18 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): expr_info, lb = info _id, coef = expr_info.linear.popitem() id2, coef2 = expr_info.linear.popitem() - # For no particularly good reason, we will solve for - # (and substitute out) the variable with the smaller - # magnitude) - if abs(coef2) < abs(coef): + # In an attempt to improve numerical stability, we will + # solve for (and substitute out) the variable with the + # coefficient closer to +/-1) + print(coef, var_map[_id], coef2, var_map[id2]) + print(abs(_log10(abs(coef2))), abs(_log10(abs(coef)))) + print(abs(coef2), abs(coef)) + # if abs(coef2) < abs(coef): + log_coef = abs(_log10(abs(coef))) + log_coef2 = abs(_log10(abs(coef2))) + if log_coef2 < log_coef or ( + log_coef2 == log_coef and abs(coef2) - abs(coef) + ): _id, id2 = id2, _id coef, coef2 = coef2, coef # substituting _id with a*x + b From 4e16a6c8b40fbf71df059956ff1a48a06a418838 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 15 Nov 2023 02:33:15 -0700 Subject: [PATCH 0421/1204] Switch default to linear_presolve=True (for write()) --- pyomo/repn/plugins/nl_writer.py | 2 +- pyomo/repn/tests/ampl/test_nlv2.py | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 0685b05de6f..c904d8c73f5 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -308,7 +308,7 @@ class NLWriter(object): CONFIG.declare( 'linear_presolve', ConfigValue( - default=False, + default=True, domain=bool, description='Perform linear presolve', doc=""" diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 663e795b661..a4fbaee77ed 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -996,7 +996,7 @@ def test_linear_constraint_npv_const(self): m.p = Param(initialize=5, mutable=True) m.o = Objective(expr=1) m.c = Constraint( - expr=LinearExpression([m.p**2, 5 * m.x[1], 10 * m.x[2]]) == 0 + expr=LinearExpression([m.p**2, 5 * m.x[1], 10 * m.x[2]]) <= 0 ) OUT = io.StringIO() @@ -1004,7 +1004,7 @@ def test_linear_constraint_npv_const(self): self.assertEqual( *nl_diff( """g3 1 1 0 # problem unknown - 2 1 1 0 1 # vars, constraints, objectives, ranges, eqns + 2 1 1 0 0 # vars, constraints, objectives, ranges, eqns 0 0 0 0 0 0 # nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb 0 0 # network constraints: nonlinear, linear 0 0 0 # nonlinear vars in constraints, objectives, both @@ -1019,7 +1019,7 @@ def test_linear_constraint_npv_const(self): n1.0 x0 r -4 -25 +1 -25 b 3 3 @@ -1509,7 +1509,9 @@ def test_scaling(self): OUT = io.StringIO() with LoggingIntercept() as LOG: - nlinfo = nl_writer.NLWriter().write(m, OUT, scale_model=False) + nlinfo = nl_writer.NLWriter().write( + m, OUT, scale_model=False, linear_presolve=False + ) self.assertEqual(LOG.getvalue(), "") nl1 = OUT.getvalue() @@ -1591,7 +1593,9 @@ def test_scaling(self): OUT = io.StringIO() with LoggingIntercept() as LOG: - nlinfo = nl_writer.NLWriter().write(m, OUT, scale_model=True) + nlinfo = nl_writer.NLWriter().write( + m, OUT, scale_model=True, linear_presolve=False + ) self.assertEqual(LOG.getvalue(), "") nl2 = OUT.getvalue() From 22f82885a6c622c6b6eee8bd84b8a84eadb50a6e Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 15 Nov 2023 02:33:38 -0700 Subject: [PATCH 0422/1204] Make AMPLRepn hashable --- pyomo/repn/plugins/nl_writer.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index c904d8c73f5..d9cbe703340 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -9,6 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +import ctypes import logging import os from collections import deque, defaultdict, namedtuple @@ -1896,6 +1897,13 @@ def __eq__(self, other): and self.named_exprs == other.named_exprs ) + def __hash__(self): + # Approximation of the Python default object hash + # (4 LSB are rolled to the MSB to reduce hash collisions) + return id(self) // 16 + ( + (id(self) & 15) << 8 * ctypes.sizeof(ctypes.c_void_p) - 4 + ) + def duplicate(self): ans = self.__class__.__new__(self.__class__) ans.nl = self.nl From ce0dae45ed82bed08db29b220096fbb0f690307e Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 15 Nov 2023 02:34:12 -0700 Subject: [PATCH 0423/1204] Bugfix for presolving models with unbounded variables --- pyomo/repn/plugins/nl_writer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index d9cbe703340..9b15bf40f45 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1745,7 +1745,7 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): if x_ub is None or (ub is not None and ub < x_ub): x_ub = ub var_bounds[x] = x_lb, x_ub - if x_lb == x_ub: + if x_lb == x_ub and x_lb is not None: fixed_vars.append(x) eliminated_cons.add(con_id) else: From 323c930eb619824ade9e90ca64f9c5567fa40c0a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 15 Nov 2023 08:28:43 -0700 Subject: [PATCH 0424/1204] Preserve discreteness when eliminating variables --- pyomo/repn/plugins/nl_writer.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 9b15bf40f45..61ee9cdb3a4 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1703,20 +1703,25 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): expr_info, lb = info _id, coef = expr_info.linear.popitem() id2, coef2 = expr_info.linear.popitem() - # In an attempt to improve numerical stability, we will - # solve for (and substitute out) the variable with the - # coefficient closer to +/-1) - print(coef, var_map[_id], coef2, var_map[id2]) - print(abs(_log10(abs(coef2))), abs(_log10(abs(coef)))) - print(abs(coef2), abs(coef)) - # if abs(coef2) < abs(coef): - log_coef = abs(_log10(abs(coef))) - log_coef2 = abs(_log10(abs(coef2))) - if log_coef2 < log_coef or ( - log_coef2 == log_coef and abs(coef2) - abs(coef) - ): - _id, id2 = id2, _id - coef, coef2 = coef2, coef + # + id2_isdiscrete = var_map[id2].domain.isdiscrete() + if var_map[_id].domain.isdiscrete() ^ id2_isdiscrete: + # if only one variable is discrete, then we need to + # substiitute out the other + if id2_isdiscrete: + _id, id2 = id2, _id + coef, coef2 = coef2, coef + else: + # In an attempt to improve numerical stability, we will + # solve for (and substitute out) the variable with the + # coefficient closer to +/-1) + log_coef = abs(_log10(abs(coef))) + log_coef2 = abs(_log10(abs(coef2))) + if log_coef2 < log_coef or ( + log_coef2 == log_coef and abs(coef2) - abs(coef) + ): + _id, id2 = id2, _id + coef, coef2 = coef2, coef # substituting _id with a*x + b a = -coef2 / coef x = id2 From 7ac1a567d9ab6fe77f57d96798ca04f81c782325 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 15 Nov 2023 08:39:33 -0700 Subject: [PATCH 0425/1204] Fix logic when comparing coefficients --- pyomo/repn/plugins/nl_writer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 61ee9cdb3a4..32cec880320 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1715,10 +1715,10 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): # In an attempt to improve numerical stability, we will # solve for (and substitute out) the variable with the # coefficient closer to +/-1) - log_coef = abs(_log10(abs(coef))) - log_coef2 = abs(_log10(abs(coef2))) - if log_coef2 < log_coef or ( - log_coef2 == log_coef and abs(coef2) - abs(coef) + log_coef = _log10(abs(coef)) + log_coef2 = _log10(abs(coef2)) + if abs(log_coef2) < abs(log_coef) or ( + log_coef2 == -log_coef and log_coef2 < log_coef ): _id, id2 = id2, _id coef, coef2 = coef2, coef From ae8455821f6f36c56ddb23bcad357b23f661c230 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 15 Nov 2023 13:11:37 -0500 Subject: [PATCH 0426/1204] remove load_solutions configuration --- pyomo/contrib/mindtpy/config_options.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pyomo/contrib/mindtpy/config_options.py b/pyomo/contrib/mindtpy/config_options.py index 507cbd995f8..ed0c86baae9 100644 --- a/pyomo/contrib/mindtpy/config_options.py +++ b/pyomo/contrib/mindtpy/config_options.py @@ -494,14 +494,6 @@ def _add_common_configs(CONFIG): domain=bool, ), ) - CONFIG.declare( - 'load_solutions', - ConfigValue( - default=True, - description='Whether to load solutions in solve() function', - domain=bool, - ), - ) def _add_subsolver_configs(CONFIG): From 7bf0268d47004164ef0f1c07bb2e166d5d3611b0 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 15 Nov 2023 13:12:02 -0500 Subject: [PATCH 0427/1204] add comments to enumerate over values() --- pyomo/contrib/mindtpy/cut_generation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/mindtpy/cut_generation.py b/pyomo/contrib/mindtpy/cut_generation.py index dd60b004830..a8d6948ac1d 100644 --- a/pyomo/contrib/mindtpy/cut_generation.py +++ b/pyomo/contrib/mindtpy/cut_generation.py @@ -196,6 +196,7 @@ def add_oa_cuts_for_grey_box( .evaluate_jacobian_outputs() .toarray() ) + # Enumerate over values works well now. However, it might be stable if the values() method changes. for index, output in enumerate(target_model_grey_box.outputs.values()): dual_value = jacobians_model.dual[jacobian_model_grey_box][ output.name.replace("outputs", "output_constraints") From 993290f777ee5d92192a0d3fbce8ef60b627ed0b Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 15 Nov 2023 13:12:56 -0500 Subject: [PATCH 0428/1204] black format --- pyomo/contrib/mindtpy/cut_generation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/cut_generation.py b/pyomo/contrib/mindtpy/cut_generation.py index a8d6948ac1d..28d302104a3 100644 --- a/pyomo/contrib/mindtpy/cut_generation.py +++ b/pyomo/contrib/mindtpy/cut_generation.py @@ -196,7 +196,7 @@ def add_oa_cuts_for_grey_box( .evaluate_jacobian_outputs() .toarray() ) - # Enumerate over values works well now. However, it might be stable if the values() method changes. + # Enumerate over values works well now. However, it might be stable if the values() method changes. for index, output in enumerate(target_model_grey_box.outputs.values()): dual_value = jacobians_model.dual[jacobian_model_grey_box][ output.name.replace("outputs", "output_constraints") From 444e4abad71181dbccde53d9346a8f5f17f2120b Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 15 Nov 2023 13:48:53 -0700 Subject: [PATCH 0429/1204] Explicitly collecting local vars so that we don't do anything silly with Vars declared as local that don't actually appear on the Disjunct, realizing that bound constraints at each level of the GDP tree matter. --- pyomo/gdp/plugins/hull.py | 22 +++++++++++++--------- pyomo/gdp/tests/test_hull.py | 15 ++++----------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 1a76f08ff60..54332ebc666 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -334,7 +334,7 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, if not obj.xor: raise GDP_Error( "Cannot do hull reformulation for " - "Disjunction '%s' with OR constraint. " + "Disjunction '%s' with OR constraint. " "Must be an XOR!" % obj.name ) # collect the Disjuncts we are going to transform now because we will @@ -395,6 +395,11 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, # so they will not be re-disaggregated. vars_to_disaggregate = {disj: ComponentSet() for disj in obj.disjuncts} all_vars_to_disaggregate = ComponentSet() + # We will ignore variables declared as local in a Disjunct that don't + # actually appear in any Constraints on that Disjunct, but in order to + # do this, we will explicitly collect the set of local_vars in this + # loop. + local_vars = defaultdict(lambda: ComponentSet()) for var in var_order: disjuncts = disjuncts_var_appears_in[var] # clearly not local if used in more than one disjunct @@ -411,7 +416,9 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, else: # disjuncts is a set of length 1 disjunct = next(iter(disjuncts)) if disjunct in local_vars_by_disjunct: - if var not in local_vars_by_disjunct[disjunct]: + if var in local_vars_by_disjunct[disjunct]: + local_vars[disjunct].add(var) + else: # It's not declared local to this Disjunct, so we # disaggregate vars_to_disaggregate[disjunct].add(var) @@ -424,6 +431,9 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, # Now that we know who we need to disaggregate, we will do it # while we also transform the disjuncts. + + # Get the list of local variables for the parent Disjunct so that we can + # add the disaggregated variables we're about to make to it: parent_local_var_list = self._get_local_var_list(parent_disjunct) or_expr = 0 for disjunct in obj.disjuncts: @@ -433,7 +443,7 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, disjunct, transBlock, vars_to_disaggregate[disjunct], - local_vars_by_disjunct.get(disjunct, []), + local_vars[disjunct], parent_local_var_list, local_vars_by_disjunct[parent_disjunct] ) @@ -578,12 +588,6 @@ def _transform_disjunct(self, obj, transBlock, vars_to_disaggregate, local_vars, ) for var in local_vars: - if var in vars_to_disaggregate: - logger.warning( - "Var '%s' was declared as a local Var for Disjunct '%s', " - "but it appeared in multiple Disjuncts, so it will be " - "disaggregated." % (var.name, obj.name)) - continue # we don't need to disaggregate, i.e., we can use this Var, but we # do need to set up its bounds constraints. diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index cfd0feac2b2..436367b3a89 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -406,7 +406,7 @@ def test_error_for_or(self): self.assertRaisesRegex( GDP_Error, "Cannot do hull reformulation for Disjunction " - "'disjunction' with OR constraint. Must be an XOR!*", + "'disjunction' with OR constraint. Must be an XOR!*", TransformationFactory('gdp.hull').apply_to, m, ) @@ -1572,25 +1572,18 @@ def test_transformed_model_nestedDisjuncts(self): all_cons = list(m.component_data_objects(Constraint, active=True, descend_into=Block)) num_cons = len(all_cons) - # TODO: I shouldn't have d1.binary_indicator_var in the local list - # above, but I think if I do it should be ignored when it doesn't appear - # in any Disjuncts... - - # TODO: We get duplicate bounds constraints for inner disaggregated Vars - # because we declare bounds constraints for local vars every time. We - # should actually track them separately so that we don't duplicate - # bounds constraints over and over again. + for idx, cons in enumerate(all_cons): print(idx) print(cons.name) print(cons.expr) print("") # 2 disaggregation constraints for x 0,3 - # + 4 bounds constraints for x 6,8,9,13, These are dumb: 10,14,16 + # + 6 bounds constraints for x 6,8,9,13,14,16 These are dumb: 10,14,16 # + 2 bounds constraints for inner indicator vars 11, 12 # + 2 exactly-one constraints 1,4 # + 4 transformed constraints 2,5,7,15 - self.assertEqual(num_cons, 14) + self.assertEqual(num_cons, 16) def check_transformed_model_nestedDisjuncts(self, m, d3, d4): hull = TransformationFactory('gdp.hull') From 98e8f9c8393a3981473e3ddf511a0f637f4cc8ab Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 15 Nov 2023 15:23:06 -0700 Subject: [PATCH 0430/1204] solver refactor: sol parsing --- pyomo/repn/plugins/nl_writer.py | 2 +- pyomo/solver/IPOPT.py | 243 +++++++++++++++++++++++++---- pyomo/solver/results.py | 268 +++++++++++++++++++++----------- 3 files changed, 389 insertions(+), 124 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 6a282bdeab4..ff0af67e273 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -283,7 +283,7 @@ def __call__(self, model, filename, solver_capability, io_options): return filename, symbol_map @document_kwargs_from_configdict(CONFIG) - def write(self, model, ostream, rowstream=None, colstream=None, **options): + def write(self, model, ostream, rowstream=None, colstream=None, **options) -> NLWriterInfo: """Write a model in NL format. Returns diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index 90c8a6d1bce..0c61a0117bd 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -11,17 +11,25 @@ import os import subprocess +import io +import sys +from typing import Mapping from pyomo.common import Executable -from pyomo.common.config import ConfigValue +from pyomo.common.config import ConfigValue, NonNegativeInt from pyomo.common.tempfiles import TempfileManager from pyomo.opt import WriterFactory +from pyomo.repn.plugins.nl_writer import NLWriter, NLWriterInfo from pyomo.solver.base import SolverBase from pyomo.solver.config import SolverConfig from pyomo.solver.factory import SolverFactory -from pyomo.solver.results import Results, TerminationCondition, SolutionStatus -from pyomo.solver.solution import SolutionLoaderBase +from pyomo.solver.results import Results, TerminationCondition, SolutionStatus, SolFileData, parse_sol_file +from pyomo.solver.solution import SolutionLoaderBase, SolutionLoader from pyomo.solver.util import SolverSystemError +from pyomo.common.tee import TeeStream +from pyomo.common.log import LogStream +from pyomo.core.expr.visitor import replace_expressions +from pyomo.core.expr.numvalue import value import logging @@ -51,12 +59,81 @@ def __init__( self.save_solver_io: bool = self.declare( 'save_solver_io', ConfigValue(domain=bool, default=False) ) + self.temp_dir: str = self.declare( + 'temp_dir', ConfigValue(domain=str, default=None) + ) + self.solver_output_logger = self.declare( + 'solver_output_logger', ConfigValue(default=logger) + ) + self.log_level = self.declare( + 'log_level', ConfigValue(domain=NonNegativeInt, default=logging.INFO) + ) class IPOPTSolutionLoader(SolutionLoaderBase): pass +ipopt_command_line_options = { + 'acceptable_compl_inf_tol', + 'acceptable_constr_viol_tol', + 'acceptable_dual_inf_tol', + 'acceptable_tol', + 'alpha_for_y', + 'bound_frac', + 'bound_mult_init_val', + 'bound_push', + 'bound_relax_factor', + 'compl_inf_tol', + 'constr_mult_init_max', + 'constr_viol_tol', + 'diverging_iterates_tol', + 'dual_inf_tol', + 'expect_infeasible_problem', + 'file_print_level', + 'halt_on_ampl_error', + 'hessian_approximation', + 'honor_original_bounds', + 'linear_scaling_on_demand', + 'linear_solver', + 'linear_system_scaling', + 'ma27_pivtol', + 'ma27_pivtolmax', + 'ma57_pivot_order', + 'ma57_pivtol', + 'ma57_pivtolmax', + 'max_cpu_time', + 'max_iter', + 'max_refinement_steps', + 'max_soc', + 'maxit', + 'min_refinement_steps', + 'mu_init', + 'mu_max', + 'mu_oracle', + 'mu_strategy', + 'nlp_scaling_max_gradient', + 'nlp_scaling_method', + 'obj_scaling_factor', + 'option_file_name', + 'outlev', + 'output_file', + 'pardiso_matching_strategy', + 'print_level', + 'print_options_documentation', + 'print_user_options', + 'required_infeasibility_reduction', + 'slack_bound_frac', + 'slack_bound_push', + 'tol', + 'wantsol', + 'warm_start_bound_push', + 'warm_start_init_point', + 'warm_start_mult_bound_push', + 'watchdog_shortened_iter_trigger', +} + + @SolverFactory.register('ipopt', doc='The IPOPT NLP solver (new interface)') class IPOPT(SolverBase): CONFIG = IPOPTConfig() @@ -90,6 +167,32 @@ def config(self): def config(self, val): self.config = val + def _write_options_file(self, ostream: io.TextIOBase, options: Mapping): + f = ostream + for k, val in options.items(): + if k not in ipopt_command_line_options: + f.write(str(k) + ' ' + str(val) + '\n') + + def _create_command_line(self, basename: str, config: IPOPTConfig): + cmd = [ + str(config.executable), + basename + '.nl', + '-AMPL', + 'option_file_name=' + basename + '.opt', + ] + if 'option_file_name' in config.solver_options: + raise ValueError( + 'Use IPOPT.config.temp_dir to specify the name of the options file. ' + 'Do not use IPOPT.config.solver_options["option_file_name"].' + ) + ipopt_options = dict(config.solver_options) + if config.time_limit is not None and 'max_cpu_time' not in ipopt_options: + ipopt_options['max_cpu_time'] = config.time_limit + for k, v in ipopt_options.items(): + cmd.append(str(k) + '=' + str(v)) + + return cmd + def solve(self, model, **kwds): # Check if solver is available avail = self.available() @@ -98,7 +201,7 @@ def solve(self, model, **kwds): f'Solver {self.__class__} is not available ({avail}).' ) # Update configuration options, based on keywords passed to solve - config = self.config(kwds.pop('options', {})) + config: IPOPTConfig = self.config(kwds.pop('options', {})) config.set_value(kwds) # Get a copy of the environment to pass to the subprocess env = os.environ.copy() @@ -109,16 +212,26 @@ def solve(self, model, **kwds): ) ) # Write the model to an nl file - nl_writer = WriterFactory('nl') + nl_writer = NLWriter() # Need to add check for symbolic_solver_labels; may need to generate up # to three files for nl, row, col, if ssl == True # What we have here may or may not work with IPOPT; will find out when # we try to run it. with TempfileManager.new_context() as tempfile: - dname = tempfile.mkdtemp() - with open(os.path.join(dname, model.name + '.nl')) as nl_file, open( - os.path.join(dname, model.name + '.row') - ) as row_file, open(os.path.join(dname, model.name + '.col')) as col_file: + if config.temp_dir is None: + dname = tempfile.mkdtemp() + else: + dname = config.temp_dir + if not os.path.exists(dname): + os.mkdir(dname) + basename = os.path.join(dname, model.name) + if os.path.exists(basename + '.nl'): + raise RuntimeError(f"NL file with the same name {basename + '.nl'} already exists!") + with ( + open(basename + '.nl') as nl_file, + open(basename + '.row') as row_file, + open(basename + '.col') as col_file, + ): self.info = nl_writer.write( model, nl_file, @@ -126,32 +239,96 @@ def solve(self, model, **kwds): col_file, symbolic_solver_labels=config.symbolic_solver_labels, ) + with open(basename + '.opt') as opt_file: + self._write_options_file(ostream=opt_file, options=config.solver_options) # Call IPOPT - passing the files via the subprocess - cmd = [str(config.executable), nl_file, '-AMPL'] + cmd = self._create_command_line(basename=basename, config=config) + + # this seems silly, but we have to give the subprocess slightly longer to finish than + # ipopt if config.time_limit is not None: - config.solver_options['max_cpu_time'] = config.time_limit - for key, val in config.solver_options.items(): - cmd.append(key + '=' + val) - process = subprocess.run( - cmd, timeout=config.time_limit, env=env, universal_newlines=True + timeout = config.time_limit + min(max(1.0, 0.01 * config.time_limit), 100) + else: + timeout = None + + ostreams = [ + LogStream( + level=self.config.log_level, logger=self.config.solver_output_logger + ) + ] + if self.config.tee: + ostreams.append(sys.stdout) + with TeeStream(*ostreams) as t: + process = subprocess.run( + cmd, timeout=timeout, env=env, universal_newlines=True, stdout=t.STDOUT, stderr=t.STDERR, + ) + + if process.returncode != 0: + results = Results() + results.termination_condition = TerminationCondition.error + results.solution_status = SolutionStatus.noSolution + results.solution_loader = SolutionLoader(None, None, None, None) + else: + # TODO: Make a context manager out of this and open the file + # to pass to the results, instead of doing this thing. + with open(basename + '.sol') as sol_file: + results = self._parse_solution(sol_file, self.info) + + if config.raise_exception_on_nonoptimal_result and results.solution_status != SolutionStatus.optimal: + raise RuntimeError('Solver did not find the optimal solution. Set opt.config.raise_exception_on_nonoptimal_result = False to bypass this error.') + + results.solver_name = 'ipopt' + results.solver_version = self.version() + if config.load_solution and results.solution_status == SolutionStatus.noSolution: + raise RuntimeError( + 'A feasible solution was not found, so no solution can be loaded.' + 'Please set config.load_solution=False to bypass this error.' ) + + if config.load_solution: + results.solution_loader.load_vars() - if process.returncode != 0: - if self.config.load_solution: - raise RuntimeError( - 'A feasible solution was not found, so no solution can be loaded.' - 'Please set config.load_solution=False and check ' - 'results.termination_condition and ' - 'results.incumbent_objective before loading a solution.' - ) - results = Results() - results.termination_condition = TerminationCondition.error + if results.solution_status in {SolutionStatus.feasible, SolutionStatus.optimal}: + if config.load_solution: + results.incumbent_objective = value(self.info.objectives[0]) else: - # TODO: Make a context manager out of this and open the file - # to pass to the results, instead of doing this thing. - results = self._parse_solution(os.path.join(dname, model.name + '.sol'), self.info) - - def _parse_solution(self): - # STOPPING POINT: The suggestion here is to look at the original - # parser, which hasn't failed yet, and rework it to be ... better? - pass + results.incumbent_objective = replace_expressions( + self.info.objectives[0].expr, + substitution_map={ + id(v): val for v, val in results.solution_loader.get_primals().items() + }, + descend_into_named_expressions=True, + remove_named_expressions=True, + ) + + return results + + + def _parse_solution(self, instream: io.TextIOBase, nl_info: NLWriterInfo): + suffixes_to_read = ['dual', 'ipopt_zL_out', 'ipopt_zU_out'] + res, sol_data = parse_sol_file(sol_file=instream, nl_info=nl_info, suffixes_to_read=suffixes_to_read) + + if res.solution_status == SolutionStatus.noSolution: + res.solution_loader = SolutionLoader(None, None, None, None) + else: + rc = dict() + for v in nl_info.variables: + v_id = id(v) + rc[v_id] = (v, 0) + if v_id in sol_data.var_suffixes['ipopt_zL_out']: + zl = sol_data.var_suffixes['ipopt_zL_out'][v_id][1] + if abs(zl) > abs(rc[v_id][1]): + rc[v_id] = (v, zl) + if v_id in sol_data.var_suffixes['ipopt_zU_out']: + zu = sol_data.var_suffixes['ipopt_zU_out'][v_id][1] + if abs(zu) > abs(rc[v_id][1]): + rc[v_id] = (v, zu) + + res.solution_loader = SolutionLoader( + primals=sol_data.primals, + duals=sol_data.duals, + slacks=None, + reduced_costs=rc, + ) + + return res diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index 9aa2869b414..01a56d526c6 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -10,8 +10,10 @@ # ___________________________________________________________________________ import enum -from typing import Optional, Tuple +import re +from typing import Optional, Tuple, Dict, Any, Sequence, List from datetime import datetime +import io from pyomo.common.config import ( ConfigDict, @@ -21,6 +23,10 @@ In, NonNegativeFloat, ) +from pyomo.common.collections import ComponentMap +from pyomo.core.base.var import _GeneralVarData +from pyomo.core.base.constraint import _ConstraintData +from pyomo.core.base.objective import _ObjectiveData from pyomo.opt.results.solution import SolutionStatus as LegacySolutionStatus from pyomo.opt.results.solver import ( TerminationCondition as LegacyTerminationCondition, @@ -28,6 +34,7 @@ ) from pyomo.solver.solution import SolutionLoaderBase from pyomo.solver.util import SolverSystemError +from pyomo.repn.plugins.nl_writer import NLWriterInfo class TerminationCondition(enum.Enum): @@ -199,10 +206,10 @@ def __init__( ConfigValue(domain=In(SolutionStatus), default=SolutionStatus.noSolution), ) self.incumbent_objective: Optional[float] = self.declare( - 'incumbent_objective', ConfigValue(domain=float) + 'incumbent_objective', ConfigValue(domain=float, default=None) ) self.objective_bound: Optional[float] = self.declare( - 'objective_bound', ConfigValue(domain=float) + 'objective_bound', ConfigValue(domain=float, default=None) ) self.solver_name: Optional[str] = self.declare( 'solver_name', ConfigValue(domain=str) @@ -211,7 +218,7 @@ def __init__( 'solver_version', ConfigValue(domain=tuple) ) self.iteration_count: Optional[int] = self.declare( - 'iteration_count', ConfigValue(domain=NonNegativeInt) + 'iteration_count', ConfigValue(domain=NonNegativeInt, default=None) ) self.timing_info: ConfigDict = self.declare('timing_info', ConfigDict()) @@ -227,6 +234,10 @@ def __init__( self.extra_info: ConfigDict = self.declare( 'extra_info', ConfigDict(implicit=True) ) + self.solver_message: Optional[str] = self.declare( + 'solver_message', + ConfigValue(domain=str, default=None), + ) def __str__(self): s = '' @@ -241,98 +252,175 @@ class ResultsReader: pass -def parse_sol_file(sol_file, nl_info): - # The original reader for sol files is in pyomo.opt.plugins.sol. - # Per my original complaint, it has "magic numbers" that I just don't - # know how to test. It's apparently less fragile than that in APPSI. - # NOTE: The Results object now also holds the solution loader, so we do - # not need pass in a solution like we did previously. - # nl_info is an NLWriterInfo object that has vars, cons, etc. - results = Results() - - # For backwards compatibility and general safety, we will parse all - # lines until "Options" appears. Anything before "Options" we will - # consider to be the solver message. - message = [] - for line in sol_file: +class SolFileData(object): + def __init__(self) -> None: + self.primals: Dict[int, Tuple[_GeneralVarData, float]] = dict() + self.duals: Dict[_ConstraintData, float] = dict() + self.var_suffixes: Dict[str, Dict[int, Tuple[_GeneralVarData, Any]]] = dict() + self.con_suffixes: Dict[str, Dict[_ConstraintData, Any]] = dict() + self.obj_suffixes: Dict[str, Dict[int, Tuple[_ObjectiveData, Any]]] = dict() + self.problem_suffixes: Dict[str, List[Any]] = dict() + + +def parse_sol_file(sol_file: io.TextIOBase, nl_info: NLWriterInfo, suffixes_to_read: Sequence[str]) -> Tuple[Results, SolFileData]: + suffixes_to_read = set(suffixes_to_read) + res = Results() + sol_data = SolFileData() + + fin = sol_file + # + # Some solvers (minto) do not write a message. We will assume + # all non-blank lines up the 'Options' line is the message. + msg = [] + while True: + line = fin.readline() if not line: + # EOF break line = line.strip() - if "Options" in line: + if line == 'Options': break - message.append(line) - message = '\n'.join(message) - # Once "Options" appears, we must now read the content under it. - model_objects = [] - if "Options" in line: - line = sol_file.readline() - number_of_options = int(line) - need_tolerance = False - if number_of_options > 4: # MRM: Entirely unclear why this is necessary, or if it even is - number_of_options -= 2 - need_tolerance = True - for i in range(number_of_options + 4): - line = sol_file.readline() - model_objects.append(int(line)) - if need_tolerance: # MRM: Entirely unclear why this is necessary, or if it even is - line = sol_file.readline() - model_objects.append(float(line)) - else: - raise SolverSystemError("ERROR READING `sol` FILE. No 'Options' line found.") - # Identify the total number of variables and constraints - number_of_cons = model_objects[number_of_options + 1] - number_of_vars = model_objects[number_of_options + 3] - assert number_of_cons == len(nl_info.constraints) - assert number_of_vars == len(nl_info.variables) - - duals = [float(sol_file.readline()) for i in range(number_of_cons)] - variable_vals = [float(sol_file.readline()) for i in range(number_of_vars)] - - # Parse the exit code line and capture it - exit_code = [0, 0] - line = sol_file.readline() - if line and ('objno' in line): - exit_code_line = line.split() - if (len(exit_code_line) != 3): - raise SolverSystemError(f"ERROR READING `sol` FILE. Expected two numbers in `objno` line; received {line}.") - exit_code = [int(exit_code_line[1]), int(exit_code_line[2])] + if line: + msg.append(line) + msg = '\n'.join(msg) + z = [] + if line[:7] == "Options": + line = fin.readline() + nopts = int(line) + need_vbtol = False + if nopts > 4: # WEH - when is this true? + nopts -= 2 + need_vbtol = True + for i in range(nopts + 4): + line = fin.readline() + z += [int(line)] + if need_vbtol: # WEH - when is this true? + line = fin.readline() + z += [float(line)] else: - raise SolverSystemError(f"ERROR READING `sol` FILE. Expected `objno`; received {line}.") - results.extra_info.solver_message = message.strip().replace('\n', '; ') - # Not sure if next two lines are needed - # if isinstance(res.solver.message, str): - # res.solver.message = res.solver.message.replace(':', '\\x3a') - if (exit_code[1] >= 0) and (exit_code[1] <= 99): - results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied - results.solution_status = SolutionStatus.optimal - elif (exit_code[1] >= 100) and (exit_code[1] <= 199): - exit_code_message = "Optimal solution indicated, but ERROR LIKELY!" - results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied - results.solution_status = SolutionStatus.optimal - elif (exit_code[1] >= 200) and (exit_code[1] <= 299): - exit_code_message = "INFEASIBLE SOLUTION: constraints cannot be satisfied!" - results.termination_condition = TerminationCondition.locallyInfeasible - results.solution_status = SolutionStatus.infeasible - elif (exit_code[1] >= 300) and (exit_code[1] <= 399): - exit_code_message = "UNBOUNDED PROBLEM: the objective can be improved without limit!" - results.termination_condition = TerminationCondition.unbounded - results.solution_status = SolutionStatus.infeasible - elif (exit_code[1] >= 400) and (exit_code[1] <= 499): - exit_code_message = ("EXCEEDED MAXIMUM NUMBER OF ITERATIONS: the solver " - "was stopped by a limit that you set!") - results.solver.termination_condition = TerminationCondition.iterationLimit - elif (exit_code[1] >= 500) and (exit_code[1] <= 599): - exit_code_message = ( - "FAILURE: the solver stopped by an error condition " - "in the solver routines!" - ) - results.solver.termination_condition = TerminationCondition.error + raise ValueError("no Options line found") + n = z[nopts + 3] # variables + m = z[nopts + 1] # constraints + x = [] + y = [] + i = 0 + while i < m: + line = fin.readline() + y.append(float(line)) + i += 1 + i = 0 + while i < n: + line = fin.readline() + x.append(float(line)) + i += 1 + objno = [0, 0] + line = fin.readline() + if line: # WEH - when is this true? + if line[:5] != "objno": # pragma:nocover + raise ValueError("expected 'objno', found '%s'" % (line)) + t = line.split() + if len(t) != 3: + raise ValueError( + "expected two numbers in objno line, but found '%s'" % (line) + ) + objno = [int(t[1]), int(t[2])] + res.solver_message = msg.strip().replace("\n", "; ") + res.solution_status = SolutionStatus.noSolution + res.termination_condition = TerminationCondition.unknown + if (objno[1] >= 0) and (objno[1] <= 99): + res.solution_status = SolutionStatus.optimal + res.termination_condition = TerminationCondition.convergenceCriteriaSatisfied + elif (objno[1] >= 100) and (objno[1] <= 199): + res.solution_status = SolutionStatus.feasible + res.termination_condition = TerminationCondition.error + elif (objno[1] >= 200) and (objno[1] <= 299): + res.solution_status = SolutionStatus.infeasible + # TODO: this is solver dependent + res.termination_condition = TerminationCondition.locallyInfeasible + elif (objno[1] >= 300) and (objno[1] <= 399): + res.solution_status = SolutionStatus.noSolution + res.termination_condition = TerminationCondition.unbounded + elif (objno[1] >= 400) and (objno[1] <= 499): + # TODO: this is solver dependent + res.solution_status = SolutionStatus.infeasible + res.termination_condition = TerminationCondition.iterationLimit + elif (objno[1] >= 500) and (objno[1] <= 599): + res.solution_status = SolutionStatus.noSolution + res.termination_condition = TerminationCondition.error + if res.solution_status != SolutionStatus.noSolution: + for v, val in zip(nl_info.variables, x): + sol_data[id(v)] = (v, val) + if "dual" in suffixes_to_read: + for c, val in zip(nl_info.constraints, y): + sol_data[c] = val + ### Read suffixes ### + line = fin.readline() + while line: + line = line.strip() + if line == "": + continue + line = line.split() + # Some sort of garbage we tag onto the solver message, assuming we are past the suffixes + if line[0] != 'suffix': + # We assume this is the start of a + # section like kestrel_option, which + # comes after all suffixes. + remaining = "" + line = fin.readline() + while line: + remaining += line.strip() + "; " + line = fin.readline() + res.solver_message += remaining + break + unmasked_kind = int(line[1]) + kind = unmasked_kind & 3 # 0-var, 1-con, 2-obj, 3-prob + convert_function = int + if (unmasked_kind & 4) == 4: + convert_function = float + nvalues = int(line[2]) + # namelen = int(line[3]) + # tablen = int(line[4]) + tabline = int(line[5]) + suffix_name = fin.readline().strip() + if suffix_name in suffixes_to_read: + # ignore translation of the table number to string value for now, + # this information can be obtained from the solver documentation + for n in range(tabline): + fin.readline() + if kind == 0: # Var + sol_data.var_suffixes[suffix_name] = dict() + for cnt in range(nvalues): + suf_line = fin.readline().split() + var_ndx = int(suf_line[0]) + var = nl_info.variables[var_ndx] + sol_data.var_suffixes[suffix_name][id(var)] = (var, convert_function(suf_line[1])) + elif kind == 1: # Con + sol_data.con_suffixes[suffix_name] = dict() + for cnt in range(nvalues): + suf_line = fin.readline().split() + con_ndx = int(suf_line[0]) + con = nl_info.constraints[con_ndx] + sol_data.con_suffixes[suffix_name][con] = convert_function(suf_line[1]) + elif kind == 2: # Obj + sol_data.obj_suffixes[suffix_name] = dict() + for cnt in range(nvalues): + suf_line = fin.readline().split() + obj_ndx = int(suf_line[0]) + obj = nl_info.objectives[obj_ndx] + sol_data.obj_suffixes[suffix_name][id(obj)] = (obj, convert_function(suf_line[1])) + elif kind == 3: # Prob + sol_data.problem_suffixes[suffix_name] = list() + for cnt in range(nvalues): + suf_line = fin.readline().split() + sol_data.problem_suffixes[suffix_name].append(convert_function(suf_line[1])) + else: + # do not store the suffix in the solution object + for cnt in range(nvalues): + fin.readline() + line = fin.readline() + + return res, sol_data - if results.extra_info.solver_message: - results.extra_info.solver_message += '; ' + exit_code_message - else: - results.extra_info.solver_message = exit_code_message - return results def parse_yaml(): pass From cf5b2ce9fb616e6cf7edf03f47da5440aca8d96b Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Wed, 15 Nov 2023 16:11:55 -0700 Subject: [PATCH 0431/1204] ensure zipping variable/constraint systems yields a maximum matching in dulmage-mendelsohn --- pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.py b/pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.py index 95b6cd7134f..0b3b251f2ac 100644 --- a/pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.py +++ b/pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.py @@ -90,7 +90,8 @@ def dulmage_mendelsohn(bg, top_nodes=None, matching=None): _filter.update(b_unmatched) _filter.update(b_matched_with_reachable) t_other = [t for t in top_nodes if t not in _filter] - b_other = [b for b in bot_nodes if b not in _filter] + b_other = [matching[t] for t in t_other] + #b_other = [b for b in bot_nodes if b not in _filter] return ( (t_unmatched, t_reachable, t_matched_with_reachable, t_other), From 0ee1d7bdb227147c6c156c69c1bbe3e9a74025af Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 15 Nov 2023 16:54:26 -0700 Subject: [PATCH 0432/1204] solver refactor: sol parsing --- pyomo/repn/plugins/nl_writer.py | 6 ++--- pyomo/solver/IPOPT.py | 39 +++++++++++++++++++-------------- pyomo/solver/results.py | 6 ++--- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index ff0af67e273..aec6bc036ab 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1346,9 +1346,9 @@ def write(self, model): # Generate the return information info = NLWriterInfo( - variables, - constraints, - objectives, + [i[0] for i in variables], + [i[0] for i in constraints], + [i[0] for i in objectives], sorted(amplfunc_libraries), row_labels, col_labels, diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index 0c61a0117bd..f9eded4a62b 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -30,6 +30,7 @@ from pyomo.common.log import LogStream from pyomo.core.expr.visitor import replace_expressions from pyomo.core.expr.numvalue import value +from pyomo.core.base.suffix import Suffix import logging @@ -139,7 +140,7 @@ class IPOPT(SolverBase): CONFIG = IPOPTConfig() def __init__(self, **kwds): - self.config = self.CONFIG(kwds) + self._config = self.CONFIG(kwds) def available(self): if self.config.executable.path() is None: @@ -161,11 +162,11 @@ def version(self): @property def config(self): - return self.config + return self._config @config.setter def config(self, val): - self.config = val + self._config = val def _write_options_file(self, ostream: io.TextIOBase, options: Mapping): f = ostream @@ -228,9 +229,9 @@ def solve(self, model, **kwds): if os.path.exists(basename + '.nl'): raise RuntimeError(f"NL file with the same name {basename + '.nl'} already exists!") with ( - open(basename + '.nl') as nl_file, - open(basename + '.row') as row_file, - open(basename + '.col') as col_file, + open(basename + '.nl', 'w') as nl_file, + open(basename + '.row', 'w') as row_file, + open(basename + '.col', 'w') as col_file, ): self.info = nl_writer.write( model, @@ -239,7 +240,7 @@ def solve(self, model, **kwds): col_file, symbolic_solver_labels=config.symbolic_solver_labels, ) - with open(basename + '.opt') as opt_file: + with open(basename + '.opt', 'w') as opt_file: self._write_options_file(ostream=opt_file, options=config.solver_options) # Call IPOPT - passing the files via the subprocess cmd = self._create_command_line(basename=basename, config=config) @@ -263,16 +264,16 @@ def solve(self, model, **kwds): cmd, timeout=timeout, env=env, universal_newlines=True, stdout=t.STDOUT, stderr=t.STDERR, ) - if process.returncode != 0: - results = Results() - results.termination_condition = TerminationCondition.error - results.solution_status = SolutionStatus.noSolution - results.solution_loader = SolutionLoader(None, None, None, None) - else: - # TODO: Make a context manager out of this and open the file - # to pass to the results, instead of doing this thing. - with open(basename + '.sol') as sol_file: - results = self._parse_solution(sol_file, self.info) + if process.returncode != 0: + results = Results() + results.termination_condition = TerminationCondition.error + results.solution_status = SolutionStatus.noSolution + results.solution_loader = SolutionLoader(None, None, None, None) + else: + # TODO: Make a context manager out of this and open the file + # to pass to the results, instead of doing this thing. + with open(basename + '.sol', 'r') as sol_file: + results = self._parse_solution(sol_file, self.info) if config.raise_exception_on_nonoptimal_result and results.solution_status != SolutionStatus.optimal: raise RuntimeError('Solver did not find the optimal solution. Set opt.config.raise_exception_on_nonoptimal_result = False to bypass this error.') @@ -287,6 +288,10 @@ def solve(self, model, **kwds): if config.load_solution: results.solution_loader.load_vars() + if hasattr(model, 'dual') and isinstance(model.dual, Suffix) and model.dual.import_enabled(): + model.dual.update(results.solution_loader.get_duals()) + if hasattr(model, 'rc') and isinstance(model.rc, Suffix) and model.rc.import_enabled(): + model.rc.update(results.solution_loader.get_reduced_costs()) if results.solution_status in {SolutionStatus.feasible, SolutionStatus.optimal}: if config.load_solution: diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index 01a56d526c6..17397b9aba0 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -112,7 +112,7 @@ class TerminationCondition(enum.Enum): unknown = 42 -class SolutionStatus(enum.IntEnum): +class SolutionStatus(enum.Enum): """ An enumeration for interpreting the result of a termination. This describes the designated status by the solver to be loaded back into the model. @@ -349,10 +349,10 @@ def parse_sol_file(sol_file: io.TextIOBase, nl_info: NLWriterInfo, suffixes_to_r res.termination_condition = TerminationCondition.error if res.solution_status != SolutionStatus.noSolution: for v, val in zip(nl_info.variables, x): - sol_data[id(v)] = (v, val) + sol_data.primals[id(v)] = (v, val) if "dual" in suffixes_to_read: for c, val in zip(nl_info.constraints, y): - sol_data[c] = val + sol_data.duals[c] = val ### Read suffixes ### line = fin.readline() while line: From 3631ec96928c9c3e1c6b4bcb1f63bda5747db88f Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 16 Nov 2023 09:26:53 -0700 Subject: [PATCH 0433/1204] Add test for report_timing context manager --- pyomo/common/tests/test_timing.py | 52 ++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/pyomo/common/tests/test_timing.py b/pyomo/common/tests/test_timing.py index d2ce6175801..d885359e6c6 100644 --- a/pyomo/common/tests/test_timing.py +++ b/pyomo/common/tests/test_timing.py @@ -14,6 +14,7 @@ import gc from io import StringIO +from itertools import zip_longest import logging import sys import time @@ -26,7 +27,14 @@ TicTocTimer, HierarchicalTimer, ) -from pyomo.environ import ConcreteModel, RangeSet, Var, Any, TransformationFactory +from pyomo.environ import ( + AbstractModel, + ConcreteModel, + RangeSet, + Var, + Any, + TransformationFactory, +) from pyomo.core.base.var import _VarData @@ -132,6 +140,48 @@ def test_report_timing(self): self.assertRegex(str(l.strip()), str(r.strip())) self.assertEqual(buf.getvalue().strip(), "") + def test_report_timing_context_manager(self): + ref = r""" + (0(\.\d+)?) seconds to construct Var x; 2 indices total + (0(\.\d+)?) seconds to construct Var y; 0 indices total + (0(\.\d+)?) seconds to construct Suffix Suffix + (0(\.\d+)?) seconds to apply Transformation RelaxIntegerVars \(in-place\) + """.strip() + + xfrm = TransformationFactory('core.relax_integer_vars') + + model = AbstractModel() + model.r = RangeSet(2) + model.x = Var(model.r) + model.y = Var(Any, dense=False) + + OS = StringIO() + + with report_timing(False): + with report_timing(OS): + with report_timing(False): + # Active reporting is False: nothing should be emitted + with capture_output() as OUT: + m = model.create_instance() + xfrm.apply_to(m) + self.assertEqual(OUT.getvalue(), "") + self.assertEqual(OS.getvalue(), "") + # Active reporting: we should log the timing + with capture_output() as OUT: + m = model.create_instance() + xfrm.apply_to(m) + self.assertEqual(OUT.getvalue(), "") + result = OS.getvalue().strip() + self.maxDiff = None + for l, r in zip_longest(result.splitlines(), ref.splitlines()): + self.assertRegex(str(l.strip()), str(r.strip())) + # Active reporting is False: the previous log should not have changed + with capture_output() as OUT: + m = model.create_instance() + xfrm.apply_to(m) + self.assertEqual(OUT.getvalue(), "") + self.assertEqual(result, OS.getvalue().strip()) + def test_TicTocTimer_tictoc(self): SLEEP = 0.1 RES = 0.02 # resolution (seconds): 1/5 the sleep From b12ce9546341cd5510a733d70df83da8871f8a66 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 16 Nov 2023 09:41:15 -0700 Subject: [PATCH 0434/1204] Remove code applicable only to Python<3.3 --- pyomo/common/timing.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/pyomo/common/timing.py b/pyomo/common/timing.py index 17f508804e4..b37570fa666 100644 --- a/pyomo/common/timing.py +++ b/pyomo/common/timing.py @@ -223,19 +223,14 @@ def __str__(self): # # Setup the timer # -# TODO: Remove this bit for Pyomo 6.0 - we won't care about older versions -if sys.version_info >= (3, 3): - # perf_counter is guaranteed to be monotonic and the most accurate timer - default_timer = time.perf_counter -elif sys.platform.startswith('win'): - # On old Pythons, clock() is more accurate than time() on Windows - # (.35us vs 15ms), but time() is more accurate than clock() on Linux - # (1ns vs 1us). It is unfortunate that time() is not monotonic, but - # since the TicTocTimer is used for (potentially very accurate) - # timers, we will sacrifice monotonicity on Linux for resolution. - default_timer = time.clock -else: - default_timer = time.time +# perf_counter is guaranteed to be monotonic and the most accurate +# timer. It became available in Python 3.3. Prior to that, clock() was +# more accurate than time() on Windows (.35us vs 15ms), but time() was +# more accurate than clock() on Linux (1ns vs 1us). It is unfortunate +# that time() is not monotonic, but since the TicTocTimer is used for +# (potentially very accurate) timers, we will sacrifice monotonicity on +# Linux for resolution. +default_timer = time.perf_counter class TicTocTimer(object): From abfbaffe8d356fba0b16abac330ce5bce8fb059a Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 16 Nov 2023 23:32:27 -0500 Subject: [PATCH 0435/1204] Fix PyROS logger setup function --- pyomo/contrib/pyros/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index c1a429c0ba9..19f178c70f6 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -427,7 +427,7 @@ def setup_pyros_logger(name=DEFAULT_LOGGER_NAME): # default logger: INFO level, with preformatted messages current_logger_class = logging.getLoggerClass() logging.setLoggerClass(PreformattedLogger) - logger = logging.getLogger(DEFAULT_LOGGER_NAME) + logger = logging.getLogger(name=name) logger.setLevel(logging.INFO) logging.setLoggerClass(current_logger_class) From 2b65d49b225b76f30a446db7296e9e367da91637 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 17 Nov 2023 00:48:34 -0700 Subject: [PATCH 0436/1204] Improve LP/NL file determinism --- pyomo/core/base/enums.py | 2 +- pyomo/repn/linear.py | 34 +++++++++--- pyomo/repn/plugins/lp_writer.py | 3 +- pyomo/repn/plugins/nl_writer.py | 29 ++++++++-- pyomo/repn/tests/ampl/test_nlv2.py | 1 + pyomo/repn/tests/cpxlp/test_lpv2.py | 86 +++++++++++++++++++++++++---- pyomo/repn/tests/test_linear.py | 55 +++++++++++------- pyomo/repn/tests/test_quadratic.py | 3 +- pyomo/repn/tests/test_util.py | 6 +- pyomo/repn/util.py | 33 +++++++++-- 10 files changed, 198 insertions(+), 54 deletions(-) diff --git a/pyomo/core/base/enums.py b/pyomo/core/base/enums.py index 35cca4e2ac4..972d6b09117 100644 --- a/pyomo/core/base/enums.py +++ b/pyomo/core/base/enums.py @@ -59,7 +59,7 @@ class SortComponents(enum.Flag, **strictEnum): alphabeticalOrder = alphaOrder alphabetical = alphaOrder # both alpha and decl orders are deterministic, so only must sort indices - deterministic = indices + deterministic = ORDERED_INDICES sortBoth = indices | alphabeticalOrder # Same as True alphabetizeComponentAndIndex = sortBoth diff --git a/pyomo/repn/linear.py b/pyomo/repn/linear.py index f8f87795e7c..27c256f9f43 100644 --- a/pyomo/repn/linear.py +++ b/pyomo/repn/linear.py @@ -582,14 +582,31 @@ def __init__(self): self[LinearExpression] = self._before_linear self[SumExpression] = self._before_general_expression + @staticmethod + def _record_var(visitor, var): + # We always add all indices to the var_map at once so that + # we can honor deterministic ordering of unordered sets + # (because the user could have iterated over an unordered + # set when constructing an expression, thereby altering the + # order in which we would see the variables) + vm = visitor.var_map + vo = visitor.var_order + l = len(vo) + for v in var.values(visitor.sorter): + if v.fixed: + continue + vid = id(v) + vm[vid] = v + vo[vid] = l + l += 1 + @staticmethod def _before_var(visitor, child): _id = id(child) if _id not in visitor.var_map: if child.fixed: return False, (_CONSTANT, visitor.check_constant(child.value, child)) - visitor.var_map[_id] = child - visitor.var_order[_id] = len(visitor.var_order) + LinearBeforeChildDispatcher._record_var(visitor, child.parent_component()) ans = visitor.Result() ans.linear[_id] = 1 return False, (_LINEAR, ans) @@ -618,8 +635,7 @@ def _before_monomial(visitor, child): _CONSTANT, arg1 * visitor.check_constant(arg2.value, arg2), ) - visitor.var_map[_id] = arg2 - visitor.var_order[_id] = len(visitor.var_order) + LinearBeforeChildDispatcher._record_var(visitor, arg2.parent_component()) # Trap multiplication by 0 and nan. if not arg1: @@ -643,7 +659,6 @@ def _before_monomial(visitor, child): def _before_linear(visitor, child): var_map = visitor.var_map var_order = visitor.var_order - next_i = len(var_order) ans = visitor.Result() const = 0 linear = ans.linear @@ -675,9 +690,9 @@ def _before_linear(visitor, child): if arg2.fixed: const += arg1 * visitor.check_constant(arg2.value, arg2) continue - var_map[_id] = arg2 - var_order[_id] = next_i - next_i += 1 + LinearBeforeChildDispatcher._record_var( + visitor, arg2.parent_component() + ) linear[_id] = arg1 elif _id in linear: linear[_id] += arg1 @@ -744,11 +759,12 @@ class LinearRepnVisitor(StreamBasedExpressionVisitor): expand_nonlinear_products = False max_exponential_expansion = 1 - def __init__(self, subexpression_cache, var_map, var_order): + def __init__(self, subexpression_cache, var_map, var_order, sorter): super().__init__() self.subexpression_cache = subexpression_cache self.var_map = var_map self.var_order = var_order + self.sorter = sorter self._eval_expr_visitor = _EvaluationVisitor(True) self.evaluate = self._eval_expr_visitor.dfs_postorder_stack diff --git a/pyomo/repn/plugins/lp_writer.py b/pyomo/repn/plugins/lp_writer.py index 23f5c82280a..be718ee696e 100644 --- a/pyomo/repn/plugins/lp_writer.py +++ b/pyomo/repn/plugins/lp_writer.py @@ -310,12 +310,13 @@ def write(self, model): _qp = self.config.allow_quadratic_objective _qc = self.config.allow_quadratic_constraint objective_visitor = (QuadraticRepnVisitor if _qp else LinearRepnVisitor)( - {}, var_map, self.var_order + {}, var_map, self.var_order, sorter ) constraint_visitor = (QuadraticRepnVisitor if _qc else LinearRepnVisitor)( objective_visitor.subexpression_cache if _qp == _qc else {}, var_map, self.var_order, + sorter, ) timer.toc('Initialized column order', level=logging.DEBUG) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 32cec880320..be5f8062a9b 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -519,6 +519,7 @@ def __init__(self, ostream, rowstream, colstream, config): self.external_functions = {} self.used_named_expressions = set() self.var_map = {} + self.sorter = FileDeterminism_to_SortComponents(config.file_determinism) self.visitor = AMPLRepnVisitor( self.template, self.subexpression_cache, @@ -528,6 +529,7 @@ def __init__(self, ostream, rowstream, colstream, config): self.used_named_expressions, self.symbolic_solver_labels, self.config.export_defined_variables, + self.sorter, ) self.next_V_line_id = 0 self.pause_gc = None @@ -794,8 +796,12 @@ def write(self, model): if self.config.export_nonlinear_variables: for v in self.config.export_nonlinear_variables: + # Note that because we have already walked all the + # expressions, we have already "seen" all the variables + # we will see, so we don't need to fill in any VarData + # from IndexedVar containers here. if v.is_indexed(): - _iter = v.values() + _iter = v.values(sorter) else: _iter = (v,) for _v in _iter: @@ -2575,6 +2581,19 @@ def __init__(self): self[LinearExpression] = self._before_linear self[SumExpression] = self._before_general_expression + @staticmethod + def _record_var(visitor, var): + # We always add all indices to the var_map at once so that + # we can honor deterministic ordering of unordered sets + # (because the user could have iterated over an unordered + # set when constructing an expression, thereby altering the + # order in which we would see the variables) + vm = visitor.var_map + for v in var.values(visitor.sorter): + if v.fixed: + continue + vm[id(v)] = v + @staticmethod def _before_string(visitor, child): visitor.encountered_string_arguments = True @@ -2590,7 +2609,7 @@ def _before_var(visitor, child): if _id not in visitor.fixed_vars: visitor.cache_fixed_var(_id, child) return False, (_CONSTANT, visitor.fixed_vars[_id]) - visitor.var_map[_id] = child + _before_child_handlers._record_var(visitor, child.parent_component()) return False, (_MONOMIAL, _id, 1) @staticmethod @@ -2629,7 +2648,7 @@ def _before_monomial(visitor, child): if _id not in visitor.fixed_vars: visitor.cache_fixed_var(_id, arg2) return False, (_CONSTANT, arg1 * visitor.fixed_vars[_id]) - visitor.var_map[_id] = arg2 + _before_child_handlers._record_var(visitor, arg2.parent_component()) return False, (_MONOMIAL, _id, arg1) @staticmethod @@ -2670,7 +2689,7 @@ def _before_linear(visitor, child): visitor.cache_fixed_var(_id, arg2) const += arg1 * visitor.fixed_vars[_id] continue - var_map[_id] = arg2 + _before_child_handlers._record_var(visitor, arg2.parent_component()) linear[_id] = arg1 elif _id in linear: linear[_id] += arg1 @@ -2718,6 +2737,7 @@ def __init__( used_named_expressions, symbolic_solver_labels, use_named_exprs, + sorter, ): super().__init__() self.template = template @@ -2733,6 +2753,7 @@ def __init__( self.fixed_vars = {} self._eval_expr_visitor = _EvaluationVisitor(True) self.evaluate = self._eval_expr_visitor.dfs_postorder_stack + self.sorter = sorter def check_constant(self, ans, obj): if ans.__class__ not in native_numeric_types: diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index a4fbaee77ed..9d5d9def961 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -65,6 +65,7 @@ def __init__(self, symbolic=False): self.used_named_expressions, self.symbolic_solver_labels, True, + None, ) def __enter__(self): diff --git a/pyomo/repn/tests/cpxlp/test_lpv2.py b/pyomo/repn/tests/cpxlp/test_lpv2.py index fbe4f15fbe9..336939a4d7d 100644 --- a/pyomo/repn/tests/cpxlp/test_lpv2.py +++ b/pyomo/repn/tests/cpxlp/test_lpv2.py @@ -14,22 +14,23 @@ import pyomo.common.unittest as unittest from pyomo.common.log import LoggingIntercept -from pyomo.environ import ConcreteModel, Block, Constraint, Var, Objective, Suffix + +import pyomo.environ as pyo from pyomo.repn.plugins.lp_writer import LPWriter class TestLPv2(unittest.TestCase): def test_warn_export_suffixes(self): - m = ConcreteModel() - m.x = Var() - m.obj = Objective(expr=m.x) - m.con = Constraint(expr=m.x >= 2) - m.b = Block() - m.ignored = Suffix(direction=Suffix.IMPORT) - m.duals = Suffix(direction=Suffix.IMPORT_EXPORT) - m.b.duals = Suffix(direction=Suffix.IMPORT_EXPORT) - m.b.scaling = Suffix(direction=Suffix.EXPORT) + m = pyo.ConcreteModel() + m.x = pyo.Var() + m.obj = pyo.Objective(expr=m.x) + m.con = pyo.Constraint(expr=m.x >= 2) + m.b = pyo.Block() + m.ignored = pyo.Suffix(direction=pyo.Suffix.IMPORT) + m.duals = pyo.Suffix(direction=pyo.Suffix.IMPORT_EXPORT) + m.b.duals = pyo.Suffix(direction=pyo.Suffix.IMPORT_EXPORT) + m.b.scaling = pyo.Suffix(direction=pyo.Suffix.EXPORT) # Empty suffixes are ignored writer = LPWriter() @@ -73,3 +74,68 @@ def test_warn_export_suffixes(self): LP writer cannot export suffixes to LP files. Skipping. """, ) + + def test_deterministic_unordered_sets(self): + ref = """\\* Source Pyomo model name=unknown *\\ + +min +o: ++1 x(a) ++1 x(aaaaa) ++1 x(ooo) ++1 x(z) + +s.t. + +c_l_c(a)_: ++1 x(a) +>= 1 + +c_l_c(aaaaa)_: ++1 x(aaaaa) +>= 5 + +c_l_c(ooo)_: ++1 x(ooo) +>= 3 + +c_l_c(z)_: ++1 x(z) +>= 1 + +bounds + -inf <= x(a) <= +inf + -inf <= x(aaaaa) <= +inf + -inf <= x(ooo) <= +inf + -inf <= x(z) <= +inf +end +""" + set_init = ['a', 'z', 'ooo', 'aaaaa'] + + m = pyo.ConcreteModel() + m.I = pyo.Set(initialize=set_init, ordered=False) + m.x = pyo.Var(m.I) + m.c = pyo.Constraint(m.I, rule=lambda m, i: m.x[i] >= len(i)) + m.o = pyo.Objective(expr=sum(m.x[i] for i in m.I)) + + OUT = StringIO() + with LoggingIntercept() as LOG: + LPWriter().write(m, OUT, symbolic_solver_labels=True) + self.assertEqual(LOG.getvalue(), "") + print(OUT.getvalue()) + self.assertEqual(ref, OUT.getvalue()) + + m = pyo.ConcreteModel() + m.I = pyo.Set() + m.x = pyo.Var(pyo.Any) + m.c = pyo.Constraint(pyo.Any) + for i in set_init: + m.c[i] = m.x[i] >= len(i) + m.o = pyo.Objective(expr=sum(m.x.values())) + + OUT = StringIO() + with LoggingIntercept() as LOG: + LPWriter().write(m, OUT, symbolic_solver_labels=True) + self.assertEqual(LOG.getvalue(), "") + + self.assertEqual(ref, OUT.getvalue()) diff --git a/pyomo/repn/tests/test_linear.py b/pyomo/repn/tests/test_linear.py index faf12a7da09..0eec8a1541c 100644 --- a/pyomo/repn/tests/test_linear.py +++ b/pyomo/repn/tests/test_linear.py @@ -40,9 +40,10 @@ def __init__(self): self.subexpr = {} self.var_map = {} self.var_order = {} + self.sorter = None def __iter__(self): - return iter((self.subexpr, self.var_map, self.var_order)) + return iter((self.subexpr, self.var_map, self.var_order, self.sorter)) def sum_sq(args, fixed, fgh): @@ -576,8 +577,10 @@ def test_linear(self): cfg = VisitorConfig() repn = LinearRepnVisitor(*cfg).walk_expression(e) self.assertEqual(cfg.subexpr, {}) - self.assertEqual(cfg.var_map, {id(m.x[0]): m.x[0]}) - self.assertEqual(cfg.var_order, {id(m.x[0]): 0}) + self.assertEqual( + cfg.var_map, {id(m.x[0]): m.x[0], id(m.x[1]): m.x[1], id(m.x[2]): m.x[2]} + ) + self.assertEqual(cfg.var_order, {id(m.x[0]): 0, id(m.x[1]): 1, id(m.x[2]): 2}) self.assertEqual(repn.multiplier, 1) self.assertEqual(repn.constant, 0) self.assertEqual(repn.linear, {id(m.x[0]): 1}) @@ -588,8 +591,10 @@ def test_linear(self): cfg = VisitorConfig() repn = LinearRepnVisitor(*cfg).walk_expression(e) self.assertEqual(cfg.subexpr, {}) - self.assertEqual(cfg.var_map, {id(m.x[0]): m.x[0]}) - self.assertEqual(cfg.var_order, {id(m.x[0]): 0}) + self.assertEqual( + cfg.var_map, {id(m.x[0]): m.x[0], id(m.x[1]): m.x[1], id(m.x[2]): m.x[2]} + ) + self.assertEqual(cfg.var_order, {id(m.x[0]): 0, id(m.x[1]): 1, id(m.x[2]): 2}) self.assertEqual(repn.multiplier, 1) self.assertEqual(repn.constant, 0) self.assertEqual(repn.linear, {id(m.x[0]): 3}) @@ -600,8 +605,10 @@ def test_linear(self): cfg = VisitorConfig() repn = LinearRepnVisitor(*cfg).walk_expression(e) self.assertEqual(cfg.subexpr, {}) - self.assertEqual(cfg.var_map, {id(m.x[0]): m.x[0], id(m.x[1]): m.x[1]}) - self.assertEqual(cfg.var_order, {id(m.x[0]): 0, id(m.x[1]): 1}) + self.assertEqual( + cfg.var_map, {id(m.x[0]): m.x[0], id(m.x[1]): m.x[1], id(m.x[2]): m.x[2]} + ) + self.assertEqual(cfg.var_order, {id(m.x[0]): 0, id(m.x[1]): 1, id(m.x[2]): 2}) self.assertEqual(repn.multiplier, 1) self.assertEqual(repn.constant, 0) self.assertEqual(repn.linear, {id(m.x[0]): 3, id(m.x[1]): 4}) @@ -612,8 +619,10 @@ def test_linear(self): cfg = VisitorConfig() repn = LinearRepnVisitor(*cfg).walk_expression(e) self.assertEqual(cfg.subexpr, {}) - self.assertEqual(cfg.var_map, {id(m.x[0]): m.x[0], id(m.x[1]): m.x[1]}) - self.assertEqual(cfg.var_order, {id(m.x[0]): 0, id(m.x[1]): 1}) + self.assertEqual( + cfg.var_map, {id(m.x[0]): m.x[0], id(m.x[1]): m.x[1], id(m.x[2]): m.x[2]} + ) + self.assertEqual(cfg.var_order, {id(m.x[0]): 0, id(m.x[1]): 1, id(m.x[2]): 2}) self.assertEqual(repn.multiplier, 1) self.assertEqual(repn.constant, 0) self.assertEqual(repn.linear, {id(m.x[0]): 3, id(m.x[1]): 6}) @@ -624,8 +633,10 @@ def test_linear(self): cfg = VisitorConfig() repn = LinearRepnVisitor(*cfg).walk_expression(e) self.assertEqual(cfg.subexpr, {}) - self.assertEqual(cfg.var_map, {id(m.x[0]): m.x[0], id(m.x[1]): m.x[1]}) - self.assertEqual(cfg.var_order, {id(m.x[0]): 0, id(m.x[1]): 1}) + self.assertEqual( + cfg.var_map, {id(m.x[0]): m.x[0], id(m.x[1]): m.x[1], id(m.x[2]): m.x[2]} + ) + self.assertEqual(cfg.var_order, {id(m.x[0]): 0, id(m.x[1]): 1, id(m.x[2]): 2}) self.assertEqual(repn.multiplier, 1) self.assertEqual(repn.constant, 10) self.assertEqual(repn.linear, {id(m.x[0]): 3, id(m.x[1]): 6}) @@ -636,8 +647,10 @@ def test_linear(self): cfg = VisitorConfig() repn = LinearRepnVisitor(*cfg).walk_expression(e) self.assertEqual(cfg.subexpr, {}) - self.assertEqual(cfg.var_map, {id(m.x[0]): m.x[0], id(m.x[1]): m.x[1]}) - self.assertEqual(cfg.var_order, {id(m.x[0]): 0, id(m.x[1]): 1}) + self.assertEqual( + cfg.var_map, {id(m.x[0]): m.x[0], id(m.x[1]): m.x[1], id(m.x[2]): m.x[2]} + ) + self.assertEqual(cfg.var_order, {id(m.x[0]): 0, id(m.x[1]): 1, id(m.x[2]): 2}) self.assertEqual(repn.multiplier, 1) self.assertEqual(repn.constant, 50) self.assertEqual(repn.linear, {id(m.x[0]): 3, id(m.x[1]): 6}) @@ -648,8 +661,10 @@ def test_linear(self): cfg = VisitorConfig() repn = LinearRepnVisitor(*cfg).walk_expression(e) self.assertEqual(cfg.subexpr, {}) - self.assertEqual(cfg.var_map, {id(m.x[0]): m.x[0], id(m.x[1]): m.x[1]}) - self.assertEqual(cfg.var_order, {id(m.x[0]): 0, id(m.x[1]): 1}) + self.assertEqual( + cfg.var_map, {id(m.x[0]): m.x[0], id(m.x[1]): m.x[1], id(m.x[2]): m.x[2]} + ) + self.assertEqual(cfg.var_order, {id(m.x[0]): 0, id(m.x[1]): 1, id(m.x[2]): 2}) self.assertEqual(repn.multiplier, 1) self.assertEqual(repn.constant, 0) self.assertStructuredAlmostEqual( @@ -663,8 +678,10 @@ def test_linear(self): cfg = VisitorConfig() repn = LinearRepnVisitor(*cfg).walk_expression(e) self.assertEqual(cfg.subexpr, {}) - self.assertEqual(cfg.var_map, {id(m.x[0]): m.x[0], id(m.x[1]): m.x[1]}) - self.assertEqual(cfg.var_order, {id(m.x[0]): 0, id(m.x[1]): 1}) + self.assertEqual( + cfg.var_map, {id(m.x[0]): m.x[0], id(m.x[1]): m.x[1], id(m.x[2]): m.x[2]} + ) + self.assertEqual(cfg.var_order, {id(m.x[0]): 0, id(m.x[1]): 1, id(m.x[2]): 2}) self.assertEqual(repn.multiplier, 1) self.assertEqual(repn.constant, 10) self.assertStructuredAlmostEqual( @@ -677,8 +694,8 @@ def test_linear(self): cfg = VisitorConfig() repn = LinearRepnVisitor(*cfg).walk_expression(e) self.assertEqual(cfg.subexpr, {}) - self.assertEqual(cfg.var_map, {id(m.x[1]): m.x[1]}) - self.assertEqual(cfg.var_order, {id(m.x[1]): 0}) + self.assertEqual(cfg.var_map, {id(m.x[1]): m.x[1], id(m.x[2]): m.x[2]}) + self.assertEqual(cfg.var_order, {id(m.x[1]): 0, id(m.x[2]): 1}) self.assertEqual(repn.multiplier, 1) self.assertEqual(repn.constant, 40) self.assertStructuredAlmostEqual(repn.linear, {id(m.x[1]): InvalidNumber(nan)}) diff --git a/pyomo/repn/tests/test_quadratic.py b/pyomo/repn/tests/test_quadratic.py index b034099de98..605c859464a 100644 --- a/pyomo/repn/tests/test_quadratic.py +++ b/pyomo/repn/tests/test_quadratic.py @@ -29,9 +29,10 @@ def __init__(self): self.subexpr = {} self.var_map = {} self.var_order = {} + self.sorter = None def __iter__(self): - return iter((self.subexpr, self.var_map, self.var_order)) + return iter((self.subexpr, self.var_map, self.var_order, self.sorter)) class TestQuadratic(unittest.TestCase): diff --git a/pyomo/repn/tests/test_util.py b/pyomo/repn/tests/test_util.py index 01dd1392d81..4108c956d86 100644 --- a/pyomo/repn/tests/test_util.py +++ b/pyomo/repn/tests/test_util.py @@ -379,7 +379,7 @@ def test_FileDeterminism_to_SortComponents(self): ) self.assertEqual( FileDeterminism_to_SortComponents(FileDeterminism.ORDERED), - SortComponents.unsorted, + SortComponents.deterministic, ) self.assertEqual( FileDeterminism_to_SortComponents(FileDeterminism.SORT_INDICES), @@ -480,7 +480,7 @@ class MockConfig(object): MockConfig.file_determinism = FileDeterminism.ORDERED self.assertEqual( list(initialize_var_map_from_column_order(m, MockConfig, {}).values()), - [m.b.y[7], m.b.y[6], m.y[3], m.y[2], m.c.y[4], m.x], + [m.b.y[7], m.b.y[6], m.y[3], m.y[2], m.c.y[4], m.x, m.c.y[5]], ) MockConfig.file_determinism = FileDeterminism.SORT_INDICES self.assertEqual( @@ -499,7 +499,7 @@ class MockConfig(object): MockConfig.file_determinism = FileDeterminism.ORDERED self.assertEqual( list(initialize_var_map_from_column_order(m, MockConfig, {}).values()), - [m.b.y[7], m.b.y[6], m.y[3], m.y[2], m.c.y[4], m.x], + [m.b.y[7], m.b.y[6], m.y[3], m.y[2], m.c.y[4], m.x, m.c.y[5]], ) # verify no side effects self.assertEqual(MockConfig.column_order, ref) diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index ecb8ed998d9..b65aa9427d5 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -17,7 +17,7 @@ import operator import sys -from pyomo.common.collections import Sequence, ComponentMap +from pyomo.common.collections import Sequence, ComponentMap, ComponentSet from pyomo.common.deprecation import deprecation_warning from pyomo.common.errors import DeveloperError, InvalidValueError from pyomo.common.numeric_types import ( @@ -544,12 +544,13 @@ def categorize_valid_components( def FileDeterminism_to_SortComponents(file_determinism): - sorter = SortComponents.unsorted + if file_determinism >= FileDeterminism.SORT_SYMBOLS: + return SortComponents.ALPHABETICAL | SortComponents.SORTED_INDICES if file_determinism >= FileDeterminism.SORT_INDICES: - sorter = sorter | SortComponents.indices - if file_determinism >= FileDeterminism.SORT_SYMBOLS: - sorter = sorter | SortComponents.alphabetical - return sorter + return SortComponents.SORTED_INDICES + if file_determinism >= FileDeterminism.ORDERED: + return SortComponents.ORDERED_INDICES + return SortComponents.UNSORTED def initialize_var_map_from_column_order(model, config, var_map): @@ -581,13 +582,33 @@ def initialize_var_map_from_column_order(model, config, var_map): if column_order is not None: # Note that Vars that appear twice (e.g., through a # Reference) will be sorted with the FIRST occurrence. + fill_in = ComponentSet() for var in column_order: if var.is_indexed(): for _v in var.values(sorter): if not _v.fixed: var_map[id(_v)] = _v elif not var.fixed: + pc = var.parent_component() + if pc is not var and pc not in fill_in: + # For any VarData in an IndexedVar, remember the + # IndexedVar so that after all the VarData that the + # user has specified in the column ordering have + # been processed (and added to the var_map) we can + # go back and fill in any missing VarData from that + # IndexedVar. This is needed because later when + # walking expressions we assume that any VarData + # that is not in the var_map will be the first + # VarData from its Var container (indexed or scalar). + fill_in.add(pc) var_map[id(var)] = var + # Note that ComponentSet iteration is deterministic, and + # re-inserting _v into the var_map will not change the ordering + # for any pre-existing variables + for pc in fill_in: + for _v in pc.values(sorter): + if not _v.fixed: + var_map[id(_v)] = _v return var_map From 98465f5ad2ecb4ed2986f58efa8779728f942d74 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 17 Nov 2023 22:23:07 -0700 Subject: [PATCH 0437/1204] Improve Disjunction construction error for invalid types --- pyomo/gdp/disjunct.py | 27 ++++++++++++++------------- pyomo/gdp/tests/test_disjunct.py | 25 +++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/pyomo/gdp/disjunct.py b/pyomo/gdp/disjunct.py index b95ce252536..eca6d93d732 100644 --- a/pyomo/gdp/disjunct.py +++ b/pyomo/gdp/disjunct.py @@ -564,16 +564,18 @@ def set_value(self, expr): # IndexedDisjunct indexed by Any which has already been transformed, # the new Disjuncts are Blocks already. This catches them for who # they are anyway. - if isinstance(e, _DisjunctData): - self.disjuncts.append(e) - continue - # The user was lazy and gave us a single constraint - # expression or an iterable of expressions - expressions = [] - if hasattr(e, '__iter__'): + if hasattr(e, 'is_component_type') and e.is_component_type(): + if e.ctype == Disjunct and not e.is_indexed(): + self.disjuncts.append(e) + continue + e_iter = [e] + elif hasattr(e, '__iter__'): e_iter = e else: e_iter = [e] + # The user was lazy and gave us a single constraint + # expression or an iterable of expressions + expressions = [] for _tmpe in e_iter: try: if _tmpe.is_expression_type(): @@ -581,13 +583,12 @@ def set_value(self, expr): continue except AttributeError: pass - msg = "\n\tin %s" % (type(e),) if e_iter is e else "" + msg = " in '%s'" % (type(e).__name__,) if e_iter is e else "" raise ValueError( - "Unexpected term for Disjunction %s.\n" - "\tExpected a Disjunct object, relational expression, " - "or iterable of\n" - "\trelational expressions but got %s%s" - % (self.name, type(_tmpe), msg) + "Unexpected term for Disjunction '%s'.\n" + " Expected a Disjunct object, relational expression, " + "or iterable of\n relational expressions but got '%s'%s" + % (self.name, type(_tmpe).__name__, msg) ) comp = self.parent_component() diff --git a/pyomo/gdp/tests/test_disjunct.py b/pyomo/gdp/tests/test_disjunct.py index ccf5b8c2d6c..676b49a80cd 100644 --- a/pyomo/gdp/tests/test_disjunct.py +++ b/pyomo/gdp/tests/test_disjunct.py @@ -108,6 +108,31 @@ def _gen(): self.assertEqual(len(disjuncts[0].parent_component().name), 11) self.assertEqual(disjuncts[0].name, "f_disjuncts[0]") + def test_construct_invalid_component(self): + m = ConcreteModel() + m.d = Disjunct([1, 2]) + with self.assertRaisesRegex( + ValueError, + "Unexpected term for Disjunction 'dd'.\n " + "Expected a Disjunct object, relational expression, or iterable of\n" + " relational expressions but got 'IndexedDisjunct'", + ): + m.dd = Disjunction(expr=[m.d]) + with self.assertRaisesRegex( + ValueError, + "Unexpected term for Disjunction 'ee'.\n " + "Expected a Disjunct object, relational expression, or iterable of\n" + " relational expressions but got 'str' in 'list'", + ): + m.ee = Disjunction(expr=[['a']]) + with self.assertRaisesRegex( + ValueError, + "Unexpected term for Disjunction 'ff'.\n " + "Expected a Disjunct object, relational expression, or iterable of\n" + " relational expressions but got 'str'", + ): + m.ff = Disjunction(expr=['a']) + class TestDisjunct(unittest.TestCase): def test_deactivate(self): From 6d046563719982d6de2e2884c7e5dfeaff4a7aa8 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 17 Nov 2023 22:26:43 -0700 Subject: [PATCH 0438/1204] Log which suffix values were skipped at the DEBUG level --- pyomo/repn/plugins/nl_writer.py | 10 ++++++++++ pyomo/repn/tests/ampl/test_nlv2.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 32cec880320..ab1ec88a499 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -470,12 +470,22 @@ def compile(self, column_order, row_order, obj_order, model_id): "not exported as part of the NL file. " "Skipping." ) + if logger.isEnabledFor(logging.DEBUG): + logger.debug( + "Skipped component keys:\n\t" + + "\n\t".join(sorted(map(str, missing_component_data))) + ) if unknown_data: logger.warning( f"model contains export suffix '{self.name}' that " f"contains {len(unknown_data)} keys that are not " "Var, Constraint, Objective, or the model. Skipping." ) + if logger.isEnabledFor(logging.DEBUG): + logger.debug( + "Skipped component keys:\n\t" + + "\n\t".join(sorted(map(str, unknown_data))) + ) class CachingNumericSuffixFinder(SuffixFinder): diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index a4fbaee77ed..ef4be290708 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -13,6 +13,7 @@ import pyomo.common.unittest as unittest import io +import logging import math import os @@ -949,6 +950,14 @@ def d(m, i): "keys that are not exported as part of the NL file. Skipping.\n", LOG.getvalue(), ) + with LoggingIntercept(level=logging.DEBUG) as LOG: + nl_writer.NLWriter().write(m, OUT) + self.assertEqual( + "model contains export suffix 'junk' that contains 1 component " + "keys that are not exported as part of the NL file. Skipping.\n" + "Skipped component keys:\n\ty\n", + LOG.getvalue(), + ) m.junk[m.z] = 1 with LoggingIntercept() as LOG: @@ -958,6 +967,14 @@ def d(m, i): "keys that are not exported as part of the NL file. Skipping.\n", LOG.getvalue(), ) + with LoggingIntercept(level=logging.DEBUG) as LOG: + nl_writer.NLWriter().write(m, OUT) + self.assertEqual( + "model contains export suffix 'junk' that contains 3 component " + "keys that are not exported as part of the NL file. Skipping.\n" + "Skipped component keys:\n\ty\n\tz[1]\n\tz[3]\n", + LOG.getvalue(), + ) m.junk[m.c] = 2 with LoggingIntercept() as LOG: @@ -988,6 +1005,17 @@ def d(m, i): "Skipping.\n", LOG.getvalue(), ) + with LoggingIntercept(level=logging.DEBUG) as LOG: + nl_writer.NLWriter().write(m, OUT) + self.assertEqual( + "model contains export suffix 'junk' that contains 6 component " + "keys that are not exported as part of the NL file. Skipping.\n" + "Skipped component keys:\n\tc\n\td[1]\n\td[3]\n\ty\n\tz[1]\n\tz[3]\n" + "model contains export suffix 'junk' that contains 1 keys that " + "are not Var, Constraint, Objective, or the model. Skipping.\n" + "Skipped component keys:\n\t5\n", + LOG.getvalue(), + ) def test_linear_constraint_npv_const(self): # This tests an error possibly reported by #2810 From f8def296aebdaaf52f1e4965a73f488a497702f1 Mon Sep 17 00:00:00 2001 From: robbybp Date: Sat, 18 Nov 2023 16:13:24 -0700 Subject: [PATCH 0439/1204] remoce commented line --- pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.py b/pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.py index 0b3b251f2ac..09a926cdec2 100644 --- a/pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.py +++ b/pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.py @@ -91,7 +91,6 @@ def dulmage_mendelsohn(bg, top_nodes=None, matching=None): _filter.update(b_matched_with_reachable) t_other = [t for t in top_nodes if t not in _filter] b_other = [matching[t] for t in t_other] - #b_other = [b for b in bot_nodes if b not in _filter] return ( (t_unmatched, t_reachable, t_matched_with_reachable, t_other), From d9488b5bd5b0409dc71da7ac7d44cf18388047fd Mon Sep 17 00:00:00 2001 From: robbybp Date: Sat, 18 Nov 2023 16:14:06 -0700 Subject: [PATCH 0440/1204] update docstrings to note that matching can be recovered from DM subsets --- .../incidence_analysis/dulmage_mendelsohn.py | 15 +++++++++++++++ pyomo/contrib/incidence_analysis/interface.py | 16 ++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py b/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py index a3af0d1e6c9..5450327f425 100644 --- a/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py +++ b/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py @@ -70,6 +70,21 @@ def dulmage_mendelsohn(matrix_or_graph, top_nodes=None, matching=None): - **overconstrained** - The columns matched with *possibly* unmatched rows (unmatched and overconstrained rows) + While the Dulmage-Mendelsohn decomposition does not specify an order within + any of these subsets, the order returned by this function preserves the + maximum matching that is used to compute the decomposition. That is, zipping + "corresponding" row and column subsets yields pairs in this maximum matching. + For example: + + >>> row_dmpartition, col_dmpartition = dulmage_mendelsohn(matrix) + >>> rdmp = row_dmpartition + >>> cdmp = col_dmpartition + >>> matching = list(zip( + ... rdmp.underconstrained + rdmp.square + rdmp.overconstrained, + ... cdmp.underconstrained + cdmp.square + cdmp.overconstrained, + ... )) + >>> # matching is a valid maximum matching of rows and columns of the matrix! + Parameters ---------- matrix_or_graph: ``scipy.sparse.coo_matrix`` or ``networkx.Graph`` diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index f74a68b4422..5fd9605e256 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -743,6 +743,22 @@ def dulmage_mendelsohn(self, variables=None, constraints=None): - **unmatched** - Constraints that were not matched in a particular maximum cardinality matching + While the Dulmage-Mendelsohn decomposition does not specify an order + within any of these subsets, the order returned by this function + preserves the maximum matching that is used to compute the decomposition. + That is, zipping "corresponding" variable and constraint subsets yields + pairs in this maximum matching. For example: + + >>> igraph = IncidenceGraphInterface(model) + >>> var_dmpartition, con_dmpartition = igraph.dulmage_mendelsohn() + >>> vdmp = var_dmpartition + >>> cdmp = con_dmpartition + >>> matching = list(zip( + ... vdmp.underconstrained + vdmp.square + vdmp.overconstrained, + ... cdmp.underconstrained + cdmp.square + cdmp.overconstrained, + >>> )) + >>> # matching is a valid maximum matching of variables and constraints! + Returns ------- var_partition: ``ColPartition`` named tuple From 3e407d0f03eeb6daf9a0e5ac044f75e6a5b03629 Mon Sep 17 00:00:00 2001 From: robbybp Date: Sat, 18 Nov 2023 16:14:40 -0700 Subject: [PATCH 0441/1204] test that matching can be recovered from DM partition --- .../tests/test_dulmage_mendelsohn.py | 21 +++++++++++++++++++ .../tests/test_interface.py | 16 ++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/pyomo/contrib/incidence_analysis/tests/test_dulmage_mendelsohn.py b/pyomo/contrib/incidence_analysis/tests/test_dulmage_mendelsohn.py index 4aae9abc2c6..6d311df88b2 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_dulmage_mendelsohn.py +++ b/pyomo/contrib/incidence_analysis/tests/test_dulmage_mendelsohn.py @@ -120,6 +120,27 @@ def test_rectangular_system(self): potentially_unmatched_set = set(range(len(variables))) self.assertEqual(set(potentially_unmatched), potentially_unmatched_set) + def test_recover_matching(self): + N_model = 4 + m = make_gas_expansion_model(N_model) + variables = list(m.component_data_objects(pyo.Var)) + constraints = list(m.component_data_objects(pyo.Constraint)) + imat = get_structural_incidence_matrix(variables, constraints) + rdmp, cdmp = dulmage_mendelsohn(imat) + rmatch = rdmp.underconstrained + rdmp.square + rdmp.overconstrained + cmatch = cdmp.underconstrained + cdmp.square + cdmp.overconstrained + matching = list(zip(rmatch, cmatch)) + rmatch = [r for (r, c) in matching] + cmatch = [c for (r, c) in matching] + # Assert that the matched rows and columns contain no duplicates + self.assertEqual(len(set(rmatch)), len(rmatch)) + self.assertEqual(len(set(cmatch)), len(cmatch)) + entry_set = set(zip(imat.row, imat.col)) + for (i, j) in matching: + # Assert that each pair in the matching is a valid entry + # in the matrix + self.assertIn((i, j), entry_set) + @unittest.skipUnless(networkx_available, "networkx is not available.") @unittest.skipUnless(scipy_available, "scipy is not available.") diff --git a/pyomo/contrib/incidence_analysis/tests/test_interface.py b/pyomo/contrib/incidence_analysis/tests/test_interface.py index 75bac643790..490ea94f63c 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_interface.py +++ b/pyomo/contrib/incidence_analysis/tests/test_interface.py @@ -1323,6 +1323,22 @@ def test_remove(self): self.assertEqual(N_new, N - len(cons_to_remove)) self.assertEqual(M_new, M - len(vars_to_remove)) + def test_recover_matching_from_dulmage_mendelsohn(self): + m = make_degenerate_solid_phase_model() + igraph = IncidenceGraphInterface(m) + vdmp, cdmp = igraph.dulmage_mendelsohn() + vmatch = vdmp.underconstrained + vdmp.square + vdmp.overconstrained + cmatch = cdmp.underconstrained + cdmp.square + cdmp.overconstrained + # Assert no duplicates in matched variables and constraints + self.assertEqual(len(ComponentSet(vmatch)), len(vmatch)) + self.assertEqual(len(ComponentSet(cmatch)), len(cmatch)) + matching = list(zip(vmatch, cmatch)) + # Assert each matched pair contains a variable that participates + # in the constraint. + for var, con in matching: + var_in_con = ComponentSet(igraph.get_adjacent_to(con)) + self.assertIn(var, var_in_con) + @unittest.skipUnless(networkx_available, "networkx is not available.") class TestConnectedComponents(unittest.TestCase): From a166021fd926cbe04fd3f64daee1be440a819fda Mon Sep 17 00:00:00 2001 From: robbybp Date: Sat, 18 Nov 2023 18:03:40 -0700 Subject: [PATCH 0442/1204] make sure zero coeffs are filtered with linear_only=True and add test --- pyomo/contrib/incidence_analysis/incidence.py | 2 +- .../incidence_analysis/tests/test_incidence.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/incidence_analysis/incidence.py b/pyomo/contrib/incidence_analysis/incidence.py index e68268094a6..1852cf75648 100644 --- a/pyomo/contrib/incidence_analysis/incidence.py +++ b/pyomo/contrib/incidence_analysis/incidence.py @@ -59,7 +59,7 @@ def _get_incident_via_standard_repn(expr, include_fixed, linear_only): linear_vars.append(var) if linear_only: nl_var_id_set = set(id(var) for var in repn.nonlinear_vars) - return [var for var in repn.linear_vars if id(var) not in nl_var_id_set] + return [var for var in linear_vars if id(var) not in nl_var_id_set] else: # Combine linear and nonlinear variables and filter out duplicates. Note # that quadratic=False, so we don't need to include repn.quadratic_vars. diff --git a/pyomo/contrib/incidence_analysis/tests/test_incidence.py b/pyomo/contrib/incidence_analysis/tests/test_incidence.py index 3b0b6a997aa..7f57dd904a7 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_incidence.py +++ b/pyomo/contrib/incidence_analysis/tests/test_incidence.py @@ -148,6 +148,17 @@ def test_fixed_zero_linear_coefficient(self): variables = self._get_incident_variables(expr) self.assertEqual(ComponentSet(variables), ComponentSet([m.x[1], m.x[2]])) + def test_fixed_zero_coefficient_linear_only(self): + m = pyo.ConcreteModel() + m.x = pyo.Var([1, 2, 3]) + expr = m.x[1] * m.x[2] + 2 * m.x[3] + m.x[2].fix(0) + variables = get_incident_variables( + expr, method=IncidenceMethod.standard_repn, linear_only=True + ) + self.assertEqual(len(variables), 1) + self.assertIs(variables[0], m.x[3]) + def test_fixed_none_linear_coefficient(self): m = pyo.ConcreteModel() m.x = pyo.Var([1, 2, 3]) From adca9cf69815afe2421573894a02121150a561a7 Mon Sep 17 00:00:00 2001 From: robbybp Date: Sun, 19 Nov 2023 10:37:48 -0700 Subject: [PATCH 0443/1204] remove unnecessary parentheses --- .../contrib/incidence_analysis/tests/test_dulmage_mendelsohn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/incidence_analysis/tests/test_dulmage_mendelsohn.py b/pyomo/contrib/incidence_analysis/tests/test_dulmage_mendelsohn.py index 6d311df88b2..98fefea2d80 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_dulmage_mendelsohn.py +++ b/pyomo/contrib/incidence_analysis/tests/test_dulmage_mendelsohn.py @@ -136,7 +136,7 @@ def test_recover_matching(self): self.assertEqual(len(set(rmatch)), len(rmatch)) self.assertEqual(len(set(cmatch)), len(cmatch)) entry_set = set(zip(imat.row, imat.col)) - for (i, j) in matching: + for i, j in matching: # Assert that each pair in the matching is a valid entry # in the matrix self.assertIn((i, j), entry_set) From 71d1f7e3757c8f9dd6f59ced4dd4a1c354c7ddeb Mon Sep 17 00:00:00 2001 From: robbybp Date: Sun, 19 Nov 2023 11:39:32 -0700 Subject: [PATCH 0444/1204] make code examples skipped doctests --- .../incidence_analysis/dulmage_mendelsohn.py | 19 ++++++++++------- pyomo/contrib/incidence_analysis/interface.py | 21 +++++++++++-------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py b/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py index 5450327f425..d3a460446e6 100644 --- a/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py +++ b/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py @@ -76,14 +76,17 @@ def dulmage_mendelsohn(matrix_or_graph, top_nodes=None, matching=None): "corresponding" row and column subsets yields pairs in this maximum matching. For example: - >>> row_dmpartition, col_dmpartition = dulmage_mendelsohn(matrix) - >>> rdmp = row_dmpartition - >>> cdmp = col_dmpartition - >>> matching = list(zip( - ... rdmp.underconstrained + rdmp.square + rdmp.overconstrained, - ... cdmp.underconstrained + cdmp.square + cdmp.overconstrained, - ... )) - >>> # matching is a valid maximum matching of rows and columns of the matrix! + .. doctest:: + :skipif: True + + >>> row_dmpartition, col_dmpartition = dulmage_mendelsohn(matrix) + >>> rdmp = row_dmpartition + >>> cdmp = col_dmpartition + >>> matching = list(zip( + ... rdmp.underconstrained + rdmp.square + rdmp.overconstrained, + ... cdmp.underconstrained + cdmp.square + cdmp.overconstrained, + ... )) + >>> # matching is a valid maximum matching of rows and columns of the matrix! Parameters ---------- diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 5fd9605e256..3b2c54f8a60 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -749,15 +749,18 @@ def dulmage_mendelsohn(self, variables=None, constraints=None): That is, zipping "corresponding" variable and constraint subsets yields pairs in this maximum matching. For example: - >>> igraph = IncidenceGraphInterface(model) - >>> var_dmpartition, con_dmpartition = igraph.dulmage_mendelsohn() - >>> vdmp = var_dmpartition - >>> cdmp = con_dmpartition - >>> matching = list(zip( - ... vdmp.underconstrained + vdmp.square + vdmp.overconstrained, - ... cdmp.underconstrained + cdmp.square + cdmp.overconstrained, - >>> )) - >>> # matching is a valid maximum matching of variables and constraints! + .. doctest:: + :skipif: True + + >>> igraph = IncidenceGraphInterface(model) + >>> var_dmpartition, con_dmpartition = igraph.dulmage_mendelsohn() + >>> vdmp = var_dmpartition + >>> cdmp = con_dmpartition + >>> matching = list(zip( + ... vdmp.underconstrained + vdmp.square + vdmp.overconstrained, + ... cdmp.underconstrained + cdmp.square + cdmp.overconstrained, + >>> )) + >>> # matching is a valid maximum matching of variables and constraints! Returns ------- From 97352bd197405174d35cc007e9c1afb0ab4e2496 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 20 Nov 2023 07:56:10 -0700 Subject: [PATCH 0445/1204] Merge Michael's changes; apply black --- pyomo/common/formatting.py | 3 +- pyomo/contrib/appsi/solvers/highs.py | 1 - .../solvers/tests/test_persistent_solvers.py | 4 +- pyomo/repn/plugins/nl_writer.py | 4 +- pyomo/solver/IPOPT.py | 73 ++++++++++++++----- pyomo/solver/config.py | 5 +- pyomo/solver/results.py | 25 +++++-- pyomo/solver/solution.py | 21 ++++++ 8 files changed, 104 insertions(+), 32 deletions(-) diff --git a/pyomo/common/formatting.py b/pyomo/common/formatting.py index 5c2b329ce21..f76d16880df 100644 --- a/pyomo/common/formatting.py +++ b/pyomo/common/formatting.py @@ -257,8 +257,7 @@ def writelines(self, sequence): r'|(?:\[\s*[A-Za-z0-9\.]+\s*\] +)' # [PASS]|[FAIL]|[ OK ] ) _verbatim_line_start = re.compile( - r'(\| )' # line blocks - r'|(\+((-{3,})|(={3,}))\+)' # grid table + r'(\| )' r'|(\+((-{3,})|(={3,}))\+)' # line blocks # grid table ) _verbatim_line = re.compile( r'(={3,}[ =]+)' # simple tables, ======== sections diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 3d2104cdbfa..b270e4f2700 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -343,7 +343,6 @@ def set_instance(self, model): f'({self.available()}).' ) - ostreams = [ LogStream( level=self.config.log_level, logger=self.config.solver_output_logger diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 299a5bd5b7e..b50a072abbd 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -1335,7 +1335,9 @@ def test_bug_1( self.assertAlmostEqual(res.incumbent_objective, 3) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) - def test_bug_2(self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars): + def test_bug_2( + self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + ): """ This test is for a bug where an objective containing a fixed variable does not get updated properly when the variable is unfixed. diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index aec6bc036ab..e745fabba33 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -283,7 +283,9 @@ def __call__(self, model, filename, solver_capability, io_options): return filename, symbol_map @document_kwargs_from_configdict(CONFIG) - def write(self, model, ostream, rowstream=None, colstream=None, **options) -> NLWriterInfo: + def write( + self, model, ostream, rowstream=None, colstream=None, **options + ) -> NLWriterInfo: """Write a model in NL format. Returns diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index f9eded4a62b..24896e626a5 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -23,7 +23,13 @@ from pyomo.solver.base import SolverBase from pyomo.solver.config import SolverConfig from pyomo.solver.factory import SolverFactory -from pyomo.solver.results import Results, TerminationCondition, SolutionStatus, SolFileData, parse_sol_file +from pyomo.solver.results import ( + Results, + TerminationCondition, + SolutionStatus, + SolFileData, + parse_sol_file, +) from pyomo.solver.solution import SolutionLoaderBase, SolutionLoader from pyomo.solver.util import SolverSystemError from pyomo.common.tee import TeeStream @@ -65,10 +71,10 @@ def __init__( ) self.solver_output_logger = self.declare( 'solver_output_logger', ConfigValue(default=logger) - ) + ) self.log_level = self.declare( 'log_level', ConfigValue(domain=NonNegativeInt, default=logging.INFO) - ) + ) class IPOPTSolutionLoader(SolutionLoaderBase): @@ -227,10 +233,12 @@ def solve(self, model, **kwds): os.mkdir(dname) basename = os.path.join(dname, model.name) if os.path.exists(basename + '.nl'): - raise RuntimeError(f"NL file with the same name {basename + '.nl'} already exists!") + raise RuntimeError( + f"NL file with the same name {basename + '.nl'} already exists!" + ) with ( - open(basename + '.nl', 'w') as nl_file, - open(basename + '.row', 'w') as row_file, + open(basename + '.nl', 'w') as nl_file, + open(basename + '.row', 'w') as row_file, open(basename + '.col', 'w') as col_file, ): self.info = nl_writer.write( @@ -241,14 +249,18 @@ def solve(self, model, **kwds): symbolic_solver_labels=config.symbolic_solver_labels, ) with open(basename + '.opt', 'w') as opt_file: - self._write_options_file(ostream=opt_file, options=config.solver_options) + self._write_options_file( + ostream=opt_file, options=config.solver_options + ) # Call IPOPT - passing the files via the subprocess cmd = self._create_command_line(basename=basename, config=config) # this seems silly, but we have to give the subprocess slightly longer to finish than # ipopt if config.time_limit is not None: - timeout = config.time_limit + min(max(1.0, 0.01 * config.time_limit), 100) + timeout = config.time_limit + min( + max(1.0, 0.01 * config.time_limit), 100 + ) else: timeout = None @@ -261,7 +273,12 @@ def solve(self, model, **kwds): ostreams.append(sys.stdout) with TeeStream(*ostreams) as t: process = subprocess.run( - cmd, timeout=timeout, env=env, universal_newlines=True, stdout=t.STDOUT, stderr=t.STDERR, + cmd, + timeout=timeout, + env=env, + universal_newlines=True, + stdout=t.STDOUT, + stderr=t.STDERR, ) if process.returncode != 0: @@ -274,23 +291,39 @@ def solve(self, model, **kwds): # to pass to the results, instead of doing this thing. with open(basename + '.sol', 'r') as sol_file: results = self._parse_solution(sol_file, self.info) - - if config.raise_exception_on_nonoptimal_result and results.solution_status != SolutionStatus.optimal: - raise RuntimeError('Solver did not find the optimal solution. Set opt.config.raise_exception_on_nonoptimal_result = False to bypass this error.') + + if ( + config.raise_exception_on_nonoptimal_result + and results.solution_status != SolutionStatus.optimal + ): + raise RuntimeError( + 'Solver did not find the optimal solution. Set opt.config.raise_exception_on_nonoptimal_result = False to bypass this error.' + ) results.solver_name = 'ipopt' results.solver_version = self.version() - if config.load_solution and results.solution_status == SolutionStatus.noSolution: + if ( + config.load_solution + and results.solution_status == SolutionStatus.noSolution + ): raise RuntimeError( 'A feasible solution was not found, so no solution can be loaded.' 'Please set config.load_solution=False to bypass this error.' ) - + if config.load_solution: results.solution_loader.load_vars() - if hasattr(model, 'dual') and isinstance(model.dual, Suffix) and model.dual.import_enabled(): + if ( + hasattr(model, 'dual') + and isinstance(model.dual, Suffix) + and model.dual.import_enabled() + ): model.dual.update(results.solution_loader.get_duals()) - if hasattr(model, 'rc') and isinstance(model.rc, Suffix) and model.rc.import_enabled(): + if ( + hasattr(model, 'rc') + and isinstance(model.rc, Suffix) + and model.rc.import_enabled() + ): model.rc.update(results.solution_loader.get_reduced_costs()) if results.solution_status in {SolutionStatus.feasible, SolutionStatus.optimal}: @@ -300,7 +333,8 @@ def solve(self, model, **kwds): results.incumbent_objective = replace_expressions( self.info.objectives[0].expr, substitution_map={ - id(v): val for v, val in results.solution_loader.get_primals().items() + id(v): val + for v, val in results.solution_loader.get_primals().items() }, descend_into_named_expressions=True, remove_named_expressions=True, @@ -308,10 +342,11 @@ def solve(self, model, **kwds): return results - def _parse_solution(self, instream: io.TextIOBase, nl_info: NLWriterInfo): suffixes_to_read = ['dual', 'ipopt_zL_out', 'ipopt_zU_out'] - res, sol_data = parse_sol_file(sol_file=instream, nl_info=nl_info, suffixes_to_read=suffixes_to_read) + res, sol_data = parse_sol_file( + sol_file=instream, nl_info=nl_info, suffixes_to_read=suffixes_to_read + ) if res.solution_status == SolutionStatus.noSolution: res.solution_loader = SolutionLoader(None, None, None, None) diff --git a/pyomo/solver/config.py b/pyomo/solver/config.py index 3f4424a8806..551f59ccd9a 100644 --- a/pyomo/solver/config.py +++ b/pyomo/solver/config.py @@ -61,7 +61,10 @@ def __init__( self.load_solution: bool = self.declare( 'load_solution', ConfigValue(domain=bool, default=True) ) - self.raise_exception_on_nonoptimal_result: bool = self.declare('raise_exception_on_nonoptimal_result', ConfigValue(domain=bool, default=True)) + self.raise_exception_on_nonoptimal_result: bool = self.declare( + 'raise_exception_on_nonoptimal_result', + ConfigValue(domain=bool, default=True), + ) self.symbolic_solver_labels: bool = self.declare( 'symbolic_solver_labels', ConfigValue(domain=bool, default=False) ) diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index 17397b9aba0..cda8b68f715 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -235,8 +235,7 @@ def __init__( 'extra_info', ConfigDict(implicit=True) ) self.solver_message: Optional[str] = self.declare( - 'solver_message', - ConfigValue(domain=str, default=None), + 'solver_message', ConfigValue(domain=str, default=None) ) def __str__(self): @@ -262,7 +261,9 @@ def __init__(self) -> None: self.problem_suffixes: Dict[str, List[Any]] = dict() -def parse_sol_file(sol_file: io.TextIOBase, nl_info: NLWriterInfo, suffixes_to_read: Sequence[str]) -> Tuple[Results, SolFileData]: +def parse_sol_file( + sol_file: io.TextIOBase, nl_info: NLWriterInfo, suffixes_to_read: Sequence[str] +) -> Tuple[Results, SolFileData]: suffixes_to_read = set(suffixes_to_read) res = Results() sol_data = SolFileData() @@ -393,26 +394,36 @@ def parse_sol_file(sol_file: io.TextIOBase, nl_info: NLWriterInfo, suffixes_to_r suf_line = fin.readline().split() var_ndx = int(suf_line[0]) var = nl_info.variables[var_ndx] - sol_data.var_suffixes[suffix_name][id(var)] = (var, convert_function(suf_line[1])) + sol_data.var_suffixes[suffix_name][id(var)] = ( + var, + convert_function(suf_line[1]), + ) elif kind == 1: # Con sol_data.con_suffixes[suffix_name] = dict() for cnt in range(nvalues): suf_line = fin.readline().split() con_ndx = int(suf_line[0]) con = nl_info.constraints[con_ndx] - sol_data.con_suffixes[suffix_name][con] = convert_function(suf_line[1]) + sol_data.con_suffixes[suffix_name][con] = convert_function( + suf_line[1] + ) elif kind == 2: # Obj sol_data.obj_suffixes[suffix_name] = dict() for cnt in range(nvalues): suf_line = fin.readline().split() obj_ndx = int(suf_line[0]) obj = nl_info.objectives[obj_ndx] - sol_data.obj_suffixes[suffix_name][id(obj)] = (obj, convert_function(suf_line[1])) + sol_data.obj_suffixes[suffix_name][id(obj)] = ( + obj, + convert_function(suf_line[1]), + ) elif kind == 3: # Prob sol_data.problem_suffixes[suffix_name] = list() for cnt in range(nvalues): suf_line = fin.readline().split() - sol_data.problem_suffixes[suffix_name].append(convert_function(suf_line[1])) + sol_data.problem_suffixes[suffix_name].append( + convert_function(suf_line[1]) + ) else: # do not store the suffix in the solution object for cnt in range(nvalues): diff --git a/pyomo/solver/solution.py b/pyomo/solver/solution.py index 6c4b7431746..068677ea580 100644 --- a/pyomo/solver/solution.py +++ b/pyomo/solver/solution.py @@ -17,6 +17,27 @@ from pyomo.common.collections import ComponentMap from pyomo.core.staleflag import StaleFlagManager +# CHANGES: +# - `load` method: should just load the whole thing back into the model; load_solution = True +# - `load_variables` +# - `get_variables` +# - `get_constraints` +# - `get_objective` +# - `get_slacks` +# - `get_reduced_costs` + +# duals is how much better you could get if you weren't constrained. +# dual value of 0 means that the constraint isn't actively constraining anything. +# high dual value means that it is costing us a lot in the objective. +# can also be called "shadow price" + +# bounds on variables are implied constraints. +# getting a dual on the bound of a variable is the reduced cost. +# IPOPT calls these the bound multipliers (normally they are reduced costs, though). ZL, ZU + +# slacks are... something that I don't understand +# but they are necessary somewhere? I guess? + class SolutionLoaderBase(abc.ABC): def load_vars( From 6d945dad9c51736441586aca5fe52e8b21325804 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 20 Nov 2023 13:27:51 -0700 Subject: [PATCH 0446/1204] Apply black --- pyomo/solver/results.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index d0ed270924c..3287cbd704b 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -291,13 +291,17 @@ def parse_sol_file( line = sol_file.readline() number_of_options = int(line) need_tolerance = False - if number_of_options > 4: # MRM: Entirely unclear why this is necessary, or if it even is + if ( + number_of_options > 4 + ): # MRM: Entirely unclear why this is necessary, or if it even is number_of_options -= 2 need_tolerance = True for i in range(number_of_options + 4): line = sol_file.readline() model_objects.append(int(line)) - if need_tolerance: # MRM: Entirely unclear why this is necessary, or if it even is + if ( + need_tolerance + ): # MRM: Entirely unclear why this is necessary, or if it even is line = sol_file.readline() model_objects.append(float(line)) else: @@ -316,11 +320,15 @@ def parse_sol_file( line = sol_file.readline() if line and ('objno' in line): exit_code_line = line.split() - if (len(exit_code_line) != 3): - raise SolverSystemError(f"ERROR READING `sol` FILE. Expected two numbers in `objno` line; received {line}.") + if len(exit_code_line) != 3: + raise SolverSystemError( + f"ERROR READING `sol` FILE. Expected two numbers in `objno` line; received {line}." + ) exit_code = [int(exit_code_line[1]), int(exit_code_line[2])] else: - raise SolverSystemError(f"ERROR READING `sol` FILE. Expected `objno`; received {line}.") + raise SolverSystemError( + f"ERROR READING `sol` FILE. Expected `objno`; received {line}." + ) results.extra_info.solver_message = message.strip().replace('\n', '; ') if (exit_code[1] >= 0) and (exit_code[1] <= 99): res.solution_status = SolutionStatus.optimal @@ -336,12 +344,16 @@ def parse_sol_file( # But this was the way in the previous version - and has been fine thus far? res.termination_condition = TerminationCondition.locallyInfeasible elif (exit_code[1] >= 300) and (exit_code[1] <= 399): - exit_code_message = "UNBOUNDED PROBLEM: the objective can be improved without limit!" + exit_code_message = ( + "UNBOUNDED PROBLEM: the objective can be improved without limit!" + ) res.solution_status = SolutionStatus.noSolution res.termination_condition = TerminationCondition.unbounded elif (exit_code[1] >= 400) and (exit_code[1] <= 499): - exit_code_message = ("EXCEEDED MAXIMUM NUMBER OF ITERATIONS: the solver " - "was stopped by a limit that you set!") + exit_code_message = ( + "EXCEEDED MAXIMUM NUMBER OF ITERATIONS: the solver " + "was stopped by a limit that you set!" + ) # TODO: this is solver dependent # But this was the way in the previous version - and has been fine thus far? res.solution_status = SolutionStatus.infeasible From 2562d47d6f23f1bb85aee5affc64a49cf812356e Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 20 Nov 2023 13:37:26 -0700 Subject: [PATCH 0447/1204] Run black, try to fix errors --- pyomo/common/formatting.py | 3 ++- pyomo/solver/IPOPT.py | 7 +++---- pyomo/solver/results.py | 5 +---- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/pyomo/common/formatting.py b/pyomo/common/formatting.py index f76d16880df..f17fa247ad0 100644 --- a/pyomo/common/formatting.py +++ b/pyomo/common/formatting.py @@ -257,7 +257,8 @@ def writelines(self, sequence): r'|(?:\[\s*[A-Za-z0-9\.]+\s*\] +)' # [PASS]|[FAIL]|[ OK ] ) _verbatim_line_start = re.compile( - r'(\| )' r'|(\+((-{3,})|(={3,}))\+)' # line blocks # grid table + r'(\| )' + r'|(\+((-{3,})|(={3,}))\+)' # line blocks # grid table ) _verbatim_line = re.compile( r'(={3,}[ =]+)' # simple tables, ======== sections diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index 1e5c1019005..5973b24b917 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -146,7 +146,7 @@ class IPOPT(SolverBase): CONFIG = IPOPTConfig() def __init__(self, **kwds): - self._config = self.CONFIG(kwds) + self.config = self.CONFIG(kwds) def available(self): if self.config.executable.path() is None: @@ -168,11 +168,11 @@ def version(self): @property def config(self): - return self._config + return self.config @config.setter def config(self, val): - self._config = val + self.config = val def _write_options_file(self, ostream: io.TextIOBase, options: Mapping): f = ostream @@ -284,7 +284,6 @@ def solve(self, model, **kwds): if process.returncode != 0: results = Results() results.termination_condition = TerminationCondition.error - results.solution_status = SolutionStatus.noSolution results.solution_loader = SolutionLoader(None, None, None, None) else: # TODO: Make a context manager out of this and open the file diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index 3287cbd704b..515735acafe 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -234,9 +234,6 @@ def __init__( self.extra_info: ConfigDict = self.declare( 'extra_info', ConfigDict(implicit=True) ) - self.solver_message: Optional[str] = self.declare( - 'solver_message', ConfigValue(domain=str, default=None) - ) def __str__(self): s = '' @@ -251,7 +248,7 @@ class ResultsReader: pass -class SolFileData(object): +class SolFileData: def __init__(self) -> None: self.primals: Dict[int, Tuple[_GeneralVarData, float]] = dict() self.duals: Dict[_ConstraintData, float] = dict() From 7c30a257dbc6578ab995488709e78c14bfa0af0d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 20 Nov 2023 13:39:26 -0700 Subject: [PATCH 0448/1204] Fix IPOPT version reference in test --- pyomo/solver/tests/solvers/test_ipopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/solver/tests/solvers/test_ipopt.py b/pyomo/solver/tests/solvers/test_ipopt.py index afe2dbbe531..8cf046fcfef 100644 --- a/pyomo/solver/tests/solvers/test_ipopt.py +++ b/pyomo/solver/tests/solvers/test_ipopt.py @@ -39,7 +39,7 @@ def test_IPOPT_config(self): self.assertIsInstance(config.executable, ExecutableData) # Test custom initialization - solver = SolverFactory('ipopt', save_solver_io=True) + solver = SolverFactory('ipopt_v2', save_solver_io=True) self.assertTrue(solver.config.save_solver_io) self.assertFalse(solver.config.tee) From 7bf0f7f5d429d3a2f3b4be65e81beefb7e6b4fca Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 20 Nov 2023 13:49:45 -0700 Subject: [PATCH 0449/1204] Try to fix recursive config problem --- pyomo/solver/IPOPT.py | 6 +++--- pyomo/solver/results.py | 39 +++++++++++++++++++-------------------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index 5973b24b917..d0ef744aa76 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -146,7 +146,7 @@ class IPOPT(SolverBase): CONFIG = IPOPTConfig() def __init__(self, **kwds): - self.config = self.CONFIG(kwds) + self._config = self.CONFIG(kwds) def available(self): if self.config.executable.path() is None: @@ -168,11 +168,11 @@ def version(self): @property def config(self): - return self.config + return self._config @config.setter def config(self, val): - self.config = val + self._config = val def _write_options_file(self, ostream: io.TextIOBase, options: Mapping): f = ostream diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index 515735acafe..6718f954a94 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -262,13 +262,12 @@ def parse_sol_file( sol_file: io.TextIOBase, nl_info: NLWriterInfo, suffixes_to_read: Sequence[str] ) -> Tuple[Results, SolFileData]: suffixes_to_read = set(suffixes_to_read) - res = Results() sol_data = SolFileData() # # Some solvers (minto) do not write a message. We will assume # all non-blank lines up the 'Options' line is the message. - results = Results() + result = Results() # For backwards compatibility and general safety, we will parse all # lines until "Options" appears. Anything before "Options" we will @@ -326,26 +325,26 @@ def parse_sol_file( raise SolverSystemError( f"ERROR READING `sol` FILE. Expected `objno`; received {line}." ) - results.extra_info.solver_message = message.strip().replace('\n', '; ') + result.extra_info.solver_message = message.strip().replace('\n', '; ') if (exit_code[1] >= 0) and (exit_code[1] <= 99): - res.solution_status = SolutionStatus.optimal - res.termination_condition = TerminationCondition.convergenceCriteriaSatisfied + result.solution_status = SolutionStatus.optimal + result.termination_condition = TerminationCondition.convergenceCriteriaSatisfied elif (exit_code[1] >= 100) and (exit_code[1] <= 199): exit_code_message = "Optimal solution indicated, but ERROR LIKELY!" - res.solution_status = SolutionStatus.feasible - res.termination_condition = TerminationCondition.error + result.solution_status = SolutionStatus.feasible + result.termination_condition = TerminationCondition.error elif (exit_code[1] >= 200) and (exit_code[1] <= 299): exit_code_message = "INFEASIBLE SOLUTION: constraints cannot be satisfied!" - res.solution_status = SolutionStatus.infeasible + result.solution_status = SolutionStatus.infeasible # TODO: this is solver dependent # But this was the way in the previous version - and has been fine thus far? - res.termination_condition = TerminationCondition.locallyInfeasible + result.termination_condition = TerminationCondition.locallyInfeasible elif (exit_code[1] >= 300) and (exit_code[1] <= 399): exit_code_message = ( "UNBOUNDED PROBLEM: the objective can be improved without limit!" ) - res.solution_status = SolutionStatus.noSolution - res.termination_condition = TerminationCondition.unbounded + result.solution_status = SolutionStatus.noSolution + result.termination_condition = TerminationCondition.unbounded elif (exit_code[1] >= 400) and (exit_code[1] <= 499): exit_code_message = ( "EXCEEDED MAXIMUM NUMBER OF ITERATIONS: the solver " @@ -353,21 +352,21 @@ def parse_sol_file( ) # TODO: this is solver dependent # But this was the way in the previous version - and has been fine thus far? - res.solution_status = SolutionStatus.infeasible - res.termination_condition = TerminationCondition.iterationLimit + result.solution_status = SolutionStatus.infeasible + result.termination_condition = TerminationCondition.iterationLimit elif (exit_code[1] >= 500) and (exit_code[1] <= 599): exit_code_message = ( "FAILURE: the solver stopped by an error condition " "in the solver routines!" ) - res.termination_condition = TerminationCondition.error + result.termination_condition = TerminationCondition.error - if results.extra_info.solver_message: - results.extra_info.solver_message += '; ' + exit_code_message + if result.extra_info.solver_message: + result.extra_info.solver_message += '; ' + exit_code_message else: - results.extra_info.solver_message = exit_code_message + result.extra_info.solver_message = exit_code_message - if res.solution_status != SolutionStatus.noSolution: + if result.solution_status != SolutionStatus.noSolution: for v, val in zip(nl_info.variables, variable_vals): sol_data.primals[id(v)] = (v, val) if "dual" in suffixes_to_read: @@ -390,7 +389,7 @@ def parse_sol_file( while line: remaining += line.strip() + "; " line = sol_file.readline() - res.solver_message += remaining + result.solver_message += remaining break unmasked_kind = int(line[1]) kind = unmasked_kind & 3 # 0-var, 1-con, 2-obj, 3-prob @@ -449,7 +448,7 @@ def parse_sol_file( sol_file.readline() line = sol_file.readline() - return res, sol_data + return result, sol_data def parse_yaml(): From 1edc3b51715e7a734a71135f3f97798c7cd0dbf5 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 20 Nov 2023 14:03:15 -0700 Subject: [PATCH 0450/1204] Correct context manage file opening --- pyomo/solver/IPOPT.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index d0ef744aa76..79c33abcd6e 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -237,9 +237,9 @@ def solve(self, model, **kwds): f"NL file with the same name {basename + '.nl'} already exists!" ) with ( - open(basename + '.nl', 'w') as nl_file, - open(basename + '.row', 'w') as row_file, - open(basename + '.col', 'w') as col_file, + open(os.path.join(basename, '.nl'), 'w') as nl_file, + open(os.path.join(basename, '.row'), 'w') as row_file, + open(os.path.join(basename, '.col'), 'w') as col_file, ): self.info = nl_writer.write( model, From f23ed71ef662ad4553fe99b4f8e8bf9c34252de7 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 20 Nov 2023 14:24:05 -0700 Subject: [PATCH 0451/1204] More instances to be replaced --- pyomo/solver/IPOPT.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index 79c33abcd6e..0029d638290 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -183,9 +183,9 @@ def _write_options_file(self, ostream: io.TextIOBase, options: Mapping): def _create_command_line(self, basename: str, config: IPOPTConfig): cmd = [ str(config.executable), - basename + '.nl', + os.path.join(basename, '.nl'), '-AMPL', - 'option_file_name=' + basename + '.opt', + 'option_file_name=' + os.path.join(basename, '.opt'), ] if 'option_file_name' in config.solver_options: raise ValueError( @@ -232,10 +232,11 @@ def solve(self, model, **kwds): if not os.path.exists(dname): os.mkdir(dname) basename = os.path.join(dname, model.name) - if os.path.exists(basename + '.nl'): + if os.path.exists(os.path.join(basename, '.nl')): raise RuntimeError( f"NL file with the same name {basename + '.nl'} already exists!" ) + print(basename, os.path.join(basename, '.nl')) with ( open(os.path.join(basename, '.nl'), 'w') as nl_file, open(os.path.join(basename, '.row'), 'w') as row_file, @@ -248,7 +249,7 @@ def solve(self, model, **kwds): col_file, symbolic_solver_labels=config.symbolic_solver_labels, ) - with open(basename + '.opt', 'w') as opt_file: + with open(os.path.join(basename, '.opt'), 'w') as opt_file: self._write_options_file( ostream=opt_file, options=config.solver_options ) @@ -288,7 +289,7 @@ def solve(self, model, **kwds): else: # TODO: Make a context manager out of this and open the file # to pass to the results, instead of doing this thing. - with open(basename + '.sol', 'r') as sol_file: + with open(os.path.join(basename, '.sol'), 'r') as sol_file: results = self._parse_solution(sol_file, self.info) if ( From dcd7cab7fde48e318a02a3dcca1d7252f93f6727 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 20 Nov 2023 14:26:04 -0700 Subject: [PATCH 0452/1204] Revert - previous version was fine --- pyomo/solver/IPOPT.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index 0029d638290..d0ef744aa76 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -183,9 +183,9 @@ def _write_options_file(self, ostream: io.TextIOBase, options: Mapping): def _create_command_line(self, basename: str, config: IPOPTConfig): cmd = [ str(config.executable), - os.path.join(basename, '.nl'), + basename + '.nl', '-AMPL', - 'option_file_name=' + os.path.join(basename, '.opt'), + 'option_file_name=' + basename + '.opt', ] if 'option_file_name' in config.solver_options: raise ValueError( @@ -232,15 +232,14 @@ def solve(self, model, **kwds): if not os.path.exists(dname): os.mkdir(dname) basename = os.path.join(dname, model.name) - if os.path.exists(os.path.join(basename, '.nl')): + if os.path.exists(basename + '.nl'): raise RuntimeError( f"NL file with the same name {basename + '.nl'} already exists!" ) - print(basename, os.path.join(basename, '.nl')) with ( - open(os.path.join(basename, '.nl'), 'w') as nl_file, - open(os.path.join(basename, '.row'), 'w') as row_file, - open(os.path.join(basename, '.col'), 'w') as col_file, + open(basename + '.nl', 'w') as nl_file, + open(basename + '.row', 'w') as row_file, + open(basename + '.col', 'w') as col_file, ): self.info = nl_writer.write( model, @@ -249,7 +248,7 @@ def solve(self, model, **kwds): col_file, symbolic_solver_labels=config.symbolic_solver_labels, ) - with open(os.path.join(basename, '.opt'), 'w') as opt_file: + with open(basename + '.opt', 'w') as opt_file: self._write_options_file( ostream=opt_file, options=config.solver_options ) @@ -289,7 +288,7 @@ def solve(self, model, **kwds): else: # TODO: Make a context manager out of this and open the file # to pass to the results, instead of doing this thing. - with open(os.path.join(basename, '.sol'), 'r') as sol_file: + with open(basename + '.sol', 'r') as sol_file: results = self._parse_solution(sol_file, self.info) if ( From 883c2aba24a5ff4b10a6057aa57993bd5e031095 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 20 Nov 2023 14:58:18 -0700 Subject: [PATCH 0453/1204] Attempt to resolve syntax issue --- pyomo/solver/IPOPT.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index d0ef744aa76..b0749cba67b 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -236,11 +236,7 @@ def solve(self, model, **kwds): raise RuntimeError( f"NL file with the same name {basename + '.nl'} already exists!" ) - with ( - open(basename + '.nl', 'w') as nl_file, - open(basename + '.row', 'w') as row_file, - open(basename + '.col', 'w') as col_file, - ): + with open(basename + '.nl', 'w') as nl_file, open(basename + '.row', 'w') as row_file, open(basename + '.col', 'w') as col_file: self.info = nl_writer.write( model, nl_file, From 03d3aab254c5efe5b6ecf952577a0ed3ced16f66 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 20 Nov 2023 15:04:30 -0700 Subject: [PATCH 0454/1204] Apply black to context manager --- pyomo/solver/IPOPT.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index b0749cba67b..6df5914c485 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -236,7 +236,9 @@ def solve(self, model, **kwds): raise RuntimeError( f"NL file with the same name {basename + '.nl'} already exists!" ) - with open(basename + '.nl', 'w') as nl_file, open(basename + '.row', 'w') as row_file, open(basename + '.col', 'w') as col_file: + with open(basename + '.nl', 'w') as nl_file, open( + basename + '.row', 'w' + ) as row_file, open(basename + '.col', 'w') as col_file: self.info = nl_writer.write( model, nl_file, From 0e06806cd7b9515e3b641feae79a681dbf7bd1d4 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Mon, 20 Nov 2023 16:41:04 -0700 Subject: [PATCH 0455/1204] Adding all_different and count_if expression nodes to the logical expression system --- pyomo/core/__init__.py | 2 + pyomo/core/expr/__init__.py | 2 + pyomo/core/expr/logical_expr.py | 73 ++++++++++++++++++- .../tests/unit/test_logical_expr_expanded.py | 46 +++++++++++- pyomo/environ/__init__.py | 2 + 5 files changed, 121 insertions(+), 4 deletions(-) diff --git a/pyomo/core/__init__.py b/pyomo/core/__init__.py index 5cbebcee9ec..b119c6357d0 100644 --- a/pyomo/core/__init__.py +++ b/pyomo/core/__init__.py @@ -33,6 +33,8 @@ exactly, atleast, atmost, + all_different, + count_if, implies, lnot, xor, diff --git a/pyomo/core/expr/__init__.py b/pyomo/core/expr/__init__.py index 5e30fceeeaa..de2228189f9 100644 --- a/pyomo/core/expr/__init__.py +++ b/pyomo/core/expr/__init__.py @@ -79,6 +79,8 @@ exactly, atleast, atmost, + all_different, + count_if, implies, ) from .numeric_expr import ( diff --git a/pyomo/core/expr/logical_expr.py b/pyomo/core/expr/logical_expr.py index e5a2f411a6e..2b261278ee9 100644 --- a/pyomo/core/expr/logical_expr.py +++ b/pyomo/core/expr/logical_expr.py @@ -10,10 +10,8 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from __future__ import division - import types -from itertools import islice +from itertools import combinations, islice import logging import traceback @@ -37,6 +35,7 @@ from .base import ExpressionBase from .boolean_value import BooleanValue, BooleanConstant from .expr_common import _and, _or, _equiv, _inv, _xor, _impl, ExpressionType +from .numeric_expr import NumericExpression import operator @@ -240,6 +239,26 @@ def atleast(n, *args): return result +def all_different(*args): + """Creates a new AllDifferentExpression + + Requires all of the arguments to take on a different value + + Usage: all_different(m.X1, m.X2, ...) + """ + return AllDifferentExpression(list(_flattened(args))) + + +def count_if(*args): + """Creates a new CountIfExpression + + Counts the number of True-valued arguments + + Usage: count_if(m.Y1, m.Y2, ...) + """ + return CountIfExpression(list(_flattened(args))) + + class UnaryBooleanExpression(BooleanExpression): """ Abstract class for single-argument logical expressions. @@ -512,4 +531,52 @@ def _apply_operation(self, result): return sum(result[1:]) >= result[0] +class AllDifferentExpression(NaryBooleanExpression): + """ + Logical expression that all of the N child statements have different values. + All arguments are expected to be discrete-valued. + """ + __slots__ = () + + PRECEDENCE = 9 # TODO: maybe? + + def getname(self, *arg, **kwd): + return 'all_different' + + def _to_string(self, values, verbose, smap): + return "all_different(%s)" % (", ".join(values)) + + def _apply_operation(self, result): + for val1, val2 in combinations(result, 2): + if val1 == val2: + return False + return True + +class CountIfExpression(NumericExpression): + """ + Logical expression that returns the number of True child statements. + All arguments are expected to be Boolean-valued. + """ + __slots__ = () + PRECEDENCE = 10 # TODO: maybe? + + def __init__(self, args): + # require a list, a la SumExpression + if args.__class__ is not list: + args = list(args) + self._args_ = args + + # NumericExpression assumes binary operator, so we have to override. + def nargs(self): + return len(self._args_) + + def getname(self, *arg, **kwd): + return 'count_if' + + def _to_string(self, values, verbose, smap): + return "count_if(%s)" % (", ".join(values)) + + def _apply_operation(self, result): + return sum(r for r in result) + special_boolean_atom_types = {ExactlyExpression, AtMostExpression, AtLeastExpression} diff --git a/pyomo/core/tests/unit/test_logical_expr_expanded.py b/pyomo/core/tests/unit/test_logical_expr_expanded.py index f5b86d59cbd..d494c13c83c 100644 --- a/pyomo/core/tests/unit/test_logical_expr_expanded.py +++ b/pyomo/core/tests/unit/test_logical_expr_expanded.py @@ -15,7 +15,7 @@ """ from __future__ import division import operator -from itertools import product +from itertools import permutations, product import pyomo.common.unittest as unittest @@ -23,6 +23,8 @@ from pyomo.core.expr.sympy_tools import sympy_available from pyomo.core.expr.visitor import identify_variables from pyomo.environ import ( + all_different, + count_if, land, atleast, atmost, @@ -39,6 +41,8 @@ BooleanVar, lnot, xor, + Var, + Integers ) @@ -234,6 +238,42 @@ def test_nary_atleast(self): ) self.assertEqual(value(atleast(ntrue, m.Y)), correct_value) + def test_nary_all_diff(self): + m = ConcreteModel() + m.x = Var(range(4), domain=Integers, bounds=(0, 3)) + for vals in permutations(range(4)): + self.assertTrue(value(all_different(*vals))) + for i, v in enumerate(vals): + m.x[i] = v + self.assertTrue(value(all_different(m.x))) + self.assertFalse(value(all_different(1, 1, 2, 3))) + m.x[0] = 1 + m.x[1] = 1 + m.x[2] = 2 + m.x[3] = 3 + self.assertFalse(value(all_different(m.x))) + + def test_count_if(self): + nargs = 3 + m = ConcreteModel() + m.s = RangeSet(nargs) + m.Y = BooleanVar(m.s) + m.x = Var(domain=Integers, bounds=(0, 3)) + for truth_combination in _generate_possible_truth_inputs(nargs): + for ntrue in range(nargs + 1): + m.Y.set_values(dict(enumerate(truth_combination, 1))) + correct_value = sum(truth_combination) + self.assertEqual( + value(count_if(*(m.Y[i] for i in m.s))), correct_value + ) + self.assertEqual(value(count_if(m.Y)), correct_value) + m.x = 2 + self.assertEqual(value(count_if([m.Y[i] for i in m.s] + [m.x == 3,])), + correct_value) + m.x = 3 + self.assertEqual(value(count_if([m.Y[i] for i in m.s] + [m.x == 3,])), + correct_value + 1) + def test_to_string(self): m = ConcreteModel() m.Y1 = BooleanVar() @@ -249,6 +289,8 @@ def test_to_string(self): self.assertEqual(str(atleast(1, m.Y1, m.Y2)), "atleast(1: [Y1, Y2])") self.assertEqual(str(atmost(1, m.Y1, m.Y2)), "atmost(1: [Y1, Y2])") self.assertEqual(str(exactly(1, m.Y1, m.Y2)), "exactly(1: [Y1, Y2])") + self.assertEqual(str(all_different(m.Y1, m.Y2)), "all_different(Y1, Y2)") + self.assertEqual(str(count_if(m.Y1, m.Y2)), "count_if(Y1, Y2)") # Precedence checks self.assertEqual(str(m.Y1.implies(m.Y2).lor(m.Y3)), "(Y1 --> Y2) ∨ Y3") @@ -271,6 +313,8 @@ def test_node_types(self): self.assertTrue(lnot(m.Y1).is_expression_type()) self.assertTrue(equivalent(m.Y1, m.Y2).is_expression_type()) self.assertTrue(atmost(1, [m.Y1, m.Y2, m.Y3]).is_expression_type()) + self.assertTrue(all_different(m.Y1, m.Y2, m.Y3).is_expression_type()) + self.assertTrue(count_if(m.Y1, m.Y2, m.Y3).is_expression_type()) def test_numeric_invalid(self): m = ConcreteModel() diff --git a/pyomo/environ/__init__.py b/pyomo/environ/__init__.py index 51c68449247..c3fb3ec4a85 100644 --- a/pyomo/environ/__init__.py +++ b/pyomo/environ/__init__.py @@ -114,6 +114,8 @@ def _import_packages(): exactly, atleast, atmost, + all_different, + count_if, implies, lnot, xor, From b3bc17bc493a5b644543847a938d94a5b19bc7ee Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Mon, 20 Nov 2023 16:44:58 -0700 Subject: [PATCH 0456/1204] black --- pyomo/core/expr/logical_expr.py | 8 ++++++-- .../tests/unit/test_logical_expr_expanded.py | 16 ++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/pyomo/core/expr/logical_expr.py b/pyomo/core/expr/logical_expr.py index 2b261278ee9..45f6cfaf7eb 100644 --- a/pyomo/core/expr/logical_expr.py +++ b/pyomo/core/expr/logical_expr.py @@ -536,9 +536,10 @@ class AllDifferentExpression(NaryBooleanExpression): Logical expression that all of the N child statements have different values. All arguments are expected to be discrete-valued. """ + __slots__ = () - PRECEDENCE = 9 # TODO: maybe? + PRECEDENCE = 9 # TODO: maybe? def getname(self, *arg, **kwd): return 'all_different' @@ -552,13 +553,15 @@ def _apply_operation(self, result): return False return True + class CountIfExpression(NumericExpression): """ Logical expression that returns the number of True child statements. All arguments are expected to be Boolean-valued. """ + __slots__ = () - PRECEDENCE = 10 # TODO: maybe? + PRECEDENCE = 10 # TODO: maybe? def __init__(self, args): # require a list, a la SumExpression @@ -579,4 +582,5 @@ def _to_string(self, values, verbose, smap): def _apply_operation(self, result): return sum(r for r in result) + special_boolean_atom_types = {ExactlyExpression, AtMostExpression, AtLeastExpression} diff --git a/pyomo/core/tests/unit/test_logical_expr_expanded.py b/pyomo/core/tests/unit/test_logical_expr_expanded.py index d494c13c83c..9e68fee441f 100644 --- a/pyomo/core/tests/unit/test_logical_expr_expanded.py +++ b/pyomo/core/tests/unit/test_logical_expr_expanded.py @@ -42,7 +42,7 @@ lnot, xor, Var, - Integers + Integers, ) @@ -263,16 +263,16 @@ def test_count_if(self): for ntrue in range(nargs + 1): m.Y.set_values(dict(enumerate(truth_combination, 1))) correct_value = sum(truth_combination) - self.assertEqual( - value(count_if(*(m.Y[i] for i in m.s))), correct_value - ) + self.assertEqual(value(count_if(*(m.Y[i] for i in m.s))), correct_value) self.assertEqual(value(count_if(m.Y)), correct_value) m.x = 2 - self.assertEqual(value(count_if([m.Y[i] for i in m.s] + [m.x == 3,])), - correct_value) + self.assertEqual( + value(count_if([m.Y[i] for i in m.s] + [m.x == 3])), correct_value + ) m.x = 3 - self.assertEqual(value(count_if([m.Y[i] for i in m.s] + [m.x == 3,])), - correct_value + 1) + self.assertEqual( + value(count_if([m.Y[i] for i in m.s] + [m.x == 3])), correct_value + 1 + ) def test_to_string(self): m = ConcreteModel() From d750dfb3a6be955a4827c6d23c49afa11f1a5d22 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 21 Nov 2023 10:12:14 -0700 Subject: [PATCH 0457/1204] Fix minor error in exit_code_message; change to safe_dump --- pyomo/common/config.py | 7 +++++-- pyomo/solver/results.py | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index 1e11fbdc431..b79f2cfad25 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -1037,8 +1037,11 @@ class will still create ``c`` instances that only have the single def _dump(*args, **kwds): + # TODO: Change the default behavior to no longer be YAML. + # This was a legacy decision that may no longer be the best + # decision, given changes to technology over the years. try: - from yaml import dump + from yaml import safe_dump as dump except ImportError: # dump = lambda x,**y: str(x) # YAML uses lowercase True/False @@ -1099,7 +1102,7 @@ def _value2string(prefix, value, obj): try: _data = value._data if value is obj else value if getattr(builtins, _data.__class__.__name__, None) is not None: - _str += _dump(_data, default_flow_style=True).rstrip() + _str += _dump(_data, default_flow_style=True, allow_unicode=True).rstrip() if _str.endswith("..."): _str = _str[:-3].rstrip() else: diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index 6718f954a94..8165e6c6310 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -326,6 +326,7 @@ def parse_sol_file( f"ERROR READING `sol` FILE. Expected `objno`; received {line}." ) result.extra_info.solver_message = message.strip().replace('\n', '; ') + exit_code_message = '' if (exit_code[1] >= 0) and (exit_code[1] <= 99): result.solution_status = SolutionStatus.optimal result.termination_condition = TerminationCondition.convergenceCriteriaSatisfied @@ -362,7 +363,8 @@ def parse_sol_file( result.termination_condition = TerminationCondition.error if result.extra_info.solver_message: - result.extra_info.solver_message += '; ' + exit_code_message + if exit_code_message: + result.extra_info.solver_message += '; ' + exit_code_message else: result.extra_info.solver_message = exit_code_message From 4b624f4ea00c012d99c3436b8b944d8945f35949 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 21 Nov 2023 10:14:24 -0700 Subject: [PATCH 0458/1204] Apply black --- pyomo/common/config.py | 4 +++- pyomo/common/formatting.py | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index b79f2cfad25..d85df9f8286 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -1102,7 +1102,9 @@ def _value2string(prefix, value, obj): try: _data = value._data if value is obj else value if getattr(builtins, _data.__class__.__name__, None) is not None: - _str += _dump(_data, default_flow_style=True, allow_unicode=True).rstrip() + _str += _dump( + _data, default_flow_style=True, allow_unicode=True + ).rstrip() if _str.endswith("..."): _str = _str[:-3].rstrip() else: diff --git a/pyomo/common/formatting.py b/pyomo/common/formatting.py index f17fa247ad0..f76d16880df 100644 --- a/pyomo/common/formatting.py +++ b/pyomo/common/formatting.py @@ -257,8 +257,7 @@ def writelines(self, sequence): r'|(?:\[\s*[A-Za-z0-9\.]+\s*\] +)' # [PASS]|[FAIL]|[ OK ] ) _verbatim_line_start = re.compile( - r'(\| )' - r'|(\+((-{3,})|(={3,}))\+)' # line blocks # grid table + r'(\| )' r'|(\+((-{3,})|(={3,}))\+)' # line blocks # grid table ) _verbatim_line = re.compile( r'(={3,}[ =]+)' # simple tables, ======== sections From 131a1425f083de72ce9e088b3cd2dc8b4aa919f1 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 21 Nov 2023 12:55:01 -0700 Subject: [PATCH 0459/1204] Remove custom SolverFactory --- pyomo/common/formatting.py | 3 +- pyomo/contrib/appsi/plugins.py | 2 +- pyomo/solver/IPOPT.py | 18 +++++++----- pyomo/solver/__init__.py | 1 - pyomo/solver/base.py | 3 +- pyomo/solver/factory.py | 33 ---------------------- pyomo/solver/plugins.py | 7 +++-- pyomo/solver/results.py | 18 ++++++++---- pyomo/solver/util.py | 51 ++++++++++++++++++++++++++++------ 9 files changed, 75 insertions(+), 61 deletions(-) delete mode 100644 pyomo/solver/factory.py diff --git a/pyomo/common/formatting.py b/pyomo/common/formatting.py index f76d16880df..f17fa247ad0 100644 --- a/pyomo/common/formatting.py +++ b/pyomo/common/formatting.py @@ -257,7 +257,8 @@ def writelines(self, sequence): r'|(?:\[\s*[A-Za-z0-9\.]+\s*\] +)' # [PASS]|[FAIL]|[ OK ] ) _verbatim_line_start = re.compile( - r'(\| )' r'|(\+((-{3,})|(={3,}))\+)' # line blocks # grid table + r'(\| )' + r'|(\+((-{3,})|(={3,}))\+)' # line blocks # grid table ) _verbatim_line = re.compile( r'(={3,}[ =]+)' # simple tables, ======== sections diff --git a/pyomo/contrib/appsi/plugins.py b/pyomo/contrib/appsi/plugins.py index 3a132b74395..a8f4390972f 100644 --- a/pyomo/contrib/appsi/plugins.py +++ b/pyomo/contrib/appsi/plugins.py @@ -1,5 +1,5 @@ from pyomo.common.extensions import ExtensionBuilderFactory -from pyomo.solver.factory import SolverFactory +from pyomo.opt.base.solvers import SolverFactory from .solvers import Gurobi, Ipopt, Cbc, Cplex, Highs from .build import AppsiBuilder diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index 6df5914c485..11a6a7c4cb8 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -17,21 +17,19 @@ from pyomo.common import Executable from pyomo.common.config import ConfigValue, NonNegativeInt +from pyomo.common.errors import PyomoException from pyomo.common.tempfiles import TempfileManager -from pyomo.opt import WriterFactory from pyomo.repn.plugins.nl_writer import NLWriter, NLWriterInfo from pyomo.solver.base import SolverBase from pyomo.solver.config import SolverConfig -from pyomo.solver.factory import SolverFactory +from pyomo.opt.base.solvers import SolverFactory from pyomo.solver.results import ( Results, TerminationCondition, SolutionStatus, - SolFileData, parse_sol_file, ) from pyomo.solver.solution import SolutionLoaderBase, SolutionLoader -from pyomo.solver.util import SolverSystemError from pyomo.common.tee import TeeStream from pyomo.common.log import LogStream from pyomo.core.expr.visitor import replace_expressions @@ -43,6 +41,14 @@ logger = logging.getLogger(__name__) +class SolverError(PyomoException): + """ + General exception to catch solver system errors + """ + + pass + + class IPOPTConfig(SolverConfig): def __init__( self, @@ -204,9 +210,7 @@ def solve(self, model, **kwds): # Check if solver is available avail = self.available() if not avail: - raise SolverSystemError( - f'Solver {self.__class__} is not available ({avail}).' - ) + raise SolverError(f'Solver {self.__class__} is not available ({avail}).') # Update configuration options, based on keywords passed to solve config: IPOPTConfig = self.config(kwds.pop('options', {})) config.set_value(kwds) diff --git a/pyomo/solver/__init__.py b/pyomo/solver/__init__.py index 1ab9f975f0b..e3eafa991cc 100644 --- a/pyomo/solver/__init__.py +++ b/pyomo/solver/__init__.py @@ -11,7 +11,6 @@ from . import base from . import config -from . import factory from . import results from . import solution from . import util diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 07f19fbb58c..8f0bd8c116f 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -76,7 +76,8 @@ def solve( timer: HierarchicalTimer An option timer for reporting timing **kwargs - Additional keyword arguments (including solver_options - passthrough options; delivered directly to the solver (with no validation)) + Additional keyword arguments (including solver_options - passthrough + options; delivered directly to the solver (with no validation)) Returns ------- diff --git a/pyomo/solver/factory.py b/pyomo/solver/factory.py deleted file mode 100644 index 84b6cf02eac..00000000000 --- a/pyomo/solver/factory.py +++ /dev/null @@ -1,33 +0,0 @@ -# ___________________________________________________________________________ -# -# 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.opt.base import SolverFactory as LegacySolverFactory -from pyomo.common.factory import Factory -from pyomo.solver.base import LegacySolverInterface - - -class SolverFactoryClass(Factory): - def register(self, name, doc=None): - def decorator(cls): - self._cls[name] = cls - self._doc[name] = doc - - # class LegacySolver(LegacySolverInterface, cls): - # pass - - # LegacySolverFactory.register(name, doc)(LegacySolver) - - return cls - - return decorator - - -SolverFactory = SolverFactoryClass() diff --git a/pyomo/solver/plugins.py b/pyomo/solver/plugins.py index 5dfd4bce1eb..1dfcb6d2fe5 100644 --- a/pyomo/solver/plugins.py +++ b/pyomo/solver/plugins.py @@ -10,8 +10,11 @@ # ___________________________________________________________________________ -from .factory import SolverFactory +from pyomo.opt.base.solvers import SolverFactory +from .IPOPT import IPOPT def load(): - pass + SolverFactory.register(name='ipopt_v2', doc='The IPOPT NLP solver (new interface)')( + IPOPT + ) diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index 8165e6c6310..404977e8a1a 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -10,7 +10,6 @@ # ___________________________________________________________________________ import enum -import re from typing import Optional, Tuple, Dict, Any, Sequence, List from datetime import datetime import io @@ -23,7 +22,7 @@ In, NonNegativeFloat, ) -from pyomo.common.collections import ComponentMap +from pyomo.common.errors import PyomoException from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.constraint import _ConstraintData from pyomo.core.base.objective import _ObjectiveData @@ -33,10 +32,17 @@ SolverStatus as LegacySolverStatus, ) from pyomo.solver.solution import SolutionLoaderBase -from pyomo.solver.util import SolverSystemError from pyomo.repn.plugins.nl_writer import NLWriterInfo +class SolverResultsError(PyomoException): + """ + General exception to catch solver system errors + """ + + pass + + class TerminationCondition(enum.Enum): """ An Enum that enumerates all possible exit statuses for a solver call. @@ -301,7 +307,7 @@ def parse_sol_file( line = sol_file.readline() model_objects.append(float(line)) else: - raise SolverSystemError("ERROR READING `sol` FILE. No 'Options' line found.") + raise SolverResultsError("ERROR READING `sol` FILE. No 'Options' line found.") # Identify the total number of variables and constraints number_of_cons = model_objects[number_of_options + 1] number_of_vars = model_objects[number_of_options + 3] @@ -317,12 +323,12 @@ def parse_sol_file( if line and ('objno' in line): exit_code_line = line.split() if len(exit_code_line) != 3: - raise SolverSystemError( + raise SolverResultsError( f"ERROR READING `sol` FILE. Expected two numbers in `objno` line; received {line}." ) exit_code = [int(exit_code_line[1]), int(exit_code_line[2])] else: - raise SolverSystemError( + raise SolverResultsError( f"ERROR READING `sol` FILE. Expected `objno`; received {line}." ) result.extra_info.solver_message = message.strip().replace('\n', '; ') diff --git a/pyomo/solver/util.py b/pyomo/solver/util.py index 79abee1b689..16d7c4d7cd4 100644 --- a/pyomo/solver/util.py +++ b/pyomo/solver/util.py @@ -20,18 +20,10 @@ from pyomo.core.base.param import _ParamData, Param from pyomo.core.base.objective import Objective, _GeneralObjectiveData from pyomo.common.collections import ComponentMap -from pyomo.common.errors import PyomoException from pyomo.common.timing import HierarchicalTimer from pyomo.core.expr.numvalue import NumericConstant from pyomo.solver.config import UpdateConfig - - -class SolverSystemError(PyomoException): - """ - General exception to catch solver system errors - """ - - pass +from pyomo.solver.results import TerminationCondition, SolutionStatus def get_objective(block): @@ -45,6 +37,47 @@ def get_objective(block): return obj +def check_optimal_termination(results): + """ + This function returns True if the termination condition for the solver + is 'optimal', 'locallyOptimal', or 'globallyOptimal', and the status is 'ok' + + Parameters + ---------- + results : Pyomo Results object returned from solver.solve + + Returns + ------- + `bool` + """ + if results.solution_status == SolutionStatus.optimal and ( + results.termination_condition + == TerminationCondition.convergenceCriteriaSatisfied + ): + return True + return False + + +def assert_optimal_termination(results): + """ + This function checks if the termination condition for the solver + is 'optimal', 'locallyOptimal', or 'globallyOptimal', and the status is 'ok' + and it raises a RuntimeError exception if this is not true. + + Parameters + ---------- + results : Pyomo Results object returned from solver.solve + """ + if not check_optimal_termination(results): + msg = ( + 'Solver failed to return an optimal solution. ' + 'Solution status: {}, Termination condition: {}'.format( + results.solution_status, results.termination_condition + ) + ) + raise RuntimeError(msg) + + class _VarAndNamedExprCollector(ExpressionValueVisitor): def __init__(self): self.named_expressions = {} From f261fdc146d1de7b75c13d7fbc2d8a3e97a0fe7f Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 21 Nov 2023 15:19:10 -0500 Subject: [PATCH 0460/1204] fix load_solutions bug --- pyomo/contrib/mindtpy/algorithm_base_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index d485dc4651f..4ea492bd7c9 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -864,7 +864,7 @@ def init_rNLP(self, add_oa_cuts=True): results = self.nlp_opt.solve( self.rnlp, tee=config.nlp_solver_tee, - load_solutions=config.load_solutions, + load_solutions=self.load_solutions, **nlp_args, ) if len(results.solution) > 0: From 52b8b6b0670a7742a30a3b1a92d42591b648fa06 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 21 Nov 2023 13:24:03 -0700 Subject: [PATCH 0461/1204] Revert Factory changes; clean up some nonsense --- pyomo/contrib/appsi/plugins.py | 2 +- pyomo/solver/IPOPT.py | 2 +- pyomo/solver/base.py | 1 - pyomo/solver/factory.py | 33 +++++++++++++++++++++++++++++++++ pyomo/solver/plugins.py | 2 +- 5 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 pyomo/solver/factory.py diff --git a/pyomo/contrib/appsi/plugins.py b/pyomo/contrib/appsi/plugins.py index a8f4390972f..3a132b74395 100644 --- a/pyomo/contrib/appsi/plugins.py +++ b/pyomo/contrib/appsi/plugins.py @@ -1,5 +1,5 @@ from pyomo.common.extensions import ExtensionBuilderFactory -from pyomo.opt.base.solvers import SolverFactory +from pyomo.solver.factory import SolverFactory from .solvers import Gurobi, Ipopt, Cbc, Cplex, Highs from .build import AppsiBuilder diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index 11a6a7c4cb8..30f4cfc60a9 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -22,7 +22,7 @@ from pyomo.repn.plugins.nl_writer import NLWriter, NLWriterInfo from pyomo.solver.base import SolverBase from pyomo.solver.config import SolverConfig -from pyomo.opt.base.solvers import SolverFactory +from pyomo.solver.factory import SolverFactory from pyomo.solver.results import ( Results, TerminationCondition, diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 8f0bd8c116f..8c6ef0bddef 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -21,7 +21,6 @@ from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.common.timing import HierarchicalTimer from pyomo.common.errors import ApplicationError - from pyomo.opt.results.results_ import SolverResults as LegacySolverResults from pyomo.opt.results.solution import Solution as LegacySolution from pyomo.core.kernel.objective import minimize diff --git a/pyomo/solver/factory.py b/pyomo/solver/factory.py new file mode 100644 index 00000000000..84b6cf02eac --- /dev/null +++ b/pyomo/solver/factory.py @@ -0,0 +1,33 @@ +# ___________________________________________________________________________ +# +# 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.opt.base import SolverFactory as LegacySolverFactory +from pyomo.common.factory import Factory +from pyomo.solver.base import LegacySolverInterface + + +class SolverFactoryClass(Factory): + def register(self, name, doc=None): + def decorator(cls): + self._cls[name] = cls + self._doc[name] = doc + + # class LegacySolver(LegacySolverInterface, cls): + # pass + + # LegacySolverFactory.register(name, doc)(LegacySolver) + + return cls + + return decorator + + +SolverFactory = SolverFactoryClass() diff --git a/pyomo/solver/plugins.py b/pyomo/solver/plugins.py index 1dfcb6d2fe5..2f95ca9f410 100644 --- a/pyomo/solver/plugins.py +++ b/pyomo/solver/plugins.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ -from pyomo.opt.base.solvers import SolverFactory +from .factory import SolverFactory from .IPOPT import IPOPT From a6802a53882df831a8e7c0c91b18f972c608d385 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 21 Nov 2023 14:58:57 -0700 Subject: [PATCH 0462/1204] Adding all_different and count_if to docplex writer, testing them, fixing a bug with evaluation of count_if --- pyomo/contrib/cp/repn/docplex_writer.py | 19 ++- pyomo/contrib/cp/tests/test_docplex_walker.py | 58 ++++++++- pyomo/contrib/cp/tests/test_docplex_writer.py | 116 ++++++++++++++++++ pyomo/core/expr/__init__.py | 2 + pyomo/core/expr/logical_expr.py | 2 +- 5 files changed, 194 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 51c3f66140e..c5b219ae9fd 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -64,7 +64,7 @@ IndexedBooleanVar, ) from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData -from pyomo.core.base.param import IndexedParam, ScalarParam +from pyomo.core.base.param import IndexedParam, ScalarParam, _ParamData from pyomo.core.base.var import ScalarVar, _GeneralVarData, IndexedVar import pyomo.core.expr as EXPR from pyomo.core.expr.visitor import StreamBasedExpressionVisitor, identify_variables @@ -805,6 +805,20 @@ def _handle_at_least_node(visitor, node, *args): ) +def _handle_all_diff_node(visitor, node, *args): + return ( + _GENERAL, + cp.all_diff(_get_int_valued_expr(arg) for arg in args), + ) + + +def _handle_count_if_node(visitor, node, *args): + return ( + _GENERAL, + cp.count((_get_bool_valued_expr(arg) for arg in args), 1), + ) + + ## CallExpression handllers @@ -932,6 +946,8 @@ class LogicalToDoCplex(StreamBasedExpressionVisitor): EXPR.ExactlyExpression: _handle_exactly_node, EXPR.AtMostExpression: _handle_at_most_node, EXPR.AtLeastExpression: _handle_at_least_node, + EXPR.AllDifferentExpression: _handle_all_diff_node, + EXPR.CountIfExpression: _handle_count_if_node, EXPR.EqualityExpression: _handle_equality_node, EXPR.NotEqualExpression: _handle_not_equal_node, EXPR.InequalityExpression: _handle_inequality_node, @@ -960,6 +976,7 @@ class LogicalToDoCplex(StreamBasedExpressionVisitor): ScalarExpression: _before_named_expression, IndexedParam: _before_indexed_param, # Because of indirection ScalarParam: _before_param, + _ParamData: _before_param, } def __init__(self, cpx_model, symbolic_solver_labels=False): diff --git a/pyomo/contrib/cp/tests/test_docplex_walker.py b/pyomo/contrib/cp/tests/test_docplex_walker.py index 97bc538c827..f8c5f7f766f 100644 --- a/pyomo/contrib/cp/tests/test_docplex_walker.py +++ b/pyomo/contrib/cp/tests/test_docplex_walker.py @@ -21,7 +21,9 @@ from pyomo.core.base.range import NumericRange from pyomo.core.expr.numeric_expr import MinExpression, MaxExpression -from pyomo.core.expr.logical_expr import equivalent, exactly, atleast, atmost +from pyomo.core.expr.logical_expr import ( + equivalent, exactly, atleast, atmost, all_different, count_if +) from pyomo.core.expr.relational_expr import NotEqualExpression from pyomo.environ import ( @@ -401,6 +403,60 @@ def test_atmost_expression(self): expr[1].equals(cp.less_or_equal(cp.count([a[i] == 4 for i in m.I], 1), 3)) ) + def test_all_diff_expression(self): + m = self.get_model() + m.a.domain = Integers + m.a.bounds = (11, 20) + m.c = LogicalConstraint(expr=all_different(m.a)) + + visitor = self.get_visitor() + expr = visitor.walk_expression((m.c.body, m.c, 0)) + + a = {} + for i in m.I: + self.assertIn(id(m.a[i]), visitor.var_map) + a[i] = visitor.var_map[id(m.a[i])] + + self.assertTrue( + expr[1].equals(cp.all_diff(a[i] for i in m.I)) + ) + + def test_Boolean_args_in_all_diff_expression(self): + m = self.get_model() + m.a.domain = Integers + m.a.bounds = (11, 20) + m.c = LogicalConstraint(expr=all_different(m.a[1] == 13, m.b)) + + visitor = self.get_visitor() + expr = visitor.walk_expression((m.c.body, m.c, 0)) + + self.assertIn(id(m.a[1]), visitor.var_map) + a0 = visitor.var_map[id(m.a[1])] + self.assertIn(id(m.b), visitor.var_map) + b = visitor.var_map[id(m.b)] + + self.assertTrue( + expr[1].equals(cp.all_diff(a0 == 13, b)) + ) + + def test_count_if_expression(self): + m = self.get_model() + m.a.domain = Integers + m.a.bounds = (11, 20) + m.c = Constraint(expr=count_if(m.a[i] == i for i in m.I) == 5) + + visitor = self.get_visitor() + expr = visitor.walk_expression((m.c.expr, m.c, 0)) + + a = {} + for i in m.I: + self.assertIn(id(m.a[i]), visitor.var_map) + a[i] = visitor.var_map[id(m.a[i])] + + self.assertTrue( + expr[1].equals(cp.count((a[i] == i for i in m.I), 1) == 5) + ) + def test_interval_var_is_present(self): m = self.get_model() m.a.domain = Integers diff --git a/pyomo/contrib/cp/tests/test_docplex_writer.py b/pyomo/contrib/cp/tests/test_docplex_writer.py index d569ef2e696..566b4084daa 100644 --- a/pyomo/contrib/cp/tests/test_docplex_writer.py +++ b/pyomo/contrib/cp/tests/test_docplex_writer.py @@ -15,10 +15,13 @@ from pyomo.contrib.cp import IntervalVar, Pulse, Step, AlwaysIn from pyomo.contrib.cp.repn.docplex_writer import LogicalToDoCplex from pyomo.environ import ( + all_different, + count_if, ConcreteModel, Set, Var, Integers, + Param, LogicalConstraint, implies, value, @@ -254,3 +257,116 @@ def x_bounds(m, i): self.assertEqual(results.problem.sense, minimize) self.assertEqual(results.problem.lower_bound, 6) self.assertEqual(results.problem.upper_bound, 6) + + def test_matching_problem(self): + m = ConcreteModel() + + m.People = Set(initialize=['P1', 'P2', 'P3', 'P4', 'P5', 'P6', 'P7']) + m.Languages = Set(initialize=['English', 'Spanish', 'Hindi', 'Swedish']) + # People have integer names because we don't have categorical vars yet. + m.Names = Set(initialize=range(len(m.People))) + + m.Observed = Param(m.Names, m.Names, m.Languages, + initialize={ + (0, 1, 'English'): 1, + (1, 0, 'English'): 1, + (0, 2, 'English'): 1, + (2, 0, 'English'): 1, + (0, 3, 'English'): 1, + (3, 0, 'English'): 1, + (0, 4, 'English'): 1, + (4, 0, 'English'): 1, + (0, 5, 'English'): 1, + (5, 0, 'English'): 1, + (0, 6, 'English'): 1, + (6, 0, 'English'): 1, + (1, 2, 'Spanish'): 1, + (2, 1, 'Spanish'): 1, + (1, 5, 'Hindi'): 1, + (5, 1, 'Hindi'): 1, + (1, 6, 'Hindi'): 1, + (6, 1, 'Hindi'): 1, + (2, 3, 'Swedish'): 1, + (3, 2, 'Swedish'): 1, + (3, 4, 'English'): 1, + (4, 3, 'English'): 1, + }, default=0, mutable=True)# TODO: shouldn't need to + # be mutable, but waiting + # on #3045 + + m.Expected = Param(m.People, m.People, m.Languages, initialize={ + ('P1', 'P2', 'English') : 1, + ('P2', 'P1', 'English') : 1, + ('P1', 'P3', 'English') : 1, + ('P3', 'P1', 'English') : 1, + ('P1', 'P4', 'English') : 1, + ('P4', 'P1', 'English') : 1, + ('P1', 'P5', 'English') : 1, + ('P5', 'P1', 'English') : 1, + ('P1', 'P6', 'English') : 1, + ('P6', 'P1', 'English') : 1, + ('P1', 'P7', 'English') : 1, + ('P7', 'P1', 'English') : 1, + ('P2', 'P3', 'Spanish') : 1, + ('P3', 'P2', 'Spanish') : 1, + ('P2', 'P6', 'Hindi') : 1, + ('P6', 'P2', 'Hindi') : 1, + ('P2', 'P7', 'Hindi') : 1, + ('P7', 'P2', 'Hindi') : 1, + ('P3', 'P4', 'Swedish') : 1, + ('P4', 'P3', 'Swedish') : 1, + ('P4', 'P5', 'English') : 1, + ('P5', 'P4', 'English') : 1, + }, default=0, mutable=True)# TODO: shouldn't need to be mutable, but + # waiting on #3045 + + m.person_name = Var(m.People, bounds=(0, max(m.Names)), domain=Integers) + + m.one_to_one = LogicalConstraint(expr=all_different(m.person_name[person] for + person in m.People)) + + + m.obj = Objective(expr=count_if(m.Observed[m.person_name[p1], + m.person_name[p2], l] == + m.Expected[p1, p2, l] for p1 + in m.People for p2 in + m.People for l in + m.Languages), sense=maximize) + + results = SolverFactory('cp_optimizer').solve(m) + + # we can get one of two perfect matches: + perfect = 7*7*4 + self.assertEqual(results.problem.lower_bound, perfect) + self.assertEqual(results.problem.upper_bound, perfect) + self.assertEqual(results.solver.termination_condition, + TerminationCondition.optimal) + self.assertEqual(value(m.obj), perfect) + m.person_name.pprint() + self.assertEqual(value(m.person_name['P1']), 0) + self.assertEqual(value(m.person_name['P2']), 1) + self.assertEqual(value(m.person_name['P3']), 2) + self.assertEqual(value(m.person_name['P4']), 3) + self.assertEqual(value(m.person_name['P5']), 4) + # We can't distinguish P6 and P7, so they could each have either of + # names 5 and 6 + self.assertTrue(value(m.person_name['P6']) == 5 or + value(m.person_name['P6']) == 6) + self.assertTrue(value(m.person_name['P7']) == 5 or + value(m.person_name['P7']) == 6) + + m.person_name['P6'].fix(5) + m.person_name['P7'].fix(6) + + results = SolverFactory('cp_optimizer').solve(m) + self.assertEqual(results.solver.termination_condition, + TerminationCondition.optimal) + self.assertEqual(value(m.obj), perfect) + + m.person_name['P6'].fix(6) + m.person_name['P7'].fix(5) + + results = SolverFactory('cp_optimizer').solve(m) + self.assertEqual(results.solver.termination_condition, + TerminationCondition.optimal) + self.assertEqual(value(m.obj), perfect) diff --git a/pyomo/core/expr/__init__.py b/pyomo/core/expr/__init__.py index de2228189f9..bd6d1b995a1 100644 --- a/pyomo/core/expr/__init__.py +++ b/pyomo/core/expr/__init__.py @@ -70,6 +70,8 @@ ExactlyExpression, AtMostExpression, AtLeastExpression, + AllDifferentExpression, + CountIfExpression, # land, lnot, diff --git a/pyomo/core/expr/logical_expr.py b/pyomo/core/expr/logical_expr.py index 45f6cfaf7eb..31082293a71 100644 --- a/pyomo/core/expr/logical_expr.py +++ b/pyomo/core/expr/logical_expr.py @@ -580,7 +580,7 @@ def _to_string(self, values, verbose, smap): return "count_if(%s)" % (", ".join(values)) def _apply_operation(self, result): - return sum(r for r in result) + return sum(value(r) for r in result) special_boolean_atom_types = {ExactlyExpression, AtMostExpression, AtLeastExpression} From f70001b63198b6457c63e717b9b37933c999acfc Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 21 Nov 2023 14:59:32 -0700 Subject: [PATCH 0463/1204] Blackify --- pyomo/contrib/cp/repn/docplex_writer.py | 10 +- pyomo/contrib/cp/tests/test_docplex_walker.py | 19 +- pyomo/contrib/cp/tests/test_docplex_writer.py | 168 ++++++++++-------- 3 files changed, 106 insertions(+), 91 deletions(-) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index c5b219ae9fd..c2687662fe8 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -806,17 +806,11 @@ def _handle_at_least_node(visitor, node, *args): def _handle_all_diff_node(visitor, node, *args): - return ( - _GENERAL, - cp.all_diff(_get_int_valued_expr(arg) for arg in args), - ) + return (_GENERAL, cp.all_diff(_get_int_valued_expr(arg) for arg in args)) def _handle_count_if_node(visitor, node, *args): - return ( - _GENERAL, - cp.count((_get_bool_valued_expr(arg) for arg in args), 1), - ) + return (_GENERAL, cp.count((_get_bool_valued_expr(arg) for arg in args), 1)) ## CallExpression handllers diff --git a/pyomo/contrib/cp/tests/test_docplex_walker.py b/pyomo/contrib/cp/tests/test_docplex_walker.py index f8c5f7f766f..0f1c73cd3b1 100644 --- a/pyomo/contrib/cp/tests/test_docplex_walker.py +++ b/pyomo/contrib/cp/tests/test_docplex_walker.py @@ -22,7 +22,12 @@ from pyomo.core.base.range import NumericRange from pyomo.core.expr.numeric_expr import MinExpression, MaxExpression from pyomo.core.expr.logical_expr import ( - equivalent, exactly, atleast, atmost, all_different, count_if + equivalent, + exactly, + atleast, + atmost, + all_different, + count_if, ) from pyomo.core.expr.relational_expr import NotEqualExpression @@ -417,9 +422,7 @@ def test_all_diff_expression(self): self.assertIn(id(m.a[i]), visitor.var_map) a[i] = visitor.var_map[id(m.a[i])] - self.assertTrue( - expr[1].equals(cp.all_diff(a[i] for i in m.I)) - ) + self.assertTrue(expr[1].equals(cp.all_diff(a[i] for i in m.I))) def test_Boolean_args_in_all_diff_expression(self): m = self.get_model() @@ -435,9 +438,7 @@ def test_Boolean_args_in_all_diff_expression(self): self.assertIn(id(m.b), visitor.var_map) b = visitor.var_map[id(m.b)] - self.assertTrue( - expr[1].equals(cp.all_diff(a0 == 13, b)) - ) + self.assertTrue(expr[1].equals(cp.all_diff(a0 == 13, b))) def test_count_if_expression(self): m = self.get_model() @@ -453,9 +454,7 @@ def test_count_if_expression(self): self.assertIn(id(m.a[i]), visitor.var_map) a[i] = visitor.var_map[id(m.a[i])] - self.assertTrue( - expr[1].equals(cp.count((a[i] == i for i in m.I), 1) == 5) - ) + self.assertTrue(expr[1].equals(cp.count((a[i] == i for i in m.I), 1) == 5)) def test_interval_var_is_present(self): m = self.get_model() diff --git a/pyomo/contrib/cp/tests/test_docplex_writer.py b/pyomo/contrib/cp/tests/test_docplex_writer.py index 566b4084daa..b563052ef3a 100644 --- a/pyomo/contrib/cp/tests/test_docplex_writer.py +++ b/pyomo/contrib/cp/tests/test_docplex_writer.py @@ -266,81 +266,99 @@ def test_matching_problem(self): # People have integer names because we don't have categorical vars yet. m.Names = Set(initialize=range(len(m.People))) - m.Observed = Param(m.Names, m.Names, m.Languages, - initialize={ - (0, 1, 'English'): 1, - (1, 0, 'English'): 1, - (0, 2, 'English'): 1, - (2, 0, 'English'): 1, - (0, 3, 'English'): 1, - (3, 0, 'English'): 1, - (0, 4, 'English'): 1, - (4, 0, 'English'): 1, - (0, 5, 'English'): 1, - (5, 0, 'English'): 1, - (0, 6, 'English'): 1, - (6, 0, 'English'): 1, - (1, 2, 'Spanish'): 1, - (2, 1, 'Spanish'): 1, - (1, 5, 'Hindi'): 1, - (5, 1, 'Hindi'): 1, - (1, 6, 'Hindi'): 1, - (6, 1, 'Hindi'): 1, - (2, 3, 'Swedish'): 1, - (3, 2, 'Swedish'): 1, - (3, 4, 'English'): 1, - (4, 3, 'English'): 1, - }, default=0, mutable=True)# TODO: shouldn't need to - # be mutable, but waiting - # on #3045 - - m.Expected = Param(m.People, m.People, m.Languages, initialize={ - ('P1', 'P2', 'English') : 1, - ('P2', 'P1', 'English') : 1, - ('P1', 'P3', 'English') : 1, - ('P3', 'P1', 'English') : 1, - ('P1', 'P4', 'English') : 1, - ('P4', 'P1', 'English') : 1, - ('P1', 'P5', 'English') : 1, - ('P5', 'P1', 'English') : 1, - ('P1', 'P6', 'English') : 1, - ('P6', 'P1', 'English') : 1, - ('P1', 'P7', 'English') : 1, - ('P7', 'P1', 'English') : 1, - ('P2', 'P3', 'Spanish') : 1, - ('P3', 'P2', 'Spanish') : 1, - ('P2', 'P6', 'Hindi') : 1, - ('P6', 'P2', 'Hindi') : 1, - ('P2', 'P7', 'Hindi') : 1, - ('P7', 'P2', 'Hindi') : 1, - ('P3', 'P4', 'Swedish') : 1, - ('P4', 'P3', 'Swedish') : 1, - ('P4', 'P5', 'English') : 1, - ('P5', 'P4', 'English') : 1, - }, default=0, mutable=True)# TODO: shouldn't need to be mutable, but - # waiting on #3045 + m.Observed = Param( + m.Names, + m.Names, + m.Languages, + initialize={ + (0, 1, 'English'): 1, + (1, 0, 'English'): 1, + (0, 2, 'English'): 1, + (2, 0, 'English'): 1, + (0, 3, 'English'): 1, + (3, 0, 'English'): 1, + (0, 4, 'English'): 1, + (4, 0, 'English'): 1, + (0, 5, 'English'): 1, + (5, 0, 'English'): 1, + (0, 6, 'English'): 1, + (6, 0, 'English'): 1, + (1, 2, 'Spanish'): 1, + (2, 1, 'Spanish'): 1, + (1, 5, 'Hindi'): 1, + (5, 1, 'Hindi'): 1, + (1, 6, 'Hindi'): 1, + (6, 1, 'Hindi'): 1, + (2, 3, 'Swedish'): 1, + (3, 2, 'Swedish'): 1, + (3, 4, 'English'): 1, + (4, 3, 'English'): 1, + }, + default=0, + mutable=True, + ) # TODO: shouldn't need to + # be mutable, but waiting + # on #3045 + + m.Expected = Param( + m.People, + m.People, + m.Languages, + initialize={ + ('P1', 'P2', 'English'): 1, + ('P2', 'P1', 'English'): 1, + ('P1', 'P3', 'English'): 1, + ('P3', 'P1', 'English'): 1, + ('P1', 'P4', 'English'): 1, + ('P4', 'P1', 'English'): 1, + ('P1', 'P5', 'English'): 1, + ('P5', 'P1', 'English'): 1, + ('P1', 'P6', 'English'): 1, + ('P6', 'P1', 'English'): 1, + ('P1', 'P7', 'English'): 1, + ('P7', 'P1', 'English'): 1, + ('P2', 'P3', 'Spanish'): 1, + ('P3', 'P2', 'Spanish'): 1, + ('P2', 'P6', 'Hindi'): 1, + ('P6', 'P2', 'Hindi'): 1, + ('P2', 'P7', 'Hindi'): 1, + ('P7', 'P2', 'Hindi'): 1, + ('P3', 'P4', 'Swedish'): 1, + ('P4', 'P3', 'Swedish'): 1, + ('P4', 'P5', 'English'): 1, + ('P5', 'P4', 'English'): 1, + }, + default=0, + mutable=True, + ) # TODO: shouldn't need to be mutable, but + # waiting on #3045 m.person_name = Var(m.People, bounds=(0, max(m.Names)), domain=Integers) - m.one_to_one = LogicalConstraint(expr=all_different(m.person_name[person] for - person in m.People)) - + m.one_to_one = LogicalConstraint( + expr=all_different(m.person_name[person] for person in m.People) + ) - m.obj = Objective(expr=count_if(m.Observed[m.person_name[p1], - m.person_name[p2], l] == - m.Expected[p1, p2, l] for p1 - in m.People for p2 in - m.People for l in - m.Languages), sense=maximize) + m.obj = Objective( + expr=count_if( + m.Observed[m.person_name[p1], m.person_name[p2], l] + == m.Expected[p1, p2, l] + for p1 in m.People + for p2 in m.People + for l in m.Languages + ), + sense=maximize, + ) results = SolverFactory('cp_optimizer').solve(m) # we can get one of two perfect matches: - perfect = 7*7*4 + perfect = 7 * 7 * 4 self.assertEqual(results.problem.lower_bound, perfect) self.assertEqual(results.problem.upper_bound, perfect) - self.assertEqual(results.solver.termination_condition, - TerminationCondition.optimal) + self.assertEqual( + results.solver.termination_condition, TerminationCondition.optimal + ) self.assertEqual(value(m.obj), perfect) m.person_name.pprint() self.assertEqual(value(m.person_name['P1']), 0) @@ -350,23 +368,27 @@ def test_matching_problem(self): self.assertEqual(value(m.person_name['P5']), 4) # We can't distinguish P6 and P7, so they could each have either of # names 5 and 6 - self.assertTrue(value(m.person_name['P6']) == 5 or - value(m.person_name['P6']) == 6) - self.assertTrue(value(m.person_name['P7']) == 5 or - value(m.person_name['P7']) == 6) + self.assertTrue( + value(m.person_name['P6']) == 5 or value(m.person_name['P6']) == 6 + ) + self.assertTrue( + value(m.person_name['P7']) == 5 or value(m.person_name['P7']) == 6 + ) m.person_name['P6'].fix(5) m.person_name['P7'].fix(6) results = SolverFactory('cp_optimizer').solve(m) - self.assertEqual(results.solver.termination_condition, - TerminationCondition.optimal) + self.assertEqual( + results.solver.termination_condition, TerminationCondition.optimal + ) self.assertEqual(value(m.obj), perfect) m.person_name['P6'].fix(6) m.person_name['P7'].fix(5) results = SolverFactory('cp_optimizer').solve(m) - self.assertEqual(results.solver.termination_condition, - TerminationCondition.optimal) + self.assertEqual( + results.solver.termination_condition, TerminationCondition.optimal + ) self.assertEqual(value(m.obj), perfect) From 63af991b68d2d18bbabd0e22126e59638540c34c Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 21 Nov 2023 15:15:29 -0700 Subject: [PATCH 0464/1204] Push changes: pyomo --help solvers tracks all solvers, v1, v2, and appsi --- pyomo/solver/IPOPT.py | 3 ++- pyomo/solver/base.py | 10 ++++++++++ pyomo/solver/factory.py | 7 ++++--- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index 30f4cfc60a9..74b0ae25358 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -13,7 +13,7 @@ import subprocess import io import sys -from typing import Mapping +from typing import Mapping, Dict from pyomo.common import Executable from pyomo.common.config import ConfigValue, NonNegativeInt @@ -153,6 +153,7 @@ class IPOPT(SolverBase): def __init__(self, **kwds): self._config = self.CONFIG(kwds) + self.ipopt_options = ipopt_command_line_options def available(self): if self.config.executable.path() is None: diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 8c6ef0bddef..d4b46ebe5d4 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -37,6 +37,16 @@ class SolverBase(abc.ABC): + # + # Support "with" statements. Forgetting to call deactivate + # on Plugins is a common source of memory leaks + # + def __enter__(self): + return self + + def __exit__(self, t, v, traceback): + pass + class Availability(enum.IntEnum): FullLicense = 2 LimitedLicense = 1 diff --git a/pyomo/solver/factory.py b/pyomo/solver/factory.py index 84b6cf02eac..23a66acd9cb 100644 --- a/pyomo/solver/factory.py +++ b/pyomo/solver/factory.py @@ -9,6 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ + from pyomo.opt.base import SolverFactory as LegacySolverFactory from pyomo.common.factory import Factory from pyomo.solver.base import LegacySolverInterface @@ -20,10 +21,10 @@ def decorator(cls): self._cls[name] = cls self._doc[name] = doc - # class LegacySolver(LegacySolverInterface, cls): - # pass + class LegacySolver(LegacySolverInterface, cls): + pass - # LegacySolverFactory.register(name, doc)(LegacySolver) + LegacySolverFactory.register(name, doc)(LegacySolver) return cls From 5e78aa162fff7f7ca82fcd98364fb89c774cd82a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 21 Nov 2023 16:13:08 -0700 Subject: [PATCH 0465/1204] Certify backwards compatibility --- pyomo/solver/IPOPT.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index 74b0ae25358..55c97687b05 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -19,8 +19,9 @@ from pyomo.common.config import ConfigValue, NonNegativeInt from pyomo.common.errors import PyomoException from pyomo.common.tempfiles import TempfileManager +from pyomo.core.base.label import NumericLabeler from pyomo.repn.plugins.nl_writer import NLWriter, NLWriterInfo -from pyomo.solver.base import SolverBase +from pyomo.solver.base import SolverBase, SymbolMap from pyomo.solver.config import SolverConfig from pyomo.solver.factory import SolverFactory from pyomo.solver.results import ( @@ -153,7 +154,8 @@ class IPOPT(SolverBase): def __init__(self, **kwds): self._config = self.CONFIG(kwds) - self.ipopt_options = ipopt_command_line_options + self._writer = NLWriter() + self.ipopt_options = self._config.solver_options def available(self): if self.config.executable.path() is None: @@ -181,6 +183,10 @@ def config(self): def config(self, val): self._config = val + @property + def symbol_map(self): + return self._symbol_map + def _write_options_file(self, ostream: io.TextIOBase, options: Mapping): f = ostream for k, val in options.items(): @@ -199,10 +205,10 @@ def _create_command_line(self, basename: str, config: IPOPTConfig): 'Use IPOPT.config.temp_dir to specify the name of the options file. ' 'Do not use IPOPT.config.solver_options["option_file_name"].' ) - ipopt_options = dict(config.solver_options) - if config.time_limit is not None and 'max_cpu_time' not in ipopt_options: - ipopt_options['max_cpu_time'] = config.time_limit - for k, v in ipopt_options.items(): + self.ipopt_options = dict(config.solver_options) + if config.time_limit is not None and 'max_cpu_time' not in self.ipopt_options: + self.ipopt_options['max_cpu_time'] = config.time_limit + for k, v in self.ipopt_options.items(): cmd.append(str(k) + '=' + str(v)) return cmd @@ -223,8 +229,6 @@ def solve(self, model, **kwds): None, (env.get('AMPLFUNC', None), env.get('PYOMO_AMPLFUNC', None)) ) ) - # Write the model to an nl file - nl_writer = NLWriter() # Need to add check for symbolic_solver_labels; may need to generate up # to three files for nl, row, col, if ssl == True # What we have here may or may not work with IPOPT; will find out when @@ -244,13 +248,19 @@ def solve(self, model, **kwds): with open(basename + '.nl', 'w') as nl_file, open( basename + '.row', 'w' ) as row_file, open(basename + '.col', 'w') as col_file: - self.info = nl_writer.write( + self.info = self._writer.write( model, nl_file, row_file, col_file, symbolic_solver_labels=config.symbolic_solver_labels, ) + symbol_map = self._symbol_map = SymbolMap() + labeler = NumericLabeler('component') + for v in self.info.variables: + symbol_map.getSymbol(v, labeler) + for c in self.info.constraints: + symbol_map.getSymbol(c, labeler) with open(basename + '.opt', 'w') as opt_file: self._write_options_file( ostream=opt_file, options=config.solver_options From 5ff4d1428e5a61dd5a6e1d992232b34436af5487 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Wed, 22 Nov 2023 17:17:29 -0700 Subject: [PATCH 0466/1204] making requested changes --- .../model_debugging/latex_printing.rst | 24 +- pyomo/util/latex_printer.py | 276 ++--- pyomo/util/tests/test_latex_printer.py | 278 +---- .../util/tests/test_latex_printer_vartypes.py | 950 ++++++++++-------- 4 files changed, 602 insertions(+), 926 deletions(-) diff --git a/doc/OnlineDocs/model_debugging/latex_printing.rst b/doc/OnlineDocs/model_debugging/latex_printing.rst index 0654344ca2f..db5c766f100 100644 --- a/doc/OnlineDocs/model_debugging/latex_printing.rst +++ b/doc/OnlineDocs/model_debugging/latex_printing.rst @@ -3,9 +3,9 @@ Latex Printing Pyomo models can be printed to a LaTeX compatible format using the ``pyomo.util.latex_printer`` function: -.. py:function:: latex_printer(pyomo_component, latex_component_map=None, write_object=None, use_equation_environment=False, split_continuous_sets=False, use_short_descriptors=False, fontsize = None, paper_dimensions=None) +.. py:function:: latex_printer(pyomo_component, latex_component_map=None, write_object=None, use_equation_environment=False, explicit_set_summation=False, use_short_descriptors=False, fontsize = None, paper_dimensions=None) - Prints a pyomo element (Block, Model, Objective, Constraint, or Expression) to a LaTeX compatible string + Prints a pyomo component (Block, Model, Objective, Constraint, or Expression) to a LaTeX compatible string :param pyomo_component: The Pyomo component to be printed :type pyomo_component: _BlockData or Model or Objective or Constraint or Expression @@ -13,16 +13,10 @@ Pyomo models can be printed to a LaTeX compatible format using the ``pyomo.util. :type latex_component_map: pyomo.common.collections.component_map.ComponentMap :param write_object: The object to print the latex string to. Can be an open file object, string I/O object, or a string for a filename to write to :type write_object: io.TextIOWrapper or io.StringIO or str - :param use_equation_environment: If False, the equation/aligned construction is used to create a single LaTeX equation. If True, then the align environment is used in LaTeX and each constraint and objective will be given an individual equation number + :param use_equation_environment: LaTeX can render as either a single equation object or as an aligned environment, that in essence treats each objective and constraint as individual numbered equations. If False, then the align environment is used in LaTeX and each constraint and objective will be given an individual equation number. If True, the equation/aligned construction is used to create a single LaTeX equation for the entire model. The align environment (ie, flag==False which is the default) is preferred because it allows for page breaks in large models. :type use_equation_environment: bool - :param split_continuous_sets: If False, all sums will be done over 'index in set' or similar. If True, sums will be done over 'i=1' to 'N' or similar if the set is a continuous set - :type split_continuous_sets: bool - :param use_short_descriptors: If False, will print full 'minimize' and 'subject to' etc. If true, uses 'min' and 's.t.' instead - :type use_short_descriptors: bool - :param fontsize: Sets the font size of the latex output when writing to a file. Can take in any of the latex font size keywords ['tiny', 'scriptsize', 'footnotesize', 'small', 'normalsize', 'large', 'Large', 'LARGE', 'huge', 'Huge'], or an integer referenced off of 'normalsize' (ex: small is -1, Large is +2) - :type fontsize: str or int - :param paper_dimensions: A dictionary that controls the paper margins and size. Keys are: [ 'height', 'width', 'margin_left', 'margin_right', 'margin_top', 'margin_bottom' ]. Default is standard 8.5x11 with one inch margins. Values are in inches - :type paper_dimensions: dict + :param explicit_set_summation: If False, all sums will be done over 'index in set' or similar. If True, sums that have a contiguous set (ex: [1,2,3,4,5...]) will be done over 'i=1' to 'N' or similar + :type explicit_set_summation: bool :param throw_templatization_error: Option to throw an error on templatization failure rather than printing each constraint individually, useful for very large models :type throw_templatization_error: bool @@ -76,8 +70,8 @@ A Constraint >>> pstr = latex_printer(m.constraint_1) -A Constraint with a Set -+++++++++++++++++++++++ +A Constraint with Set Summation ++++++++++++++++++++++++++++++++ .. doctest:: @@ -93,8 +87,8 @@ A Constraint with a Set >>> pstr = latex_printer(m.constraint) -Using a ComponentMap -++++++++++++++++++++ +Using a ComponentMap to Specify Names ++++++++++++++++++++++++++++++++++++++ .. doctest:: diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index 750caf36b60..f93f3151418 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -62,6 +62,7 @@ from pyomo.core.base.block import IndexedBlock from pyomo.core.base.external import _PythonCallbackFunctionID +from pyomo.core.base.enums import SortComponents from pyomo.core.base.block import _BlockData @@ -73,6 +74,8 @@ _MONOMIAL = ExprType.MONOMIAL _GENERAL = ExprType.GENERAL +from pyomo.common.errors import InfeasibleConstraintException + from pyomo.common.dependencies import numpy, numpy_available if numpy_available: @@ -122,39 +125,8 @@ def indexCorrector(ixs, base): def alphabetStringGenerator(num, indexMode=False): - if indexMode: - alphabet = ['.', 'i', 'j', 'k', 'm', 'n', 'p', 'q', 'r'] + alphabet = ['.', 'i', 'j', 'k', 'm', 'n', 'p', 'q', 'r'] - else: - alphabet = [ - '.', - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i', - 'j', - 'k', - 'l', - 'm', - 'n', - 'o', - 'p', - 'q', - 'r', - 's', - 't', - 'u', - 'v', - 'w', - 'x', - 'y', - 'z', - ] ixs = decoder(num + 1, len(alphabet) - 1) pstr = '' ixs = indexCorrector(ixs, len(alphabet) - 1) @@ -304,7 +276,8 @@ def handle_exprif_node(visitor, node, arg1, arg2, arg3): def handle_external_function_node(visitor, node, *args): pstr = '' - pstr += 'f(' + visitor.externalFunctionCounter += 1 + pstr += 'f\\_' + str(visitor.externalFunctionCounter) + '(' for i in range(0, len(args) - 1): pstr += args[i] if i <= len(args) - 3: @@ -332,7 +305,7 @@ def handle_indexTemplate_node(visitor, node, *args): ) -def handle_numericGIE_node(visitor, node, *args): +def handle_numericGetItemExpression_node(visitor, node, *args): joinedName = args[0] pstr = '' @@ -367,20 +340,6 @@ def handle_str_node(visitor, node): return node.replace('_', '\\_') -def handle_npv_numericGetItemExpression_node(visitor, node, *args): - joinedName = args[0] - - pstr = '' - pstr += joinedName + '_{' - for i in range(1, len(args)): - pstr += args[i] - if i <= len(args) - 2: - pstr += ',' - else: - pstr += '}' - return pstr - - def handle_npv_structuralGetItemExpression_node(visitor, node, *args): joinedName = args[0] @@ -406,6 +365,7 @@ def handle_numericGetAttrExpression_node(visitor, node, *args): class _LatexVisitor(StreamBasedExpressionVisitor): def __init__(self): super().__init__() + self.externalFunctionCounter = 0 self._operator_handles = { ScalarVar: handle_var_node, @@ -436,12 +396,12 @@ def __init__(self): MonomialTermExpression: handle_monomialTermExpression_node, IndexedVar: handle_var_node, IndexTemplate: handle_indexTemplate_node, - Numeric_GetItemExpression: handle_numericGIE_node, + Numeric_GetItemExpression: handle_numericGetItemExpression_node, TemplateSumExpression: handle_templateSumExpression_node, ScalarParam: handle_param_node, _ParamData: handle_param_node, IndexedParam: handle_param_node, - NPV_Numeric_GetItemExpression: handle_npv_numericGetItemExpression_node, + NPV_Numeric_GetItemExpression: handle_numericGetItemExpression_node, IndexedBlock: handle_indexedBlock_node, NPV_Structural_GetItemExpression: handle_npv_structuralGetItemExpression_node, str: handle_str_node, @@ -474,7 +434,7 @@ def analyze_variable(vr): 'NonPositiveIntegers': '\\mathds{Z}_{\\leq 0}', 'NegativeIntegers': '\\mathds{Z}_{< 0}', 'NonNegativeIntegers': '\\mathds{Z}_{\\geq 0}', - 'Boolean': '\\left\\{ 0 , 1 \\right \\}', + 'Boolean': '\\left\\{ \\text{True} , \\text{False} \\right \\}', 'Binary': '\\left\\{ 0 , 1 \\right \\}', # 'Any': None, # 'AnyWithNone': None, @@ -502,17 +462,14 @@ def analyze_variable(vr): upperBound = '' elif domainName in ['PositiveReals', 'PositiveIntegers']: - # if lowerBoundValue is not None: if lowerBoundValue > 0: lowerBound = str(lowerBoundValue) + ' \\leq ' else: lowerBound = ' 0 < ' - # else: - # lowerBound = ' 0 < ' if upperBoundValue is not None: if upperBoundValue <= 0: - raise ValueError( + raise InfeasibleConstraintException( 'Formulation is infeasible due to bounds on variable %s' % (vr.name) ) else: @@ -523,7 +480,7 @@ def analyze_variable(vr): elif domainName in ['NonPositiveReals', 'NonPositiveIntegers']: if lowerBoundValue is not None: if lowerBoundValue > 0: - raise ValueError( + raise InfeasibleConstraintException( 'Formulation is infeasible due to bounds on variable %s' % (vr.name) ) elif lowerBoundValue == 0: @@ -533,18 +490,15 @@ def analyze_variable(vr): else: lowerBound = '' - # if upperBoundValue is not None: if upperBoundValue >= 0: upperBound = ' \\leq 0 ' else: upperBound = ' \\leq ' + str(upperBoundValue) - # else: - # upperBound = ' \\leq 0 ' elif domainName in ['NegativeReals', 'NegativeIntegers']: if lowerBoundValue is not None: if lowerBoundValue >= 0: - raise ValueError( + raise InfeasibleConstraintException( 'Formulation is infeasible due to bounds on variable %s' % (vr.name) ) else: @@ -552,26 +506,20 @@ def analyze_variable(vr): else: lowerBound = '' - # if upperBoundValue is not None: if upperBoundValue >= 0: upperBound = ' < 0 ' else: upperBound = ' \\leq ' + str(upperBoundValue) - # else: - # upperBound = ' < 0 ' elif domainName in ['NonNegativeReals', 'NonNegativeIntegers']: - # if lowerBoundValue is not None: if lowerBoundValue > 0: lowerBound = str(lowerBoundValue) + ' \\leq ' else: lowerBound = ' 0 \\leq ' - # else: - # lowerBound = ' 0 \\leq ' if upperBoundValue is not None: if upperBoundValue < 0: - raise ValueError( + raise InfeasibleConstraintException( 'Formulation is infeasible due to bounds on variable %s' % (vr.name) ) elif upperBoundValue == 0: @@ -586,9 +534,8 @@ def analyze_variable(vr): upperBound = '' elif domainName in ['UnitInterval', 'PercentFraction']: - # if lowerBoundValue is not None: if lowerBoundValue > 1: - raise ValueError( + raise InfeasibleConstraintException( 'Formulation is infeasible due to bounds on variable %s' % (vr.name) ) elif lowerBoundValue == 1: @@ -597,12 +544,9 @@ def analyze_variable(vr): lowerBound = str(lowerBoundValue) + ' \\leq ' else: lowerBound = ' 0 \\leq ' - # else: - # lowerBound = ' 0 \\leq ' - # if upperBoundValue is not None: if upperBoundValue < 0: - raise ValueError( + raise InfeasibleConstraintException( 'Formulation is infeasible due to bounds on variable %s' % (vr.name) ) elif upperBoundValue == 0: @@ -611,8 +555,6 @@ def analyze_variable(vr): upperBound = ' \\leq ' + str(upperBoundValue) else: upperBound = ' \\leq 1 ' - # else: - # upperBound = ' \\leq 1 ' else: raise DeveloperError( @@ -640,10 +582,7 @@ def latex_printer( latex_component_map=None, write_object=None, use_equation_environment=False, - split_continuous_sets=False, - use_short_descriptors=False, - fontsize=None, - paper_dimensions=None, + explicit_set_summation=False, throw_templatization_error=False, ): """This function produces a string that can be rendered as LaTeX @@ -668,28 +607,11 @@ def latex_printer( LaTeX equation. If True, then the align environment is used in LaTeX and each constraint and objective will be given an individual equation number - split_continuous_sets: bool + explicit_set_summation: bool If False, all sums will be done over 'index in set' or similar. If True, sums will be done over 'i=1' to 'N' or similar if the set is a continuous set - use_short_descriptors: bool - If False, will print full 'minimize' and 'subject to' etc. If true, uses - 'min' and 's.t.' instead - - fontsize: str or int - Sets the font size of the latex output when writing to a file. Can take - in any of the latex font size keywords ['tiny', 'scriptsize', - 'footnotesize', 'small', 'normalsize', 'large', 'Large', 'LARGE', huge', - 'Huge'], or an integer referenced off of 'normalsize' (ex: small is -1, - Large is +2) - - paper_dimensions: dict - A dictionary that controls the paper margins and size. Keys are: - [ 'height', 'width', 'margin_left', 'margin_right', 'margin_top', - 'margin_bottom' ]. Default is standard 8.5x11 with one inch margins. - Values are in inches - throw_templatization_error: bool Option to throw an error on templatization failure rather than printing each constraint individually, useful for very large models @@ -708,6 +630,14 @@ def latex_printer( # these objects require a slight modification of behavior # isSingle==False means a model or block + use_short_descriptors = True + + # Cody's backdoor because he got outvoted + if latex_component_map is not None: + if 'use_short_descriptors' in list(latex_component_map.keys()): + if latex_component_map['use_short_descriptors'] == False: + use_short_descriptors = False + if latex_component_map is None: latex_component_map = ComponentMap() existing_components = ComponentSet([]) @@ -716,78 +646,6 @@ def latex_printer( isSingle = False - fontSizes = [ - '\\tiny', - '\\scriptsize', - '\\footnotesize', - '\\small', - '\\normalsize', - '\\large', - '\\Large', - '\\LARGE', - '\\huge', - '\\Huge', - ] - fontSizes_noSlash = [ - 'tiny', - 'scriptsize', - 'footnotesize', - 'small', - 'normalsize', - 'large', - 'Large', - 'LARGE', - 'huge', - 'Huge', - ] - fontsizes_ints = [-4, -3, -2, -1, 0, 1, 2, 3, 4, 5] - - if fontsize is None: - fontsize = '\\normalsize' - - elif fontsize in fontSizes: - # no editing needed - pass - elif fontsize in fontSizes_noSlash: - fontsize = '\\' + fontsize - elif fontsize in fontsizes_ints: - fontsize = fontSizes[fontsizes_ints.index(fontsize)] - else: - raise ValueError('passed an invalid font size option %s' % (fontsize)) - - paper_dimensions_used = {} - paper_dimensions_used['height'] = 11.0 - paper_dimensions_used['width'] = 8.5 - paper_dimensions_used['margin_left'] = 1.0 - paper_dimensions_used['margin_right'] = 1.0 - paper_dimensions_used['margin_top'] = 1.0 - paper_dimensions_used['margin_bottom'] = 1.0 - - if paper_dimensions is not None: - for ky in [ - 'height', - 'width', - 'margin_left', - 'margin_right', - 'margin_top', - 'margin_bottom', - ]: - if ky in paper_dimensions.keys(): - paper_dimensions_used[ky] = paper_dimensions[ky] - - if paper_dimensions_used['height'] >= 225: - raise ValueError('Paper height exceeds maximum dimension of 225') - if paper_dimensions_used['width'] >= 225: - raise ValueError('Paper width exceeds maximum dimension of 225') - if paper_dimensions_used['margin_left'] < 0.0: - raise ValueError('Paper margin_left must be greater than or equal to zero') - if paper_dimensions_used['margin_right'] < 0.0: - raise ValueError('Paper margin_right must be greater than or equal to zero') - if paper_dimensions_used['margin_top'] < 0.0: - raise ValueError('Paper margin_top must be greater than or equal to zero') - if paper_dimensions_used['margin_bottom'] < 0.0: - raise ValueError('Paper margin_bottom must be greater than or equal to zero') - if isinstance(pyomo_component, pyo.Objective): objectives = [pyomo_component] constraints = [] @@ -824,13 +682,19 @@ def latex_printer( objectives = [ obj for obj in pyomo_component.component_data_objects( - pyo.Objective, descend_into=True, active=True + pyo.Objective, + descend_into=True, + active=True, + sort=SortComponents.deterministic, ) ] constraints = [ con for con in pyomo_component.component_objects( - pyo.Constraint, descend_into=True, active=True + pyo.Constraint, + descend_into=True, + active=True, + sort=SortComponents.deterministic, ) ] expressions = [] @@ -875,21 +739,30 @@ def latex_printer( variableList = [ vr for vr in pyomo_component.component_objects( - pyo.Var, descend_into=True, active=True + pyo.Var, + descend_into=True, + active=True, + sort=SortComponents.deterministic, ) ] parameterList = [ pm for pm in pyomo_component.component_objects( - pyo.Param, descend_into=True, active=True + pyo.Param, + descend_into=True, + active=True, + sort=SortComponents.deterministic, ) ] setList = [ st for st in pyomo_component.component_objects( - pyo.Set, descend_into=True, active=True + pyo.Set, + descend_into=True, + active=True, + sort=SortComponents.deterministic, ) ] @@ -913,8 +786,7 @@ def latex_printer( variableMap = ComponentMap() vrIdx = 0 - for i in range(0, len(variableList)): - vr = variableList[i] + for vr in variableList: vrIdx += 1 if isinstance(vr, ScalarVar): variableMap[vr] = 'x_' + str(vrIdx) @@ -931,8 +803,7 @@ def latex_printer( parameterMap = ComponentMap() pmIdx = 0 - for i in range(0, len(parameterList)): - vr = parameterList[i] + for vr in parameterList: pmIdx += 1 if isinstance(vr, ScalarParam): parameterMap[vr] = 'p_' + str(pmIdx) @@ -975,12 +846,13 @@ def latex_printer( except: if throw_templatization_error: raise RuntimeError( - "An objective has been constructed that cannot be templatized" + "An objective named '%s' has been constructed that cannot be templatized" + % (obj.__str__()) ) else: obj_template = obj - if obj.sense == 1: + if obj.sense == pyo.minimize: # or == 1 pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['minimize']) else: pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['maximize']) @@ -1010,7 +882,7 @@ def latex_printer( # The double '& &' renders better for some reason - for i in range(0, len(constraints)): + for i, con in enumerate(constraints): if not isSingle: if i == 0: algn = '& &' @@ -1025,14 +897,14 @@ def latex_printer( tail = '\n' # grab the constraint and templatize - con = constraints[i] try: con_template, indices = templatize_fcn(con) con_template_list = [con_template] except: if throw_templatization_error: raise RuntimeError( - "A constraint has been constructed that cannot be templatized" + "A constraint named '%s' has been constructed that cannot be templatized" + % (con.__str__()) ) else: con_template_list = [c.expr for c in con.values()] @@ -1127,14 +999,11 @@ def latex_printer( 'Variable is not a variable. Should not happen. Contact developers' ) - # print(varBoundData) - # print the accumulated data to the string bstr = '' appendBoundString = False useThreeAlgn = False - for i in range(0, len(varBoundData)): - vbd = varBoundData[i] + for i, vbd in enumerate(varBoundData): if ( vbd['lowerBound'] == '' and vbd['upperBound'] == '' @@ -1244,7 +1113,7 @@ def latex_printer( ] = r'sum_{__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__}' % (ky) # setInfo[ky]['idxRegEx'] = r'__I_PLACEHOLDER_8675309_GROUP_[0-9*]_%s__'%(ky) - if split_continuous_sets: + if explicit_set_summation: for ky, vl in setInfo.items(): st = vl['setObject'] stData = st.data() @@ -1258,7 +1127,7 @@ def latex_printer( # replace the sets for ky, vl in setInfo.items(): # if the set is continuous and the flag has been set - if split_continuous_sets and setInfo[ky]['continuous']: + if explicit_set_summation and setInfo[ky]['continuous']: st = setInfo[ky]['setObject'] stData = st.data() bgn = stData[0] @@ -1320,7 +1189,7 @@ def latex_printer( ln = ln.replace( '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % (vl['indices'][i], ky), - alphabetStringGenerator(indexCounter, True), + alphabetStringGenerator(indexCounter), ) indexCounter += 1 else: @@ -1328,7 +1197,7 @@ def latex_printer( ln = ln.replace( '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % (vl['indices'][i], ky), - alphabetStringGenerator(indexCounter, True), + alphabetStringGenerator(indexCounter), ) indexCounter += 1 @@ -1336,17 +1205,13 @@ def latex_printer( pstr = '\n'.join(latexLines) - vrIdx = 0 new_variableMap = ComponentMap() - for i in range(0, len(variableList)): - vr = variableList[i] - vrIdx += 1 + for i, vr in enumerate(variableList): if isinstance(vr, ScalarVar): new_variableMap[vr] = vr.name elif isinstance(vr, IndexedVar): new_variableMap[vr] = vr.name for sd in vr.index_set().data(): - # vrIdx += 1 sdString = str(sd) if sdString[0] == '(': sdString = sdString[1:] @@ -1358,17 +1223,14 @@ def latex_printer( 'Variable is not a variable. Should not happen. Contact developers' ) - pmIdx = 0 new_parameterMap = ComponentMap() - for i in range(0, len(parameterList)): + for i, pm in enumerate(parameterList): pm = parameterList[i] - pmIdx += 1 if isinstance(pm, ScalarParam): new_parameterMap[pm] = pm.name elif isinstance(pm, IndexedParam): new_parameterMap[pm] = pm.name for sd in pm.index_set().data(): - # pmIdx += 1 sdString = str(sd) if sdString[0] == '(': sdString = sdString[1:] @@ -1451,20 +1313,10 @@ def latex_printer( fstr += '\\usepackage{amsmath} \n' fstr += '\\usepackage{amssymb} \n' fstr += '\\usepackage{dsfont} \n' - fstr += ( - '\\usepackage[paperheight=%.4fin, paperwidth=%.4fin, left=%.4fin, right=%.4fin, top=%.4fin, bottom=%.4fin]{geometry} \n' - % ( - paper_dimensions_used['height'], - paper_dimensions_used['width'], - paper_dimensions_used['margin_left'], - paper_dimensions_used['margin_right'], - paper_dimensions_used['margin_top'], - paper_dimensions_used['margin_bottom'], - ) - ) + fstr += '\\usepackage[paperheight=11in, paperwidth=8.5in, left=1in, right=1in, top=1in, bottom=1in]{geometry} \n' fstr += '\\allowdisplaybreaks \n' fstr += '\\begin{document} \n' - fstr += fontsize + ' \n' + fstr += '\\normalsize \n' fstr += pstr + '\n' fstr += '\\end{document} \n' diff --git a/pyomo/util/tests/test_latex_printer.py b/pyomo/util/tests/test_latex_printer.py index 685e7e2df38..1564291b7a7 100644 --- a/pyomo/util/tests/test_latex_printer.py +++ b/pyomo/util/tests/test_latex_printer.py @@ -227,9 +227,9 @@ def ruleMaker(m): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x + y + z & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} + y^{2} - z^{2} \leq c & \label{con:basicFormulation_constraint_1} \end{align} """ @@ -258,21 +258,13 @@ def ruleMaker(m): ) self.assertEqual('\n' + pstr + '\n', bstr) - def test_latexPrinter_checkAlphabetFunction(self): - from pyomo.util.latex_printer import alphabetStringGenerator - - self.assertEqual('z', alphabetStringGenerator(25)) - self.assertEqual('aa', alphabetStringGenerator(26)) - self.assertEqual('alm', alphabetStringGenerator(1000)) - self.assertEqual('iqni', alphabetStringGenerator(1000, True)) - def test_latexPrinter_objective(self): m = generate_model() pstr = latex_printer(m.objective_1) bstr = dedent( r""" \begin{equation} - & \text{minimize} + & \min & & x + y + z \end{equation} """ @@ -283,7 +275,7 @@ def test_latexPrinter_objective(self): bstr = dedent( r""" \begin{equation} - & \text{maximize} + & \max & & x + y + z \end{equation} """ @@ -420,7 +412,7 @@ def test_latexPrinter_blackBox(self): bstr = dedent( r""" \begin{equation} - x + f(x,y) = 2 + x + f\_1(x,y) = 2 \end{equation} """ ) @@ -492,208 +484,6 @@ def test_latexPrinter_fileWriter(self): ValueError, latex_printer, **{'pyomo_component': m, 'write_object': 2.0} ) - def test_latexPrinter_fontSizes_1(self): - m = generate_simple_model() - strio = io.StringIO('') - tsh = latex_printer(m, write_object=strio, fontsize='\\normalsize') - strio.seek(0) - pstr = strio.read() - - bstr = dedent( - r""" - \documentclass{article} - \usepackage{amsmath} - \usepackage{amssymb} - \usepackage{dsfont} - \usepackage[paperheight=11.0000in, paperwidth=8.5000in, left=1.0000in, right=1.0000in, top=1.0000in, bottom=1.0000in]{geometry} - \allowdisplaybreaks - \begin{document} - \normalsize - \begin{align} - & \text{minimize} - & & x + y & \label{obj:basicFormulation_objective_1} \\ - & \text{subject to} - & & x^{2} + y^{2} \leq 1 & \label{con:basicFormulation_constraint_1} \\ - &&& 0 \leq x & \label{con:basicFormulation_constraint_2} \\ - &&& \left( x + y \right) \sum_{ i \in I } v_{i} + u_{i,j}^{2} \leq 0 & \qquad \forall j \in I \label{con:basicFormulation_constraint_7} \\ - &&& \sum_{ i \in K } p_{i} = 1 & \label{con:basicFormulation_constraint_8} - \end{align} - \end{document} - """ - ) - strio.close() - self.assertEqual('\n' + pstr, bstr) - - def test_latexPrinter_fontSizes_2(self): - m = generate_simple_model() - strio = io.StringIO('') - tsh = latex_printer(m, write_object=strio, fontsize='normalsize') - strio.seek(0) - pstr = strio.read() - - bstr = dedent( - r""" - \documentclass{article} - \usepackage{amsmath} - \usepackage{amssymb} - \usepackage{dsfont} - \usepackage[paperheight=11.0000in, paperwidth=8.5000in, left=1.0000in, right=1.0000in, top=1.0000in, bottom=1.0000in]{geometry} - \allowdisplaybreaks - \begin{document} - \normalsize - \begin{align} - & \text{minimize} - & & x + y & \label{obj:basicFormulation_objective_1} \\ - & \text{subject to} - & & x^{2} + y^{2} \leq 1 & \label{con:basicFormulation_constraint_1} \\ - &&& 0 \leq x & \label{con:basicFormulation_constraint_2} \\ - &&& \left( x + y \right) \sum_{ i \in I } v_{i} + u_{i,j}^{2} \leq 0 & \qquad \forall j \in I \label{con:basicFormulation_constraint_7} \\ - &&& \sum_{ i \in K } p_{i} = 1 & \label{con:basicFormulation_constraint_8} - \end{align} - \end{document} - """ - ) - strio.close() - self.assertEqual('\n' + pstr, bstr) - - def test_latexPrinter_fontSizes_3(self): - m = generate_simple_model() - strio = io.StringIO('') - tsh = latex_printer(m, write_object=strio, fontsize=0) - strio.seek(0) - pstr = strio.read() - - bstr = dedent( - r""" - \documentclass{article} - \usepackage{amsmath} - \usepackage{amssymb} - \usepackage{dsfont} - \usepackage[paperheight=11.0000in, paperwidth=8.5000in, left=1.0000in, right=1.0000in, top=1.0000in, bottom=1.0000in]{geometry} - \allowdisplaybreaks - \begin{document} - \normalsize - \begin{align} - & \text{minimize} - & & x + y & \label{obj:basicFormulation_objective_1} \\ - & \text{subject to} - & & x^{2} + y^{2} \leq 1 & \label{con:basicFormulation_constraint_1} \\ - &&& 0 \leq x & \label{con:basicFormulation_constraint_2} \\ - &&& \left( x + y \right) \sum_{ i \in I } v_{i} + u_{i,j}^{2} \leq 0 & \qquad \forall j \in I \label{con:basicFormulation_constraint_7} \\ - &&& \sum_{ i \in K } p_{i} = 1 & \label{con:basicFormulation_constraint_8} - \end{align} - \end{document} - """ - ) - strio.close() - self.assertEqual('\n' + pstr, bstr) - - def test_latexPrinter_fontSizes_4(self): - m = generate_simple_model() - strio = io.StringIO('') - self.assertRaises( - ValueError, - latex_printer, - **{'pyomo_component': m, 'write_object': strio, 'fontsize': -10} - ) - strio.close() - - def test_latexPrinter_paperDims(self): - m = generate_simple_model() - strio = io.StringIO('') - pdms = {} - pdms['height'] = 13.0 - pdms['width'] = 10.5 - pdms['margin_left'] = 2.0 - pdms['margin_right'] = 2.0 - pdms['margin_top'] = 2.0 - pdms['margin_bottom'] = 2.0 - tsh = latex_printer(m, write_object=strio, paper_dimensions=pdms) - strio.seek(0) - pstr = strio.read() - - bstr = dedent( - r""" - \documentclass{article} - \usepackage{amsmath} - \usepackage{amssymb} - \usepackage{dsfont} - \usepackage[paperheight=13.0000in, paperwidth=10.5000in, left=2.0000in, right=2.0000in, top=2.0000in, bottom=2.0000in]{geometry} - \allowdisplaybreaks - \begin{document} - \normalsize - \begin{align} - & \text{minimize} - & & x + y & \label{obj:basicFormulation_objective_1} \\ - & \text{subject to} - & & x^{2} + y^{2} \leq 1 & \label{con:basicFormulation_constraint_1} \\ - &&& 0 \leq x & \label{con:basicFormulation_constraint_2} \\ - &&& \left( x + y \right) \sum_{ i \in I } v_{i} + u_{i,j}^{2} \leq 0 & \qquad \forall j \in I \label{con:basicFormulation_constraint_7} \\ - &&& \sum_{ i \in K } p_{i} = 1 & \label{con:basicFormulation_constraint_8} - \end{align} - \end{document} - """ - ) - strio.close() - self.assertEqual('\n' + pstr, bstr) - - strio = io.StringIO('') - self.assertRaises( - ValueError, - latex_printer, - **{ - 'pyomo_component': m, - 'write_object': strio, - 'paper_dimensions': {'height': 230}, - } - ) - self.assertRaises( - ValueError, - latex_printer, - **{ - 'pyomo_component': m, - 'write_object': strio, - 'paper_dimensions': {'width': 230}, - } - ) - self.assertRaises( - ValueError, - latex_printer, - **{ - 'pyomo_component': m, - 'write_object': strio, - 'paper_dimensions': {'margin_left': -1}, - } - ) - self.assertRaises( - ValueError, - latex_printer, - **{ - 'pyomo_component': m, - 'write_object': strio, - 'paper_dimensions': {'margin_right': -1}, - } - ) - self.assertRaises( - ValueError, - latex_printer, - **{ - 'pyomo_component': m, - 'write_object': strio, - 'paper_dimensions': {'margin_top': -1}, - } - ) - self.assertRaises( - ValueError, - latex_printer, - **{ - 'pyomo_component': m, - 'write_object': strio, - 'paper_dimensions': {'margin_bottom': -1}, - } - ) - strio.close() - def test_latexPrinter_overwriteError(self): m = pyo.ConcreteModel(name='basicFormulation') m.I = pyo.Set(initialize=[1, 2, 3, 4, 5]) @@ -733,9 +523,9 @@ def ruleMaker_2(m): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & \sum_{ i \in I } \sum_{ j \in I } c_{i,j} x_{i,j} & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & \sum_{ i \in I } \sum_{ j \in I } x_{i,j}^{2} \leq 1 & \label{con:basicFormulation_constraint_1} \end{align} """ @@ -759,19 +549,19 @@ def test_latexPrinter_involvedModel(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x + y + z & \label{obj:basicFormulation_objective_1} \\ - & \text{minimize} + & \min & & \left( x + y \right) \sum_{ i \in J } w_{i} & \label{obj:basicFormulation_objective_2} \\ - & \text{maximize} + & \max & & x + y + z & \label{obj:basicFormulation_objective_3} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} + y^{-2} - x y z + 1 = 2 & \label{con:basicFormulation_constraint_1} \\ &&& \left| \frac{x}{z^{-2}} \right| \left( x + y \right) \leq 2 & \label{con:basicFormulation_constraint_2} \\ &&& \sqrt { \frac{x}{z^{-2}} } \leq 2 & \label{con:basicFormulation_constraint_3} \\ &&& 1 \leq x \leq 2 & \label{con:basicFormulation_constraint_4} \\ &&& f_{\text{exprIf}}(x \leq 1,z,y) \leq 1 & \label{con:basicFormulation_constraint_5} \\ - &&& x + f(x,y) = 2 & \label{con:basicFormulation_constraint_6} \\ + &&& x + f\_1(x,y) = 2 & \label{con:basicFormulation_constraint_6} \\ &&& \left( x + y \right) \sum_{ i \in I } v_{i} + u_{i,j}^{2} \leq 0 & \qquad \forall j \in I \label{con:basicFormulation_constraint_7} \\ &&& \sum_{ i \in K } p_{i} = 1 & \label{con:basicFormulation_constraint_8} \end{align} @@ -789,7 +579,7 @@ def ruleMaker(m): return sum(m.v[i] for i in m.I) <= 0 m.constraint = pyo.Constraint(rule=ruleMaker) - pstr = latex_printer(m.constraint, split_continuous_sets=True) + pstr = latex_printer(m.constraint, explicit_set_summation=True) bstr = dedent( r""" @@ -809,7 +599,7 @@ def ruleMaker(m): return sum(m.v[i] for i in m.I) <= 0 m.constraint = pyo.Constraint(rule=ruleMaker) - pstr = latex_printer(m.constraint, split_continuous_sets=True) + pstr = latex_printer(m.constraint, explicit_set_summation=True) bstr = dedent( r""" @@ -856,9 +646,9 @@ def test_latexPrinter_equationEnvironment(self): r""" \begin{equation} \begin{aligned} - & \text{minimize} + & \min & & x + y + z \\ - & \text{subject to} + & \text{s.t.} & & x^{2} + y^{2} - z^{2} \leq c \end{aligned} \label{basicFormulation} @@ -881,9 +671,9 @@ def test_latexPrinter_manyVariablesWithDomains(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x + y + z + u + v + w & \label{obj:basicFormulation_objective} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x \leq 10 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} \\ &&& y & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_y_bound} \\ &&& 0 < z \leq 10 & \qquad \in \mathds{R}_{> 0} \label{con:basicFormulation_z_bound} \\ @@ -911,9 +701,9 @@ def test_latexPrinter_manyVariablesWithDomains_eqn(self): r""" \begin{equation} \begin{aligned} - & \text{minimize} + & \min & & x + y + z + u + v + w \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x \leq 10 \qquad \in \mathds{Z}\\ &&& y \qquad \in \left\{ 0 , 1 \right \}\\ &&& 0 < z \leq 10 \qquad \in \mathds{R}_{> 0}\\ @@ -928,28 +718,6 @@ def test_latexPrinter_manyVariablesWithDomains_eqn(self): self.assertEqual("\n" + pstr + "\n", bstr) - def test_latexPrinter_shortDescriptors(self): - m = pyo.ConcreteModel(name='basicFormulation') - m.x = pyo.Var() - m.y = pyo.Var() - m.z = pyo.Var() - m.c = pyo.Param(initialize=1.0, mutable=True) - m.objective = pyo.Objective(expr=m.x + m.y + m.z) - m.constraint_1 = pyo.Constraint(expr=m.x**2 + m.y**2.0 - m.z**2.0 <= m.c) - pstr = latex_printer(m, use_short_descriptors=True) - - bstr = dedent( - r""" - \begin{align} - & \min - & & x + y + z & \label{obj:basicFormulation_objective} \\ - & \text{s.t.} - & & x^{2} + y^{2} - z^{2} \leq c & \label{con:basicFormulation_constraint_1} - \end{align} - """ - ) - self.assertEqual('\n' + pstr + '\n', bstr) - def test_latexPrinter_indexedParamSingle(self): m = pyo.ConcreteModel(name='basicFormulation') m.I = pyo.Set(initialize=[1, 2, 3, 4, 5]) @@ -1003,14 +771,14 @@ def ruleMaker_2(m, i): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & \sum_{ i \in I } c_{i} x_{i} & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x[2] \leq 1 & \label{con:basicFormulation_constraint_1} \\ & & x[3] \leq 1 & \label{con:basicFormulation_constraint_1} \\ & & x[4] \leq 1 & \label{con:basicFormulation_constraint_1} \\ & & x[5] \leq 1 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x \leq 10 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ diff --git a/pyomo/util/tests/test_latex_printer_vartypes.py b/pyomo/util/tests/test_latex_printer_vartypes.py index df1641e1db1..4ff6d7cf699 100644 --- a/pyomo/util/tests/test_latex_printer_vartypes.py +++ b/pyomo/util/tests/test_latex_printer_vartypes.py @@ -38,6 +38,8 @@ # IntegerInterval, ) +from pyomo.common.errors import InfeasibleConstraintException + class TestLatexPrinterVariableTypes(unittest.TestCase): def test_latexPrinter_variableType_Reals_1(self): @@ -50,9 +52,9 @@ def test_latexPrinter_variableType_Reals_1(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \end{align} """ @@ -70,9 +72,9 @@ def test_latexPrinter_variableType_Reals_2(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \end{align} """ @@ -90,11 +92,11 @@ def test_latexPrinter_variableType_Reals_3(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x \leq 10 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -112,11 +114,11 @@ def test_latexPrinter_variableType_Reals_4(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x \leq 0 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -134,11 +136,11 @@ def test_latexPrinter_variableType_Reals_5(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x \leq -2 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -156,11 +158,11 @@ def test_latexPrinter_variableType_Reals_6(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 0 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -178,11 +180,11 @@ def test_latexPrinter_variableType_Reals_7(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 10 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -200,11 +202,11 @@ def test_latexPrinter_variableType_Reals_8(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 2 \leq x \leq 10 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -222,11 +224,11 @@ def test_latexPrinter_variableType_Reals_9(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -244,11 +246,11 @@ def test_latexPrinter_variableType_Reals_10(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 1 \leq x \leq 10 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -266,11 +268,11 @@ def test_latexPrinter_variableType_Reals_11(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0.25 \leq x \leq 0.75 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -288,11 +290,11 @@ def test_latexPrinter_variableType_PositiveReals_1(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 < x & \qquad \in \mathds{R}_{> 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -310,11 +312,11 @@ def test_latexPrinter_variableType_PositiveReals_2(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 < x & \qquad \in \mathds{R}_{> 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -332,11 +334,11 @@ def test_latexPrinter_variableType_PositiveReals_3(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 < x \leq 10 & \qquad \in \mathds{R}_{> 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -349,21 +351,27 @@ def test_latexPrinter_variableType_PositiveReals_4(self): m.x = pyo.Var(domain=PositiveReals, bounds=(-10, 0)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_PositiveReals_5(self): m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var(domain=PositiveReals, bounds=(-10, -2)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_PositiveReals_6(self): m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var(domain=PositiveReals, bounds=(0, 0)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_PositiveReals_7(self): m = pyo.ConcreteModel(name='basicFormulation') @@ -375,11 +383,11 @@ def test_latexPrinter_variableType_PositiveReals_7(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 < x \leq 10 & \qquad \in \mathds{R}_{> 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -397,11 +405,11 @@ def test_latexPrinter_variableType_PositiveReals_8(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 2 \leq x \leq 10 & \qquad \in \mathds{R}_{> 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -419,11 +427,11 @@ def test_latexPrinter_variableType_PositiveReals_9(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 < x \leq 1 & \qquad \in \mathds{R}_{> 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -441,11 +449,11 @@ def test_latexPrinter_variableType_PositiveReals_10(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 1 \leq x \leq 10 & \qquad \in \mathds{R}_{> 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -463,11 +471,11 @@ def test_latexPrinter_variableType_PositiveReals_11(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0.25 \leq x \leq 0.75 & \qquad \in \mathds{R}_{> 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -485,11 +493,11 @@ def test_latexPrinter_variableType_NonPositiveReals_1(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x \leq 0 & \qquad \in \mathds{R}_{\leq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -507,11 +515,11 @@ def test_latexPrinter_variableType_NonPositiveReals_2(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x \leq 0 & \qquad \in \mathds{R}_{\leq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -529,11 +537,11 @@ def test_latexPrinter_variableType_NonPositiveReals_3(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x \leq 0 & \qquad \in \mathds{R}_{\leq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -551,11 +559,11 @@ def test_latexPrinter_variableType_NonPositiveReals_4(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x \leq 0 & \qquad \in \mathds{R}_{\leq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -573,11 +581,11 @@ def test_latexPrinter_variableType_NonPositiveReals_5(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x \leq -2 & \qquad \in \mathds{R}_{\leq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -595,11 +603,11 @@ def test_latexPrinter_variableType_NonPositiveReals_6(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 = x \leq 0 & \qquad \in \mathds{R}_{\leq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -617,11 +625,11 @@ def test_latexPrinter_variableType_NonPositiveReals_7(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 = x \leq 0 & \qquad \in \mathds{R}_{\leq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -634,7 +642,9 @@ def test_latexPrinter_variableType_NonPositiveReals_8(self): m.x = pyo.Var(domain=NonPositiveReals, bounds=(2, 10)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NonPositiveReals_9(self): m = pyo.ConcreteModel(name='basicFormulation') @@ -646,11 +656,11 @@ def test_latexPrinter_variableType_NonPositiveReals_9(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 = x \leq 0 & \qquad \in \mathds{R}_{\leq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -663,14 +673,18 @@ def test_latexPrinter_variableType_NonPositiveReals_10(self): m.x = pyo.Var(domain=NonPositiveReals, bounds=(1, 10)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NonPositiveReals_11(self): m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var(domain=NonPositiveReals, bounds=(0.25, 0.75)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NegativeReals_1(self): m = pyo.ConcreteModel(name='basicFormulation') @@ -682,11 +696,11 @@ def test_latexPrinter_variableType_NegativeReals_1(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x < 0 & \qquad \in \mathds{R}_{< 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -704,11 +718,11 @@ def test_latexPrinter_variableType_NegativeReals_2(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x < 0 & \qquad \in \mathds{R}_{< 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -726,11 +740,11 @@ def test_latexPrinter_variableType_NegativeReals_3(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x < 0 & \qquad \in \mathds{R}_{< 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -748,11 +762,11 @@ def test_latexPrinter_variableType_NegativeReals_4(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x < 0 & \qquad \in \mathds{R}_{< 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -770,11 +784,11 @@ def test_latexPrinter_variableType_NegativeReals_5(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x \leq -2 & \qquad \in \mathds{R}_{< 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -787,42 +801,54 @@ def test_latexPrinter_variableType_NegativeReals_6(self): m.x = pyo.Var(domain=NegativeReals, bounds=(0, 0)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NegativeReals_7(self): m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var(domain=NegativeReals, bounds=(0, 10)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NegativeReals_8(self): m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var(domain=NegativeReals, bounds=(2, 10)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NegativeReals_9(self): m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var(domain=NegativeReals, bounds=(0, 1)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NegativeReals_10(self): m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var(domain=NegativeReals, bounds=(1, 10)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NegativeReals_11(self): m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var(domain=NegativeReals, bounds=(0.25, 0.75)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NonNegativeReals_1(self): m = pyo.ConcreteModel(name='basicFormulation') @@ -834,11 +860,11 @@ def test_latexPrinter_variableType_NonNegativeReals_1(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -856,11 +882,11 @@ def test_latexPrinter_variableType_NonNegativeReals_2(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -878,11 +904,11 @@ def test_latexPrinter_variableType_NonNegativeReals_3(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 10 & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -900,11 +926,11 @@ def test_latexPrinter_variableType_NonNegativeReals_4(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x = 0 & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -917,7 +943,9 @@ def test_latexPrinter_variableType_NonNegativeReals_5(self): m.x = pyo.Var(domain=NonNegativeReals, bounds=(-10, -2)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NonNegativeReals_6(self): m = pyo.ConcreteModel(name='basicFormulation') @@ -929,11 +957,11 @@ def test_latexPrinter_variableType_NonNegativeReals_6(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x = 0 & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -951,11 +979,11 @@ def test_latexPrinter_variableType_NonNegativeReals_7(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 10 & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -973,11 +1001,11 @@ def test_latexPrinter_variableType_NonNegativeReals_8(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 2 \leq x \leq 10 & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -995,11 +1023,11 @@ def test_latexPrinter_variableType_NonNegativeReals_9(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 1 & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1017,11 +1045,11 @@ def test_latexPrinter_variableType_NonNegativeReals_10(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 1 \leq x \leq 10 & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1039,11 +1067,11 @@ def test_latexPrinter_variableType_NonNegativeReals_11(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0.25 \leq x \leq 0.75 & \qquad \in \mathds{R}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1061,11 +1089,11 @@ def test_latexPrinter_variableType_Integers_1(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1083,11 +1111,11 @@ def test_latexPrinter_variableType_Integers_2(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1105,11 +1133,11 @@ def test_latexPrinter_variableType_Integers_3(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x \leq 10 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1127,11 +1155,11 @@ def test_latexPrinter_variableType_Integers_4(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x \leq 0 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1149,11 +1177,11 @@ def test_latexPrinter_variableType_Integers_5(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x \leq -2 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1171,11 +1199,11 @@ def test_latexPrinter_variableType_Integers_6(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 0 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1193,11 +1221,11 @@ def test_latexPrinter_variableType_Integers_7(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 10 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1215,11 +1243,11 @@ def test_latexPrinter_variableType_Integers_8(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 2 \leq x \leq 10 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1237,11 +1265,11 @@ def test_latexPrinter_variableType_Integers_9(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 1 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1259,11 +1287,11 @@ def test_latexPrinter_variableType_Integers_10(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 1 \leq x \leq 10 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1281,11 +1309,11 @@ def test_latexPrinter_variableType_Integers_11(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0.25 \leq x \leq 0.75 & \qquad \in \mathds{Z} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1303,11 +1331,11 @@ def test_latexPrinter_variableType_PositiveIntegers_1(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 1 \leq x & \qquad \in \mathds{Z}_{> 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1325,11 +1353,11 @@ def test_latexPrinter_variableType_PositiveIntegers_2(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 1 \leq x & \qquad \in \mathds{Z}_{> 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1347,11 +1375,11 @@ def test_latexPrinter_variableType_PositiveIntegers_3(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 1 \leq x \leq 10 & \qquad \in \mathds{Z}_{> 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1364,21 +1392,27 @@ def test_latexPrinter_variableType_PositiveIntegers_4(self): m.x = pyo.Var(domain=PositiveIntegers, bounds=(-10, 0)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_PositiveIntegers_5(self): m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var(domain=PositiveIntegers, bounds=(-10, -2)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_PositiveIntegers_6(self): m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var(domain=PositiveIntegers, bounds=(0, 0)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_PositiveIntegers_7(self): m = pyo.ConcreteModel(name='basicFormulation') @@ -1390,11 +1424,11 @@ def test_latexPrinter_variableType_PositiveIntegers_7(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 1 \leq x \leq 10 & \qquad \in \mathds{Z}_{> 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1412,11 +1446,11 @@ def test_latexPrinter_variableType_PositiveIntegers_8(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 2 \leq x \leq 10 & \qquad \in \mathds{Z}_{> 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1434,11 +1468,11 @@ def test_latexPrinter_variableType_PositiveIntegers_9(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 1 \leq x \leq 1 & \qquad \in \mathds{Z}_{> 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1456,11 +1490,11 @@ def test_latexPrinter_variableType_PositiveIntegers_10(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 1 \leq x \leq 10 & \qquad \in \mathds{Z}_{> 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1478,11 +1512,11 @@ def test_latexPrinter_variableType_PositiveIntegers_11(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 1 \leq x \leq 0.75 & \qquad \in \mathds{Z}_{> 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1500,11 +1534,11 @@ def test_latexPrinter_variableType_NonPositiveIntegers_1(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x \leq 0 & \qquad \in \mathds{Z}_{\leq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1522,11 +1556,11 @@ def test_latexPrinter_variableType_NonPositiveIntegers_2(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x \leq 0 & \qquad \in \mathds{Z}_{\leq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1544,11 +1578,11 @@ def test_latexPrinter_variableType_NonPositiveIntegers_3(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x \leq 0 & \qquad \in \mathds{Z}_{\leq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1566,11 +1600,11 @@ def test_latexPrinter_variableType_NonPositiveIntegers_4(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x \leq 0 & \qquad \in \mathds{Z}_{\leq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1588,11 +1622,11 @@ def test_latexPrinter_variableType_NonPositiveIntegers_5(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x \leq -2 & \qquad \in \mathds{Z}_{\leq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1610,11 +1644,11 @@ def test_latexPrinter_variableType_NonPositiveIntegers_6(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 = x \leq 0 & \qquad \in \mathds{Z}_{\leq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1632,11 +1666,11 @@ def test_latexPrinter_variableType_NonPositiveIntegers_7(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 = x \leq 0 & \qquad \in \mathds{Z}_{\leq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1649,7 +1683,9 @@ def test_latexPrinter_variableType_NonPositiveIntegers_8(self): m.x = pyo.Var(domain=NonPositiveIntegers, bounds=(2, 10)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NonPositiveIntegers_9(self): m = pyo.ConcreteModel(name='basicFormulation') @@ -1661,11 +1697,11 @@ def test_latexPrinter_variableType_NonPositiveIntegers_9(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 = x \leq 0 & \qquad \in \mathds{Z}_{\leq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1678,14 +1714,18 @@ def test_latexPrinter_variableType_NonPositiveIntegers_10(self): m.x = pyo.Var(domain=NonPositiveIntegers, bounds=(1, 10)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NonPositiveIntegers_11(self): m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var(domain=NonPositiveIntegers, bounds=(0.25, 0.75)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NegativeIntegers_1(self): m = pyo.ConcreteModel(name='basicFormulation') @@ -1697,11 +1737,11 @@ def test_latexPrinter_variableType_NegativeIntegers_1(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x \leq -1 & \qquad \in \mathds{Z}_{< 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1719,11 +1759,11 @@ def test_latexPrinter_variableType_NegativeIntegers_2(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x \leq -1 & \qquad \in \mathds{Z}_{< 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1741,11 +1781,11 @@ def test_latexPrinter_variableType_NegativeIntegers_3(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x \leq -1 & \qquad \in \mathds{Z}_{< 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1763,11 +1803,11 @@ def test_latexPrinter_variableType_NegativeIntegers_4(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x \leq -1 & \qquad \in \mathds{Z}_{< 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1785,11 +1825,11 @@ def test_latexPrinter_variableType_NegativeIntegers_5(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & -10 \leq x \leq -2 & \qquad \in \mathds{Z}_{< 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1802,42 +1842,54 @@ def test_latexPrinter_variableType_NegativeIntegers_6(self): m.x = pyo.Var(domain=NegativeIntegers, bounds=(0, 0)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NegativeIntegers_7(self): m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var(domain=NegativeIntegers, bounds=(0, 10)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NegativeIntegers_8(self): m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var(domain=NegativeIntegers, bounds=(2, 10)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NegativeIntegers_9(self): m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var(domain=NegativeIntegers, bounds=(0, 1)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NegativeIntegers_10(self): m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var(domain=NegativeIntegers, bounds=(1, 10)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NegativeIntegers_11(self): m = pyo.ConcreteModel(name='basicFormulation') m.x = pyo.Var(domain=NegativeIntegers, bounds=(0.25, 0.75)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NonNegativeIntegers_1(self): m = pyo.ConcreteModel(name='basicFormulation') @@ -1849,11 +1901,11 @@ def test_latexPrinter_variableType_NonNegativeIntegers_1(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1871,11 +1923,11 @@ def test_latexPrinter_variableType_NonNegativeIntegers_2(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1893,11 +1945,11 @@ def test_latexPrinter_variableType_NonNegativeIntegers_3(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 10 & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1915,11 +1967,11 @@ def test_latexPrinter_variableType_NonNegativeIntegers_4(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x = 0 & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1932,7 +1984,9 @@ def test_latexPrinter_variableType_NonNegativeIntegers_5(self): m.x = pyo.Var(domain=NonNegativeIntegers, bounds=(-10, -2)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_NonNegativeIntegers_6(self): m = pyo.ConcreteModel(name='basicFormulation') @@ -1944,11 +1998,11 @@ def test_latexPrinter_variableType_NonNegativeIntegers_6(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x = 0 & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1966,11 +2020,11 @@ def test_latexPrinter_variableType_NonNegativeIntegers_7(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 10 & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -1988,11 +2042,11 @@ def test_latexPrinter_variableType_NonNegativeIntegers_8(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 2 \leq x \leq 10 & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2010,11 +2064,11 @@ def test_latexPrinter_variableType_NonNegativeIntegers_9(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 1 & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2032,11 +2086,11 @@ def test_latexPrinter_variableType_NonNegativeIntegers_10(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 1 \leq x \leq 10 & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2054,11 +2108,11 @@ def test_latexPrinter_variableType_NonNegativeIntegers_11(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0.25 \leq x \leq 0.75 & \qquad \in \mathds{Z}_{\geq 0} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2076,12 +2130,12 @@ def test_latexPrinter_variableType_Boolean_1(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} - & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + & \text{w.b.} + & & x & \qquad \in \left\{ \text{True} , \text{False} \right \} \label{con:basicFormulation_x_bound} \end{align} """ ) @@ -2098,12 +2152,12 @@ def test_latexPrinter_variableType_Boolean_2(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} - & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + & \text{w.b.} + & & x & \qquad \in \left\{ \text{True} , \text{False} \right \} \label{con:basicFormulation_x_bound} \end{align} """ ) @@ -2120,12 +2174,12 @@ def test_latexPrinter_variableType_Boolean_3(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} - & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + & \text{w.b.} + & & x & \qquad \in \left\{ \text{True} , \text{False} \right \} \label{con:basicFormulation_x_bound} \end{align} """ ) @@ -2142,12 +2196,12 @@ def test_latexPrinter_variableType_Boolean_4(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} - & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + & \text{w.b.} + & & x & \qquad \in \left\{ \text{True} , \text{False} \right \} \label{con:basicFormulation_x_bound} \end{align} """ ) @@ -2164,12 +2218,12 @@ def test_latexPrinter_variableType_Boolean_5(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} - & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + & \text{w.b.} + & & x & \qquad \in \left\{ \text{True} , \text{False} \right \} \label{con:basicFormulation_x_bound} \end{align} """ ) @@ -2186,12 +2240,12 @@ def test_latexPrinter_variableType_Boolean_6(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} - & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + & \text{w.b.} + & & x & \qquad \in \left\{ \text{True} , \text{False} \right \} \label{con:basicFormulation_x_bound} \end{align} """ ) @@ -2208,12 +2262,12 @@ def test_latexPrinter_variableType_Boolean_7(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} - & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + & \text{w.b.} + & & x & \qquad \in \left\{ \text{True} , \text{False} \right \} \label{con:basicFormulation_x_bound} \end{align} """ ) @@ -2230,12 +2284,12 @@ def test_latexPrinter_variableType_Boolean_8(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} - & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + & \text{w.b.} + & & x & \qquad \in \left\{ \text{True} , \text{False} \right \} \label{con:basicFormulation_x_bound} \end{align} """ ) @@ -2252,12 +2306,12 @@ def test_latexPrinter_variableType_Boolean_9(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} - & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + & \text{w.b.} + & & x & \qquad \in \left\{ \text{True} , \text{False} \right \} \label{con:basicFormulation_x_bound} \end{align} """ ) @@ -2274,12 +2328,12 @@ def test_latexPrinter_variableType_Boolean_10(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} - & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + & \text{w.b.} + & & x & \qquad \in \left\{ \text{True} , \text{False} \right \} \label{con:basicFormulation_x_bound} \end{align} """ ) @@ -2296,12 +2350,12 @@ def test_latexPrinter_variableType_Boolean_11(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} - & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} + & \text{w.b.} + & & x & \qquad \in \left\{ \text{True} , \text{False} \right \} \label{con:basicFormulation_x_bound} \end{align} """ ) @@ -2318,11 +2372,11 @@ def test_latexPrinter_variableType_Binary_1(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2340,11 +2394,11 @@ def test_latexPrinter_variableType_Binary_2(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2362,11 +2416,11 @@ def test_latexPrinter_variableType_Binary_3(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2384,11 +2438,11 @@ def test_latexPrinter_variableType_Binary_4(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2406,11 +2460,11 @@ def test_latexPrinter_variableType_Binary_5(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2428,11 +2482,11 @@ def test_latexPrinter_variableType_Binary_6(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2450,11 +2504,11 @@ def test_latexPrinter_variableType_Binary_7(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2472,11 +2526,11 @@ def test_latexPrinter_variableType_Binary_8(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2494,11 +2548,11 @@ def test_latexPrinter_variableType_Binary_9(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2516,11 +2570,11 @@ def test_latexPrinter_variableType_Binary_10(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2538,11 +2592,11 @@ def test_latexPrinter_variableType_Binary_11(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \left\{ 0 , 1 \right \} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2560,11 +2614,11 @@ def test_latexPrinter_variableType_EmptySet_1(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} \end{align} """ @@ -2582,11 +2636,11 @@ def test_latexPrinter_variableType_EmptySet_2(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} \end{align} """ @@ -2604,11 +2658,11 @@ def test_latexPrinter_variableType_EmptySet_3(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} \end{align} """ @@ -2626,11 +2680,11 @@ def test_latexPrinter_variableType_EmptySet_4(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} \end{align} """ @@ -2648,11 +2702,11 @@ def test_latexPrinter_variableType_EmptySet_5(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} \end{align} """ @@ -2670,11 +2724,11 @@ def test_latexPrinter_variableType_EmptySet_6(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} \end{align} """ @@ -2692,11 +2746,11 @@ def test_latexPrinter_variableType_EmptySet_7(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} \end{align} """ @@ -2714,11 +2768,11 @@ def test_latexPrinter_variableType_EmptySet_8(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} \end{align} """ @@ -2736,11 +2790,11 @@ def test_latexPrinter_variableType_EmptySet_9(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} \end{align} """ @@ -2758,11 +2812,11 @@ def test_latexPrinter_variableType_EmptySet_10(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} \end{align} """ @@ -2780,11 +2834,11 @@ def test_latexPrinter_variableType_EmptySet_11(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & x & \qquad \in \varnothing \label{con:basicFormulation_x_bound} \end{align} """ @@ -2802,11 +2856,11 @@ def test_latexPrinter_variableType_UnitInterval_1(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2824,11 +2878,11 @@ def test_latexPrinter_variableType_UnitInterval_2(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2846,11 +2900,11 @@ def test_latexPrinter_variableType_UnitInterval_3(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2868,11 +2922,11 @@ def test_latexPrinter_variableType_UnitInterval_4(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x = 0 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2885,7 +2939,9 @@ def test_latexPrinter_variableType_UnitInterval_5(self): m.x = pyo.Var(domain=UnitInterval, bounds=(-10, -2)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_UnitInterval_6(self): m = pyo.ConcreteModel(name='basicFormulation') @@ -2897,11 +2953,11 @@ def test_latexPrinter_variableType_UnitInterval_6(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x = 0 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2919,11 +2975,11 @@ def test_latexPrinter_variableType_UnitInterval_7(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2936,7 +2992,9 @@ def test_latexPrinter_variableType_UnitInterval_8(self): m.x = pyo.Var(domain=UnitInterval, bounds=(2, 10)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_UnitInterval_9(self): m = pyo.ConcreteModel(name='basicFormulation') @@ -2948,11 +3006,11 @@ def test_latexPrinter_variableType_UnitInterval_9(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2970,11 +3028,11 @@ def test_latexPrinter_variableType_UnitInterval_10(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & = 1 x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -2992,11 +3050,11 @@ def test_latexPrinter_variableType_UnitInterval_11(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0.25 \leq x \leq 0.75 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -3014,11 +3072,11 @@ def test_latexPrinter_variableType_PercentFraction_1(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -3036,11 +3094,11 @@ def test_latexPrinter_variableType_PercentFraction_2(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -3058,11 +3116,11 @@ def test_latexPrinter_variableType_PercentFraction_3(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -3080,11 +3138,11 @@ def test_latexPrinter_variableType_PercentFraction_4(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x = 0 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -3097,7 +3155,9 @@ def test_latexPrinter_variableType_PercentFraction_5(self): m.x = pyo.Var(domain=PercentFraction, bounds=(-10, -2)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_PercentFraction_6(self): m = pyo.ConcreteModel(name='basicFormulation') @@ -3109,11 +3169,11 @@ def test_latexPrinter_variableType_PercentFraction_6(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x = 0 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -3131,11 +3191,11 @@ def test_latexPrinter_variableType_PercentFraction_7(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -3148,7 +3208,9 @@ def test_latexPrinter_variableType_PercentFraction_8(self): m.x = pyo.Var(domain=PercentFraction, bounds=(2, 10)) m.objective = pyo.Objective(expr=m.x) m.constraint_1 = pyo.Constraint(expr=m.x**2 <= 5.0) - self.assertRaises(ValueError, latex_printer, **{'pyomo_component': m}) + self.assertRaises( + InfeasibleConstraintException, latex_printer, **{'pyomo_component': m} + ) def test_latexPrinter_variableType_PercentFraction_9(self): m = pyo.ConcreteModel(name='basicFormulation') @@ -3160,11 +3222,11 @@ def test_latexPrinter_variableType_PercentFraction_9(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0 \leq x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -3182,11 +3244,11 @@ def test_latexPrinter_variableType_PercentFraction_10(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & = 1 x \leq 1 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ @@ -3204,11 +3266,11 @@ def test_latexPrinter_variableType_PercentFraction_11(self): bstr = dedent( r""" \begin{align} - & \text{minimize} + & \min & & x & \label{obj:basicFormulation_objective} \\ - & \text{subject to} + & \text{s.t.} & & x^{2} \leq 5 & \label{con:basicFormulation_constraint_1} \\ - & \text{with bounds} + & \text{w.b.} & & 0.25 \leq x \leq 0.75 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} \end{align} """ From e458b58c35b6958c2cf8e099cea5181867056c5e Mon Sep 17 00:00:00 2001 From: robbybp Date: Fri, 24 Nov 2023 12:40:05 -0700 Subject: [PATCH 0467/1204] add some context in hidden code blocks so doctests pass --- .../incidence_analysis/dulmage_mendelsohn.py | 11 ++++++++++- pyomo/contrib/incidence_analysis/interface.py | 13 ++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py b/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py index d3a460446e6..5a1b125c0ae 100644 --- a/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py +++ b/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py @@ -77,7 +77,16 @@ def dulmage_mendelsohn(matrix_or_graph, top_nodes=None, matching=None): For example: .. doctest:: - :skipif: True + :hide: + :skipif: not (networkx_available and scipy_available) + + >>> # Hidden code block to make the following example runnable + >>> import scipy.sparse as sps + >>> from pyomo.contrib.incidence_analysis.dulmage_mendelsohn import dulmage_mendelsohn + >>> matrix = sps.identity(3) + + .. doctest:: + :skipif: not (networkx_available and scipy_available) >>> row_dmpartition, col_dmpartition = dulmage_mendelsohn(matrix) >>> rdmp = row_dmpartition diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 3b2c54f8a60..7670d3a1fae 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -750,7 +750,18 @@ def dulmage_mendelsohn(self, variables=None, constraints=None): pairs in this maximum matching. For example: .. doctest:: - :skipif: True + :hide: + :skipif: not (networkx_available and scipy_available) + + >>> # Hidden code block creating a dummy model so the following doctest runs + >>> import pyomo.environ as pyo + >>> from pyomo.contrib.incidence_analysis import IncidenceGraphInterface + >>> model = pyo.ConcreteModel() + >>> model.x = pyo.Var([1,2,3]) + >>> model.eq = pyo.Constraint(expr=sum(m.x[:]) == 1) + + .. doctest:: + :skipif: not (networkx_available and scipy_available) >>> igraph = IncidenceGraphInterface(model) >>> var_dmpartition, con_dmpartition = igraph.dulmage_mendelsohn() From e634fded5cd9894ad376677cfdda71f073681f8a Mon Sep 17 00:00:00 2001 From: robbybp Date: Fri, 24 Nov 2023 13:16:32 -0700 Subject: [PATCH 0468/1204] fix typo in doctest --- pyomo/contrib/incidence_analysis/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 7670d3a1fae..e922551c6a4 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -770,7 +770,7 @@ def dulmage_mendelsohn(self, variables=None, constraints=None): >>> matching = list(zip( ... vdmp.underconstrained + vdmp.square + vdmp.overconstrained, ... cdmp.underconstrained + cdmp.square + cdmp.overconstrained, - >>> )) + ... )) >>> # matching is a valid maximum matching of variables and constraints! Returns From bae88522907a423fcca0c47f8bc6f22b2d63cbf4 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 26 Nov 2023 15:45:10 -0700 Subject: [PATCH 0469/1204] NFC: clean up docstring --- pyomo/repn/plugins/nl_writer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 8dfaf0d7f42..6282dc95ce5 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -165,9 +165,9 @@ class NLWriterInfo(object): eliminated_vars: List[Tuple[_VarData, NumericExpression]] The list of variables in the model that were eliminated by the - presolve. each entry is a 2-tuple of (:py:class:`_VarData`, - :py:class`NumericExpression`|`float`). the list is ordered in - the necessary order for correct evaluation (i.e., all variables + presolve. Each entry is a 2-tuple of (:py:class:`_VarData`, + :py:class`NumericExpression`|`float`). The list is in the + necessary order for correct evaluation (i.e., all variables appearing in the expression must either have been sent to the solver, or appear *earlier* in this list. From 8262bd5430654ea4f8bef3454f1a0106fab70371 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 26 Nov 2023 15:47:02 -0700 Subject: [PATCH 0470/1204] Initial implementation and tests for generating standard linear forms --- pyomo/repn/plugins/__init__.py | 1 + pyomo/repn/plugins/standard_form.py | 540 +++++++++++++++++++++++++ pyomo/repn/tests/test_standard_form.py | 267 ++++++++++++ 3 files changed, 808 insertions(+) create mode 100644 pyomo/repn/plugins/standard_form.py create mode 100644 pyomo/repn/tests/test_standard_form.py diff --git a/pyomo/repn/plugins/__init__.py b/pyomo/repn/plugins/__init__.py index f1e8270b8c7..56b221d3129 100644 --- a/pyomo/repn/plugins/__init__.py +++ b/pyomo/repn/plugins/__init__.py @@ -18,6 +18,7 @@ def load(): import pyomo.repn.plugins.gams_writer import pyomo.repn.plugins.lp_writer import pyomo.repn.plugins.nl_writer + import pyomo.repn.plugins.standard_form from pyomo.opt import WriterFactory diff --git a/pyomo/repn/plugins/standard_form.py b/pyomo/repn/plugins/standard_form.py new file mode 100644 index 00000000000..6e74faca7d1 --- /dev/null +++ b/pyomo/repn/plugins/standard_form.py @@ -0,0 +1,540 @@ +# ___________________________________________________________________________ +# +# 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 collections +import logging +from operator import attrgetter, neg + +from pyomo.common.config import ( + ConfigBlock, + ConfigValue, + InEnum, + document_kwargs_from_configdict, +) +from pyomo.common.dependencies import scipy, numpy as np +from pyomo.common.gc_manager import PauseGC +from pyomo.common.timing import TicTocTimer + +from pyomo.core.base import ( + Block, + Objective, + Constraint, + Var, + Param, + Expression, + SOSConstraint, + SortComponents, + Suffix, + SymbolMap, + minimize, +) +from pyomo.core.base.component import ActiveComponent +from pyomo.core.base.label import LPFileLabeler, NumericLabeler +from pyomo.opt import WriterFactory +from pyomo.repn.linear import LinearRepnVisitor +from pyomo.repn.quadratic import QuadraticRepnVisitor +from pyomo.repn.util import ( + FileDeterminism, + FileDeterminism_to_SortComponents, + categorize_valid_components, + initialize_var_map_from_column_order, + int_float, + ordered_active_constraints, +) + +### FIXME: Remove the following as soon as non-active components no +### longer report active==True +from pyomo.core.base import Set, RangeSet, ExternalFunction +from pyomo.network import Port + +logger = logging.getLogger(__name__) +inf = float('inf') +neg_inf = float('-inf') + + +RowEntry = collections.namedtuple('RowEntry', ['constraint', 'bound_type']) + + +# TODO: make a proper base class +class LinearStandardFormInfo(object): + """Return type for LinearStandardFormCompiler.write() + + Attributes + ---------- + c : scipy.sparse.csr_array + + The objective coefficients. Note that this is a sparse array + and may contain multiple rows (for multiobjective problems). The + objectives may be calculated by "c @ x" + + A : scipy.sparse.csc_array + + The constraint coefficients. The constraint bodies may be + calculated by "A @ x" + + rhs : numpy.ndarray + + The constraint right-hand sides. + + rows : List[Tuple[_ConstraintData, int]] + + The list of Pyomo constraint objects corresponding to the rows + in `A`. Each element in the list is a 2-tuple of + (_ConstraintData, row_multiplier). The `row_multiplier` will be + +/- 1 (indicating if the row was multiplied by -1 (corresponding + to a constraint lower bound or +1 (upper bound). + + columns : List[_VarData] + + The list of Pyomo variable objects corresponding to columns in + the `A` and `c` matricies. + + eliminated_vars: List[Tuple[_VarData, NumericExpression]] + + The list of variables from the original model that do not appear + in the standard form (usually because they were replaced by + nonnegative variables). Each entry is a 2-tuple of + (:py:class:`_VarData`, :py:class`NumericExpression`|`float`). + The list is in the necessary order for correct evaluation (i.e., + all variables appearing in the expression must either have + appeared in the standard form, or appear *earlier* in this list. + + """ + + def __init__(self, c, A, rhs, rows, columns, eliminated_vars): + self.c = c + self.A = A + self.rhs = rhs + self.rows = rows + self.columns = columns + self.eliminated_vars = eliminated_vars + + @property + def x(self): + return self.columns + + @property + def b(self): + return self.rhs + + +@WriterFactory.register( + 'compile_standard_form', 'Compile an LP to standard form (`min cTx s.t. Ax <= b`)' +) +class LinearStandardFormCompiler(object): + CONFIG = ConfigBlock('compile_standard_form') + CONFIG.declare( + 'nonnegative_vars', + ConfigValue( + default=False, + domain=bool, + description='Convert all variables to be nonnegative variables', + ), + ) + CONFIG.declare( + 'slack_form', + ConfigValue( + default=False, + domain=bool, + description='Add slack variables and return `min cTx s.t. Ax == b`', + ), + ) + CONFIG.declare( + 'show_section_timing', + ConfigValue( + default=False, + domain=bool, + description='Print timing after writing each section of the LP file', + ), + ) + CONFIG.declare( + 'file_determinism', + ConfigValue( + default=FileDeterminism.ORDERED, + domain=InEnum(FileDeterminism), + description='How much effort to ensure result is deterministic', + doc=""" + How much effort do we want to put into ensuring the + resulting matricies are produced deterministically: + NONE (0) : None + ORDERED (10): rely on underlying component ordering (default) + SORT_INDICES (20) : sort keys of indexed components + SORT_SYMBOLS (30) : sort keys AND sort names (not declaration order) + """, + ), + ) + CONFIG.declare( + 'row_order', + ConfigValue( + default=None, + description='Preferred constraint ordering', + doc=""" + List of constraints in the order that they should appear in the + LP file. Unspecified constraints will appear at the end.""", + ), + ) + CONFIG.declare( + 'column_order', + ConfigValue( + default=None, + description='Preferred variable ordering', + doc=""" + List of variables in the order that they should appear in + the LP file. Note that this is only a suggestion, as the LP + file format is row-major and the columns are inferred from + the order in which variables appear in the objective + followed by each constraint.""", + ), + ) + + def __init__(self): + self.config = self.CONFIG() + + @document_kwargs_from_configdict(CONFIG) + def write(self, model, ostream=None, **options): + """Convert a model to standard form (`min cTx s.t. Ax <= b`) + + Returns + ------- + LinearStandardFormInfo + + Parameters + ---------- + model: ConcreteModel + The concrete Pyomo model to write out. + + ostream: None + This is provided for API compatibility with other writers + and is ignored here. + + """ + config = self.config(options) + + # Pause the GC, as the walker that generates the compiled LP + # representation generates (and disposes of) a large number of + # small objects. + with PauseGC(): + return _LinearStandardFormCompiler_impl(config).write(model) + + +class _LinearStandardFormCompiler_impl(object): + def __init__(self, config): + self.config = config + + def write(self, model): + timing_logger = logging.getLogger('pyomo.common.timing.writer') + timer = TicTocTimer(logger=timing_logger) + with_debug_timing = ( + timing_logger.isEnabledFor(logging.DEBUG) and timing_logger.hasHandlers() + ) + + sorter = FileDeterminism_to_SortComponents(self.config.file_determinism) + component_map, unknown = categorize_valid_components( + model, + active=True, + sort=sorter, + valid={ + Block, + Constraint, + Var, + Param, + Expression, + # FIXME: Non-active components should not report as Active + ExternalFunction, + Set, + RangeSet, + Port, + # TODO: Piecewise, Complementarity + }, + targets={Suffix, Objective}, + ) + if unknown: + raise ValueError( + "The model ('%s') contains the following active components " + "that the LP compiler does not know how to process:\n\t%s" + % ( + model.name, + "\n\t".join( + "%s:\n\t\t%s" % (k, "\n\t\t".join(map(attrgetter('name'), v))) + for k, v in unknown.items() + ), + ) + ) + + self.var_map = var_map = {} + initialize_var_map_from_column_order(model, self.config, var_map) + var_order = {_id: i for i, _id in enumerate(var_map)} + + visitor = LinearRepnVisitor({}, var_map, var_order) + + timer.toc('Initialized column order', level=logging.DEBUG) + + # We don't export any suffix information to the Standard Form + # + if component_map[Suffix]: + suffixesByName = {} + for block in component_map[Suffix]: + for suffix in block.component_objects( + Suffix, active=True, descend_into=False, sort=sorter + ): + if not suffix.export_enabled() or not suffix: + continue + name = suffix.local_name + if name in suffixesByName: + suffixesByName[name].append(suffix) + else: + suffixesByName[name] = [suffix] + for name, suffixes in suffixesByName.items(): + n = len(suffixes) + plural = 's' if n > 1 else '' + logger.warning( + f"EXPORT Suffix '{name}' found on {n} block{plural}:\n " + + "\n ".join(s.name for s in suffixes) + + "\nStandard Form compiler ignores export suffixes. Skipping." + ) + + # + # Process objective + # + if not component_map[Objective]: + objectives = [Objective(expr=1)] + objectives[0].construct() + else: + objectives = [] + for blk in component_map[Objective]: + objectives.extend( + blk.component_data_objects( + Objective, active=True, descend_into=False, sort=sorter + ) + ) + obj_data = [] + obj_index = [] + obj_index_ptr = [0] + for i, obj in enumerate(objectives): + repn = visitor.walk_expression(obj.expr) + if repn.nonlinear is not None: + raise ValueError( + f"Model objective ({obj.name}) contains nonlinear terms that " + "cannot be compiled to standard (linear) form." + ) + obj_data.extend(repn.linear.values()) + obj_index.extend(map(var_order.__getitem__, repn.linear)) + obj_index_ptr.append(len(obj_index)) + if with_debug_timing: + timer.toc('Objective %s', obj, level=logging.DEBUG) + + # + # Tabulate constraints + # + slack_form = self.config.slack_form + rows = [] + rhs = [] + con_data = [] + con_index = [] + con_index_ptr = [0] + last_parent = None + for con in ordered_active_constraints(model, self.config): + if with_debug_timing and con.parent_component() is not last_parent: + if last_parent is not None: + timer.toc('Constraint %s', last_parent, level=logging.DEBUG) + last_parent = con.parent_component() + # Note: Constraint.lb/ub guarantee a return value that is + # either a (finite) native_numeric_type, or None + lb = con.lb + ub = con.ub + + repn = visitor.walk_expression(con.body) + + if lb is None and ub is None: + # Note: you *cannot* output trivial (unbounded) + # constraints in matrix format. I suppose we could add a + # slack variable, but that seems rather silly. + continue + if repn.nonlinear is not None: + raise ValueError( + f"Model constraint ({con.name}) contains nonlinear terms that " + "cannot be compiled to standard (linear) form." + ) + + # Pull out the constant: we will move it to the bounds + offset = repn.constant + repn.constant = 0 + + if not repn.linear: + if (lb is None or lb <= offset) and (ub is None or ub >= offset): + continue + raise InfeasibleError( + f"model contains a trivially infeasible constraint, '{con.name}'" + ) + + if slack_form: + _data = list(repn.linear.values()) + _index = list(map(var_order.__getitem__, repn.linear)) + if lb == ub: # TODO: add tolerance? + rhs.append(ub - offset) + else: + # add slack variable + v = Var(name=f'_slack_{len(rhs)}', bounds=(None, None)) + v.construct() + if lb is None: + rhs.append(ub - offset) + v.lb = 0 + else: + rhs.append(lb - offset) + v.ub = 0 + if ub is not None: + v.lb = lb - ub + var_map[id(v)] = v + var_order[id(v)] = slack_col = len(var_order) + _data.append(1) + _index.append(slack_col) + rows.append(RowEntry(con, 1)) + con_data.append(np.array(_data)) + con_index.append(np.array(_index)) + con_index_ptr.append(con_index_ptr[-1] + len(_index)) + else: + if ub is not None: + rows.append(RowEntry(con, 1)) + rhs.append(ub - offset) + con_data.append(np.array(list(repn.linear.values()))) + con_index.append( + np.array(list(map(var_order.__getitem__, repn.linear))) + ) + con_index_ptr.append(con_index_ptr[-1] + len(con_index[-1])) + if lb is not None: + rows.append(RowEntry(con, -1)) + rhs.append(offset - lb) + con_data.append(np.array(list(map(neg, repn.linear.values())))) + con_index.append( + np.array(list(map(var_order.__getitem__, repn.linear))) + ) + con_index_ptr.append(con_index_ptr[-1] + len(con_index[-1])) + + if with_debug_timing: + # report the last constraint + timer.toc('Constraint %s', last_parent, level=logging.DEBUG) + + # Get the variable list + columns = list(var_map.values()) + # Convert the compiled data to scipy sparse matricies + c = scipy.sparse.csr_array( + (obj_data, obj_index, obj_index_ptr), [len(obj_index_ptr) - 1, len(columns)] + ).tocsc() + A = scipy.sparse.csr_array( + (np.concatenate(con_data), np.concatenate(con_index), con_index_ptr), + [len(rows), len(columns)], + ).tocsc() + + # Some variables in the var_map may not actually have been + # written out to the LP file (e.g., added from col_order, or + # multiplied by 0 in the expressions). The easiest way to check + # for empty columns is to convert from CSR to CSC and then look + # at the index pointer list (an O(num_var) operation). + c_ip = c.indptr + A_ip = A.indptr + active_var_idx = list( + filter( + lambda i: A_ip[i] != A_ip[i + 1] or c_ip[i] != c_ip[i + 1], + range(len(columns)), + ) + ) + nCol = len(active_var_idx) + if nCol != len(columns): + # Note that the indptr can't just use range() because a var + # may only appear in the objectives or the constraints. + columns = list(map(columns.__getitem__, active_var_idx)) + active_var_idx.append(c.indptr[-1]) + c = scipy.sparse.csc_array( + (c.data, c.indices, c.indptr.take(active_var_idx)), [c.shape[0], nCol] + ) + active_var_idx[-1] = A.indptr[-1] + A = scipy.sparse.csc_array( + (A.data, A.indices, A.indptr.take(active_var_idx)), [A.shape[0], nCol] + ) + + if self.config.nonnegative_vars: + c, A, columns, eliminated_vars = _csc_to_nonnegative_vars(c, A, columns) + else: + eliminated_vars = [] + + info = LinearStandardFormInfo(c, A, rhs, rows, columns, eliminated_vars) + timer.toc("Generated linear standard form representation", delta=False) + return info + + +def _csc_to_nonnegative_vars(c, A, columns): + eliminated_vars = [] + new_columns = [] + new_c_data = [] + new_c_indices = [] + new_c_indptr = [0] + new_A_data = [] + new_A_indices = [] + new_A_indptr = [0] + for i, v in enumerate(columns): + lb, ub = v.bounds + if lb is None or lb < 0: + name = v.name + new_columns.append( + Var( + name=f'_neg_{i}', + domain=v.domain, + bounds=(0, None if lb is None else -lb), + ) + ) + new_columns[-1].construct() + s, e = A.indptr[i : i + 2] + new_A_data.append(-A.data[s:e]) + new_A_indices.append(A.indices[s:e]) + new_A_indptr.append(new_A_indptr[-1] + e - s) + s, e = c.indptr[i : i + 2] + new_c_data.append(-c.data[s:e]) + new_c_indices.append(c.indices[s:e]) + new_c_indptr.append(new_c_indptr[-1] + e - s) + if ub is None or ub > 0: + # Crosses 0; split into 2 vars + new_columns.append( + Var(name=f'_pos_{i}', domain=v.domain, bounds=(0, ub)) + ) + new_columns[-1].construct() + s, e = A.indptr[i : i + 2] + new_A_data.append(A.data[s:e]) + new_A_indices.append(A.indices[s:e]) + new_A_indptr.append(new_A_indptr[-1] + e - s) + s, e = c.indptr[i : i + 2] + new_c_data.append(c.data[s:e]) + new_c_indices.append(c.indices[s:e]) + new_c_indptr.append(new_c_indptr[-1] + e - s) + eliminated_vars.append((v, new_columns[-1] - new_columns[-2])) + else: + new_columns[-1].lb = -ub + eliminated_vars.append((v, -new_columns[-1])) + else: # lb >= 0 + new_columns.append(v) + s, e = A.indptr[i : i + 2] + new_A_data.append(A.data[s:e]) + new_A_indices.append(A.indices[s:e]) + new_A_indptr.append(new_A_indptr[-1] + e - s) + s, e = c.indptr[i : i + 2] + new_c_data.append(c.data[s:e]) + new_c_indices.append(c.indices[s:e]) + new_c_indptr.append(new_c_indptr[-1] + e - s) + + nCol = len(new_columns) + c = scipy.sparse.csc_array( + (np.concatenate(new_c_data), np.concatenate(new_c_indices), new_c_indptr), + [c.shape[0], nCol], + ) + A = scipy.sparse.csc_array( + (np.concatenate(new_A_data), np.concatenate(new_A_indices), new_A_indptr), + [A.shape[0], nCol], + ) + return c, A, new_columns, eliminated_vars diff --git a/pyomo/repn/tests/test_standard_form.py b/pyomo/repn/tests/test_standard_form.py new file mode 100644 index 00000000000..6cd95f78c5b --- /dev/null +++ b/pyomo/repn/tests/test_standard_form.py @@ -0,0 +1,267 @@ +# ___________________________________________________________________________ +# +# 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.common.unittest as unittest + +import pyomo.environ as pyo + +from pyomo.common.dependencies import numpy as np, scipy_available, numpy_available +from pyomo.common.log import LoggingIntercept +from pyomo.repn.plugins.standard_form import LinearStandardFormCompiler + +for sol in ['glpk', 'cbc', 'gurobi', 'cplex', 'xpress']: + linear_solver = pyo.SolverFactory(sol) + if linear_solver.available(): + break +else: + linear_solver = None + + +@unittest.skipUnless( + scipy_available & numpy_available, "standard_form requires scipy and numpy" +) +class TestLinearStandardFormCompiler(unittest.TestCase): + def test_linear_model(self): + m = pyo.ConcreteModel() + m.x = pyo.Var() + m.y = pyo.Var([1, 2, 3]) + m.c = pyo.Constraint(expr=m.x + 2 * m.y[1] >= 3) + m.d = pyo.Constraint(expr=m.y[1] + 4 * m.y[3] <= 5) + + repn = LinearStandardFormCompiler().write(m) + + self.assertTrue(np.all(repn.c == np.array([0, 0, 0]))) + self.assertTrue(np.all(repn.A == np.array([[-1, -2, 0], [0, 1, 4]]))) + self.assertTrue(np.all(repn.rhs == np.array([-3, 5]))) + + def test_linear_model_row_col_order(self): + m = pyo.ConcreteModel() + m.x = pyo.Var() + m.y = pyo.Var([1, 2, 3]) + m.c = pyo.Constraint(expr=m.x + 2 * m.y[1] >= 3) + m.d = pyo.Constraint(expr=m.y[1] + 4 * m.y[3] <= 5) + + repn = LinearStandardFormCompiler().write( + m, column_order=[m.y[3], m.y[2], m.x, m.y[1]], row_order=[m.d, m.c] + ) + + self.assertTrue(np.all(repn.c == np.array([0, 0, 0]))) + self.assertTrue(np.all(repn.A == np.array([[4, 0, 1], [0, -1, -2]]))) + self.assertTrue(np.all(repn.rhs == np.array([5, -3]))) + + def test_suffix_warning(self): + m = pyo.ConcreteModel() + m.x = pyo.Var() + m.y = pyo.Var([1, 2, 3]) + m.c = pyo.Constraint(expr=m.x + 2 * m.y[1] >= 3) + m.d = pyo.Constraint(expr=m.y[1] + 4 * m.y[3] <= 5) + + m.dual = pyo.Suffix(direction=pyo.Suffix.EXPORT) + m.b = pyo.Block() + m.b.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT_EXPORT) + + with LoggingIntercept() as LOG: + repn = LinearStandardFormCompiler().write(m) + self.assertEqual(LOG.getvalue(), "") + + m.dual[m.c] = 5 + with LoggingIntercept() as LOG: + repn = LinearStandardFormCompiler().write(m) + self.assertEqual( + LOG.getvalue(), + "EXPORT Suffix 'dual' found on 1 block:\n" + " dual\n" + "Standard Form compiler ignores export suffixes. Skipping.\n", + ) + + m.b.dual[m.d] = 1 + with LoggingIntercept() as LOG: + repn = LinearStandardFormCompiler().write(m) + self.assertEqual( + LOG.getvalue(), + "EXPORT Suffix 'dual' found on 2 blocks:\n" + " dual\n" + " b.dual\n" + "Standard Form compiler ignores export suffixes. Skipping.\n", + ) + + def _verify_solution(self, soln, repn, eq): + # clear out any old solution + for v, val in soln: + v.value = None + for v in repn.x: + v.value = None + + x = np.array(repn.x, dtype=object) + ax = repn.A.todense() @ x + + def c_rule(m, i): + if eq: + return ax[i] == repn.b[i] + else: + return ax[i] <= repn.b[i] + + test_model = pyo.ConcreteModel() + test_model.o = pyo.Objective(expr=repn.c[[1], :].todense()[0] @ x) + test_model.c = pyo.Constraint(range(len(repn.b)), rule=c_rule) + pyo.SolverFactory('glpk').solve(test_model, tee=True) + + # Propagate any solution back to the original variables + for v, expr in repn.eliminated_vars: + v.value = pyo.value(expr) + self.assertEqual(*zip(*((v.value, val) for v, val in soln))) + + @unittest.skipIf( + linear_solver is None, 'verifying results requires a linear solver' + ) + def test_alternative_forms(self): + m = pyo.ConcreteModel() + m.x = pyo.Var() + m.y = pyo.Var( + [0, 1, 3], bounds=lambda m, i: (-1 * (i % 2) * 5, 10 - 12 * (i // 2)) + ) + m.c = pyo.Constraint(expr=m.x + 2 * m.y[1] >= 3) + m.d = pyo.Constraint(expr=m.y[1] + 4 * m.y[3] <= 5) + m.e = pyo.Constraint(expr=pyo.inequality(-2, m.y[0] + 1 + 6 * m.y[1], 7)) + m.f = pyo.Constraint(expr=m.x + m.y[0] + 2 == 10) + m.o = pyo.Objective([1, 3], rule=lambda m, i: m.x + i * 5 * m.y[i]) + + col_order = [m.x, m.y[0], m.y[1], m.y[3]] + + m.o[1].deactivate() + pyo.SolverFactory('glpk').solve(m) + m.o[1].activate() + soln = [(v, v.value) for v in col_order] + + repn = LinearStandardFormCompiler().write(m, column_order=col_order) + + self.assertEqual( + repn.rows, [(m.c, -1), (m.d, 1), (m.e, 1), (m.e, -1), (m.f, 1), (m.f, -1)] + ) + self.assertEqual(repn.x, [m.x, m.y[0], m.y[1], m.y[3]]) + ref = np.array( + [ + [-1, 0, -2, 0], + [0, 0, 1, 4], + [0, 1, 6, 0], + [0, -1, -6, 0], + [1, 1, 0, 0], + [-1, -1, 0, 0], + ] + ) + self.assertTrue(np.all(repn.A == ref)) + self.assertTrue(np.all(repn.b == np.array([-3, 5, 6, 3, 8, -8]))) + self.assertTrue(np.all(repn.c == np.array([[1, 0, 5, 0], [1, 0, 0, 15]]))) + self._verify_solution(soln, repn, False) + + repn = LinearStandardFormCompiler().write( + m, nonnegative_vars=True, column_order=col_order + ) + + self.assertEqual( + repn.rows, [(m.c, -1), (m.d, 1), (m.e, 1), (m.e, -1), (m.f, 1), (m.f, -1)] + ) + self.assertEqual( + list(map(str, repn.x)), + ['_neg_0', '_pos_0', 'y[0]', '_neg_2', '_pos_2', '_neg_3'], + ) + ref = np.array( + [ + [1, -1, 0, 2, -2, 0], + [0, 0, 0, -1, 1, -4], + [0, 0, 1, -6, 6, 0], + [0, 0, -1, 6, -6, 0], + [-1, 1, 1, 0, 0, 0], + [1, -1, -1, 0, 0, 0], + ] + ) + self.assertTrue(np.all(repn.A == ref)) + self.assertTrue(np.all(repn.b == np.array([-3, 5, 6, 3, 8, -8]))) + self.assertTrue( + np.all(repn.c == np.array([[-1, 1, 0, -5, 5, 0], [-1, 1, 0, 0, 0, -15]])) + ) + self._verify_solution(soln, repn, False) + + repn = LinearStandardFormCompiler().write( + m, slack_form=True, column_order=col_order + ) + + self.assertEqual(repn.rows, [(m.c, 1), (m.d, 1), (m.e, 1), (m.f, 1)]) + self.assertEqual( + list(map(str, repn.x)), + ['x', 'y[0]', 'y[1]', 'y[3]', '_slack_0', '_slack_1', '_slack_2'], + ) + self.assertEqual( + list(v.bounds for v in repn.x), + [(None, None), (0, 10), (-5, 10), (-5, -2), (None, 0), (0, None), (-9, 0)], + ) + ref = np.array( + [ + [1, 0, 2, 0, 1, 0, 0], + [0, 0, 1, 4, 0, 1, 0], + [0, 1, 6, 0, 0, 0, 1], + [1, 1, 0, 0, 0, 0, 0], + ] + ) + self.assertTrue(np.all(repn.A == ref)) + self.assertTrue(np.all(repn.b == np.array([3, 5, -3, 8]))) + self.assertTrue( + np.all(repn.c == np.array([[1, 0, 5, 0, 0, 0, 0], [1, 0, 0, 15, 0, 0, 0]])) + ) + self._verify_solution(soln, repn, True) + + repn = LinearStandardFormCompiler().write( + m, slack_form=True, nonnegative_vars=True, column_order=col_order + ) + + self.assertEqual(repn.rows, [(m.c, 1), (m.d, 1), (m.e, 1), (m.f, 1)]) + self.assertEqual( + list(map(str, repn.x)), + [ + '_neg_0', + '_pos_0', + 'y[0]', + '_neg_2', + '_pos_2', + '_neg_3', + '_neg_4', + '_slack_1', + '_neg_6', + ], + ) + self.assertEqual( + list(v.bounds for v in repn.x), + [ + (0, None), + (0, None), + (0, 10), + (0, 5), + (0, 10), + (2, 5), + (0, None), + (0, None), + (0, 9), + ], + ) + ref = np.array( + [ + [-1, 1, 0, -2, 2, 0, -1, 0, 0], + [0, 0, 0, -1, 1, -4, 0, 1, 0], + [0, 0, 1, -6, 6, 0, 0, 0, -1], + [-1, 1, 1, 0, 0, 0, 0, 0, 0], + ] + ) + self.assertTrue(np.all(repn.A == ref)) + self.assertTrue(np.all(repn.b == np.array([3, 5, -3, 8]))) + ref = np.array([[-1, 1, 0, -5, 5, 0, 0, 0, 0], [-1, 1, 0, 0, 0, -15, 0, 0, 0]]) + self.assertTrue(np.all(repn.c == ref)) + self._verify_solution(soln, repn, True) From 38df7032d1c521709cb0778417a90b07b594ad31 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 26 Nov 2023 16:42:49 -0700 Subject: [PATCH 0471/1204] cleanup unneeded definitions/imports --- pyomo/repn/plugins/standard_form.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pyomo/repn/plugins/standard_form.py b/pyomo/repn/plugins/standard_form.py index 6e74faca7d1..13d5a005910 100644 --- a/pyomo/repn/plugins/standard_form.py +++ b/pyomo/repn/plugins/standard_form.py @@ -30,23 +30,18 @@ Var, Param, Expression, - SOSConstraint, SortComponents, Suffix, SymbolMap, minimize, ) -from pyomo.core.base.component import ActiveComponent -from pyomo.core.base.label import LPFileLabeler, NumericLabeler from pyomo.opt import WriterFactory from pyomo.repn.linear import LinearRepnVisitor -from pyomo.repn.quadratic import QuadraticRepnVisitor from pyomo.repn.util import ( FileDeterminism, FileDeterminism_to_SortComponents, categorize_valid_components, initialize_var_map_from_column_order, - int_float, ordered_active_constraints, ) @@ -56,9 +51,6 @@ from pyomo.network import Port logger = logging.getLogger(__name__) -inf = float('inf') -neg_inf = float('-inf') - RowEntry = collections.namedtuple('RowEntry', ['constraint', 'bound_type']) From 3eda0b50bce7563f86fcc7c6760b7b7c1ba8c168 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 26 Nov 2023 16:43:32 -0700 Subject: [PATCH 0472/1204] Switch to np.fromiter for performance --- pyomo/repn/plugins/standard_form.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pyomo/repn/plugins/standard_form.py b/pyomo/repn/plugins/standard_form.py index 13d5a005910..9f40199ff84 100644 --- a/pyomo/repn/plugins/standard_form.py +++ b/pyomo/repn/plugins/standard_form.py @@ -393,22 +393,25 @@ def write(self, model): con_index.append(np.array(_index)) con_index_ptr.append(con_index_ptr[-1] + len(_index)) else: + N = len(repn.linear) if ub is not None: rows.append(RowEntry(con, 1)) rhs.append(ub - offset) - con_data.append(np.array(list(repn.linear.values()))) + con_data.append(np.fromiter(repn.linear.values(), float, N)) con_index.append( - np.array(list(map(var_order.__getitem__, repn.linear))) + np.fromiter(map(var_order.__getitem__, repn.linear), float, N) ) - con_index_ptr.append(con_index_ptr[-1] + len(con_index[-1])) + con_index_ptr.append(con_index_ptr[-1] + N) if lb is not None: rows.append(RowEntry(con, -1)) rhs.append(offset - lb) - con_data.append(np.array(list(map(neg, repn.linear.values())))) + con_data.append( + np.fromiter(map(neg, repn.linear.values()), float, N) + ) con_index.append( - np.array(list(map(var_order.__getitem__, repn.linear))) + np.fromiter(map(var_order.__getitem__, repn.linear), float, N) ) - con_index_ptr.append(con_index_ptr[-1] + len(con_index[-1])) + con_index_ptr.append(con_index_ptr[-1] + N) if with_debug_timing: # report the last constraint From 6eb570452e2176ba5214973cbcd96370c2376df3 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 26 Nov 2023 16:44:08 -0700 Subject: [PATCH 0473/1204] Convert maximize to minimize --- pyomo/repn/plugins/standard_form.py | 16 +++++++++++----- pyomo/repn/tests/test_standard_form.py | 11 +++++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/pyomo/repn/plugins/standard_form.py b/pyomo/repn/plugins/standard_form.py index 9f40199ff84..8e5e5dbb942 100644 --- a/pyomo/repn/plugins/standard_form.py +++ b/pyomo/repn/plugins/standard_form.py @@ -33,7 +33,7 @@ SortComponents, Suffix, SymbolMap, - minimize, + maximize, ) from pyomo.opt import WriterFactory from pyomo.repn.linear import LinearRepnVisitor @@ -317,9 +317,14 @@ def write(self, model): f"Model objective ({obj.name}) contains nonlinear terms that " "cannot be compiled to standard (linear) form." ) - obj_data.extend(repn.linear.values()) - obj_index.extend(map(var_order.__getitem__, repn.linear)) - obj_index_ptr.append(len(obj_index)) + N = len(repn.linear) + obj_data.append(np.fromiter(repn.linear.values(), float, N)) + if obj.sense == maximize: + obj_data[-1] *= -1 + obj_index.append( + np.fromiter(map(var_order.__getitem__, repn.linear), float, N) + ) + obj_index_ptr.append(obj_index_ptr[-1] + N) if with_debug_timing: timer.toc('Objective %s', obj, level=logging.DEBUG) @@ -421,7 +426,8 @@ def write(self, model): columns = list(var_map.values()) # Convert the compiled data to scipy sparse matricies c = scipy.sparse.csr_array( - (obj_data, obj_index, obj_index_ptr), [len(obj_index_ptr) - 1, len(columns)] + (np.concatenate(obj_data), np.concatenate(obj_index), obj_index_ptr), + [len(obj_index_ptr) - 1, len(columns)], ).tocsc() A = scipy.sparse.csr_array( (np.concatenate(con_data), np.concatenate(con_index), con_index_ptr), diff --git a/pyomo/repn/tests/test_standard_form.py b/pyomo/repn/tests/test_standard_form.py index 6cd95f78c5b..b805d18b303 100644 --- a/pyomo/repn/tests/test_standard_form.py +++ b/pyomo/repn/tests/test_standard_form.py @@ -134,6 +134,7 @@ def test_alternative_forms(self): m.e = pyo.Constraint(expr=pyo.inequality(-2, m.y[0] + 1 + 6 * m.y[1], 7)) m.f = pyo.Constraint(expr=m.x + m.y[0] + 2 == 10) m.o = pyo.Objective([1, 3], rule=lambda m, i: m.x + i * 5 * m.y[i]) + m.o[1].sense = pyo.maximize col_order = [m.x, m.y[0], m.y[1], m.y[3]] @@ -160,7 +161,7 @@ def test_alternative_forms(self): ) self.assertTrue(np.all(repn.A == ref)) self.assertTrue(np.all(repn.b == np.array([-3, 5, 6, 3, 8, -8]))) - self.assertTrue(np.all(repn.c == np.array([[1, 0, 5, 0], [1, 0, 0, 15]]))) + self.assertTrue(np.all(repn.c == np.array([[-1, 0, -5, 0], [1, 0, 0, 15]]))) self._verify_solution(soln, repn, False) repn = LinearStandardFormCompiler().write( @@ -187,7 +188,7 @@ def test_alternative_forms(self): self.assertTrue(np.all(repn.A == ref)) self.assertTrue(np.all(repn.b == np.array([-3, 5, 6, 3, 8, -8]))) self.assertTrue( - np.all(repn.c == np.array([[-1, 1, 0, -5, 5, 0], [-1, 1, 0, 0, 0, -15]])) + np.all(repn.c == np.array([[1, -1, 0, 5, -5, 0], [-1, 1, 0, 0, 0, -15]])) ) self._verify_solution(soln, repn, False) @@ -215,7 +216,9 @@ def test_alternative_forms(self): self.assertTrue(np.all(repn.A == ref)) self.assertTrue(np.all(repn.b == np.array([3, 5, -3, 8]))) self.assertTrue( - np.all(repn.c == np.array([[1, 0, 5, 0, 0, 0, 0], [1, 0, 0, 15, 0, 0, 0]])) + np.all( + repn.c == np.array([[-1, 0, -5, 0, 0, 0, 0], [1, 0, 0, 15, 0, 0, 0]]) + ) ) self._verify_solution(soln, repn, True) @@ -262,6 +265,6 @@ def test_alternative_forms(self): ) self.assertTrue(np.all(repn.A == ref)) self.assertTrue(np.all(repn.b == np.array([3, 5, -3, 8]))) - ref = np.array([[-1, 1, 0, -5, 5, 0, 0, 0, 0], [-1, 1, 0, 0, 0, -15, 0, 0, 0]]) + ref = np.array([[1, -1, 0, 5, -5, 0, 0, 0, 0], [-1, 1, 0, 0, 0, -15, 0, 0, 0]]) self.assertTrue(np.all(repn.c == ref)) self._verify_solution(soln, repn, True) From f2c7f53a37adb361f6c2a074956a832e5d38ed74 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 26 Nov 2023 16:44:32 -0700 Subject: [PATCH 0474/1204] Fix checks for solver availability --- pyomo/repn/tests/test_standard_form.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pyomo/repn/tests/test_standard_form.py b/pyomo/repn/tests/test_standard_form.py index b805d18b303..d2c096efd79 100644 --- a/pyomo/repn/tests/test_standard_form.py +++ b/pyomo/repn/tests/test_standard_form.py @@ -20,12 +20,11 @@ for sol in ['glpk', 'cbc', 'gurobi', 'cplex', 'xpress']: linear_solver = pyo.SolverFactory(sol) - if linear_solver.available(): + if linear_solver.available(exception_flag=False): break else: linear_solver = None - @unittest.skipUnless( scipy_available & numpy_available, "standard_form requires scipy and numpy" ) @@ -113,7 +112,7 @@ def c_rule(m, i): test_model = pyo.ConcreteModel() test_model.o = pyo.Objective(expr=repn.c[[1], :].todense()[0] @ x) test_model.c = pyo.Constraint(range(len(repn.b)), rule=c_rule) - pyo.SolverFactory('glpk').solve(test_model, tee=True) + linear_solver.solve(test_model, tee=True) # Propagate any solution back to the original variables for v, expr in repn.eliminated_vars: @@ -139,7 +138,7 @@ def test_alternative_forms(self): col_order = [m.x, m.y[0], m.y[1], m.y[3]] m.o[1].deactivate() - pyo.SolverFactory('glpk').solve(m) + linear_solver.solve(m) m.o[1].activate() soln = [(v, v.value) for v in col_order] From 5ac9da1c9fa1dd850bbf56fc1f8ed4a890275266 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 26 Nov 2023 17:12:49 -0700 Subject: [PATCH 0475/1204] NFC: apply black --- pyomo/repn/tests/test_standard_form.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/repn/tests/test_standard_form.py b/pyomo/repn/tests/test_standard_form.py index d2c096efd79..d186f28dab8 100644 --- a/pyomo/repn/tests/test_standard_form.py +++ b/pyomo/repn/tests/test_standard_form.py @@ -25,6 +25,7 @@ else: linear_solver = None + @unittest.skipUnless( scipy_available & numpy_available, "standard_form requires scipy and numpy" ) From ced74d4cbb0e11d29ead70e04079a4412942e20a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 26 Nov 2023 18:42:35 -0700 Subject: [PATCH 0476/1204] simplify construction of numpy vectors --- pyomo/repn/plugins/standard_form.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/pyomo/repn/plugins/standard_form.py b/pyomo/repn/plugins/standard_form.py index 8e5e5dbb942..2b2c81b2ca9 100644 --- a/pyomo/repn/plugins/standard_form.py +++ b/pyomo/repn/plugins/standard_form.py @@ -11,7 +11,7 @@ import collections import logging -from operator import attrgetter, neg +from operator import attrgetter from pyomo.common.config import ( ConfigBlock, @@ -399,23 +399,19 @@ def write(self, model): con_index_ptr.append(con_index_ptr[-1] + len(_index)) else: N = len(repn.linear) + _data = np.fromiter(repn.linear.values(), float, N) + _index = np.fromiter(map(var_order.__getitem__, repn.linear), float, N) if ub is not None: rows.append(RowEntry(con, 1)) rhs.append(ub - offset) - con_data.append(np.fromiter(repn.linear.values(), float, N)) - con_index.append( - np.fromiter(map(var_order.__getitem__, repn.linear), float, N) - ) + con_data.append(_data) + con_index.append(_index) con_index_ptr.append(con_index_ptr[-1] + N) if lb is not None: rows.append(RowEntry(con, -1)) rhs.append(offset - lb) - con_data.append( - np.fromiter(map(neg, repn.linear.values()), float, N) - ) - con_index.append( - np.fromiter(map(var_order.__getitem__, repn.linear), float, N) - ) + con_data.append(-_data) + con_index.append(_index) con_index_ptr.append(con_index_ptr[-1] + N) if with_debug_timing: From 15e85a4df798cc5665425f864072f28c5ffdbec5 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 27 Nov 2023 09:03:24 -0700 Subject: [PATCH 0477/1204] update wntr install for tests --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 69c835f7c34..6faa7f167c9 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -258,7 +258,7 @@ jobs: || echo "WARNING: Gurobi is not available" python -m pip install --cache-dir cache/pip xpress \ || echo "WARNING: Xpress Community Edition is not available" - python -m pip install git+https://github.com/usepa/wntr.git@main \ + python -m pip install wntr \ || echo "WARNING: WNTR is not available" fi python -c 'import sys; print("PYTHON_EXE=%s" \ diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index b771c314ac2..3e5ca2b3110 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -288,7 +288,7 @@ jobs: || echo "WARNING: Gurobi is not available" python -m pip install --cache-dir cache/pip xpress \ || echo "WARNING: Xpress Community Edition is not available" - python -m pip install git+https://github.com/usepa/wntr.git@main \ + python -m pip install wntr \ || echo "WARNING: WNTR is not available" fi python -c 'import sys; print("PYTHON_EXE=%s" \ From dee45eb0caffaff024bf85f97eea3a39139982bf Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Mon, 27 Nov 2023 12:00:25 -0700 Subject: [PATCH 0478/1204] working on requested changes --- doc/OnlineDocs/contributed_packages/index.rst | 1 + .../latex_printer.rst} | 35 ++++---------- pyomo/contrib/latex_printer/Readme.md | 37 +++++++++++++++ pyomo/contrib/latex_printer/__init__.py | 22 +++++++++ .../latex_printer}/latex_printer.py | 46 ++++++------------- .../tests/test_latex_printer.py | 2 +- .../tests/test_latex_printer_vartypes.py | 2 +- 7 files changed, 84 insertions(+), 61 deletions(-) rename doc/OnlineDocs/{model_debugging/latex_printing.rst => contributed_packages/latex_printer.rst} (51%) create mode 100644 pyomo/contrib/latex_printer/Readme.md create mode 100644 pyomo/contrib/latex_printer/__init__.py rename pyomo/{util => contrib/latex_printer}/latex_printer.py (97%) rename pyomo/{util => contrib/latex_printer}/tests/test_latex_printer.py (99%) rename pyomo/{util => contrib/latex_printer}/tests/test_latex_printer_vartypes.py (99%) diff --git a/doc/OnlineDocs/contributed_packages/index.rst b/doc/OnlineDocs/contributed_packages/index.rst index f893753780e..b1d9cbbad3b 100644 --- a/doc/OnlineDocs/contributed_packages/index.rst +++ b/doc/OnlineDocs/contributed_packages/index.rst @@ -20,6 +20,7 @@ Contributed packages distributed with Pyomo: gdpopt.rst iis.rst incidence/index.rst + latex_printer.rst mindtpy.rst mpc/index.rst multistart.rst diff --git a/doc/OnlineDocs/model_debugging/latex_printing.rst b/doc/OnlineDocs/contributed_packages/latex_printer.rst similarity index 51% rename from doc/OnlineDocs/model_debugging/latex_printing.rst rename to doc/OnlineDocs/contributed_packages/latex_printer.rst index db5c766f100..ff3f628c0c8 100644 --- a/doc/OnlineDocs/model_debugging/latex_printing.rst +++ b/doc/OnlineDocs/contributed_packages/latex_printer.rst @@ -1,28 +1,9 @@ Latex Printing ============== -Pyomo models can be printed to a LaTeX compatible format using the ``pyomo.util.latex_printer`` function: +Pyomo models can be printed to a LaTeX compatible format using the ``pyomo.contrib.latex_printer.latex_printer`` function: -.. py:function:: latex_printer(pyomo_component, latex_component_map=None, write_object=None, use_equation_environment=False, explicit_set_summation=False, use_short_descriptors=False, fontsize = None, paper_dimensions=None) - - Prints a pyomo component (Block, Model, Objective, Constraint, or Expression) to a LaTeX compatible string - - :param pyomo_component: The Pyomo component to be printed - :type pyomo_component: _BlockData or Model or Objective or Constraint or Expression - :param latex_component_map: A map keyed by Pyomo component, values become the latex representation in the printer - :type latex_component_map: pyomo.common.collections.component_map.ComponentMap - :param write_object: The object to print the latex string to. Can be an open file object, string I/O object, or a string for a filename to write to - :type write_object: io.TextIOWrapper or io.StringIO or str - :param use_equation_environment: LaTeX can render as either a single equation object or as an aligned environment, that in essence treats each objective and constraint as individual numbered equations. If False, then the align environment is used in LaTeX and each constraint and objective will be given an individual equation number. If True, the equation/aligned construction is used to create a single LaTeX equation for the entire model. The align environment (ie, flag==False which is the default) is preferred because it allows for page breaks in large models. - :type use_equation_environment: bool - :param explicit_set_summation: If False, all sums will be done over 'index in set' or similar. If True, sums that have a contiguous set (ex: [1,2,3,4,5...]) will be done over 'i=1' to 'N' or similar - :type explicit_set_summation: bool - :param throw_templatization_error: Option to throw an error on templatization failure rather than printing each constraint individually, useful for very large models - :type throw_templatization_error: bool - - - :return: A LaTeX style string that represents the passed in pyomoElement - :rtype: str +.. autofunction:: pyomo.contrib.latex_printer.latex_printer.latex_printer .. note:: @@ -41,7 +22,7 @@ A Model .. doctest:: >>> import pyomo.environ as pyo - >>> from pyomo.util.latex_printer import latex_printer + >>> from pyomo.contrib.latex_printer import latex_printer >>> m = pyo.ConcreteModel(name = 'basicFormulation') >>> m.x = pyo.Var() @@ -60,7 +41,7 @@ A Constraint .. doctest:: >>> import pyomo.environ as pyo - >>> from pyomo.util.latex_printer import latex_printer + >>> from pyomo.contrib.latex_printer import latex_printer >>> m = pyo.ConcreteModel(name = 'basicFormulation') >>> m.x = pyo.Var() @@ -76,7 +57,7 @@ A Constraint with Set Summation .. doctest:: >>> import pyomo.environ as pyo - >>> from pyomo.util.latex_printer import latex_printer + >>> from pyomo.contrib.latex_printer import latex_printer >>> m = pyo.ConcreteModel(name='basicFormulation') >>> m.I = pyo.Set(initialize=[1, 2, 3, 4, 5]) >>> m.v = pyo.Var(m.I) @@ -93,7 +74,7 @@ Using a ComponentMap to Specify Names .. doctest:: >>> import pyomo.environ as pyo - >>> from pyomo.util.latex_printer import latex_printer + >>> from pyomo.contrib.latex_printer import latex_printer >>> from pyomo.common.collections.component_map import ComponentMap >>> m = pyo.ConcreteModel(name='basicFormulation') @@ -117,7 +98,7 @@ An Expression .. doctest:: >>> import pyomo.environ as pyo - >>> from pyomo.util.latex_printer import latex_printer + >>> from pyomo.contrib.latex_printer import latex_printer >>> m = pyo.ConcreteModel(name = 'basicFormulation') >>> m.x = pyo.Var() @@ -134,7 +115,7 @@ A Simple Expression .. doctest:: >>> import pyomo.environ as pyo - >>> from pyomo.util.latex_printer import latex_printer + >>> from pyomo.contrib.latex_printer import latex_printer >>> m = pyo.ConcreteModel(name = 'basicFormulation') >>> m.x = pyo.Var() diff --git a/pyomo/contrib/latex_printer/Readme.md b/pyomo/contrib/latex_printer/Readme.md new file mode 100644 index 00000000000..9b9febf9644 --- /dev/null +++ b/pyomo/contrib/latex_printer/Readme.md @@ -0,0 +1,37 @@ +# Pyomo LaTeX Printer + +This is a prototype latex printer for Pyomo models. DISCLAIMER: The API for the LaTeX printer is not finalized and may have a future breaking change. Use at your own risk. + +## Usage + +```python +import pyomo.environ as pyo +from pyomo.contrib.latex_printer import latex_printer + +m = pyo.ConcreteModel(name = 'basicFormulation') +m.x = pyo.Var() +m.y = pyo.Var() +m.z = pyo.Var() +m.c = pyo.Param(initialize=1.0, mutable=True) +m.objective = pyo.Objective( expr = m.x + m.y + m.z ) +m.constraint_1 = pyo.Constraint(expr = m.x**2 + m.y**2.0 - m.z**2.0 <= m.c ) + +pstr = latex_printer(m) +``` + + +## Acknowledgement + +Pyomo: Python Optimization Modeling Objects +Copyright (c) 2008-2023 +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. + +Development of this module was conducted as part of the Institute for +the Design of Advanced Energy Systems (IDAES) with support through the +Simulation-Based Engineering, Crosscutting Research Program within the +U.S. Department of Energy’s Office of Fossil Energy and Carbon Management. + +This software is distributed under the 3-clause BSD License. \ No newline at end of file diff --git a/pyomo/contrib/latex_printer/__init__.py b/pyomo/contrib/latex_printer/__init__.py new file mode 100644 index 00000000000..27c1552017a --- /dev/null +++ b/pyomo/contrib/latex_printer/__init__.py @@ -0,0 +1,22 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2023 +# 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. +# ___________________________________________________________________________ + +# Recommended just to build all of the appropriate things +import pyomo.environ + +# Remove one layer of .latex_printer +# import statemnt is now: +# from pyomo.contrib.latex_printer import latex_printer +try: + from pyomo.contrib.latex_printer.latex_printer import latex_printer +except: + pass + # in this case, the dependencies are not installed, nothing will work diff --git a/pyomo/util/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py similarity index 97% rename from pyomo/util/latex_printer.py rename to pyomo/contrib/latex_printer/latex_printer.py index f93f3151418..6111563dc3c 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -76,40 +76,22 @@ from pyomo.common.errors import InfeasibleConstraintException -from pyomo.common.dependencies import numpy, numpy_available - -if numpy_available: - import numpy as np +from pyomo.common.dependencies import numpy as np, numpy_available def decoder(num, base): - # Needed in the general case, but not as implemented - # if isinstance(base, float): - # if not base.is_integer(): - # raise ValueError('Invalid base') - # else: - # base = int(base) - - # Needed in the general case, but not as implemented - # if base <= 1: - # raise ValueError('Invalid base') - - # Needed in the general case, but not as implemented - # if num == 0: - # numDigs = 1 - # else: - numDigs = math.ceil(math.log(num, base)) - if math.log(num, base).is_integer(): - numDigs += 1 - - digs = [0.0 for i in range(0, numDigs)] - rem = num - for i in range(0, numDigs): - ix = numDigs - i - 1 - dg = math.floor(rem / base**ix) - rem = rem % base**ix - digs[i] = dg - return digs + if int(num) != abs(num): + # Requiring an integer is nice, but not strictly necessary; + # the algorithm works for floating point + raise ValueError("num should be a nonnegative integer") + if int(base) != abs(base) or not base: + raise ValueError("base should be a positive integer") + ans = [] + while 1: + ans.append(num % base) + num //= base + if not num: + return list(reversed(ans)) def indexCorrector(ixs, base): @@ -337,7 +319,7 @@ def handle_param_node(visitor, node): def handle_str_node(visitor, node): - return node.replace('_', '\\_') + return "\\mathtt{'" + node.replace('_', '\\_') + "'}" def handle_npv_structuralGetItemExpression_node(visitor, node, *args): diff --git a/pyomo/util/tests/test_latex_printer.py b/pyomo/contrib/latex_printer/tests/test_latex_printer.py similarity index 99% rename from pyomo/util/tests/test_latex_printer.py rename to pyomo/contrib/latex_printer/tests/test_latex_printer.py index 1564291b7a7..fde4643fc98 100644 --- a/pyomo/util/tests/test_latex_printer.py +++ b/pyomo/contrib/latex_printer/tests/test_latex_printer.py @@ -11,7 +11,7 @@ import io import pyomo.common.unittest as unittest -from pyomo.util.latex_printer import latex_printer +from pyomo.contrib.latex_printer import latex_printer import pyomo.environ as pyo from textwrap import dedent from pyomo.common.tempfiles import TempfileManager diff --git a/pyomo/util/tests/test_latex_printer_vartypes.py b/pyomo/contrib/latex_printer/tests/test_latex_printer_vartypes.py similarity index 99% rename from pyomo/util/tests/test_latex_printer_vartypes.py rename to pyomo/contrib/latex_printer/tests/test_latex_printer_vartypes.py index 4ff6d7cf699..14e9ebbe0e6 100644 --- a/pyomo/util/tests/test_latex_printer_vartypes.py +++ b/pyomo/contrib/latex_printer/tests/test_latex_printer_vartypes.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ import pyomo.common.unittest as unittest -from pyomo.util.latex_printer import latex_printer +from pyomo.contrib.latex_printer import latex_printer import pyomo.environ as pyo from textwrap import dedent from pyomo.common.tempfiles import TempfileManager From b474f2f9f1c22fd152bebe0fe6130f095dbc4fb3 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 27 Nov 2023 12:02:45 -0700 Subject: [PATCH 0479/1204] update workflows --- .github/workflows/test_branches.yml | 8 ++++++-- .github/workflows/test_pr_and_main.yml | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 6faa7f167c9..529299bc73f 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -258,8 +258,12 @@ jobs: || echo "WARNING: Gurobi is not available" python -m pip install --cache-dir cache/pip xpress \ || echo "WARNING: Xpress Community Edition is not available" - python -m pip install wntr \ - || echo "WARNING: WNTR is not available" + if [[ ${{matrix.python}} == pypy* ]]; then + echo "skipping wntr for pypy" + else + python -m pip install wntr \ + || echo "WARNING: WNTR is not available" + fi fi python -c 'import sys; print("PYTHON_EXE=%s" \ % (sys.executable,))' >> $GITHUB_ENV diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 3e5ca2b3110..36c9c45c6a4 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -288,8 +288,12 @@ jobs: || echo "WARNING: Gurobi is not available" python -m pip install --cache-dir cache/pip xpress \ || echo "WARNING: Xpress Community Edition is not available" - python -m pip install wntr \ - || echo "WARNING: WNTR is not available" + if [[ ${{matrix.python}} == pypy* ]]; then + echo "skipping wntr for pypy" + else + python -m pip install wntr \ + || echo "WARNING: WNTR is not available" + fi fi python -c 'import sys; print("PYTHON_EXE=%s" \ % (sys.executable,))' >> $GITHUB_ENV From f6b18e804f8cd6975d2dde890568b35bb5e80a22 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 27 Nov 2023 12:05:41 -0700 Subject: [PATCH 0480/1204] Update index processing for hashable slices --- pyomo/core/base/indexed_component.py | 64 +++++++++++++++++----------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/pyomo/core/base/indexed_component.py b/pyomo/core/base/indexed_component.py index 6e356a8304e..562f8dd9101 100644 --- a/pyomo/core/base/indexed_component.py +++ b/pyomo/core/base/indexed_component.py @@ -20,6 +20,7 @@ from copy import deepcopy import pyomo.core.expr as EXPR +import pyomo.core.base as BASE from pyomo.core.expr.numeric_expr import NumericNDArray from pyomo.core.expr.numvalue import native_types from pyomo.core.base.indexed_component_slice import IndexedComponent_slice @@ -42,6 +43,7 @@ logger = logging.getLogger('pyomo.core') sequence_types = {tuple, list} +slicer_types = {slice, Ellipsis.__class__, IndexedComponent_slice} def normalize_index(x): @@ -296,8 +298,6 @@ class Skip(object): _DEFAULT_INDEX_CHECKING_ENABLED = True def __init__(self, *args, **kwds): - from pyomo.core.base.set import process_setarg - # kwds.pop('noruleinit', None) Component.__init__(self, **kwds) @@ -315,7 +315,7 @@ def __init__(self, *args, **kwds): # If a single indexing set is provided, just process it. # self._implicit_subsets = None - self._index_set = process_setarg(args[0]) + self._index_set = BASE.set.process_setarg(args[0]) else: # # If multiple indexing sets are provided, process them all, @@ -332,7 +332,7 @@ def __init__(self, *args, **kwds): # is assigned to a model (where the implicit subsets can be # "transferred" to the model). # - tmp = [process_setarg(x) for x in args] + tmp = [BASE.set.process_setarg(x) for x in args] self._implicit_subsets = tmp self._index_set = tmp[0].cross(*tmp[1:]) @@ -833,16 +833,23 @@ def _validate_index(self, idx): return idx # This is only called through __{get,set,del}item__, which has - # already trapped unhashable objects. - validated_idx = self._index_set.get(idx, _NotFound) - if validated_idx is not _NotFound: - # If the index is in the underlying index set, then return it - # Note: This check is potentially expensive (e.g., when the - # indexing set is a complex set operation)! - return validated_idx - - if idx.__class__ is IndexedComponent_slice: - return idx + # already trapped unhashable objects. Unfortunately, Python + # 3.12 made slices hashable. This means that slices will get + # here and potentially be looked up in the index_set. This will + # cause problems with Any, where Any will hapilly return the + # index as a valid set. We will only validate the index for + # non-Any sets. Any will pass through so that normalize_index + # can be called (which can generate the TypeError for slices) + _any = isinstance(self._index_set, BASE.set._AnySet) + if _any: + validated_idx = _NotFound + else: + validated_idx = self._index_set.get(idx, _NotFound) + if validated_idx is not _NotFound: + # If the index is in the underlying index set, then return it + # Note: This check is potentially expensive (e.g., when the + # indexing set is a complex set operation)! + return validated_idx if normalize_index.flatten: # Now we normalize the index and check again. Usually, @@ -850,16 +857,24 @@ def _validate_index(self, idx): # "automatic" call to normalize_index until now for the # sake of efficiency. normalized_idx = normalize_index(idx) - if normalized_idx is not idx: - idx = normalized_idx - if idx in self._data: - return idx - if idx in self._index_set: - return idx + if normalized_idx is not idx and not _any: + if normalized_idx in self._data: + return normalized_idx + if normalized_idx in self._index_set: + return normalized_idx + else: + normalized_idx = idx + # There is the chance that the index contains an Ellipsis, # so we should generate a slicer - if idx is Ellipsis or idx.__class__ is tuple and Ellipsis in idx: - return self._processUnhashableIndex(idx) + if ( + normalized_idx.__class__ in slicer_types + or normalized_idx.__class__ is tuple + and any(_.__class__ in slicer_types for _ in normalized_idx) + ): + return self._processUnhashableIndex(normalized_idx) + if _any: + return idx # # Generate different errors, depending on the state of the index. # @@ -872,7 +887,8 @@ def _validate_index(self, idx): # Raise an exception # raise KeyError( - "Index '%s' is not valid for indexed component '%s'" % (idx, self.name) + "Index '%s' is not valid for indexed component '%s'" + % (normalized_idx, self.name) ) def _processUnhashableIndex(self, idx): @@ -881,7 +897,7 @@ def _processUnhashableIndex(self, idx): There are three basic ways to get here: 1) the index contains one or more slices or ellipsis 2) the index contains an unhashable type (e.g., a Pyomo - (Scalar)Component + (Scalar)Component) 3) the index contains an IndexTemplate """ # From 6b58e699591dda9e7c796266c5c94b7ffe101227 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 27 Nov 2023 12:06:00 -0700 Subject: [PATCH 0481/1204] Update 'magic testing number' for Python 3.12 --- pyomo/core/tests/unit/test_visitor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyomo/core/tests/unit/test_visitor.py b/pyomo/core/tests/unit/test_visitor.py index b70996a13dc..086c57aa560 100644 --- a/pyomo/core/tests/unit/test_visitor.py +++ b/pyomo/core/tests/unit/test_visitor.py @@ -1822,8 +1822,9 @@ def run_walker(self, walker): cases = [] else: # 3 sufficed through Python 3.10, but appeared to need to be - # raised to 5 for recent 3.11 builds (3.11.2) - cases = [(0, ""), (5, warn_msg)] + # raised to 5 for Python 3.11 builds (3.11.2), and again to + # 10 for Python 3.12 builds (3.12.0) + cases = [(0, ""), (10, warn_msg)] head_room = sys.getrecursionlimit() - get_stack_depth() for n, msg in cases: From 317cd9d5feba8fbdd193ae475a6d8c495da3cbe6 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Mon, 27 Nov 2023 12:08:54 -0700 Subject: [PATCH 0482/1204] finishing pr request triage --- pyomo/contrib/latex_printer/latex_printer.py | 30 +++++++++---------- .../latex_printer/tests/test_latex_printer.py | 6 ++-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index 6111563dc3c..63c8caddcd2 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -106,7 +106,7 @@ def indexCorrector(ixs, base): return ixs -def alphabetStringGenerator(num, indexMode=False): +def alphabetStringGenerator(num): alphabet = ['.', 'i', 'j', 'k', 'm', 'n', 'p', 'q', 'r'] ixs = decoder(num + 1, len(alphabet) - 1) @@ -230,7 +230,7 @@ def handle_monomialTermExpression_node(visitor, node, arg1, arg2): def handle_named_expression_node(visitor, node, arg1): - # needed to preserve consistencency with the exitNode function call + # needed to preserve consistency with the exitNode function call # prevents the need to type check in the exitNode function return arg1 @@ -539,8 +539,8 @@ def analyze_variable(vr): upperBound = ' \\leq 1 ' else: - raise DeveloperError( - 'Invalid domain somehow encountered, contact the developers' + raise NotImplementedError( + 'Invalid domain encountered, will be supported in a future update' ) varBoundData = { @@ -562,14 +562,14 @@ def multiple_replace(pstr, rep_dict): def latex_printer( pyomo_component, latex_component_map=None, - write_object=None, + ostream=None, use_equation_environment=False, explicit_set_summation=False, throw_templatization_error=False, ): """This function produces a string that can be rendered as LaTeX - As described, this function produces a string that can be rendered as LaTeX + Prints a Pyomo component (Block, Model, Objective, Constraint, or Expression) to a LaTeX compatible string Parameters ---------- @@ -577,11 +577,11 @@ def latex_printer( The Pyomo component to be printed latex_component_map: pyomo.common.collections.component_map.ComponentMap - A map keyed by Pyomo component, values become the latex representation in + A map keyed by Pyomo component, values become the LaTeX representation in the printer - write_object: io.TextIOWrapper or io.StringIO or str - The object to print the latex string to. Can be an open file object, + ostream: io.TextIOWrapper or io.StringIO or str + The object to print the LaTeX string to. Can be an open file object, string I/O object, or a string for a filename to write to use_equation_environment: bool @@ -1289,7 +1289,7 @@ def latex_printer( pstr = '\n'.join(finalLines) - if write_object is not None: + if ostream is not None: fstr = '' fstr += '\\documentclass{article} \n' fstr += '\\usepackage{amsmath} \n' @@ -1303,15 +1303,15 @@ def latex_printer( fstr += '\\end{document} \n' # optional write to output file - if isinstance(write_object, (io.TextIOWrapper, io.StringIO)): - write_object.write(fstr) - elif isinstance(write_object, str): - f = open(write_object, 'w') + if isinstance(ostream, (io.TextIOWrapper, io.StringIO)): + ostream.write(fstr) + elif isinstance(ostream, str): + f = open(ostream, 'w') f.write(fstr) f.close() else: raise ValueError( - 'Invalid type %s encountered when parsing the write_object. Must be a StringIO, FileIO, or valid filename string' + 'Invalid type %s encountered when parsing the ostream. Must be a StringIO, FileIO, or valid filename string' ) # return the latex string diff --git a/pyomo/contrib/latex_printer/tests/test_latex_printer.py b/pyomo/contrib/latex_printer/tests/test_latex_printer.py index fde4643fc98..e9de4e4ad05 100644 --- a/pyomo/contrib/latex_printer/tests/test_latex_printer.py +++ b/pyomo/contrib/latex_printer/tests/test_latex_printer.py @@ -446,7 +446,7 @@ def test_latexPrinter_fileWriter(self): with TempfileManager.new_context() as tempfile: fd, fname = tempfile.mkstemp() - pstr = latex_printer(m, write_object=fname) + pstr = latex_printer(m, ostream=fname) f = open(fname) bstr = f.read() @@ -468,7 +468,7 @@ def test_latexPrinter_fileWriter(self): with TempfileManager.new_context() as tempfile: fd, fname = tempfile.mkstemp() - pstr = latex_printer(m, write_object=fname) + pstr = latex_printer(m, ostream=fname) f = open(fname) bstr = f.read() @@ -481,7 +481,7 @@ def test_latexPrinter_fileWriter(self): self.assertEqual(pstr + '\n', bstr) self.assertRaises( - ValueError, latex_printer, **{'pyomo_component': m, 'write_object': 2.0} + ValueError, latex_printer, **{'pyomo_component': m, 'ostream': 2.0} ) def test_latexPrinter_overwriteError(self): From 23bcd8141f4e5b8acfcccb7f8d78d93f617e75da Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 27 Nov 2023 12:22:36 -0700 Subject: [PATCH 0483/1204] Add tolerance to test comparisons; update deprecated API --- .../trustregion/tests/test_interface.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pyomo/contrib/trustregion/tests/test_interface.py b/pyomo/contrib/trustregion/tests/test_interface.py index 24517041b2c..a7e6457a5ca 100644 --- a/pyomo/contrib/trustregion/tests/test_interface.py +++ b/pyomo/contrib/trustregion/tests/test_interface.py @@ -107,7 +107,7 @@ def test_replaceRF(self): expr = self.interface.model.c1.expr new_expr = self.interface.replaceEF(expr) self.assertIsNot(expr, new_expr) - self.assertEquals( + self.assertEqual( str(new_expr), 'x[0]*z[0]**2 + trf_data.ef_outputs[1] == 2.8284271247461903', ) @@ -382,17 +382,17 @@ def test_solveModel(self): self.interface.data.value_of_ef_inputs[...] = 0 # Run the solve objective, step_norm, feasibility = self.interface.solveModel() - self.assertEqual(objective, 5.150744273013601) - self.assertEqual(step_norm, 3.393437471478297) - self.assertEqual(feasibility, 0.09569982275514467) + self.assertAlmostEqual(objective, 5.150744273013601) + self.assertAlmostEqual(step_norm, 3.393437471478297) + self.assertAlmostEqual(feasibility, 0.09569982275514467) self.interface.data.basis_constraint.deactivate() # Change the constraint and update the surrogate model self.interface.updateSurrogateModel() self.interface.data.sm_constraint_basis.activate() objective, step_norm, feasibility = self.interface.solveModel() - self.assertEqual(objective, 5.15065981284333) - self.assertEqual(step_norm, 0.0017225116628372117) - self.assertEqual(feasibility, 0.00014665023773349772) + self.assertAlmostEqual(objective, 5.15065981284333) + self.assertAlmostEqual(step_norm, 0.0017225116628372117) + self.assertAlmostEqual(feasibility, 0.00014665023773349772) @unittest.skipIf( not SolverFactory('ipopt').available(False), "The IPOPT solver is not available" @@ -407,8 +407,8 @@ def test_initializeProblem(self): self.assertEqual( self.interface.initial_decision_bounds[var.name], [var.lb, var.ub] ) - self.assertEqual(objective, 5.150744273013601) - self.assertEqual(feasibility, 0.09569982275514467) + self.assertAlmostEqual(objective, 5.150744273013601) + self.assertAlmostEqual(feasibility, 0.09569982275514467) self.assertTrue(self.interface.data.sm_constraint_basis.active) self.assertFalse(self.interface.data.basis_constraint.active) From 015ebaf8593d0f21336af323ac8d0d440e17c9f4 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 27 Nov 2023 14:41:36 -0500 Subject: [PATCH 0484/1204] fix typo: change try to trying --- pyomo/contrib/mindtpy/algorithm_base_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 4ea492bd7c9..a7a8a41cd70 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -857,7 +857,7 @@ def init_rNLP(self, add_oa_cuts=True): not in self.mip_objective_polynomial_degree ): config.logger.info( - 'Initial relaxed NLP problem is infeasible. This might be related to partition_obj_nonlinear_terms. Try to solve it again without partitioning nonlinear objective function.' + 'Initial relaxed NLP problem is infeasible. This might be related to partition_obj_nonlinear_terms. Trying to solve it again without partitioning nonlinear objective function.' ) self.rnlp.MindtPy_utils.objective.deactivate() self.rnlp.MindtPy_utils.objective_list[0].activate() From 3472d0dff53ae89808adbf387fd1974de2299c90 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 27 Nov 2023 12:48:27 -0700 Subject: [PATCH 0485/1204] Commit other changes --- pyomo/solver/IPOPT.py | 2 +- pyomo/solver/results.py | 2 +- pyomo/solver/util.py | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index 55c97687b05..90a92b0de24 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -13,7 +13,7 @@ import subprocess import io import sys -from typing import Mapping, Dict +from typing import Mapping from pyomo.common import Executable from pyomo.common.config import ConfigValue, NonNegativeInt diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index 404977e8a1a..728e47fc7a1 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -397,7 +397,7 @@ def parse_sol_file( while line: remaining += line.strip() + "; " line = sol_file.readline() - result.solver_message += remaining + result.extra_info.solver_message += remaining break unmasked_kind = int(line[1]) kind = unmasked_kind & 3 # 0-var, 1-con, 2-obj, 3-prob diff --git a/pyomo/solver/util.py b/pyomo/solver/util.py index 16d7c4d7cd4..ec59f7e80f7 100644 --- a/pyomo/solver/util.py +++ b/pyomo/solver/util.py @@ -38,6 +38,8 @@ def get_objective(block): def check_optimal_termination(results): + # TODO: Make work for legacy and new results objects. + # Look at the original version of this function to make that happen. """ This function returns True if the termination condition for the solver is 'optimal', 'locallyOptimal', or 'globallyOptimal', and the status is 'ok' From 4a0a1dfcb9a9aa35ad6a66258a2d8f244d8e93ab Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 27 Nov 2023 13:05:07 -0700 Subject: [PATCH 0486/1204] Add 3.12 support to wheel builder --- .github/workflows/release_wheel_creation.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index fba293b3a8f..72a3ce1110b 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -25,7 +25,7 @@ jobs: matrix: os: [ubuntu-22.04, windows-latest, macos-latest] arch: [all] - wheel-version: ['cp38*', 'cp39*', 'cp310*', 'cp311*'] + wheel-version: ['cp38*', 'cp39*', 'cp310*', 'cp311*', 'cp312*'] steps: - uses: actions/checkout@v4 - name: Build wheels @@ -53,7 +53,7 @@ jobs: matrix: os: [ubuntu-22.04] arch: [all] - wheel-version: ['cp38*', 'cp39*', 'cp310*', 'cp311*'] + wheel-version: ['cp38*', 'cp39*', 'cp310*', 'cp311*', 'cp312*'] steps: - uses: actions/checkout@v4 - name: Set up QEMU From c29439e9c2443acab41ffcefb7405414ae90cbb2 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Mon, 27 Nov 2023 13:13:52 -0700 Subject: [PATCH 0487/1204] Add missing __init__.py file --- pyomo/contrib/latex_printer/tests/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 pyomo/contrib/latex_printer/tests/__init__.py diff --git a/pyomo/contrib/latex_printer/tests/__init__.py b/pyomo/contrib/latex_printer/tests/__init__.py new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/pyomo/contrib/latex_printer/tests/__init__.py @@ -0,0 +1 @@ + From a6079d50b319cc0288ee8612bf58e2f02a88fe09 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 27 Nov 2023 15:37:57 -0500 Subject: [PATCH 0488/1204] add more details of the error in copy_var_list_values --- pyomo/contrib/mindtpy/util.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index 48c8aab31c4..ea2136b0589 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -1010,4 +1010,5 @@ def copy_var_list_values( elif abs(var_val) <= config.zero_tolerance and 0 in v_to.domain: v_to.set_value(0) else: - raise ValueError("copy_var_list_values failed.") + raise ValueError("copy_var_list_values failed with variable {}, value = {} and rounded value = {}" + "".format(v_to.name, var_val, rounded_val)) From 0590ae632fb1ce7bf9c580655c47891ec0a0523d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 27 Nov 2023 13:50:18 -0700 Subject: [PATCH 0489/1204] Explicitly install setuptools --- .github/workflows/test_pr_and_main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index cebe3f49517..5cab44e2545 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -265,6 +265,7 @@ jobs: run: | python -c 'import sys;print(sys.executable)' python -m pip install --cache-dir cache/pip --upgrade pip + python -m pip install --cache-dir cache/pip setuptools PYOMO_DEPENDENCIES=`python setup.py dependencies \ --extras "$EXTRAS" | tail -1` PACKAGES="${PYTHON_CORE_PKGS} ${PYTHON_PACKAGES} ${PYOMO_DEPENDENCIES} " From 1ee8c3cf655e5c41440b54557969afb430547665 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 27 Nov 2023 13:55:10 -0700 Subject: [PATCH 0490/1204] Change configuration of items in branches --- .github/workflows/test_branches.yml | 4 ++-- .github/workflows/test_pr_and_main.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 797ba120937..0e05794326e 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -78,7 +78,7 @@ jobs: PACKAGES: glpk - os: ubuntu-latest - python: 3.9 + python: '3.11' other: /conda skip_doctest: 1 TARGET: linux @@ -86,7 +86,7 @@ jobs: PACKAGES: - os: ubuntu-latest - python: 3.8 + python: 3.9 other: /mpi mpi: 3 skip_doctest: 1 diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 5cab44e2545..059ed3d5c48 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -79,7 +79,7 @@ jobs: PACKAGES: glpk - os: ubuntu-latest - python: 3.9 + python: '3.11' other: /conda skip_doctest: 1 TARGET: linux @@ -96,7 +96,7 @@ jobs: PACKAGES: mpi4py - os: ubuntu-latest - python: 3.11 + python: '3.11' other: /singletest category: "-m 'neos or importtest'" skip_doctest: 1 From 2055543e749242304447897f700bdbf2d352be69 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 27 Nov 2023 14:18:13 -0700 Subject: [PATCH 0491/1204] Adding 3.12 to the list of supported Python versions --- .coin-or/projDesc.xml | 2 +- README.md | 2 +- setup.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.coin-or/projDesc.xml b/.coin-or/projDesc.xml index c08006d08e8..bb0741ac389 100644 --- a/.coin-or/projDesc.xml +++ b/.coin-or/projDesc.xml @@ -287,7 +287,7 @@ Carl D. Laird, Chair, Pyomo Management Committee, claird at andrew dot cmu dot e Any - Python 3.8, 3.9, 3.10, 3.11 + Python 3.8, 3.9, 3.10, 3.11, 3.12 diff --git a/README.md b/README.md index 42923a0339d..bd399252efb 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Pyomo is available under the BSD License - see the Pyomo is currently tested with the following Python implementations: -* CPython: 3.8, 3.9, 3.10, 3.11 +* CPython: 3.8, 3.9, 3.10, 3.11, 3.12 * PyPy: 3.9 _Testing and support policy_: diff --git a/setup.py b/setup.py index 252ef2d063e..b019abe91cb 100644 --- a/setup.py +++ b/setup.py @@ -232,6 +232,7 @@ def __ne__(self, other): 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Scientific/Engineering :: Mathematics', From 4f5cfae475b6418f451eb421c2e3ab4f437b368d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 27 Nov 2023 15:17:33 -0700 Subject: [PATCH 0492/1204] Really deprecated assertion that should have been removed forever ago --- pyomo/repn/tests/test_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/repn/tests/test_util.py b/pyomo/repn/tests/test_util.py index 01dd1392d81..4d9b0ac8811 100644 --- a/pyomo/repn/tests/test_util.py +++ b/pyomo/repn/tests/test_util.py @@ -100,7 +100,7 @@ def test_ftoa_precision(self): # Depending on the platform, np.longdouble may or may not have # higher precision than float: if f == float(f): - test = self.assertNotRegexpMatches + test = self.assertNotRegex else: test = self.assertRegex test( From 5bd9bf0db7d73a2450e6a4fbbc0e50220fb7a30c Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Mon, 27 Nov 2023 16:07:05 -0700 Subject: [PATCH 0493/1204] Updating CHANGELOG in preparation for the 6.7.0 release --- CHANGELOG.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0ba1f885cb..91cb9ab4cbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,63 @@ Pyomo CHANGELOG =============== +------------------------------------------------------------------------------- +Pyomo 6.7.0 (27 Nov 2023) +------------------------------------------------------------------------------- + +- General + - Log which suffix values were skipped at the DEBUG level (#3043) + - Update report_timing() to support context manager API (#3039) + - Update Performance Plot URL (#3033) + - Track change in Black rules (#3021) + - Remove Python 3.7 support (#2956) + - Fix 'because' typos (#3010) + - Add `Preformatted` class for logging preformatted messages (#2998) + - QuadraticRepnVisitor: Improve nonlinear expression expansion (#2997) + - Add `CITATION` file to main repository (#2992) + - LINTING: New Version of `crate-ci/typos` (#2987) + - Minor typo / formatting fixes (#2975) +- Core + - Fix exception due to interaction among Gurobi, Pint, Dask, and Threading (#3026) + - Fix differentiation of `Expressions` containing `native_numeric_types` (#3017) + - Warn for explicit declaration of immutable params with units (#3004) + - Use `SetInitializer` for initializing `Param` domains; reinitializing `IndexedVar` domains (#3001) + - Ensure templatize_constraint returns an expression (#2983) + - Prevent multiple applications of the scaling transform (#2979) +- Solver Interfaces + - NLv2: add linear presolve and general problem scaling support (#3037) + - Adjusting mps writer to the correct structure regarding integer variables declaration (#2946) + - Fix scip results processing (#3023) + - Fix quadratic objective off-diagonal-terms in cplex_direct interface (#3025) + - Consolidate walker logic in LP/NL representations (#3015) + - LP writer: warn user for ignored suffixes (#2982) + - Update handling of `0*` in linear, quadratic walkers (#2981) +- Testing + - Resolve build infrastructure errors (with mpi4py, gams, networkx) (#3018) + - Improve GHA conda env package setup (#3013) + - Update Gurobi license checks in tests (#3011) + - Skip `fileutils` test failure that persists in OSX 12.7 (#3008) + - GHA: Improve conda environment setup time (#2967) +- GDP + - Improve Disjunction construction error for invalid types (#3042) + - Adding new walker for compute_bounds_on_expr (#3027) + - Fix bugs in gdp.bound_pretransformation (#2973) + - Fix various bugs in GDP transformations (#3009) + - Add a few more GDP examples (#2932) +- Contributed Packages + - APPSI: Add interface to WNTR (#2902) + - APPSI: Capture HiGHS output when initializing model (#3005) + - APPSI: Fix auto-update when unfixing a variable and changing its bound (#2996) + - APPSI: Fix reference bug in HiGHS interface (#2995) + - FBBT: Adding new walker for compute_bounds_on_expr (#3027) + - incidence_analysis: Fix bugs related to subset ordering and zero coefficients (#3041) + - incidence_analysis: Update paper reference (#2969) + - MindtPy: Add support for GreyBox models (#2988) + - parmest: Cleanup examples and tests (#3028) + - PyNumero: Handle evaluation errors in CyIpopt solver (#2994) + - PyROS: Report relative variable shifts in solver logs (#3035) + - PyROS: Update logging system (#2990) + ------------------------------------------------------------------------------- Pyomo 6.6.2 (23 Aug 2023) ------------------------------------------------------------------------------- From e8b3b72df0d5c869be0a169ea5310940da342049 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 27 Nov 2023 18:26:12 -0500 Subject: [PATCH 0494/1204] create copy_var_value function --- pyomo/contrib/mindtpy/algorithm_base_class.py | 1 - pyomo/contrib/mindtpy/single_tree.py | 53 +------- pyomo/contrib/mindtpy/tests/test_mindtpy.py | 1 + pyomo/contrib/mindtpy/util.py | 121 ++++++++++-------- 4 files changed, 74 insertions(+), 102 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index a7a8a41cd70..92e1075fe90 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -1853,7 +1853,6 @@ def handle_main_optimal(self, main_mip, update_bound=True): f"Integer variable {var.name} not initialized. " "Setting it to its lower bound" ) - # nlp_var.bounds[0] var.set_value(var.lb, skip_validation=True) # warm start for the nlp subproblem copy_var_list_values( diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index 66435c2587f..a5d4401d623 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -16,12 +16,11 @@ from pyomo.repn import generate_standard_repn import pyomo.core.expr as EXPR from math import copysign -from pyomo.contrib.mindtpy.util import get_integer_solution, copy_var_list_values +from pyomo.contrib.mindtpy.util import get_integer_solution, copy_var_list_values, copy_var_value from pyomo.contrib.gdpopt.util import get_main_elapsed_time, time_code from pyomo.opt import TerminationCondition as tc from pyomo.core import minimize, value from pyomo.core.expr import identify_variables -import math cplex, cplex_available = attempt_import('cplex') @@ -35,7 +34,6 @@ def copy_lazy_var_list_values( self, opt, from_list, to_list, config, skip_stale=False, skip_fixed=True ): """This function copies variable values from one list to another. - Rounds to Binary/Integer if necessary. Sets to zero for NonNegativeReals if necessary. @@ -44,17 +42,15 @@ def copy_lazy_var_list_values( opt : SolverFactory The cplex_persistent solver. from_list : list - The variables that provides the values to copy from. + The variable list that provides the values to copy from. to_list : list - The variables that need to set value. + The variable list that needs to set value. config : ConfigBlock The specific configurations for MindtPy. skip_stale : bool, optional Whether to skip the stale variables, by default False. skip_fixed : bool, optional Whether to skip the fixed variables, by default True. - ignore_integrality : bool, optional - Whether to ignore the integrality of integer variables, by default False. """ for v_from, v_to in zip(from_list, to_list): if skip_stale and v_from.stale: @@ -62,48 +58,7 @@ def copy_lazy_var_list_values( if skip_fixed and v_to.is_fixed(): continue # Skip fixed variables. v_val = self.get_values(opt._pyomo_var_to_solver_var_map[v_from]) - rounded_val = int(round(v_val)) - # We don't want to trigger the reset of the global stale - # indicator, so we will set this variable to be "stale", - # knowing that set_value will switch it back to "not - # stale" - v_to.stale = True - # NOTE: PEP 2180 changes the var behavior so that domain - # / bounds violations no longer generate exceptions (and - # instead log warnings). This means that the following - # will always succeed and the ValueError should never be - # raised. - if ( - v_val in v_to.domain - and not ((v_to.has_lb() and v_val < v_to.lb)) - and not ((v_to.has_ub() and v_val > v_to.ub)) - ): - v_to.set_value(v_val) - # Snap the value to the bounds - # TODO: check the performance of - # v_to.lb - v_val <= config.variable_tolerance - elif ( - v_to.has_lb() - and v_val < v_to.lb - # and v_to.lb - v_val <= config.variable_tolerance - ): - v_to.set_value(v_to.lb) - elif ( - v_to.has_ub() - and v_val > v_to.ub - # and v_val - v_to.ub <= config.variable_tolerance - ): - v_to.set_value(v_to.ub) - # ... or the nearest integer - elif ( - v_to.is_integer() - and math.fabs(v_val - rounded_val) <= config.integer_tolerance - ): # and rounded_val in v_to.domain: - v_to.set_value(rounded_val) - elif abs(v_val) <= config.zero_tolerance and 0 in v_to.domain: - v_to.set_value(0) - else: - raise ValueError('copy_lazy_var_list_values failed.') + copy_var_value(v_from, v_to, v_val, config, ignore_integrality=False) def add_lazy_oa_cuts( self, diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy.py b/pyomo/contrib/mindtpy/tests/test_mindtpy.py index e872eccc670..ae531f9bd84 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy.py @@ -327,6 +327,7 @@ def test_OA_APPSI_ipopt(self): value(model.objective.expr), model.optimal_value, places=1 ) + # CYIPOPT will raise WARNING (W1002) during loading solution. @unittest.skipUnless( SolverFactory('cyipopt').available(exception_flag=False), "APPSI_IPOPT not available.", diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index ea2136b0589..2970a805540 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -693,37 +693,7 @@ def copy_var_list_values_from_solution_pool( elif config.mip_solver == 'gurobi_persistent': solver_model.setParam(gurobipy.GRB.Param.SolutionNumber, solution_name) var_val = var_map[v_from].Xn - # We don't want to trigger the reset of the global stale - # indicator, so we will set this variable to be "stale", - # knowing that set_value will switch it back to "not - # stale" - v_to.stale = True - rounded_val = int(round(var_val)) - # NOTE: PEP 2180 changes the var behavior so that domain / - # bounds violations no longer generate exceptions (and - # instead log warnings). This means that the following will - # always succeed and the ValueError should never be raised. - if ( - var_val in v_to.domain - and not ((v_to.has_lb() and var_val < v_to.lb)) - and not ((v_to.has_ub() and var_val > v_to.ub)) - ): - v_to.set_value(var_val, skip_validation=True) - elif v_to.has_lb() and var_val < v_to.lb: - v_to.set_value(v_to.lb) - elif v_to.has_ub() and var_val > v_to.ub: - v_to.set_value(v_to.ub) - # Check to see if this is just a tolerance issue - elif ignore_integrality and v_to.is_integer(): - v_to.set_value(var_val, skip_validation=True) - elif v_to.is_integer() and ( - abs(var_val - rounded_val) <= config.integer_tolerance - ): - v_to.set_value(rounded_val, skip_validation=True) - elif abs(var_val) <= config.zero_tolerance and 0 in v_to.domain: - v_to.set_value(0, skip_validation=True) - else: - raise ValueError("copy_var_list_values_from_solution_pool failed.") + copy_var_value(v_from, v_to, var_val, config, ignore_integrality) class GurobiPersistent4MindtPy(GurobiPersistent): @@ -983,6 +953,19 @@ def copy_var_list_values( """Copy variable values from one list to another. Rounds to Binary/Integer if necessary Sets to zero for NonNegativeReals if necessary + + from_list : list + The variables that provides the values to copy from. + to_list : list + The variables that need to set value. + config : ConfigBlock + The specific configurations for MindtPy. + skip_stale : bool, optional + Whether to skip the stale variables, by default False. + skip_fixed : bool, optional + Whether to skip the fixed variables, by default True. + ignore_integrality : bool, optional + Whether to ignore the integrality of integer variables, by default False. """ for v_from, v_to in zip(from_list, to_list): if skip_stale and v_from.stale: @@ -990,25 +973,59 @@ def copy_var_list_values( if skip_fixed and v_to.is_fixed(): continue # Skip fixed variables. var_val = value(v_from, exception=False) - rounded_val = int(round(var_val)) - if ( - var_val in v_to.domain - and not ((v_to.has_lb() and var_val < v_to.lb)) - and not ((v_to.has_ub() and var_val > v_to.ub)) - ): - v_to.set_value(value(v_from, exception=False)) - elif v_to.has_lb() and var_val < v_to.lb: - v_to.set_value(v_to.lb) - elif v_to.has_ub() and var_val > v_to.ub: - v_to.set_value(v_to.ub) - elif ignore_integrality and v_to.is_integer(): - v_to.set_value(value(v_from, exception=False), skip_validation=True) - elif v_to.is_integer() and ( - math.fabs(var_val - rounded_val) <= config.integer_tolerance + copy_var_value(v_from, v_to, var_val, config, ignore_integrality) + + +def copy_var_value(v_from, v_to, var_val, config, ignore_integrality): + """This function copies variable value from one to another. + Rounds to Binary/Integer if necessary. + Sets to zero for NonNegativeReals if necessary. + + NOTE: PEP 2180 changes the var behavior so that domain / + bounds violations no longer generate exceptions (and + instead log warnings). This means that the following will + always succeed and the ValueError should never be raised. + + Parameters + ---------- + v_from : Var + The variable that provides the values to copy from. + v_to : Var + The variable that needs to set value. + var_val : float + The value of v_to variable. + config : ConfigBlock + The specific configurations for MindtPy. + ignore_integrality : bool, optional + Whether to ignore the integrality of integer variables, by default False. + + Raises + ------ + ValueError + Cannot successfully set the value to variable v_to. + """ + # We don't want to trigger the reset of the global stale + # indicator, so we will set this variable to be "stale", + # knowing that set_value will switch it back to "not stale". + v_to.stale = True + rounded_val = int(round(var_val)) + if (var_val in v_to.domain + and not ((v_to.has_lb() and var_val < v_to.lb)) + and not ((v_to.has_ub() and var_val > v_to.ub)) ): - v_to.set_value(rounded_val) - elif abs(var_val) <= config.zero_tolerance and 0 in v_to.domain: - v_to.set_value(0) - else: - raise ValueError("copy_var_list_values failed with variable {}, value = {} and rounded value = {}" - "".format(v_to.name, var_val, rounded_val)) + v_to.set_value(var_val) + elif v_to.has_lb() and var_val < v_to.lb: + v_to.set_value(v_to.lb) + elif v_to.has_ub() and var_val > v_to.ub: + v_to.set_value(v_to.ub) + elif ignore_integrality and v_to.is_integer(): + v_to.set_value(var_val, skip_validation=True) + elif v_to.is_integer() and ( + math.fabs(var_val - rounded_val) <= config.integer_tolerance + ): + v_to.set_value(rounded_val) + elif abs(var_val) <= config.zero_tolerance and 0 in v_to.domain: + v_to.set_value(0) + else: + raise ValueError("copy_var_list_values failed with variable {}, value = {} and rounded value = {}" + "".format(v_to.name, var_val, rounded_val)) From a6662cd37d03f8776a4136c16840e42a9a89f1b0 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 27 Nov 2023 16:32:04 -0700 Subject: [PATCH 0495/1204] NFC: fix errors / typos in comments and docstrings --- pyomo/repn/plugins/standard_form.py | 30 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/pyomo/repn/plugins/standard_form.py b/pyomo/repn/plugins/standard_form.py index 2b2c81b2ca9..8440c1ab92d 100644 --- a/pyomo/repn/plugins/standard_form.py +++ b/pyomo/repn/plugins/standard_form.py @@ -81,13 +81,13 @@ class LinearStandardFormInfo(object): The list of Pyomo constraint objects corresponding to the rows in `A`. Each element in the list is a 2-tuple of (_ConstraintData, row_multiplier). The `row_multiplier` will be - +/- 1 (indicating if the row was multiplied by -1 (corresponding - to a constraint lower bound or +1 (upper bound). + +/- 1 indicating if the row was multiplied by -1 (corresponding + to a constraint lower bound) or +1 (upper bound). columns : List[_VarData] The list of Pyomo variable objects corresponding to columns in - the `A` and `c` matricies. + the `A` and `c` matrices. eliminated_vars: List[Tuple[_VarData, NumericExpression]] @@ -144,7 +144,7 @@ class LinearStandardFormCompiler(object): ConfigValue( default=False, domain=bool, - description='Print timing after writing each section of the LP file', + description='Print timing after each stage of the compilation process', ), ) CONFIG.declare( @@ -155,7 +155,7 @@ class LinearStandardFormCompiler(object): description='How much effort to ensure result is deterministic', doc=""" How much effort do we want to put into ensuring the - resulting matricies are produced deterministically: + resulting matrices are produced deterministically: NONE (0) : None ORDERED (10): rely on underlying component ordering (default) SORT_INDICES (20) : sort keys of indexed components @@ -169,8 +169,9 @@ class LinearStandardFormCompiler(object): default=None, description='Preferred constraint ordering', doc=""" - List of constraints in the order that they should appear in the - LP file. Unspecified constraints will appear at the end.""", + List of constraints in the order that they should appear in + the resulting `A` matrix. Unspecified constraints will + appear at the end.""", ), ) CONFIG.declare( @@ -180,10 +181,8 @@ class LinearStandardFormCompiler(object): description='Preferred variable ordering', doc=""" List of variables in the order that they should appear in - the LP file. Note that this is only a suggestion, as the LP - file format is row-major and the columns are inferred from - the order in which variables appear in the objective - followed by each constraint.""", + the compiled representation. Unspecified variables will be + appended to the end of this list.""", ), ) @@ -251,7 +250,8 @@ def write(self, model): if unknown: raise ValueError( "The model ('%s') contains the following active components " - "that the LP compiler does not know how to process:\n\t%s" + "that the Linear Standard Form compiler does not know how to " + "process:\n\t%s" % ( model.name, "\n\t".join( @@ -420,7 +420,7 @@ def write(self, model): # Get the variable list columns = list(var_map.values()) - # Convert the compiled data to scipy sparse matricies + # Convert the compiled data to scipy sparse matrices c = scipy.sparse.csr_array( (np.concatenate(obj_data), np.concatenate(obj_index), obj_index_ptr), [len(obj_index_ptr) - 1, len(columns)], @@ -430,8 +430,8 @@ def write(self, model): [len(rows), len(columns)], ).tocsc() - # Some variables in the var_map may not actually have been - # written out to the LP file (e.g., added from col_order, or + # Some variables in the var_map may not actually appear in the + # objective or constraints (e.g., added from col_order, or # multiplied by 0 in the expressions). The easiest way to check # for empty columns is to convert from CSR to CSC and then look # at the index pointer list (an O(num_var) operation). From 0fbd3bb3d97344b2d1089b0b3b8c640ffb30c7ca Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 27 Nov 2023 16:34:58 -0700 Subject: [PATCH 0496/1204] Adding missing supported version --- doc/OnlineDocs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/OnlineDocs/installation.rst b/doc/OnlineDocs/installation.rst index 323d7be1632..ecba05e13fb 100644 --- a/doc/OnlineDocs/installation.rst +++ b/doc/OnlineDocs/installation.rst @@ -3,7 +3,7 @@ Installation Pyomo currently supports the following versions of Python: -* CPython: 3.8, 3.9, 3.10, 3.11 +* CPython: 3.8, 3.9, 3.10, 3.11, 3.12 * PyPy: 3 At the time of the first Pyomo release after the end-of-life of a minor Python From 049634714512501fd85d3abdc363e4843e2a5d75 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 27 Nov 2023 16:38:01 -0700 Subject: [PATCH 0497/1204] Caught typo missed by crate-ci --- pyomo/core/base/indexed_component.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/core/base/indexed_component.py b/pyomo/core/base/indexed_component.py index 562f8dd9101..b474281f5b9 100644 --- a/pyomo/core/base/indexed_component.py +++ b/pyomo/core/base/indexed_component.py @@ -836,7 +836,7 @@ def _validate_index(self, idx): # already trapped unhashable objects. Unfortunately, Python # 3.12 made slices hashable. This means that slices will get # here and potentially be looked up in the index_set. This will - # cause problems with Any, where Any will hapilly return the + # cause problems with Any, where Any will happily return the # index as a valid set. We will only validate the index for # non-Any sets. Any will pass through so that normalize_index # can be called (which can generate the TypeError for slices) From dc41b8e969490e15d1c7948e386a8f2ba3ebedb8 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 27 Nov 2023 20:07:43 -0500 Subject: [PATCH 0498/1204] add exc_info for the error message --- pyomo/contrib/mindtpy/algorithm_base_class.py | 17 ++++++++++------- pyomo/contrib/mindtpy/cut_generation.py | 11 +++++++---- pyomo/contrib/mindtpy/extended_cutting_plane.py | 4 ++-- .../mindtpy/global_outer_approximation.py | 3 ++- pyomo/contrib/mindtpy/single_tree.py | 9 +++++---- 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 92e1075fe90..141e7f9f09f 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -802,7 +802,7 @@ def MindtPy_initialization(self): try: self.curr_int_sol = get_integer_solution(self.working_model) except TypeError as e: - config.logger.error(e) + config.logger.error(e, exc_info=True) raise ValueError( 'The initial integer combination is not provided or not complete. ' 'Please provide the complete integer combination or use other initialization strategy.' @@ -1083,7 +1083,7 @@ def solve_subproblem(self): 0, c_geq * (rhs - value(c.body)) ) except (ValueError, OverflowError) as e: - config.logger.error(e) + config.logger.error(e, exc_info=True) self.fixed_nlp.tmp_duals[c] = None evaluation_error = True if evaluation_error: @@ -1100,8 +1100,9 @@ def solve_subproblem(self): tolerance=config.constraint_tolerance, ) except InfeasibleConstraintException as e: + config.logger.error(e, exc_info=True) config.logger.error( - str(e) + '\nInfeasibility detected in deactivate_trivial_constraints.' + 'Infeasibility detected in deactivate_trivial_constraints.' ) results = SolverResults() results.solver.termination_condition = tc.infeasible @@ -1401,7 +1402,7 @@ def solve_feasibility_subproblem(self): if len(feas_soln.solution) > 0: feas_subproblem.solutions.load_from(feas_soln) except (ValueError, OverflowError) as e: - config.logger.error(e) + config.logger.error(e, exc_info=True) for nlp_var, orig_val in zip( MindtPy.variable_list, self.initial_var_values ): @@ -1542,8 +1543,9 @@ def fix_dual_bound(self, last_iter_cuts): try: self.dual_bound = self.stored_bound[self.primal_bound] except KeyError as e: + config.logger.error(e, exc_info=True) config.logger.error( - str(e) + '\nNo stored bound found. Bound fix failed.' + 'No stored bound found. Bound fix failed.' ) else: config.logger.info( @@ -1670,7 +1672,7 @@ def solve_main(self): if len(main_mip_results.solution) > 0: self.mip.solutions.load_from(main_mip_results) except (ValueError, AttributeError, RuntimeError) as e: - config.logger.error(e) + config.logger.error(e, exc_info=True) if config.single_tree: config.logger.warning('Single tree terminate.') if get_main_elapsed_time(self.timing) >= config.time_limit: @@ -2369,8 +2371,9 @@ def solve_fp_subproblem(self): tolerance=config.constraint_tolerance, ) except InfeasibleConstraintException as e: + config.logger.error(e, exc_info=True) config.logger.error( - str(e) + '\nInfeasibility detected in deactivate_trivial_constraints.' + 'Infeasibility detected in deactivate_trivial_constraints.' ) results = SolverResults() results.solver.termination_condition = tc.infeasible diff --git a/pyomo/contrib/mindtpy/cut_generation.py b/pyomo/contrib/mindtpy/cut_generation.py index 28d302104a3..343170aabac 100644 --- a/pyomo/contrib/mindtpy/cut_generation.py +++ b/pyomo/contrib/mindtpy/cut_generation.py @@ -271,8 +271,9 @@ def add_ecp_cuts( try: upper_slack = constr.uslack() except (ValueError, OverflowError) as e: + config.logger.error(e, exc_info=True) config.logger.error( - str(e) + '\nConstraint {} has caused either a ' + 'Constraint {} has caused either a ' 'ValueError or OverflowError.' '\n'.format(constr) ) @@ -300,8 +301,9 @@ def add_ecp_cuts( try: lower_slack = constr.lslack() except (ValueError, OverflowError) as e: + config.logger.error(e, exc_info=True) config.logger.error( - str(e) + '\nConstraint {} has caused either a ' + 'Constraint {} has caused either a ' 'ValueError or OverflowError.' '\n'.format(constr) ) @@ -424,9 +426,10 @@ def add_affine_cuts(target_model, config, timing): try: mc_eqn = mc(constr.body) except MCPP_Error as e: + config.logger.error(e, exc_info=True) config.logger.error( - '\nSkipping constraint %s due to MCPP error %s' - % (constr.name, str(e)) + 'Skipping constraint %s due to MCPP error' + % (constr.name) ) continue # skip to the next constraint diff --git a/pyomo/contrib/mindtpy/extended_cutting_plane.py b/pyomo/contrib/mindtpy/extended_cutting_plane.py index 446304b1361..3a09af155a0 100644 --- a/pyomo/contrib/mindtpy/extended_cutting_plane.py +++ b/pyomo/contrib/mindtpy/extended_cutting_plane.py @@ -140,7 +140,7 @@ def all_nonlinear_constraint_satisfied(self): lower_slack = nlc.lslack() except (ValueError, OverflowError) as e: # Set lower_slack (upper_slack below) less than -config.ecp_tolerance in this case. - config.logger.error(e) + config.logger.error(e, exc_info=True) lower_slack = -10 * config.ecp_tolerance if lower_slack < -config.ecp_tolerance: config.logger.debug( @@ -153,7 +153,7 @@ def all_nonlinear_constraint_satisfied(self): try: upper_slack = nlc.uslack() except (ValueError, OverflowError) as e: - config.logger.error(e) + config.logger.error(e, exc_info=True) upper_slack = -10 * config.ecp_tolerance if upper_slack < -config.ecp_tolerance: config.logger.debug( diff --git a/pyomo/contrib/mindtpy/global_outer_approximation.py b/pyomo/contrib/mindtpy/global_outer_approximation.py index dfb7ef54630..817fb0bf4a8 100644 --- a/pyomo/contrib/mindtpy/global_outer_approximation.py +++ b/pyomo/contrib/mindtpy/global_outer_approximation.py @@ -108,4 +108,5 @@ def deactivate_no_good_cuts_when_fixing_bound(self, no_good_cuts): if self.config.use_tabu_list: self.integer_list = self.integer_list[:valid_no_good_cuts_num] except KeyError as e: - self.config.logger.error(str(e) + '\nDeactivating no-good cuts failed.') + self.config.logger.error(e, exc_info=True) + self.config.logger.error('Deactivating no-good cuts failed.') diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index a5d4401d623..5485e0298f2 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -259,9 +259,10 @@ def add_lazy_affine_cuts(self, mindtpy_solver, config, opt): try: mc_eqn = mc(constr.body) except MCPP_Error as e: + config.logger.error(e, exc_info=True) config.logger.debug( - 'Skipping constraint %s due to MCPP error %s' - % (constr.name, str(e)) + 'Skipping constraint %s due to MCPP error' + % (constr.name) ) continue # skip to the next constraint # TODO: check if the value of ccSlope and cvSlope is not Nan or inf. If so, we skip this. @@ -696,9 +697,9 @@ def __call__(self): mindtpy_solver.mip, None, mindtpy_solver, config, opt ) except ValueError as e: + config.logger.error(e, exc_info=True) config.logger.error( - str(e) - + "\nUsually this error is caused by the MIP start solution causing a math domain error. " + "Usually this error is caused by the MIP start solution causing a math domain error. " "We will skip it." ) return From dbe9f490fd49e47ea5634c8425eeddd019d731ce Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 27 Nov 2023 20:34:04 -0500 Subject: [PATCH 0499/1204] black format --- pyomo/contrib/mindtpy/algorithm_base_class.py | 4 +--- pyomo/contrib/mindtpy/cut_generation.py | 3 +-- pyomo/contrib/mindtpy/single_tree.py | 9 ++++++--- pyomo/contrib/mindtpy/util.py | 11 +++++++---- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 141e7f9f09f..b06a4c730b4 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -1544,9 +1544,7 @@ def fix_dual_bound(self, last_iter_cuts): self.dual_bound = self.stored_bound[self.primal_bound] except KeyError as e: config.logger.error(e, exc_info=True) - config.logger.error( - 'No stored bound found. Bound fix failed.' - ) + config.logger.error('No stored bound found. Bound fix failed.') else: config.logger.info( 'Solve the main problem without the last no_good cut to fix the bound.' diff --git a/pyomo/contrib/mindtpy/cut_generation.py b/pyomo/contrib/mindtpy/cut_generation.py index 343170aabac..e57cfd2eada 100644 --- a/pyomo/contrib/mindtpy/cut_generation.py +++ b/pyomo/contrib/mindtpy/cut_generation.py @@ -428,8 +428,7 @@ def add_affine_cuts(target_model, config, timing): except MCPP_Error as e: config.logger.error(e, exc_info=True) config.logger.error( - 'Skipping constraint %s due to MCPP error' - % (constr.name) + 'Skipping constraint %s due to MCPP error' % (constr.name) ) continue # skip to the next constraint diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index 5485e0298f2..5e4e378d6c5 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -16,7 +16,11 @@ from pyomo.repn import generate_standard_repn import pyomo.core.expr as EXPR from math import copysign -from pyomo.contrib.mindtpy.util import get_integer_solution, copy_var_list_values, copy_var_value +from pyomo.contrib.mindtpy.util import ( + get_integer_solution, + copy_var_list_values, + copy_var_value, +) from pyomo.contrib.gdpopt.util import get_main_elapsed_time, time_code from pyomo.opt import TerminationCondition as tc from pyomo.core import minimize, value @@ -261,8 +265,7 @@ def add_lazy_affine_cuts(self, mindtpy_solver, config, opt): except MCPP_Error as e: config.logger.error(e, exc_info=True) config.logger.debug( - 'Skipping constraint %s due to MCPP error' - % (constr.name) + 'Skipping constraint %s due to MCPP error' % (constr.name) ) continue # skip to the next constraint # TODO: check if the value of ccSlope and cvSlope is not Nan or inf. If so, we skip this. diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index 2970a805540..7e3fbe415d4 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -1009,10 +1009,11 @@ def copy_var_value(v_from, v_to, var_val, config, ignore_integrality): # knowing that set_value will switch it back to "not stale". v_to.stale = True rounded_val = int(round(var_val)) - if (var_val in v_to.domain + if ( + var_val in v_to.domain and not ((v_to.has_lb() and var_val < v_to.lb)) and not ((v_to.has_ub() and var_val > v_to.ub)) - ): + ): v_to.set_value(var_val) elif v_to.has_lb() and var_val < v_to.lb: v_to.set_value(v_to.lb) @@ -1027,5 +1028,7 @@ def copy_var_value(v_from, v_to, var_val, config, ignore_integrality): elif abs(var_val) <= config.zero_tolerance and 0 in v_to.domain: v_to.set_value(0) else: - raise ValueError("copy_var_list_values failed with variable {}, value = {} and rounded value = {}" - "".format(v_to.name, var_val, rounded_val)) + raise ValueError( + "copy_var_list_values failed with variable {}, value = {} and rounded value = {}" + "".format(v_to.name, var_val, rounded_val) + ) From 4ac390e12fcfa3277a9808ff7f7325bfde808124 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 28 Nov 2023 09:34:22 -0500 Subject: [PATCH 0500/1204] change dir() to locals() --- pyomo/contrib/mindtpy/algorithm_base_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index b06a4c730b4..d5d015d180d 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -1684,7 +1684,7 @@ def solve_main(self): 'No integer solution is found, so the CPLEX solver will report an error status. ' ) # Value error will be raised if the MIP problem is unbounded and appsi solver is used when loading solutions. Although the problem is unbounded, a valid result is provided and we do not return None to let the algorithm continue. - if 'main_mip_results' in dir(): + if 'main_mip_results' in locals(): return self.mip, main_mip_results else: return None, None From b8e06b5ad81088956000397f4e116f598d3bebf8 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Tue, 28 Nov 2023 08:18:50 -0700 Subject: [PATCH 0501/1204] More edits to the CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91cb9ab4cbe..b0b76af1469 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ Pyomo 6.7.0 (27 Nov 2023) - FBBT: Adding new walker for compute_bounds_on_expr (#3027) - incidence_analysis: Fix bugs related to subset ordering and zero coefficients (#3041) - incidence_analysis: Update paper reference (#2969) + - latex_printer: Add contrib.latex_printer package (#2984) - MindtPy: Add support for GreyBox models (#2988) - parmest: Cleanup examples and tests (#3028) - PyNumero: Handle evaluation errors in CyIpopt solver (#2994) From 368482f4bd15c02d7061f742c295bb781caf22ac Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Tue, 28 Nov 2023 08:29:16 -0700 Subject: [PATCH 0502/1204] More edits to the CHANGELOG --- CHANGELOG.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0b76af1469..36c1e635b11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,11 @@ Pyomo CHANGELOG ------------------------------------------------------------------------------- -Pyomo 6.7.0 (27 Nov 2023) +Pyomo 6.7.0 (28 Nov 2023) ------------------------------------------------------------------------------- - General + - Add Python 3.12 Support (#3050) - Log which suffix values were skipped at the DEBUG level (#3043) - Update report_timing() to support context manager API (#3039) - Update Performance Plot URL (#3033) @@ -19,15 +20,17 @@ Pyomo 6.7.0 (27 Nov 2023) - LINTING: New Version of `crate-ci/typos` (#2987) - Minor typo / formatting fixes (#2975) - Core - - Fix exception due to interaction among Gurobi, Pint, Dask, and Threading (#3026) - - Fix differentiation of `Expressions` containing `native_numeric_types` (#3017) + - Fix exception from interaction of Gurobi, Pint, Dask, and Threading (#3026) + - Fix differentiation of `Expressions` with `native_numeric_types` (#3017) - Warn for explicit declaration of immutable params with units (#3004) - - Use `SetInitializer` for initializing `Param` domains; reinitializing `IndexedVar` domains (#3001) + - Use `SetInitializer` for initializing `Param` domains; reinitializing + `IndexedVar` domains (#3001) - Ensure templatize_constraint returns an expression (#2983) - Prevent multiple applications of the scaling transform (#2979) - Solver Interfaces + - Add "writer" for converting linear models to standard matrix form (#3046) - NLv2: add linear presolve and general problem scaling support (#3037) - - Adjusting mps writer to the correct structure regarding integer variables declaration (#2946) + - Adjust mps writer format for integer variable declaration (#2946) - Fix scip results processing (#3023) - Fix quadratic objective off-diagonal-terms in cplex_direct interface (#3025) - Consolidate walker logic in LP/NL representations (#3015) @@ -48,10 +51,11 @@ Pyomo 6.7.0 (27 Nov 2023) - Contributed Packages - APPSI: Add interface to WNTR (#2902) - APPSI: Capture HiGHS output when initializing model (#3005) - - APPSI: Fix auto-update when unfixing a variable and changing its bound (#2996) + - APPSI: Fix auto-update when unfixing variable and changing bounds (#2996) - APPSI: Fix reference bug in HiGHS interface (#2995) - - FBBT: Adding new walker for compute_bounds_on_expr (#3027) - - incidence_analysis: Fix bugs related to subset ordering and zero coefficients (#3041) + - FBBT: Add new walker for compute_bounds_on_expr (#3027) + - incidence_analysis: Fix bugs with subset ordering and zero coefficients + (#3041) - incidence_analysis: Update paper reference (#2969) - latex_printer: Add contrib.latex_printer package (#2984) - MindtPy: Add support for GreyBox models (#2988) From 6522c7bbff10e722c1b66868e357ae11bbf5a62d Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Tue, 28 Nov 2023 08:39:00 -0700 Subject: [PATCH 0503/1204] Finalizing Pyomo 6.7.0 --- .coin-or/projDesc.xml | 4 ++-- RELEASE.md | 31 ++++++------------------------- pyomo/version/info.py | 4 ++-- 3 files changed, 10 insertions(+), 29 deletions(-) diff --git a/.coin-or/projDesc.xml b/.coin-or/projDesc.xml index bb0741ac389..1ee247e100f 100644 --- a/.coin-or/projDesc.xml +++ b/.coin-or/projDesc.xml @@ -227,8 +227,8 @@ Carl D. Laird, Chair, Pyomo Management Committee, claird at andrew dot cmu dot e Use explicit overrides to disable use of automated version reporting. --> - 6.6.2 - 6.6.2 + 6.7.0 + 6.7.0 diff --git a/RELEASE.md b/RELEASE.md index da97ba78701..1fcf19a0da9 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,34 +1,15 @@ -We are pleased to announce the release of Pyomo 6.6.2. +We are pleased to announce the release of Pyomo 6.7.0. Pyomo is a collection of Python software packages that supports a diverse set of optimization capabilities for formulating and analyzing optimization models. -The following are highlights of the 6.0 release series: - - - Improved stability and robustness of core Pyomo code and solver interfaces - - Integration of Boolean variables into GDP - - Integration of NumPy support into the Pyomo expression system - - Implemented a more performant and robust expression generation system - - Implemented a more performant NL file writer (NLv2) - - Implemented a more performant LP file writer (LPv2) - - Applied [PEP8 standards](https://peps.python.org/pep-0008/) throughout the - codebase - - Added support for Python 3.10, 3.11 - - Removed support for Python 3.6 - - Removed the `pyomo check` command +The following are highlights of the 6.7 minor release series: + + - Added support for Python 3.12 + - Removed support for Python 3.7 - New packages: - - APPSI (Auto-Persistent Pyomo Solver Interfaces) - - CP (Constraint programming models and solver interfaces) - - DoE (Model based design of experiments) - - External grey box models - - IIS (Standard interface to solver IIS capabilities) - - MPC (Data structures/utils for rolling horizon dynamic optimization) - - piecewise (Modeling with and reformulating multivariate piecewise linear - functions) - - PyROS (Pyomo Robust Optimization Solver) - - Structural model analysis - - Rewrite of the TrustRegion Solver + - latex_printer (print Pyomo models to a LaTeX compatible format) A full list of updates and changes is available in the [`CHANGELOG.md`](https://github.com/Pyomo/pyomo/blob/main/CHANGELOG.md). diff --git a/pyomo/version/info.py b/pyomo/version/info.py index d274d0dead1..4c149a4caca 100644 --- a/pyomo/version/info.py +++ b/pyomo/version/info.py @@ -27,8 +27,8 @@ major = 6 minor = 7 micro = 0 -releaselevel = 'invalid' -# releaselevel = 'final' +#releaselevel = 'invalid' + releaselevel = 'final' serial = 0 if releaselevel == 'final': From d12d1397995d60b9bbb4f00722d83acfa5e28926 Mon Sep 17 00:00:00 2001 From: Miranda Mundt <55767766+mrmundt@users.noreply.github.com> Date: Tue, 28 Nov 2023 08:41:52 -0700 Subject: [PATCH 0504/1204] Fix spacing --- pyomo/version/info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/version/info.py b/pyomo/version/info.py index 4c149a4caca..be466b2f9ec 100644 --- a/pyomo/version/info.py +++ b/pyomo/version/info.py @@ -28,7 +28,7 @@ minor = 7 micro = 0 #releaselevel = 'invalid' - releaselevel = 'final' +releaselevel = 'final' serial = 0 if releaselevel == 'final': From fdae8cd5d9e47a2c83d9d1b72ee3a9e5a8127350 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 28 Nov 2023 08:46:26 -0700 Subject: [PATCH 0505/1204] Black strikes again --- pyomo/version/info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/version/info.py b/pyomo/version/info.py index be466b2f9ec..e38e844ad9b 100644 --- a/pyomo/version/info.py +++ b/pyomo/version/info.py @@ -27,7 +27,7 @@ major = 6 minor = 7 micro = 0 -#releaselevel = 'invalid' +# releaselevel = 'invalid' releaselevel = 'final' serial = 0 From a755067a6276e62569308d5ce80ef47574eaf63b Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 28 Nov 2023 10:51:56 -0500 Subject: [PATCH 0506/1204] improve int_sol_2_cuts_ind --- pyomo/contrib/mindtpy/algorithm_base_class.py | 9 +++++---- pyomo/contrib/mindtpy/single_tree.py | 14 +++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index d5d015d180d..2eec150453f 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -108,7 +108,7 @@ def __init__(self, **kwds): self.curr_int_sol = [] self.should_terminate = False self.integer_list = [] - # dictionary {integer solution (list): cuts index (list)} + # dictionary {integer solution (list): [cuts begin index, cuts end index] (list)} self.int_sol_2_cuts_ind = dict() # Set up iteration counters @@ -810,9 +810,10 @@ def MindtPy_initialization(self): self.integer_list.append(self.curr_int_sol) fixed_nlp, fixed_nlp_result = self.solve_subproblem() self.handle_nlp_subproblem_tc(fixed_nlp, fixed_nlp_result) - self.int_sol_2_cuts_ind[self.curr_int_sol] = list( - range(1, len(self.mip.MindtPy_utils.cuts.oa_cuts) + 1) - ) + self.int_sol_2_cuts_ind[self.curr_int_sol] = [ + 1, + len(self.mip.MindtPy_utils.cuts.oa_cuts), + ] elif config.init_strategy == 'FP': self.init_rNLP() self.fp_loop() diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index 5e4e378d6c5..4733843d6a2 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -900,9 +900,10 @@ def LazyOACallback_gurobi(cb_m, cb_opt, cb_where, mindtpy_solver, config): # Your callback should be prepared to cut off solutions that violate any of your lazy constraints, including those that have already been added. Node solutions will usually respect previously added lazy constraints, but not always. # https://www.gurobi.com/documentation/current/refman/cs_cb_addlazy.html # If this happens, MindtPy will look for the index of corresponding cuts, instead of solving the fixed-NLP again. - for ind in mindtpy_solver.int_sol_2_cuts_ind[ + begin_index, end_index = mindtpy_solver.int_sol_2_cuts_ind[ mindtpy_solver.curr_int_sol - ]: + ] + for ind in range(begin_index, end_index + 1): cb_opt.cbLazy(mindtpy_solver.mip.MindtPy_utils.cuts.oa_cuts[ind]) return else: @@ -917,11 +918,10 @@ def LazyOACallback_gurobi(cb_m, cb_opt, cb_where, mindtpy_solver, config): mindtpy_solver.handle_nlp_subproblem_tc(fixed_nlp, fixed_nlp_result, cb_opt) if config.strategy == 'OA': # store the cut index corresponding to current integer solution. - mindtpy_solver.int_sol_2_cuts_ind[mindtpy_solver.curr_int_sol] = list( - range( - cut_ind + 1, len(mindtpy_solver.mip.MindtPy_utils.cuts.oa_cuts) + 1 - ) - ) + mindtpy_solver.int_sol_2_cuts_ind[mindtpy_solver.curr_int_sol] = [ + cut_ind + 1, + len(mindtpy_solver.mip.MindtPy_utils.cuts.oa_cuts), + ] def handle_lazy_main_feasible_solution_gurobi(cb_m, cb_opt, mindtpy_solver, config): From 0f604f6c3595628f912358ea7ca61870f7dd1d49 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 28 Nov 2023 09:01:48 -0700 Subject: [PATCH 0507/1204] Change deprecation version to 6.7.0 --- pyomo/common/backports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/common/backports.py b/pyomo/common/backports.py index 0854715baeb..3349dfcce0a 100644 --- a/pyomo/common/backports.py +++ b/pyomo/common/backports.py @@ -12,5 +12,5 @@ from pyomo.common.deprecation import relocated_module_attribute relocated_module_attribute( - 'nullcontext', 'contextlib.nullcontext', version='6.7.0.dev0' + 'nullcontext', 'contextlib.nullcontext', version='6.7.0' ) From 83a4a4ef47a8bfed89507a3623a3b67a7a22efa5 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 28 Nov 2023 09:09:15 -0700 Subject: [PATCH 0508/1204] Update CHANGELOG; fix black snarking --- CHANGELOG.md | 15 ++++++--------- pyomo/common/backports.py | 4 +--- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36c1e635b11..d7a0fd59dae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,18 +7,13 @@ Pyomo 6.7.0 (28 Nov 2023) ------------------------------------------------------------------------------- - General - - Add Python 3.12 Support (#3050) + - Remove Python 3.7, add Python 3.12 Support (#3050, #2956) - Log which suffix values were skipped at the DEBUG level (#3043) - Update report_timing() to support context manager API (#3039) - - Update Performance Plot URL (#3033) - - Track change in Black rules (#3021) - - Remove Python 3.7 support (#2956) - - Fix 'because' typos (#3010) - Add `Preformatted` class for logging preformatted messages (#2998) - QuadraticRepnVisitor: Improve nonlinear expression expansion (#2997) - Add `CITATION` file to main repository (#2992) - - LINTING: New Version of `crate-ci/typos` (#2987) - - Minor typo / formatting fixes (#2975) + - Minor typo / formatting fixes (#3010, #2975) - Core - Fix exception from interaction of Gurobi, Pint, Dask, and Threading (#3026) - Fix differentiation of `Expressions` with `native_numeric_types` (#3017) @@ -37,11 +32,13 @@ Pyomo 6.7.0 (28 Nov 2023) - LP writer: warn user for ignored suffixes (#2982) - Update handling of `0*` in linear, quadratic walkers (#2981) - Testing + - Update Performance Plot URL (#3033) + - Track change in Black rules (#3021) - Resolve build infrastructure errors (with mpi4py, gams, networkx) (#3018) - - Improve GHA conda env package setup (#3013) + - Improve GHA conda env package setup (#3013, #2967) - Update Gurobi license checks in tests (#3011) - Skip `fileutils` test failure that persists in OSX 12.7 (#3008) - - GHA: Improve conda environment setup time (#2967) + - LINTING: New Version of `crate-ci/typos` (#2987) - GDP - Improve Disjunction construction error for invalid types (#3042) - Adding new walker for compute_bounds_on_expr (#3027) diff --git a/pyomo/common/backports.py b/pyomo/common/backports.py index 3349dfcce0a..36f2dac87ab 100644 --- a/pyomo/common/backports.py +++ b/pyomo/common/backports.py @@ -11,6 +11,4 @@ from pyomo.common.deprecation import relocated_module_attribute -relocated_module_attribute( - 'nullcontext', 'contextlib.nullcontext', version='6.7.0' -) +relocated_module_attribute('nullcontext', 'contextlib.nullcontext', version='6.7.0') From 30abeff34656ddc944636d23aea7f0d317dd15c0 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 28 Nov 2023 09:11:22 -0700 Subject: [PATCH 0509/1204] Update for compatibility with kernel API --- pyomo/repn/linear.py | 14 ++++++++++---- pyomo/repn/plugins/nl_writer.py | 14 ++++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/pyomo/repn/linear.py b/pyomo/repn/linear.py index 27c256f9f43..771337e2cf0 100644 --- a/pyomo/repn/linear.py +++ b/pyomo/repn/linear.py @@ -592,7 +592,13 @@ def _record_var(visitor, var): vm = visitor.var_map vo = visitor.var_order l = len(vo) - for v in var.values(visitor.sorter): + try: + _iter = var.parent_component().values(visitor.sorter) + except AttributeError: + # Note that this only works for the AML, as kernel does not + # provide a parent_component() + _iter = (var,) + for v in _iter: if v.fixed: continue vid = id(v) @@ -606,7 +612,7 @@ def _before_var(visitor, child): if _id not in visitor.var_map: if child.fixed: return False, (_CONSTANT, visitor.check_constant(child.value, child)) - LinearBeforeChildDispatcher._record_var(visitor, child.parent_component()) + LinearBeforeChildDispatcher._record_var(visitor, child) ans = visitor.Result() ans.linear[_id] = 1 return False, (_LINEAR, ans) @@ -635,7 +641,7 @@ def _before_monomial(visitor, child): _CONSTANT, arg1 * visitor.check_constant(arg2.value, arg2), ) - LinearBeforeChildDispatcher._record_var(visitor, arg2.parent_component()) + LinearBeforeChildDispatcher._record_var(visitor, arg2) # Trap multiplication by 0 and nan. if not arg1: @@ -691,7 +697,7 @@ def _before_linear(visitor, child): const += arg1 * visitor.check_constant(arg2.value, arg2) continue LinearBeforeChildDispatcher._record_var( - visitor, arg2.parent_component() + visitor, arg2 ) linear[_id] = arg1 elif _id in linear: diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 307a9ddaec6..59a83e61730 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -2610,7 +2610,13 @@ def _record_var(visitor, var): # set when constructing an expression, thereby altering the # order in which we would see the variables) vm = visitor.var_map - for v in var.values(visitor.sorter): + try: + _iter = var.parent_component().values(visitor.sorter) + except AttributeError: + # Note that this only works for the AML, as kernel does not + # provide a parent_component() + _iter = (var,) + for v in _iter: if v.fixed: continue vm[id(v)] = v @@ -2630,7 +2636,7 @@ def _before_var(visitor, child): if _id not in visitor.fixed_vars: visitor.cache_fixed_var(_id, child) return False, (_CONSTANT, visitor.fixed_vars[_id]) - _before_child_handlers._record_var(visitor, child.parent_component()) + _before_child_handlers._record_var(visitor, child) return False, (_MONOMIAL, _id, 1) @staticmethod @@ -2669,7 +2675,7 @@ def _before_monomial(visitor, child): if _id not in visitor.fixed_vars: visitor.cache_fixed_var(_id, arg2) return False, (_CONSTANT, arg1 * visitor.fixed_vars[_id]) - _before_child_handlers._record_var(visitor, arg2.parent_component()) + _before_child_handlers._record_var(visitor, arg2) return False, (_MONOMIAL, _id, arg1) @staticmethod @@ -2710,7 +2716,7 @@ def _before_linear(visitor, child): visitor.cache_fixed_var(_id, arg2) const += arg1 * visitor.fixed_vars[_id] continue - _before_child_handlers._record_var(visitor, arg2.parent_component()) + _before_child_handlers._record_var(visitor, arg2) linear[_id] = arg1 elif _id in linear: linear[_id] += arg1 From 6bc8cbd2e6081cd24edef6beb06f7defc94a1d7f Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 28 Nov 2023 09:11:56 -0700 Subject: [PATCH 0510/1204] Track change in the LinearRepnVisitor API --- pyomo/repn/plugins/standard_form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/repn/plugins/standard_form.py b/pyomo/repn/plugins/standard_form.py index 8440c1ab92d..c72661daaf0 100644 --- a/pyomo/repn/plugins/standard_form.py +++ b/pyomo/repn/plugins/standard_form.py @@ -265,7 +265,7 @@ def write(self, model): initialize_var_map_from_column_order(model, self.config, var_map) var_order = {_id: i for i, _id in enumerate(var_map)} - visitor = LinearRepnVisitor({}, var_map, var_order) + visitor = LinearRepnVisitor({}, var_map, var_order, sorter) timer.toc('Initialized column order', level=logging.DEBUG) From fb62b306bfdbc353cd109fe493bb621271b9f7ae Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 28 Nov 2023 09:12:24 -0700 Subject: [PATCH 0511/1204] Update LP baseline to reflect more deterministic output --- .../solvers/tests/piecewise_linear/indexed.lp | 22 +++++++++---------- pyomo/solvers/tests/piecewise_linear/step.lp | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pyomo/solvers/tests/piecewise_linear/indexed.lp b/pyomo/solvers/tests/piecewise_linear/indexed.lp index 32e9a0161fc..e73fb8331bd 100644 --- a/pyomo/solvers/tests/piecewise_linear/indexed.lp +++ b/pyomo/solvers/tests/piecewise_linear/indexed.lp @@ -25,11 +25,11 @@ c_e_linearized_constraint(0_1)_LOG_constraint2_: +0.49663531783502585 linearized_constraint(0_1)_LOG_lambda(2) +0.3836621854632263 linearized_constraint(0_1)_LOG_lambda(3) -0.7511436155469337 linearized_constraint(0_1)_LOG_lambda(4) ++1.0 linearized_constraint(0_1)_LOG_lambda(5) -0.8511436155469337 linearized_constraint(0_1)_LOG_lambda(6) +0.18366218546322624 linearized_constraint(0_1)_LOG_lambda(7) +0.1966353178350258 linearized_constraint(0_1)_LOG_lambda(8) -1.0390715290764525 linearized_constraint(0_1)_LOG_lambda(9) -+1.0 linearized_constraint(0_1)_LOG_lambda(5) = 0 c_e_linearized_constraint(0_1)_LOG_constraint3_: @@ -37,11 +37,11 @@ c_e_linearized_constraint(0_1)_LOG_constraint3_: +1 linearized_constraint(0_1)_LOG_lambda(2) +1 linearized_constraint(0_1)_LOG_lambda(3) +1 linearized_constraint(0_1)_LOG_lambda(4) ++1 linearized_constraint(0_1)_LOG_lambda(5) +1 linearized_constraint(0_1)_LOG_lambda(6) +1 linearized_constraint(0_1)_LOG_lambda(7) +1 linearized_constraint(0_1)_LOG_lambda(8) +1 linearized_constraint(0_1)_LOG_lambda(9) -+1 linearized_constraint(0_1)_LOG_lambda(5) = 1 c_u_linearized_constraint(0_1)_LOG_constraint4(1)_: @@ -54,8 +54,8 @@ c_u_linearized_constraint(0_1)_LOG_constraint4(1)_: c_u_linearized_constraint(0_1)_LOG_constraint4(2)_: +1 linearized_constraint(0_1)_LOG_lambda(4) -+1 linearized_constraint(0_1)_LOG_lambda(6) +1 linearized_constraint(0_1)_LOG_lambda(5) ++1 linearized_constraint(0_1)_LOG_lambda(6) -1 linearized_constraint(0_1)_LOG_bin_y(2) <= 0 @@ -83,8 +83,8 @@ c_u_linearized_constraint(0_1)_LOG_constraint5(2)_: c_u_linearized_constraint(0_1)_LOG_constraint5(3)_: +1 linearized_constraint(0_1)_LOG_lambda(1) -+1 linearized_constraint(0_1)_LOG_lambda(9) +1 linearized_constraint(0_1)_LOG_lambda(5) ++1 linearized_constraint(0_1)_LOG_lambda(9) +1 linearized_constraint(0_1)_LOG_bin_y(3) <= 1 @@ -106,11 +106,11 @@ c_e_linearized_constraint(8_3)_LOG_constraint2_: +0.49663531783502585 linearized_constraint(8_3)_LOG_lambda(2) +0.3836621854632263 linearized_constraint(8_3)_LOG_lambda(3) -0.7511436155469337 linearized_constraint(8_3)_LOG_lambda(4) ++1.0 linearized_constraint(8_3)_LOG_lambda(5) -0.8511436155469337 linearized_constraint(8_3)_LOG_lambda(6) +0.18366218546322624 linearized_constraint(8_3)_LOG_lambda(7) +0.1966353178350258 linearized_constraint(8_3)_LOG_lambda(8) -1.0390715290764525 linearized_constraint(8_3)_LOG_lambda(9) -+1.0 linearized_constraint(8_3)_LOG_lambda(5) = 0 c_e_linearized_constraint(8_3)_LOG_constraint3_: @@ -118,11 +118,11 @@ c_e_linearized_constraint(8_3)_LOG_constraint3_: +1 linearized_constraint(8_3)_LOG_lambda(2) +1 linearized_constraint(8_3)_LOG_lambda(3) +1 linearized_constraint(8_3)_LOG_lambda(4) ++1 linearized_constraint(8_3)_LOG_lambda(5) +1 linearized_constraint(8_3)_LOG_lambda(6) +1 linearized_constraint(8_3)_LOG_lambda(7) +1 linearized_constraint(8_3)_LOG_lambda(8) +1 linearized_constraint(8_3)_LOG_lambda(9) -+1 linearized_constraint(8_3)_LOG_lambda(5) = 1 c_u_linearized_constraint(8_3)_LOG_constraint4(1)_: @@ -135,8 +135,8 @@ c_u_linearized_constraint(8_3)_LOG_constraint4(1)_: c_u_linearized_constraint(8_3)_LOG_constraint4(2)_: +1 linearized_constraint(8_3)_LOG_lambda(4) -+1 linearized_constraint(8_3)_LOG_lambda(6) +1 linearized_constraint(8_3)_LOG_lambda(5) ++1 linearized_constraint(8_3)_LOG_lambda(6) -1 linearized_constraint(8_3)_LOG_bin_y(2) <= 0 @@ -164,8 +164,8 @@ c_u_linearized_constraint(8_3)_LOG_constraint5(2)_: c_u_linearized_constraint(8_3)_LOG_constraint5(3)_: +1 linearized_constraint(8_3)_LOG_lambda(1) -+1 linearized_constraint(8_3)_LOG_lambda(9) +1 linearized_constraint(8_3)_LOG_lambda(5) ++1 linearized_constraint(8_3)_LOG_lambda(9) +1 linearized_constraint(8_3)_LOG_bin_y(3) <= 1 @@ -173,28 +173,28 @@ bounds -inf <= Z(0_1) <= +inf -inf <= Z(8_3) <= +inf -2 <= X(0_1) <= 2 + -2 <= X(8_3) <= 2 0 <= linearized_constraint(0_1)_LOG_lambda(1) <= +inf 0 <= linearized_constraint(0_1)_LOG_lambda(2) <= +inf 0 <= linearized_constraint(0_1)_LOG_lambda(3) <= +inf 0 <= linearized_constraint(0_1)_LOG_lambda(4) <= +inf + 0 <= linearized_constraint(0_1)_LOG_lambda(5) <= +inf 0 <= linearized_constraint(0_1)_LOG_lambda(6) <= +inf 0 <= linearized_constraint(0_1)_LOG_lambda(7) <= +inf 0 <= linearized_constraint(0_1)_LOG_lambda(8) <= +inf 0 <= linearized_constraint(0_1)_LOG_lambda(9) <= +inf - 0 <= linearized_constraint(0_1)_LOG_lambda(5) <= +inf 0 <= linearized_constraint(0_1)_LOG_bin_y(1) <= 1 0 <= linearized_constraint(0_1)_LOG_bin_y(2) <= 1 0 <= linearized_constraint(0_1)_LOG_bin_y(3) <= 1 - -2 <= X(8_3) <= 2 0 <= linearized_constraint(8_3)_LOG_lambda(1) <= +inf 0 <= linearized_constraint(8_3)_LOG_lambda(2) <= +inf 0 <= linearized_constraint(8_3)_LOG_lambda(3) <= +inf 0 <= linearized_constraint(8_3)_LOG_lambda(4) <= +inf + 0 <= linearized_constraint(8_3)_LOG_lambda(5) <= +inf 0 <= linearized_constraint(8_3)_LOG_lambda(6) <= +inf 0 <= linearized_constraint(8_3)_LOG_lambda(7) <= +inf 0 <= linearized_constraint(8_3)_LOG_lambda(8) <= +inf 0 <= linearized_constraint(8_3)_LOG_lambda(9) <= +inf - 0 <= linearized_constraint(8_3)_LOG_lambda(5) <= +inf 0 <= linearized_constraint(8_3)_LOG_bin_y(1) <= 1 0 <= linearized_constraint(8_3)_LOG_bin_y(2) <= 1 0 <= linearized_constraint(8_3)_LOG_bin_y(3) <= 1 diff --git a/pyomo/solvers/tests/piecewise_linear/step.lp b/pyomo/solvers/tests/piecewise_linear/step.lp index 7ecd9e7e34e..68574f9658e 100644 --- a/pyomo/solvers/tests/piecewise_linear/step.lp +++ b/pyomo/solvers/tests/piecewise_linear/step.lp @@ -64,10 +64,10 @@ bounds -inf <= Z <= +inf 0 <= X <= 3 -inf <= con_INC_delta(1) <= 1 - -inf <= con_INC_delta(3) <= +inf - 0 <= con_INC_delta(5) <= +inf -inf <= con_INC_delta(2) <= +inf + -inf <= con_INC_delta(3) <= +inf -inf <= con_INC_delta(4) <= +inf + 0 <= con_INC_delta(5) <= +inf 0 <= con_INC_bin_y(1) <= 1 0 <= con_INC_bin_y(2) <= 1 0 <= con_INC_bin_y(3) <= 1 From 65242b7bff0255b3525855d548644a164b65d566 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 28 Nov 2023 09:18:52 -0700 Subject: [PATCH 0512/1204] Temporary hack to get gurobipy working again; 11.0.0 causes failures --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index c7b647aaa56..c20fbc625b7 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -255,7 +255,7 @@ jobs: python -m pip install --cache-dir cache/pip cplex docplex \ || echo "WARNING: CPLEX Community Edition is not available" python -m pip install --cache-dir cache/pip \ - -i https://pypi.gurobi.com gurobipy \ + -i https://pypi.gurobi.com gurobipy==10.0.3 \ || echo "WARNING: Gurobi is not available" python -m pip install --cache-dir cache/pip xpress \ || echo "WARNING: Xpress Community Edition is not available" diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index fa5553de71c..6349d8bb15e 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -285,7 +285,7 @@ jobs: python -m pip install --cache-dir cache/pip cplex docplex \ || echo "WARNING: CPLEX Community Edition is not available" python -m pip install --cache-dir cache/pip \ - -i https://pypi.gurobi.com gurobipy \ + -i https://pypi.gurobi.com gurobipy==10.0.3 \ || echo "WARNING: Gurobi is not available" python -m pip install --cache-dir cache/pip xpress \ || echo "WARNING: Xpress Community Edition is not available" From a70162e5c1e1390ecb71b019c0fd2b8cc6834ee0 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 28 Nov 2023 09:26:38 -0700 Subject: [PATCH 0513/1204] NFC: apply black --- pyomo/repn/linear.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyomo/repn/linear.py b/pyomo/repn/linear.py index 771337e2cf0..59bc0b58d99 100644 --- a/pyomo/repn/linear.py +++ b/pyomo/repn/linear.py @@ -696,9 +696,7 @@ def _before_linear(visitor, child): if arg2.fixed: const += arg1 * visitor.check_constant(arg2.value, arg2) continue - LinearBeforeChildDispatcher._record_var( - visitor, arg2 - ) + LinearBeforeChildDispatcher._record_var(visitor, arg2) linear[_id] = arg1 elif _id in linear: linear[_id] += arg1 From 20d98ac24ed4b122546b7cb15793880771504418 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 28 Nov 2023 10:08:44 -0700 Subject: [PATCH 0514/1204] Missed a pinning location --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index c20fbc625b7..f3f19b78591 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -331,7 +331,7 @@ jobs: if test -z "${{matrix.slim}}"; then PYVER=$(echo "py${{matrix.python}}" | sed 's/\.//g') echo "Installing for $PYVER" - for PKG in 'cplex>=12.10' docplex gurobi xpress cyipopt pymumps scip; do + for PKG in 'cplex>=12.10' docplex 'gurobi=10.0.3' xpress cyipopt pymumps scip; do echo "" echo "*** Install $PKG ***" # conda can literally take an hour to determine that a diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 6349d8bb15e..13dc828c639 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -361,7 +361,7 @@ jobs: if test -z "${{matrix.slim}}"; then PYVER=$(echo "py${{matrix.python}}" | sed 's/\.//g') echo "Installing for $PYVER" - for PKG in 'cplex>=12.10' docplex gurobi xpress cyipopt pymumps scip; do + for PKG in 'cplex>=12.10' docplex 'gurobi=10.0.3' xpress cyipopt pymumps scip; do echo "" echo "*** Install $PKG ***" # conda can literally take an hour to determine that a From 8de219001fd2800ed567102de9d810d42d78f933 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 28 Nov 2023 11:19:42 -0700 Subject: [PATCH 0515/1204] Update CHANGELOG to reflect 3053 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7a0fd59dae..1936b356905 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Pyomo 6.7.0 (28 Nov 2023) - LP writer: warn user for ignored suffixes (#2982) - Update handling of `0*` in linear, quadratic walkers (#2981) - Testing + - Pin `gurobipy` version for testing to 10.0.3 (#3053) - Update Performance Plot URL (#3033) - Track change in Black rules (#3021) - Resolve build infrastructure errors (with mpi4py, gams, networkx) (#3018) From 1aaf571fde8a27d4df75745cc45aa5c56ada6a67 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 28 Nov 2023 13:08:03 -0700 Subject: [PATCH 0516/1204] Update baselsine to reflect improved writer determinism --- pyomo/gdp/tests/jobshop_large_hull.lp | 476 +++++++++++++------------- pyomo/gdp/tests/jobshop_small_hull.lp | 28 +- 2 files changed, 252 insertions(+), 252 deletions(-) diff --git a/pyomo/gdp/tests/jobshop_large_hull.lp b/pyomo/gdp/tests/jobshop_large_hull.lp index 983770880b7..df3833bdee3 100644 --- a/pyomo/gdp/tests/jobshop_large_hull.lp +++ b/pyomo/gdp/tests/jobshop_large_hull.lp @@ -461,89 +461,89 @@ c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(69)_: -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_disaggregatedVars__t(A)_ = 0 -c_e__pyomo_gdp_hull_reformulation_disj_xor(F_G_4)_: -+1 NoClash(F_G_4_0)_binary_indicator_var -+1 NoClash(F_G_4_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(A_B_3)_: ++1 NoClash(A_B_3_0)_binary_indicator_var ++1 NoClash(A_B_3_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(E_G_5)_: -+1 NoClash(E_G_5_0)_binary_indicator_var -+1 NoClash(E_G_5_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(A_B_5)_: ++1 NoClash(A_B_5_0)_binary_indicator_var ++1 NoClash(A_B_5_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(E_G_2)_: -+1 NoClash(E_G_2_0)_binary_indicator_var -+1 NoClash(E_G_2_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(A_C_1)_: ++1 NoClash(A_C_1_0)_binary_indicator_var ++1 NoClash(A_C_1_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(E_F_3)_: -+1 NoClash(E_F_3_0)_binary_indicator_var -+1 NoClash(E_F_3_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(A_D_3)_: ++1 NoClash(A_D_3_0)_binary_indicator_var ++1 NoClash(A_D_3_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(D_G_4)_: -+1 NoClash(D_G_4_0)_binary_indicator_var -+1 NoClash(D_G_4_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(A_E_3)_: ++1 NoClash(A_E_3_0)_binary_indicator_var ++1 NoClash(A_E_3_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(D_G_2)_: -+1 NoClash(D_G_2_0)_binary_indicator_var -+1 NoClash(D_G_2_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(A_E_5)_: ++1 NoClash(A_E_5_0)_binary_indicator_var ++1 NoClash(A_E_5_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(D_F_4)_: -+1 NoClash(D_F_4_0)_binary_indicator_var -+1 NoClash(D_F_4_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(A_F_1)_: ++1 NoClash(A_F_1_0)_binary_indicator_var ++1 NoClash(A_F_1_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(D_F_3)_: -+1 NoClash(D_F_3_0)_binary_indicator_var -+1 NoClash(D_F_3_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(A_F_3)_: ++1 NoClash(A_F_3_0)_binary_indicator_var ++1 NoClash(A_F_3_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(D_E_3)_: -+1 NoClash(D_E_3_0)_binary_indicator_var -+1 NoClash(D_E_3_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(A_G_5)_: ++1 NoClash(A_G_5_0)_binary_indicator_var ++1 NoClash(A_G_5_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(D_E_2)_: -+1 NoClash(D_E_2_0)_binary_indicator_var -+1 NoClash(D_E_2_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(B_C_2)_: ++1 NoClash(B_C_2_0)_binary_indicator_var ++1 NoClash(B_C_2_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(C_G_4)_: -+1 NoClash(C_G_4_0)_binary_indicator_var -+1 NoClash(C_G_4_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(B_D_2)_: ++1 NoClash(B_D_2_0)_binary_indicator_var ++1 NoClash(B_D_2_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(C_G_2)_: -+1 NoClash(C_G_2_0)_binary_indicator_var -+1 NoClash(C_G_2_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(B_D_3)_: ++1 NoClash(B_D_3_0)_binary_indicator_var ++1 NoClash(B_D_3_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(C_F_4)_: -+1 NoClash(C_F_4_0)_binary_indicator_var -+1 NoClash(C_F_4_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(B_E_2)_: ++1 NoClash(B_E_2_0)_binary_indicator_var ++1 NoClash(B_E_2_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(C_F_1)_: -+1 NoClash(C_F_1_0)_binary_indicator_var -+1 NoClash(C_F_1_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(B_E_3)_: ++1 NoClash(B_E_3_0)_binary_indicator_var ++1 NoClash(B_E_3_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(C_E_2)_: -+1 NoClash(C_E_2_0)_binary_indicator_var -+1 NoClash(C_E_2_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(B_E_5)_: ++1 NoClash(B_E_5_0)_binary_indicator_var ++1 NoClash(B_E_5_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(C_D_4)_: -+1 NoClash(C_D_4_0)_binary_indicator_var -+1 NoClash(C_D_4_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(B_F_3)_: ++1 NoClash(B_F_3_0)_binary_indicator_var ++1 NoClash(B_F_3_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(C_D_2)_: -+1 NoClash(C_D_2_0)_binary_indicator_var -+1 NoClash(C_D_2_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(B_G_2)_: ++1 NoClash(B_G_2_0)_binary_indicator_var ++1 NoClash(B_G_2_1)_binary_indicator_var = 1 c_e__pyomo_gdp_hull_reformulation_disj_xor(B_G_5)_: @@ -551,89 +551,89 @@ c_e__pyomo_gdp_hull_reformulation_disj_xor(B_G_5)_: +1 NoClash(B_G_5_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(B_G_2)_: -+1 NoClash(B_G_2_0)_binary_indicator_var -+1 NoClash(B_G_2_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(C_D_2)_: ++1 NoClash(C_D_2_0)_binary_indicator_var ++1 NoClash(C_D_2_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(B_F_3)_: -+1 NoClash(B_F_3_0)_binary_indicator_var -+1 NoClash(B_F_3_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(C_D_4)_: ++1 NoClash(C_D_4_0)_binary_indicator_var ++1 NoClash(C_D_4_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(B_E_5)_: -+1 NoClash(B_E_5_0)_binary_indicator_var -+1 NoClash(B_E_5_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(C_E_2)_: ++1 NoClash(C_E_2_0)_binary_indicator_var ++1 NoClash(C_E_2_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(B_E_3)_: -+1 NoClash(B_E_3_0)_binary_indicator_var -+1 NoClash(B_E_3_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(C_F_1)_: ++1 NoClash(C_F_1_0)_binary_indicator_var ++1 NoClash(C_F_1_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(B_E_2)_: -+1 NoClash(B_E_2_0)_binary_indicator_var -+1 NoClash(B_E_2_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(C_F_4)_: ++1 NoClash(C_F_4_0)_binary_indicator_var ++1 NoClash(C_F_4_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(B_D_3)_: -+1 NoClash(B_D_3_0)_binary_indicator_var -+1 NoClash(B_D_3_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(C_G_2)_: ++1 NoClash(C_G_2_0)_binary_indicator_var ++1 NoClash(C_G_2_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(B_D_2)_: -+1 NoClash(B_D_2_0)_binary_indicator_var -+1 NoClash(B_D_2_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(C_G_4)_: ++1 NoClash(C_G_4_0)_binary_indicator_var ++1 NoClash(C_G_4_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(B_C_2)_: -+1 NoClash(B_C_2_0)_binary_indicator_var -+1 NoClash(B_C_2_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(D_E_2)_: ++1 NoClash(D_E_2_0)_binary_indicator_var ++1 NoClash(D_E_2_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(A_G_5)_: -+1 NoClash(A_G_5_0)_binary_indicator_var -+1 NoClash(A_G_5_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(D_E_3)_: ++1 NoClash(D_E_3_0)_binary_indicator_var ++1 NoClash(D_E_3_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(A_F_3)_: -+1 NoClash(A_F_3_0)_binary_indicator_var -+1 NoClash(A_F_3_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(D_F_3)_: ++1 NoClash(D_F_3_0)_binary_indicator_var ++1 NoClash(D_F_3_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(A_F_1)_: -+1 NoClash(A_F_1_0)_binary_indicator_var -+1 NoClash(A_F_1_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(D_F_4)_: ++1 NoClash(D_F_4_0)_binary_indicator_var ++1 NoClash(D_F_4_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(A_E_5)_: -+1 NoClash(A_E_5_0)_binary_indicator_var -+1 NoClash(A_E_5_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(D_G_2)_: ++1 NoClash(D_G_2_0)_binary_indicator_var ++1 NoClash(D_G_2_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(A_E_3)_: -+1 NoClash(A_E_3_0)_binary_indicator_var -+1 NoClash(A_E_3_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(D_G_4)_: ++1 NoClash(D_G_4_0)_binary_indicator_var ++1 NoClash(D_G_4_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(A_D_3)_: -+1 NoClash(A_D_3_0)_binary_indicator_var -+1 NoClash(A_D_3_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(E_F_3)_: ++1 NoClash(E_F_3_0)_binary_indicator_var ++1 NoClash(E_F_3_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(A_C_1)_: -+1 NoClash(A_C_1_0)_binary_indicator_var -+1 NoClash(A_C_1_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(E_G_2)_: ++1 NoClash(E_G_2_0)_binary_indicator_var ++1 NoClash(E_G_2_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(A_B_5)_: -+1 NoClash(A_B_5_0)_binary_indicator_var -+1 NoClash(A_B_5_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(E_G_5)_: ++1 NoClash(E_G_5_0)_binary_indicator_var ++1 NoClash(E_G_5_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(A_B_3)_: -+1 NoClash(A_B_3_0)_binary_indicator_var -+1 NoClash(A_B_3_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(F_G_4)_: ++1 NoClash(F_G_4_0)_binary_indicator_var ++1 NoClash(F_G_4_1)_binary_indicator_var = 1 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_transformedConstraints(c_0_ub)_: @@ -1901,145 +1901,145 @@ bounds 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_disaggregatedVars__t(B)_ <= 92 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)_disaggregatedVars__t(A)_ <= 92 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_disaggregatedVars__t(A)_ <= 92 - 0 <= NoClash(F_G_4_0)_binary_indicator_var <= 1 - 0 <= NoClash(F_G_4_1)_binary_indicator_var <= 1 - 0 <= NoClash(E_G_5_0)_binary_indicator_var <= 1 - 0 <= NoClash(E_G_5_1)_binary_indicator_var <= 1 - 0 <= NoClash(E_G_2_0)_binary_indicator_var <= 1 - 0 <= NoClash(E_G_2_1)_binary_indicator_var <= 1 - 0 <= NoClash(E_F_3_0)_binary_indicator_var <= 1 - 0 <= NoClash(E_F_3_1)_binary_indicator_var <= 1 - 0 <= NoClash(D_G_4_0)_binary_indicator_var <= 1 - 0 <= NoClash(D_G_4_1)_binary_indicator_var <= 1 - 0 <= NoClash(D_G_2_0)_binary_indicator_var <= 1 - 0 <= NoClash(D_G_2_1)_binary_indicator_var <= 1 - 0 <= NoClash(D_F_4_0)_binary_indicator_var <= 1 - 0 <= NoClash(D_F_4_1)_binary_indicator_var <= 1 - 0 <= NoClash(D_F_3_0)_binary_indicator_var <= 1 - 0 <= NoClash(D_F_3_1)_binary_indicator_var <= 1 - 0 <= NoClash(D_E_3_0)_binary_indicator_var <= 1 - 0 <= NoClash(D_E_3_1)_binary_indicator_var <= 1 - 0 <= NoClash(D_E_2_0)_binary_indicator_var <= 1 - 0 <= NoClash(D_E_2_1)_binary_indicator_var <= 1 - 0 <= NoClash(C_G_4_0)_binary_indicator_var <= 1 - 0 <= NoClash(C_G_4_1)_binary_indicator_var <= 1 - 0 <= NoClash(C_G_2_0)_binary_indicator_var <= 1 - 0 <= NoClash(C_G_2_1)_binary_indicator_var <= 1 - 0 <= NoClash(C_F_4_0)_binary_indicator_var <= 1 - 0 <= NoClash(C_F_4_1)_binary_indicator_var <= 1 - 0 <= NoClash(C_F_1_0)_binary_indicator_var <= 1 - 0 <= NoClash(C_F_1_1)_binary_indicator_var <= 1 - 0 <= NoClash(C_E_2_0)_binary_indicator_var <= 1 - 0 <= NoClash(C_E_2_1)_binary_indicator_var <= 1 - 0 <= NoClash(C_D_4_0)_binary_indicator_var <= 1 - 0 <= NoClash(C_D_4_1)_binary_indicator_var <= 1 - 0 <= NoClash(C_D_2_0)_binary_indicator_var <= 1 - 0 <= NoClash(C_D_2_1)_binary_indicator_var <= 1 - 0 <= NoClash(B_G_5_0)_binary_indicator_var <= 1 - 0 <= NoClash(B_G_5_1)_binary_indicator_var <= 1 - 0 <= NoClash(B_G_2_0)_binary_indicator_var <= 1 - 0 <= NoClash(B_G_2_1)_binary_indicator_var <= 1 - 0 <= NoClash(B_F_3_0)_binary_indicator_var <= 1 - 0 <= NoClash(B_F_3_1)_binary_indicator_var <= 1 - 0 <= NoClash(B_E_5_0)_binary_indicator_var <= 1 - 0 <= NoClash(B_E_5_1)_binary_indicator_var <= 1 - 0 <= NoClash(B_E_3_0)_binary_indicator_var <= 1 - 0 <= NoClash(B_E_3_1)_binary_indicator_var <= 1 - 0 <= NoClash(B_E_2_0)_binary_indicator_var <= 1 - 0 <= NoClash(B_E_2_1)_binary_indicator_var <= 1 - 0 <= NoClash(B_D_3_0)_binary_indicator_var <= 1 - 0 <= NoClash(B_D_3_1)_binary_indicator_var <= 1 - 0 <= NoClash(B_D_2_0)_binary_indicator_var <= 1 - 0 <= NoClash(B_D_2_1)_binary_indicator_var <= 1 - 0 <= NoClash(B_C_2_0)_binary_indicator_var <= 1 - 0 <= NoClash(B_C_2_1)_binary_indicator_var <= 1 - 0 <= NoClash(A_G_5_0)_binary_indicator_var <= 1 - 0 <= NoClash(A_G_5_1)_binary_indicator_var <= 1 - 0 <= NoClash(A_F_3_0)_binary_indicator_var <= 1 - 0 <= NoClash(A_F_3_1)_binary_indicator_var <= 1 - 0 <= NoClash(A_F_1_0)_binary_indicator_var <= 1 - 0 <= NoClash(A_F_1_1)_binary_indicator_var <= 1 - 0 <= NoClash(A_E_5_0)_binary_indicator_var <= 1 - 0 <= NoClash(A_E_5_1)_binary_indicator_var <= 1 - 0 <= NoClash(A_E_3_0)_binary_indicator_var <= 1 - 0 <= NoClash(A_E_3_1)_binary_indicator_var <= 1 - 0 <= NoClash(A_D_3_0)_binary_indicator_var <= 1 - 0 <= NoClash(A_D_3_1)_binary_indicator_var <= 1 - 0 <= NoClash(A_C_1_0)_binary_indicator_var <= 1 - 0 <= NoClash(A_C_1_1)_binary_indicator_var <= 1 - 0 <= NoClash(A_B_5_0)_binary_indicator_var <= 1 - 0 <= NoClash(A_B_5_1)_binary_indicator_var <= 1 0 <= NoClash(A_B_3_0)_binary_indicator_var <= 1 0 <= NoClash(A_B_3_1)_binary_indicator_var <= 1 + 0 <= NoClash(A_B_5_0)_binary_indicator_var <= 1 + 0 <= NoClash(A_B_5_1)_binary_indicator_var <= 1 + 0 <= NoClash(A_C_1_0)_binary_indicator_var <= 1 + 0 <= NoClash(A_C_1_1)_binary_indicator_var <= 1 + 0 <= NoClash(A_D_3_0)_binary_indicator_var <= 1 + 0 <= NoClash(A_D_3_1)_binary_indicator_var <= 1 + 0 <= NoClash(A_E_3_0)_binary_indicator_var <= 1 + 0 <= NoClash(A_E_3_1)_binary_indicator_var <= 1 + 0 <= NoClash(A_E_5_0)_binary_indicator_var <= 1 + 0 <= NoClash(A_E_5_1)_binary_indicator_var <= 1 + 0 <= NoClash(A_F_1_0)_binary_indicator_var <= 1 + 0 <= NoClash(A_F_1_1)_binary_indicator_var <= 1 + 0 <= NoClash(A_F_3_0)_binary_indicator_var <= 1 + 0 <= NoClash(A_F_3_1)_binary_indicator_var <= 1 + 0 <= NoClash(A_G_5_0)_binary_indicator_var <= 1 + 0 <= NoClash(A_G_5_1)_binary_indicator_var <= 1 + 0 <= NoClash(B_C_2_0)_binary_indicator_var <= 1 + 0 <= NoClash(B_C_2_1)_binary_indicator_var <= 1 + 0 <= NoClash(B_D_2_0)_binary_indicator_var <= 1 + 0 <= NoClash(B_D_2_1)_binary_indicator_var <= 1 + 0 <= NoClash(B_D_3_0)_binary_indicator_var <= 1 + 0 <= NoClash(B_D_3_1)_binary_indicator_var <= 1 + 0 <= NoClash(B_E_2_0)_binary_indicator_var <= 1 + 0 <= NoClash(B_E_2_1)_binary_indicator_var <= 1 + 0 <= NoClash(B_E_3_0)_binary_indicator_var <= 1 + 0 <= NoClash(B_E_3_1)_binary_indicator_var <= 1 + 0 <= NoClash(B_E_5_0)_binary_indicator_var <= 1 + 0 <= NoClash(B_E_5_1)_binary_indicator_var <= 1 + 0 <= NoClash(B_F_3_0)_binary_indicator_var <= 1 + 0 <= NoClash(B_F_3_1)_binary_indicator_var <= 1 + 0 <= NoClash(B_G_2_0)_binary_indicator_var <= 1 + 0 <= NoClash(B_G_2_1)_binary_indicator_var <= 1 + 0 <= NoClash(B_G_5_0)_binary_indicator_var <= 1 + 0 <= NoClash(B_G_5_1)_binary_indicator_var <= 1 + 0 <= NoClash(C_D_2_0)_binary_indicator_var <= 1 + 0 <= NoClash(C_D_2_1)_binary_indicator_var <= 1 + 0 <= NoClash(C_D_4_0)_binary_indicator_var <= 1 + 0 <= NoClash(C_D_4_1)_binary_indicator_var <= 1 + 0 <= NoClash(C_E_2_0)_binary_indicator_var <= 1 + 0 <= NoClash(C_E_2_1)_binary_indicator_var <= 1 + 0 <= NoClash(C_F_1_0)_binary_indicator_var <= 1 + 0 <= NoClash(C_F_1_1)_binary_indicator_var <= 1 + 0 <= NoClash(C_F_4_0)_binary_indicator_var <= 1 + 0 <= NoClash(C_F_4_1)_binary_indicator_var <= 1 + 0 <= NoClash(C_G_2_0)_binary_indicator_var <= 1 + 0 <= NoClash(C_G_2_1)_binary_indicator_var <= 1 + 0 <= NoClash(C_G_4_0)_binary_indicator_var <= 1 + 0 <= NoClash(C_G_4_1)_binary_indicator_var <= 1 + 0 <= NoClash(D_E_2_0)_binary_indicator_var <= 1 + 0 <= NoClash(D_E_2_1)_binary_indicator_var <= 1 + 0 <= NoClash(D_E_3_0)_binary_indicator_var <= 1 + 0 <= NoClash(D_E_3_1)_binary_indicator_var <= 1 + 0 <= NoClash(D_F_3_0)_binary_indicator_var <= 1 + 0 <= NoClash(D_F_3_1)_binary_indicator_var <= 1 + 0 <= NoClash(D_F_4_0)_binary_indicator_var <= 1 + 0 <= NoClash(D_F_4_1)_binary_indicator_var <= 1 + 0 <= NoClash(D_G_2_0)_binary_indicator_var <= 1 + 0 <= NoClash(D_G_2_1)_binary_indicator_var <= 1 + 0 <= NoClash(D_G_4_0)_binary_indicator_var <= 1 + 0 <= NoClash(D_G_4_1)_binary_indicator_var <= 1 + 0 <= NoClash(E_F_3_0)_binary_indicator_var <= 1 + 0 <= NoClash(E_F_3_1)_binary_indicator_var <= 1 + 0 <= NoClash(E_G_2_0)_binary_indicator_var <= 1 + 0 <= NoClash(E_G_2_1)_binary_indicator_var <= 1 + 0 <= NoClash(E_G_5_0)_binary_indicator_var <= 1 + 0 <= NoClash(E_G_5_1)_binary_indicator_var <= 1 + 0 <= NoClash(F_G_4_0)_binary_indicator_var <= 1 + 0 <= NoClash(F_G_4_1)_binary_indicator_var <= 1 binary - NoClash(F_G_4_0)_binary_indicator_var - NoClash(F_G_4_1)_binary_indicator_var - NoClash(E_G_5_0)_binary_indicator_var - NoClash(E_G_5_1)_binary_indicator_var - NoClash(E_G_2_0)_binary_indicator_var - NoClash(E_G_2_1)_binary_indicator_var - NoClash(E_F_3_0)_binary_indicator_var - NoClash(E_F_3_1)_binary_indicator_var - NoClash(D_G_4_0)_binary_indicator_var - NoClash(D_G_4_1)_binary_indicator_var - NoClash(D_G_2_0)_binary_indicator_var - NoClash(D_G_2_1)_binary_indicator_var - NoClash(D_F_4_0)_binary_indicator_var - NoClash(D_F_4_1)_binary_indicator_var - NoClash(D_F_3_0)_binary_indicator_var - NoClash(D_F_3_1)_binary_indicator_var - NoClash(D_E_3_0)_binary_indicator_var - NoClash(D_E_3_1)_binary_indicator_var - NoClash(D_E_2_0)_binary_indicator_var - NoClash(D_E_2_1)_binary_indicator_var - NoClash(C_G_4_0)_binary_indicator_var - NoClash(C_G_4_1)_binary_indicator_var - NoClash(C_G_2_0)_binary_indicator_var - NoClash(C_G_2_1)_binary_indicator_var - NoClash(C_F_4_0)_binary_indicator_var - NoClash(C_F_4_1)_binary_indicator_var - NoClash(C_F_1_0)_binary_indicator_var - NoClash(C_F_1_1)_binary_indicator_var - NoClash(C_E_2_0)_binary_indicator_var - NoClash(C_E_2_1)_binary_indicator_var - NoClash(C_D_4_0)_binary_indicator_var - NoClash(C_D_4_1)_binary_indicator_var - NoClash(C_D_2_0)_binary_indicator_var - NoClash(C_D_2_1)_binary_indicator_var - NoClash(B_G_5_0)_binary_indicator_var - NoClash(B_G_5_1)_binary_indicator_var - NoClash(B_G_2_0)_binary_indicator_var - NoClash(B_G_2_1)_binary_indicator_var - NoClash(B_F_3_0)_binary_indicator_var - NoClash(B_F_3_1)_binary_indicator_var - NoClash(B_E_5_0)_binary_indicator_var - NoClash(B_E_5_1)_binary_indicator_var - NoClash(B_E_3_0)_binary_indicator_var - NoClash(B_E_3_1)_binary_indicator_var - NoClash(B_E_2_0)_binary_indicator_var - NoClash(B_E_2_1)_binary_indicator_var - NoClash(B_D_3_0)_binary_indicator_var - NoClash(B_D_3_1)_binary_indicator_var - NoClash(B_D_2_0)_binary_indicator_var - NoClash(B_D_2_1)_binary_indicator_var - NoClash(B_C_2_0)_binary_indicator_var - NoClash(B_C_2_1)_binary_indicator_var - NoClash(A_G_5_0)_binary_indicator_var - NoClash(A_G_5_1)_binary_indicator_var - NoClash(A_F_3_0)_binary_indicator_var - NoClash(A_F_3_1)_binary_indicator_var - NoClash(A_F_1_0)_binary_indicator_var - NoClash(A_F_1_1)_binary_indicator_var - NoClash(A_E_5_0)_binary_indicator_var - NoClash(A_E_5_1)_binary_indicator_var - NoClash(A_E_3_0)_binary_indicator_var - NoClash(A_E_3_1)_binary_indicator_var - NoClash(A_D_3_0)_binary_indicator_var - NoClash(A_D_3_1)_binary_indicator_var - NoClash(A_C_1_0)_binary_indicator_var - NoClash(A_C_1_1)_binary_indicator_var - NoClash(A_B_5_0)_binary_indicator_var - NoClash(A_B_5_1)_binary_indicator_var NoClash(A_B_3_0)_binary_indicator_var NoClash(A_B_3_1)_binary_indicator_var + NoClash(A_B_5_0)_binary_indicator_var + NoClash(A_B_5_1)_binary_indicator_var + NoClash(A_C_1_0)_binary_indicator_var + NoClash(A_C_1_1)_binary_indicator_var + NoClash(A_D_3_0)_binary_indicator_var + NoClash(A_D_3_1)_binary_indicator_var + NoClash(A_E_3_0)_binary_indicator_var + NoClash(A_E_3_1)_binary_indicator_var + NoClash(A_E_5_0)_binary_indicator_var + NoClash(A_E_5_1)_binary_indicator_var + NoClash(A_F_1_0)_binary_indicator_var + NoClash(A_F_1_1)_binary_indicator_var + NoClash(A_F_3_0)_binary_indicator_var + NoClash(A_F_3_1)_binary_indicator_var + NoClash(A_G_5_0)_binary_indicator_var + NoClash(A_G_5_1)_binary_indicator_var + NoClash(B_C_2_0)_binary_indicator_var + NoClash(B_C_2_1)_binary_indicator_var + NoClash(B_D_2_0)_binary_indicator_var + NoClash(B_D_2_1)_binary_indicator_var + NoClash(B_D_3_0)_binary_indicator_var + NoClash(B_D_3_1)_binary_indicator_var + NoClash(B_E_2_0)_binary_indicator_var + NoClash(B_E_2_1)_binary_indicator_var + NoClash(B_E_3_0)_binary_indicator_var + NoClash(B_E_3_1)_binary_indicator_var + NoClash(B_E_5_0)_binary_indicator_var + NoClash(B_E_5_1)_binary_indicator_var + NoClash(B_F_3_0)_binary_indicator_var + NoClash(B_F_3_1)_binary_indicator_var + NoClash(B_G_2_0)_binary_indicator_var + NoClash(B_G_2_1)_binary_indicator_var + NoClash(B_G_5_0)_binary_indicator_var + NoClash(B_G_5_1)_binary_indicator_var + NoClash(C_D_2_0)_binary_indicator_var + NoClash(C_D_2_1)_binary_indicator_var + NoClash(C_D_4_0)_binary_indicator_var + NoClash(C_D_4_1)_binary_indicator_var + NoClash(C_E_2_0)_binary_indicator_var + NoClash(C_E_2_1)_binary_indicator_var + NoClash(C_F_1_0)_binary_indicator_var + NoClash(C_F_1_1)_binary_indicator_var + NoClash(C_F_4_0)_binary_indicator_var + NoClash(C_F_4_1)_binary_indicator_var + NoClash(C_G_2_0)_binary_indicator_var + NoClash(C_G_2_1)_binary_indicator_var + NoClash(C_G_4_0)_binary_indicator_var + NoClash(C_G_4_1)_binary_indicator_var + NoClash(D_E_2_0)_binary_indicator_var + NoClash(D_E_2_1)_binary_indicator_var + NoClash(D_E_3_0)_binary_indicator_var + NoClash(D_E_3_1)_binary_indicator_var + NoClash(D_F_3_0)_binary_indicator_var + NoClash(D_F_3_1)_binary_indicator_var + NoClash(D_F_4_0)_binary_indicator_var + NoClash(D_F_4_1)_binary_indicator_var + NoClash(D_G_2_0)_binary_indicator_var + NoClash(D_G_2_1)_binary_indicator_var + NoClash(D_G_4_0)_binary_indicator_var + NoClash(D_G_4_1)_binary_indicator_var + NoClash(E_F_3_0)_binary_indicator_var + NoClash(E_F_3_1)_binary_indicator_var + NoClash(E_G_2_0)_binary_indicator_var + NoClash(E_G_2_1)_binary_indicator_var + NoClash(E_G_5_0)_binary_indicator_var + NoClash(E_G_5_1)_binary_indicator_var + NoClash(F_G_4_0)_binary_indicator_var + NoClash(F_G_4_1)_binary_indicator_var end diff --git a/pyomo/gdp/tests/jobshop_small_hull.lp b/pyomo/gdp/tests/jobshop_small_hull.lp index 95434e3122f..c07b9cd048e 100644 --- a/pyomo/gdp/tests/jobshop_small_hull.lp +++ b/pyomo/gdp/tests/jobshop_small_hull.lp @@ -57,9 +57,9 @@ c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(5)_: -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(A)_ = 0 -c_e__pyomo_gdp_hull_reformulation_disj_xor(B_C_2)_: -+1 NoClash(B_C_2_0)_binary_indicator_var -+1 NoClash(B_C_2_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(A_B_3)_: ++1 NoClash(A_B_3_0)_binary_indicator_var ++1 NoClash(A_B_3_1)_binary_indicator_var = 1 c_e__pyomo_gdp_hull_reformulation_disj_xor(A_C_1)_: @@ -67,9 +67,9 @@ c_e__pyomo_gdp_hull_reformulation_disj_xor(A_C_1)_: +1 NoClash(A_C_1_1)_binary_indicator_var = 1 -c_e__pyomo_gdp_hull_reformulation_disj_xor(A_B_3)_: -+1 NoClash(A_B_3_0)_binary_indicator_var -+1 NoClash(A_B_3_1)_binary_indicator_var +c_e__pyomo_gdp_hull_reformulation_disj_xor(B_C_2)_: ++1 NoClash(B_C_2_0)_binary_indicator_var ++1 NoClash(B_C_2_1)_binary_indicator_var = 1 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_transformedConstraints(c_0_ub)_: @@ -184,17 +184,17 @@ bounds 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(B)_ <= 19 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(A)_ <= 19 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(A)_ <= 19 - 0 <= NoClash(B_C_2_0)_binary_indicator_var <= 1 - 0 <= NoClash(B_C_2_1)_binary_indicator_var <= 1 - 0 <= NoClash(A_C_1_0)_binary_indicator_var <= 1 - 0 <= NoClash(A_C_1_1)_binary_indicator_var <= 1 0 <= NoClash(A_B_3_0)_binary_indicator_var <= 1 0 <= NoClash(A_B_3_1)_binary_indicator_var <= 1 + 0 <= NoClash(A_C_1_0)_binary_indicator_var <= 1 + 0 <= NoClash(A_C_1_1)_binary_indicator_var <= 1 + 0 <= NoClash(B_C_2_0)_binary_indicator_var <= 1 + 0 <= NoClash(B_C_2_1)_binary_indicator_var <= 1 binary - NoClash(B_C_2_0)_binary_indicator_var - NoClash(B_C_2_1)_binary_indicator_var - NoClash(A_C_1_0)_binary_indicator_var - NoClash(A_C_1_1)_binary_indicator_var NoClash(A_B_3_0)_binary_indicator_var NoClash(A_B_3_1)_binary_indicator_var + NoClash(A_C_1_0)_binary_indicator_var + NoClash(A_C_1_1)_binary_indicator_var + NoClash(B_C_2_0)_binary_indicator_var + NoClash(B_C_2_1)_binary_indicator_var end From d9d29bf04806a3d666cae8f6e20773440ed07928 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 28 Nov 2023 15:40:32 -0500 Subject: [PATCH 0517/1204] rename copy_var_value to set_var_value --- pyomo/contrib/mindtpy/single_tree.py | 10 +++++++-- pyomo/contrib/mindtpy/util.py | 32 ++++++++++++++++++---------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index 4733843d6a2..481ff38df8f 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -19,7 +19,7 @@ from pyomo.contrib.mindtpy.util import ( get_integer_solution, copy_var_list_values, - copy_var_value, + set_var_value, ) from pyomo.contrib.gdpopt.util import get_main_elapsed_time, time_code from pyomo.opt import TerminationCondition as tc @@ -62,7 +62,13 @@ def copy_lazy_var_list_values( if skip_fixed and v_to.is_fixed(): continue # Skip fixed variables. v_val = self.get_values(opt._pyomo_var_to_solver_var_map[v_from]) - copy_var_value(v_from, v_to, v_val, config, ignore_integrality=False) + set_var_value( + v_to, + v_val, + config.integer_tolerance, + config.zero_tolerance, + ignore_integrality=False, + ) def add_lazy_oa_cuts( self, diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index 7e3fbe415d4..ea22eb1ec3a 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -693,7 +693,13 @@ def copy_var_list_values_from_solution_pool( elif config.mip_solver == 'gurobi_persistent': solver_model.setParam(gurobipy.GRB.Param.SolutionNumber, solution_name) var_val = var_map[v_from].Xn - copy_var_value(v_from, v_to, var_val, config, ignore_integrality) + set_var_value( + v_to, + var_val, + config.integer_tolerance, + config.zero_tolerance, + ignore_integrality, + ) class GurobiPersistent4MindtPy(GurobiPersistent): @@ -973,10 +979,16 @@ def copy_var_list_values( if skip_fixed and v_to.is_fixed(): continue # Skip fixed variables. var_val = value(v_from, exception=False) - copy_var_value(v_from, v_to, var_val, config, ignore_integrality) + set_var_value( + v_to, + var_val, + config.integer_tolerance, + config.zero_tolerance, + ignore_integrality, + ) -def copy_var_value(v_from, v_to, var_val, config, ignore_integrality): +def set_var_value(v_to, var_val, integer_tolerance, zero_tolerance, ignore_integrality): """This function copies variable value from one to another. Rounds to Binary/Integer if necessary. Sets to zero for NonNegativeReals if necessary. @@ -988,14 +1000,14 @@ def copy_var_value(v_from, v_to, var_val, config, ignore_integrality): Parameters ---------- - v_from : Var - The variable that provides the values to copy from. v_to : Var The variable that needs to set value. var_val : float The value of v_to variable. - config : ConfigBlock - The specific configurations for MindtPy. + integer_tolerance: float + Tolerance on integral values. + zero_tolerance: float + Tolerance on variable equal to zero. ignore_integrality : bool, optional Whether to ignore the integrality of integer variables, by default False. @@ -1021,11 +1033,9 @@ def copy_var_value(v_from, v_to, var_val, config, ignore_integrality): v_to.set_value(v_to.ub) elif ignore_integrality and v_to.is_integer(): v_to.set_value(var_val, skip_validation=True) - elif v_to.is_integer() and ( - math.fabs(var_val - rounded_val) <= config.integer_tolerance - ): + elif v_to.is_integer() and (math.fabs(var_val - rounded_val) <= integer_tolerance): v_to.set_value(rounded_val) - elif abs(var_val) <= config.zero_tolerance and 0 in v_to.domain: + elif abs(var_val) <= zero_tolerance and 0 in v_to.domain: v_to.set_value(0) else: raise ValueError( From f04424e0d747d4d6986b9224e3ca8d7e4ad246ac Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 28 Nov 2023 15:57:37 -0500 Subject: [PATCH 0518/1204] add unit test for mindtpy --- pyomo/contrib/mindtpy/tests/unit_test.py | 70 ++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 pyomo/contrib/mindtpy/tests/unit_test.py diff --git a/pyomo/contrib/mindtpy/tests/unit_test.py b/pyomo/contrib/mindtpy/tests/unit_test.py new file mode 100644 index 00000000000..d9b2e494ab0 --- /dev/null +++ b/pyomo/contrib/mindtpy/tests/unit_test.py @@ -0,0 +1,70 @@ +# ___________________________________________________________________________ +# +# 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.common.unittest as unittest +from pyomo.contrib.mindtpy.util import set_var_value + +from pyomo.environ import Var, Integers, ConcreteModel, Integers + + +class UnitTestMindtPy(unittest.TestCase): + def test_set_var_value(self): + m = ConcreteModel() + m.x1 = Var(within=Integers, bounds=(-1, 4), initialize=0) + + set_var_value( + m.x1, + var_val=5, + integer_tolerance=1e-6, + zero_tolerance=1e-6, + ignore_integrality=False, + ) + self.assertEqual(m.x1.value, 4) + + set_var_value( + m.x1, + var_val=-2, + integer_tolerance=1e-6, + zero_tolerance=1e-6, + ignore_integrality=False, + ) + self.assertEqual(m.x1.value, -1) + + set_var_value( + m.x1, + var_val=1.1, + integer_tolerance=1e-6, + zero_tolerance=1e-6, + ignore_integrality=True, + ) + self.assertEqual(m.x1.value, 1.1) + + set_var_value( + m.x1, + var_val=2.00000001, + integer_tolerance=1e-6, + zero_tolerance=1e-6, + ignore_integrality=False, + ) + self.assertEqual(m.x1.value, 2) + + set_var_value( + m.x1, + var_val=0.0000001, + integer_tolerance=1e-9, + zero_tolerance=1e-6, + ignore_integrality=False, + ) + self.assertEqual(m.x1.value, 0) + + +if __name__ == '__main__': + unittest.main() From 4036dfa637104bb1a0cd627471df349fe99f7ef2 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 29 Nov 2023 13:27:41 -0700 Subject: [PATCH 0519/1204] Remove presolve-eliminated variables from named expressions --- pyomo/repn/plugins/nl_writer.py | 9 ++++ pyomo/repn/tests/ampl/test_nlv2.py | 79 ++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 1941e1e0c64..fa706337035 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1685,6 +1685,15 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): if not self.config.linear_presolve: return eliminated_cons, eliminated_vars + # We need to record all named expressions with linear components + # so that any eliminated variables are removed from them. + for expr, info, _ in self.subexpression_cache.values(): + if not info.linear: + continue + expr_id = id(expr) + for _id in info.linear: + comp_by_linear_var[_id].append((expr_id, info)) + fixed_vars = [ _id for _id, (lb, ub) in var_bounds.items() if lb == ub and lb is not None ] diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 7e47de24f29..71877e0b6c6 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1557,6 +1557,85 @@ def test_presolve_lower_triangular_out_of_bounds(self): nlinfo = nl_writer.NLWriter().write(m, OUT, linear_presolve=True) self.assertEqual(LOG.getvalue(), "") + def test_presolve_named_expressions(self): + # Test from #3055 + m = pyo.ConcreteModel() + m.x = pyo.Var([1, 2, 3], initialize=1, bounds=(0, 10)) + m.subexpr = pyo.Expression(pyo.Integers) + m.subexpr[1] = m.x[1] + m.x[2] + m.eq = pyo.Constraint(pyo.Integers) + m.eq[1] = m.x[1] == 7 + m.eq[2] = m.x[3] == 0.1 * m.subexpr[1] * m.x[2] + m.obj = pyo.Objective(expr=m.x[1]**2 + m.x[2]**2 + m.x[3]**3) + + OUT = io.StringIO() + with LoggingIntercept() as LOG: + nlinfo = nl_writer.NLWriter().write(m, OUT, symbolic_solver_labels=True, linear_presolve=True) + self.assertEqual(LOG.getvalue(), "") + + self.assertEqual( + nlinfo.eliminated_vars, + [ + (m.x[1], nl_writer.AMPLRepn(7, {}, None)), + ], + ) + + self.assertEqual( + *nl_diff( + """g3 1 1 0 # problem unknown + 2 1 1 0 1 # vars, constraints, objectives, ranges, eqns + 1 1 0 0 0 0 # nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb + 0 0 # network constraints: nonlinear, linear + 1 2 1 # nonlinear vars in constraints, objectives, both + 0 0 0 1 # linear network variables; functions; arith, flags + 0 0 0 0 0 # discrete variables: binary, integer, nonlinear (b,c,o) + 2 2 # nonzeros in Jacobian, obj. gradient + 5 4 # max name lengths: constraints, variables + 0 0 0 1 0 # common exprs: b,c,o,c1,o1 +V2 1 1 #subexpr[1] +0 1 +n7.0 +C0 #eq[2] +o16 #- +o2 #* +o2 #* +n0.1 +v2 #subexpr[1] +v0 #x[2] +O0 0 #obj +o54 # sumlist +3 # (n) +o5 #^ +n7.0 +n2 +o5 #^ +v0 #x[2] +n2 +o5 #^ +v1 #x[3] +n3 +x2 # initial guess +0 1 #x[2] +1 1 #x[3] +r #1 ranges (rhs's) +4 0 #eq[2] +b #2 bounds (on variables) +0 0 10 #x[2] +0 0 10 #x[3] +k1 #intermediate Jacobian column lengths +1 +J0 2 #eq[2] +0 0 +1 1 +G0 2 #obj +0 0 +1 0 +""", + OUT.getvalue(), + ) + ) + + def test_scaling(self): m = pyo.ConcreteModel() m.x = pyo.Var(initialize=0) From ae10ad29ad2865ff66ac793623d007c6c9a11f49 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 29 Nov 2023 13:28:11 -0700 Subject: [PATCH 0520/1204] NFC: remove debugging print() --- pyomo/repn/tests/ampl/test_nlv2.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 71877e0b6c6..4bdbef4e31e 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1480,7 +1480,6 @@ def test_presolve_almost_lower_triangular_nonlinear(self): # Note: bounds on x[1] are: # min(22/3, 82/17, 23/4, -39/-6) == 4.823529411764706 # max(2/3, 62/17, 3/4, -19/-6) == 3.6470588235294117 - print(OUT.getvalue()) self.assertEqual( *nl_diff( """g3 1 1 0 # problem unknown @@ -1743,7 +1742,7 @@ def test_scaling(self): self.assertEqual(LOG.getvalue(), "") nl2 = OUT.getvalue() - print(nl2) + self.assertEqual( *nl_diff( """g3 1 1 0 # problem unknown @@ -1837,7 +1836,7 @@ def test_named_expressions(self): OUT = io.StringIO() nl_writer.NLWriter().write(m, OUT, symbolic_solver_labels=True) - print(OUT.getvalue()) + self.assertEqual( *nl_diff( """g3 1 1 0 # problem unknown From bb5cee6fd6dae6e216c5fea49aef9a8713ab7f98 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 29 Nov 2023 13:38:30 -0700 Subject: [PATCH 0521/1204] NFC: apply black --- pyomo/repn/tests/ampl/test_nlv2.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 4bdbef4e31e..32274f26a0c 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1565,18 +1565,17 @@ def test_presolve_named_expressions(self): m.eq = pyo.Constraint(pyo.Integers) m.eq[1] = m.x[1] == 7 m.eq[2] = m.x[3] == 0.1 * m.subexpr[1] * m.x[2] - m.obj = pyo.Objective(expr=m.x[1]**2 + m.x[2]**2 + m.x[3]**3) + m.obj = pyo.Objective(expr=m.x[1] ** 2 + m.x[2] ** 2 + m.x[3] ** 3) OUT = io.StringIO() with LoggingIntercept() as LOG: - nlinfo = nl_writer.NLWriter().write(m, OUT, symbolic_solver_labels=True, linear_presolve=True) + nlinfo = nl_writer.NLWriter().write( + m, OUT, symbolic_solver_labels=True, linear_presolve=True + ) self.assertEqual(LOG.getvalue(), "") self.assertEqual( - nlinfo.eliminated_vars, - [ - (m.x[1], nl_writer.AMPLRepn(7, {}, None)), - ], + nlinfo.eliminated_vars, [(m.x[1], nl_writer.AMPLRepn(7, {}, None))] ) self.assertEqual( @@ -1634,7 +1633,6 @@ def test_presolve_named_expressions(self): ) ) - def test_scaling(self): m = pyo.ConcreteModel() m.x = pyo.Var(initialize=0) From ef6666085071f235458a36dbe3cb62192e391a2f Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 29 Nov 2023 17:28:51 -0500 Subject: [PATCH 0522/1204] improve var_val description --- pyomo/contrib/mindtpy/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index ea22eb1ec3a..51ed59e80a2 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -1003,7 +1003,7 @@ def set_var_value(v_to, var_val, integer_tolerance, zero_tolerance, ignore_integ v_to : Var The variable that needs to set value. var_val : float - The value of v_to variable. + The desired value to set for Var v_to. integer_tolerance: float Tolerance on integral values. zero_tolerance: float From 04ea15effcc83213c49a82b1230ffa3c0a945211 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 29 Nov 2023 17:31:55 -0500 Subject: [PATCH 0523/1204] rename set_var_value to set_var_valid_value --- pyomo/contrib/mindtpy/single_tree.py | 4 ++-- pyomo/contrib/mindtpy/tests/unit_test.py | 14 +++++++------- pyomo/contrib/mindtpy/util.py | 8 +++++--- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index 481ff38df8f..c4d49e3afd6 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -19,7 +19,7 @@ from pyomo.contrib.mindtpy.util import ( get_integer_solution, copy_var_list_values, - set_var_value, + set_var_valid_value, ) from pyomo.contrib.gdpopt.util import get_main_elapsed_time, time_code from pyomo.opt import TerminationCondition as tc @@ -62,7 +62,7 @@ def copy_lazy_var_list_values( if skip_fixed and v_to.is_fixed(): continue # Skip fixed variables. v_val = self.get_values(opt._pyomo_var_to_solver_var_map[v_from]) - set_var_value( + set_var_valid_value( v_to, v_val, config.integer_tolerance, diff --git a/pyomo/contrib/mindtpy/tests/unit_test.py b/pyomo/contrib/mindtpy/tests/unit_test.py index d9b2e494ab0..baf5e16bb4b 100644 --- a/pyomo/contrib/mindtpy/tests/unit_test.py +++ b/pyomo/contrib/mindtpy/tests/unit_test.py @@ -10,17 +10,17 @@ # ___________________________________________________________________________ import pyomo.common.unittest as unittest -from pyomo.contrib.mindtpy.util import set_var_value +from pyomo.contrib.mindtpy.util import set_var_valid_value from pyomo.environ import Var, Integers, ConcreteModel, Integers class UnitTestMindtPy(unittest.TestCase): - def test_set_var_value(self): + def test_set_var_valid_value(self): m = ConcreteModel() m.x1 = Var(within=Integers, bounds=(-1, 4), initialize=0) - set_var_value( + set_var_valid_value( m.x1, var_val=5, integer_tolerance=1e-6, @@ -29,7 +29,7 @@ def test_set_var_value(self): ) self.assertEqual(m.x1.value, 4) - set_var_value( + set_var_valid_value( m.x1, var_val=-2, integer_tolerance=1e-6, @@ -38,7 +38,7 @@ def test_set_var_value(self): ) self.assertEqual(m.x1.value, -1) - set_var_value( + set_var_valid_value( m.x1, var_val=1.1, integer_tolerance=1e-6, @@ -47,7 +47,7 @@ def test_set_var_value(self): ) self.assertEqual(m.x1.value, 1.1) - set_var_value( + set_var_valid_value( m.x1, var_val=2.00000001, integer_tolerance=1e-6, @@ -56,7 +56,7 @@ def test_set_var_value(self): ) self.assertEqual(m.x1.value, 2) - set_var_value( + set_var_valid_value( m.x1, var_val=0.0000001, integer_tolerance=1e-9, diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index 51ed59e80a2..f6cc0567286 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -693,7 +693,7 @@ def copy_var_list_values_from_solution_pool( elif config.mip_solver == 'gurobi_persistent': solver_model.setParam(gurobipy.GRB.Param.SolutionNumber, solution_name) var_val = var_map[v_from].Xn - set_var_value( + set_var_valid_value( v_to, var_val, config.integer_tolerance, @@ -979,7 +979,7 @@ def copy_var_list_values( if skip_fixed and v_to.is_fixed(): continue # Skip fixed variables. var_val = value(v_from, exception=False) - set_var_value( + set_var_valid_value( v_to, var_val, config.integer_tolerance, @@ -988,7 +988,9 @@ def copy_var_list_values( ) -def set_var_value(v_to, var_val, integer_tolerance, zero_tolerance, ignore_integrality): +def set_var_valid_value( + v_to, var_val, integer_tolerance, zero_tolerance, ignore_integrality +): """This function copies variable value from one to another. Rounds to Binary/Integer if necessary. Sets to zero for NonNegativeReals if necessary. From f84ff8d3429eb88bcd50021a8f4d22bcc691f2fb Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 29 Nov 2023 17:39:52 -0500 Subject: [PATCH 0524/1204] change v_to to var --- pyomo/contrib/mindtpy/util.py | 42 +++++++++++++++++------------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index f6cc0567286..a9802a8bd1e 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -989,9 +989,9 @@ def copy_var_list_values( def set_var_valid_value( - v_to, var_val, integer_tolerance, zero_tolerance, ignore_integrality + var, var_val, integer_tolerance, zero_tolerance, ignore_integrality ): - """This function copies variable value from one to another. + """This function tries to set a valid value for variable with the given input. Rounds to Binary/Integer if necessary. Sets to zero for NonNegativeReals if necessary. @@ -1002,10 +1002,10 @@ def set_var_valid_value( Parameters ---------- - v_to : Var + var : Var The variable that needs to set value. var_val : float - The desired value to set for Var v_to. + The desired value to set for var. integer_tolerance: float Tolerance on integral values. zero_tolerance: float @@ -1016,31 +1016,31 @@ def set_var_valid_value( Raises ------ ValueError - Cannot successfully set the value to variable v_to. + Cannot successfully set the value to the variable. """ # We don't want to trigger the reset of the global stale # indicator, so we will set this variable to be "stale", # knowing that set_value will switch it back to "not stale". - v_to.stale = True + var.stale = True rounded_val = int(round(var_val)) if ( - var_val in v_to.domain - and not ((v_to.has_lb() and var_val < v_to.lb)) - and not ((v_to.has_ub() and var_val > v_to.ub)) + var_val in var.domain + and not ((var.has_lb() and var_val < var.lb)) + and not ((var.has_ub() and var_val > var.ub)) ): - v_to.set_value(var_val) - elif v_to.has_lb() and var_val < v_to.lb: - v_to.set_value(v_to.lb) - elif v_to.has_ub() and var_val > v_to.ub: - v_to.set_value(v_to.ub) - elif ignore_integrality and v_to.is_integer(): - v_to.set_value(var_val, skip_validation=True) - elif v_to.is_integer() and (math.fabs(var_val - rounded_val) <= integer_tolerance): - v_to.set_value(rounded_val) - elif abs(var_val) <= zero_tolerance and 0 in v_to.domain: - v_to.set_value(0) + var.set_value(var_val) + elif var.has_lb() and var_val < var.lb: + var.set_value(var.lb) + elif var.has_ub() and var_val > var.ub: + var.set_value(var.ub) + elif ignore_integrality and var.is_integer(): + var.set_value(var_val, skip_validation=True) + elif var.is_integer() and (math.fabs(var_val - rounded_val) <= integer_tolerance): + var.set_value(rounded_val) + elif abs(var_val) <= zero_tolerance and 0 in var.domain: + var.set_value(0) else: raise ValueError( "copy_var_list_values failed with variable {}, value = {} and rounded value = {}" - "".format(v_to.name, var_val, rounded_val) + "".format(var.name, var_val, rounded_val) ) From 83b28cb0d4216b337d8308d07ea090092a71a880 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 29 Nov 2023 17:42:10 -0500 Subject: [PATCH 0525/1204] move NOTE from docstring to comment --- pyomo/contrib/mindtpy/util.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index a9802a8bd1e..afcb129e40e 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -995,11 +995,6 @@ def set_var_valid_value( Rounds to Binary/Integer if necessary. Sets to zero for NonNegativeReals if necessary. - NOTE: PEP 2180 changes the var behavior so that domain / - bounds violations no longer generate exceptions (and - instead log warnings). This means that the following will - always succeed and the ValueError should never be raised. - Parameters ---------- var : Var @@ -1018,6 +1013,11 @@ def set_var_valid_value( ValueError Cannot successfully set the value to the variable. """ + # NOTE: PEP 2180 changes the var behavior so that domain + # bounds violations no longer generate exceptions (and + # instead log warnings). This means that the set_value method + # will always succeed and the ValueError should never be raised. + # We don't want to trigger the reset of the global stale # indicator, so we will set this variable to be "stale", # knowing that set_value will switch it back to "not stale". From 11cb6d9a32bc113a71bece46fb81d2f16631201d Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 29 Nov 2023 18:32:26 -0700 Subject: [PATCH 0526/1204] Final edits to the CHANGELOG for the 6.7.0 release --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1936b356905..1a97af0075b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ Pyomo CHANGELOG ------------------------------------------------------------------------------- -Pyomo 6.7.0 (28 Nov 2023) +Pyomo 6.7.0 (29 Nov 2023) ------------------------------------------------------------------------------- - General @@ -23,6 +23,8 @@ Pyomo 6.7.0 (28 Nov 2023) - Ensure templatize_constraint returns an expression (#2983) - Prevent multiple applications of the scaling transform (#2979) - Solver Interfaces + - Remove presolve-eliminated variables from named expressions (#3056) + - Improve writer determinism (#3054) - Add "writer" for converting linear models to standard matrix form (#3046) - NLv2: add linear presolve and general problem scaling support (#3037) - Adjust mps writer format for integer variable declaration (#2946) From a398b45a08224ebcc3db0ffcc032ffe3b4e8cd10 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 29 Nov 2023 18:48:18 -0700 Subject: [PATCH 0527/1204] NFC: update RELEASE.md, CHANGELOG.md --- CHANGELOG.md | 7 +++---- RELEASE.md | 4 +++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a97af0075b..553a4f1c3bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,6 @@ Pyomo 6.7.0 (29 Nov 2023) - General - Remove Python 3.7, add Python 3.12 Support (#3050, #2956) - - Log which suffix values were skipped at the DEBUG level (#3043) - Update report_timing() to support context manager API (#3039) - Add `Preformatted` class for logging preformatted messages (#2998) - QuadraticRepnVisitor: Improve nonlinear expression expansion (#2997) @@ -24,8 +23,9 @@ Pyomo 6.7.0 (29 Nov 2023) - Prevent multiple applications of the scaling transform (#2979) - Solver Interfaces - Remove presolve-eliminated variables from named expressions (#3056) - - Improve writer determinism (#3054) + - Improve LP/NL writer determinism (#3054) - Add "writer" for converting linear models to standard matrix form (#3046) + - NLv2/LPv2: Log which suffix values were skipped at the DEBUG level (#3043) - NLv2: add linear presolve and general problem scaling support (#3037) - Adjust mps writer format for integer variable declaration (#2946) - Fix scip results processing (#3023) @@ -54,8 +54,7 @@ Pyomo 6.7.0 (29 Nov 2023) - APPSI: Fix auto-update when unfixing variable and changing bounds (#2996) - APPSI: Fix reference bug in HiGHS interface (#2995) - FBBT: Add new walker for compute_bounds_on_expr (#3027) - - incidence_analysis: Fix bugs with subset ordering and zero coefficients - (#3041) + - incidence_analysis: Fix bugs with subset ordering and 0 coefficients (#3041) - incidence_analysis: Update paper reference (#2969) - latex_printer: Add contrib.latex_printer package (#2984) - MindtPy: Add support for GreyBox models (#2988) diff --git a/RELEASE.md b/RELEASE.md index 1fcf19a0da9..03baa803ac9 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -4,12 +4,14 @@ Pyomo is a collection of Python software packages that supports a diverse set of optimization capabilities for formulating and analyzing optimization models. -The following are highlights of the 6.7 minor release series: +The following are highlights of the 6.7 release series: - Added support for Python 3.12 - Removed support for Python 3.7 + - New writer for converting linear models to matrix form - New packages: - latex_printer (print Pyomo models to a LaTeX compatible format) + - ...and of course numerous minor bug fixes and performance enhancements A full list of updates and changes is available in the [`CHANGELOG.md`](https://github.com/Pyomo/pyomo/blob/main/CHANGELOG.md). From 169acf3fe618335554a44a860dcc90f8a158a2c5 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 29 Nov 2023 19:26:41 -0700 Subject: [PATCH 0528/1204] Resetting main for development (6.7.1.dev0) --- pyomo/version/info.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/version/info.py b/pyomo/version/info.py index e38e844ad9b..cedb30c2dd4 100644 --- a/pyomo/version/info.py +++ b/pyomo/version/info.py @@ -26,9 +26,9 @@ # main and needs a hard reference to "suitably new" development. major = 6 minor = 7 -micro = 0 -# releaselevel = 'invalid' -releaselevel = 'final' +micro = 1 +releaselevel = 'invalid' +# releaselevel = 'final' serial = 0 if releaselevel == 'final': From 355df8b4a112597d4c1c45b0b6cd6a4ca13ff6af Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 30 Nov 2023 10:44:36 -0500 Subject: [PATCH 0529/1204] remove redundant test --- pyomo/repn/tests/ampl/test_nlv2.py | 34 ------------------------------ 1 file changed, 34 deletions(-) diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 460c45b4ebb..fe5f422d323 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1055,40 +1055,6 @@ def test_log_timing(self): re.sub(r'\d\.\d\d\]', '#.##]', LOG.getvalue()), ) - def test_log_timing(self): - # This tests an error possibly reported by #2810 - m = ConcreteModel() - m.x = Var(range(6)) - m.x[0].domain = pyo.Binary - m.x[1].domain = pyo.Integers - m.x[2].domain = pyo.Integers - m.p = Param(initialize=5, mutable=True) - m.o1 = Objective([1, 2], rule=lambda m, i: 1) - m.o2 = Objective(expr=m.x[1] * m.x[2]) - m.c1 = Constraint([1, 2], rule=lambda m, i: sum(m.x.values()) == 1) - m.c2 = Constraint(expr=m.p * m.x[1] ** 2 + m.x[2] ** 3 <= 100) - - self.maxDiff = None - OUT = io.StringIO() - with capture_output() as LOG: - with report_timing(level=logging.DEBUG): - nl_writer.NLWriter().write(m, OUT) - self.assertEqual( - """ [+ #.##] Initialized column order - [+ #.##] Collected suffixes - [+ #.##] Objective o1 - [+ #.##] Objective o2 - [+ #.##] Constraint c1 - [+ #.##] Constraint c2 - [+ #.##] Categorized model variables: 14 nnz - [+ #.##] Set row / column ordering: 6 var [3, 1, 2 R/B/Z], 3 con [2, 1 L/NL] - [+ #.##] Generated row/col labels & comments - [+ #.##] Wrote NL stream - [ #.##] Generated NL representation -""", - re.sub(r'\d\.\d\d\]', '#.##]', LOG.getvalue()), - ) - def test_linear_constraint_npv_const(self): # This tests an error possibly reported by #2810 m = ConcreteModel() From a7a01c229738fe680ad8d2f7f6814ab0e2c38a0c Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 30 Nov 2023 18:28:06 -0500 Subject: [PATCH 0530/1204] add test_add_var_bound --- pyomo/contrib/mindtpy/tests/unit_test.py | 31 ++++++++++++++++++++++++ pyomo/contrib/mindtpy/util.py | 4 +-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/unit_test.py b/pyomo/contrib/mindtpy/tests/unit_test.py index baf5e16bb4b..a1ceadda41e 100644 --- a/pyomo/contrib/mindtpy/tests/unit_test.py +++ b/pyomo/contrib/mindtpy/tests/unit_test.py @@ -13,6 +13,10 @@ from pyomo.contrib.mindtpy.util import set_var_valid_value from pyomo.environ import Var, Integers, ConcreteModel, Integers +from pyomo.contrib.mindtpy.algorithm_base_class import _MindtPyAlgorithm +from pyomo.contrib.mindtpy.config_options import _get_MindtPy_OA_config +from pyomo.contrib.mindtpy.tests.MINLP5_simple import SimpleMINLP5 +from pyomo.contrib.mindtpy.util import add_var_bound class UnitTestMindtPy(unittest.TestCase): @@ -65,6 +69,33 @@ def test_set_var_valid_value(self): ) self.assertEqual(m.x1.value, 0) + def test_add_var_bound(self): + m = SimpleMINLP5().clone() + m.x.lb = None + m.x.ub = None + m.y.lb = None + m.y.ub = None + solver_object = _MindtPyAlgorithm() + solver_object.config = _get_MindtPy_OA_config() + solver_object.set_up_solve_data(m) + solver_object.create_utility_block(solver_object.working_model, 'MindtPy_utils') + add_var_bound(solver_object.working_model, solver_object.config) + self.assertEqual( + solver_object.working_model.x.lower, + -solver_object.config.continuous_var_bound - 1, + ) + self.assertEqual( + solver_object.working_model.x.upper, + solver_object.config.continuous_var_bound, + ) + self.assertEqual( + solver_object.working_model.y.lower, + -solver_object.config.integer_var_bound - 1, + ) + self.assertEqual( + solver_object.working_model.y.upper, solver_object.config.integer_var_bound + ) + if __name__ == '__main__': unittest.main() diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index afcb129e40e..1173dfe0cca 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -134,12 +134,12 @@ def add_var_bound(model, config): for var in EXPR.identify_variables(c.body): if var.has_lb() and var.has_ub(): continue - elif not var.has_lb(): + if not var.has_lb(): if var.is_integer(): var.setlb(-config.integer_var_bound - 1) else: var.setlb(-config.continuous_var_bound - 1) - elif not var.has_ub(): + if not var.has_ub(): if var.is_integer(): var.setub(config.integer_var_bound) else: From 875269fb7b7d5cdb3396ff1b7a2e5e2b5fc4e0d2 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 30 Nov 2023 18:29:28 -0500 Subject: [PATCH 0531/1204] delete redundant set_up_logger function --- pyomo/contrib/mindtpy/util.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index 1173dfe0cca..ec2829c6a18 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -724,25 +724,6 @@ def f(gurobi_model, where): return f -def set_up_logger(config): - """Set up the formatter and handler for logger. - - Parameters - ---------- - config : ConfigBlock - The specific configurations for MindtPy. - """ - config.logger.handlers.clear() - config.logger.propagate = False - ch = logging.StreamHandler() - ch.setLevel(config.logging_level) - # create formatter and add it to the handlers - formatter = logging.Formatter('%(message)s') - ch.setFormatter(formatter) - # add the handlers to logger - config.logger.addHandler(ch) - - def epigraph_reformulation(exp, slack_var_list, constraint_list, use_mcpp, sense): """Epigraph reformulation. From ada43df5138918e02309d0cfea26804ca500f3eb Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 30 Nov 2023 19:45:07 -0500 Subject: [PATCH 0532/1204] Fix nominal focus DR polishing optimality constraint --- pyomo/contrib/pyros/master_problem_methods.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index dc4b6b957bb..58583c65fba 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -352,7 +352,7 @@ def minimize_dr_vars(model_data, config): nom_block = polishing_model.scenarios[0, 0] if config.objective_focus == ObjectiveType.nominal: obj_val = value( - this_iter.second_stage_objective + this_iter.first_stage_objective + nom_block.second_stage_objective + nom_block.first_stage_objective ) polishing_model.scenarios[0, 0].polishing_constraint = Constraint( expr=obj_val From 65e58f531c40fa466da60954b4bc5cc9b508055d Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 30 Nov 2023 19:50:14 -0500 Subject: [PATCH 0533/1204] add test_FP_L1_norm --- .../mindtpy/tests/test_mindtpy_feas_pump.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py index 697a63d17c8..dcb5c4bce75 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py @@ -17,7 +17,7 @@ from pyomo.contrib.mindtpy.tests.feasibility_pump1 import FeasPump1 from pyomo.contrib.mindtpy.tests.feasibility_pump2 import FeasPump2 -required_solvers = ('ipopt', 'cplex') +required_solvers = ('ipopt', 'glpk') # TODO: 'appsi_highs' will fail here. if all(SolverFactory(s).available(exception_flag=False) for s in required_solvers): subsolvers_available = True @@ -69,6 +69,22 @@ def test_FP(self): log_infeasible_constraints(model) self.assertTrue(is_feasible(model, self.get_config(opt))) + def test_FP_L1_norm(self): + """Test the feasibility pump algorithm.""" + with SolverFactory('mindtpy') as opt: + for model in model_list: + model = model.clone() + results = opt.solve( + model, + strategy='FP', + mip_solver=required_solvers[1], + nlp_solver=required_solvers[0], + absolute_bound_tolerance=1e-5, + fp_main_norm='L1', + ) + log_infeasible_constraints(model) + self.assertTrue(is_feasible(model, self.get_config(opt))) + def test_FP_OA_8PP(self): """Test the FP-OA algorithm.""" with SolverFactory('mindtpy') as opt: From c8eead976a96ee87e79ce22bf8866e6b28abeb66 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 30 Nov 2023 20:08:16 -0500 Subject: [PATCH 0534/1204] improve mindtpy logging --- pyomo/contrib/mindtpy/algorithm_base_class.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 2eec150453f..78250d1ba59 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -125,6 +125,9 @@ def __init__(self, **kwds): self.log_formatter = ( ' {:>9} {:>15} {:>15g} {:>12g} {:>12g} {:>7.2%} {:>7.2f}' ) + self.termination_condition_log_formatter = ( + ' {:>9} {:>15} {:>15} {:>12g} {:>12g} {:>7.2%} {:>7.2f}' + ) self.fixed_nlp_log_formatter = ( '{:1}{:>9} {:>15} {:>15g} {:>12g} {:>12g} {:>7.2%} {:>7.2f}' ) @@ -1919,11 +1922,6 @@ def handle_main_max_timelimit(self, main_mip, main_mip_results): """ # If we have found a valid feasible solution, we take that. If not, we can at least use the dual bound. MindtPy = main_mip.MindtPy_utils - self.config.logger.info( - 'Unable to optimize MILP main problem ' - 'within time limit. ' - 'Using current solver feasible solution.' - ) copy_var_list_values( main_mip.MindtPy_utils.variable_list, self.fixed_nlp.MindtPy_utils.variable_list, @@ -1932,10 +1930,10 @@ def handle_main_max_timelimit(self, main_mip, main_mip_results): ) self.update_suboptimal_dual_bound(main_mip_results) self.config.logger.info( - self.log_formatter.format( + self.termination_condition_log_formatter.format( self.mip_iter, 'MILP', - value(MindtPy.mip_obj.expr), + 'maxTimeLimit', self.primal_bound, self.dual_bound, self.rel_gap, @@ -1962,8 +1960,18 @@ def handle_main_unbounded(self, main_mip): # to the constraints, and deactivated for the linear main problem. config = self.config MindtPy = main_mip.MindtPy_utils + config.logger.info( + self.termination_condition_log_formatter.format( + self.mip_iter, + 'MILP', + 'Unbounded', + self.primal_bound, + self.dual_bound, + self.rel_gap, + get_main_elapsed_time(self.timing), + ) + ) config.logger.warning( - 'main MILP was unbounded. ' 'Resolving with arbitrary bound values of (-{0:.10g}, {0:.10g}) on the objective. ' 'You can change this bound with the option obj_bound.'.format( config.obj_bound From 86f64375e3207667083a113ec8107eeff0d334cc Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 30 Nov 2023 21:49:44 -0700 Subject: [PATCH 0535/1204] bugfix: range difference with offset start values --- pyomo/core/base/range.py | 11 ++++++++--- pyomo/core/tests/unit/test_set.py | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/pyomo/core/base/range.py b/pyomo/core/base/range.py index b0863f11207..a0c96472f5c 100644 --- a/pyomo/core/base/range.py +++ b/pyomo/core/base/range.py @@ -557,9 +557,14 @@ def range_difference(self, other_ranges): NumericRange(t.start, start, 0, (t.closed[0], False)) ) if s.step: # i.e., not a single point - for i in range(int(start // s.step), int(end // s.step)): + for i in range(int((end - start) // s.step)): _new_subranges.append( - NumericRange(i * s.step, (i + 1) * s.step, 0, '()') + NumericRange( + start + i * s.step, + start + (i + 1) * s.step, + 0, + '()', + ) ) if t.end > end: _new_subranges.append( @@ -605,7 +610,7 @@ def range_difference(self, other_ranges): ) elif t_max == s_max and t_c[1] and not s_c[1]: _new_subranges.append(NumericRange(t_max, t_max, 0)) - _this = _new_subranges + _this = _new_subranges return _this def range_intersection(self, other_ranges): diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index 4263bdef153..ed295ef0e1b 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -2647,6 +2647,30 @@ def test_infinite_setdifference(self): list(RangeSet(ranges=[NR(0, 2, 0, (True, False))]).ranges()), ) + x = RangeSet(0, 6, 0) - RangeSet(1, 5, 2) + self.assertIs(type(x), SetDifference_InfiniteSet) + self.assertFalse(x.isfinite()) + self.assertFalse(x.isordered()) + + self.assertIn(0, x) + self.assertNotIn(1, x) + self.assertIn(2, x) + self.assertNotIn(3, x) + self.assertIn(4, x) + self.assertNotIn(5, x) + self.assertIn(6, x) + self.assertNotIn(7, x) + + self.assertEqual( + list(x.ranges()), + list(RangeSet(ranges=[ + NR(0, 1, 0, (True, False)), + NR(1, 3, 0, (False, False)), + NR(3, 5, 0, (False, False)), + NR(5, 6, 0, (False, True)), + ]).ranges()), + ) + class TestSetSymmetricDifference(unittest.TestCase): def test_pickle(self): From c39b5156a29a39139cc36ec758840a61d4a7af76 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 30 Nov 2023 22:14:39 -0700 Subject: [PATCH 0536/1204] preserve ints in NumericRange where possible --- pyomo/core/base/range.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pyomo/core/base/range.py b/pyomo/core/base/range.py index a0c96472f5c..f650680df26 100644 --- a/pyomo/core/base/range.py +++ b/pyomo/core/base/range.py @@ -208,7 +208,7 @@ def __contains__(self, value): return False if self.step: - _dir = math.copysign(1, self.step) + _dir = int(math.copysign(1, self.step)) _from_start = value - self.start return ( 0 <= _dir * _from_start <= _dir * (self.end - self.start) @@ -411,14 +411,13 @@ def _split_ranges(cnr, new_step): assert new_step >= abs(cnr.step) assert new_step % cnr.step == 0 - _dir = math.copysign(1, cnr.step) + _dir = int(math.copysign(1, cnr.step)) _subranges = [] for i in range(int(abs(new_step // cnr.step))): if _dir * (cnr.start + i * cnr.step) > _dir * cnr.end: # Once we walk past the end of the range, we are done # (all remaining offsets will be farther past the end) break - _subranges.append( NumericRange(cnr.start + i * cnr.step, cnr.end, _dir * new_step) ) @@ -458,7 +457,7 @@ def _step_lcm(self, other_ranges): else: # one of the steps was 0: add to preserve the non-zero step a += b - return abs(a) + return int(abs(a)) def _push_to_discrete_element(self, val, push_to_next_larger_value): if not self.step or val in _infinite: From 2e6ce49469f3cc868d940aa1b65fb629d69cc719 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 1 Dec 2023 15:41:33 -0700 Subject: [PATCH 0537/1204] pyomo.solver.ipopt: account for presolve when loading results --- pyomo/contrib/appsi/solvers/wntr.py | 15 ++++++--------- pyomo/solver/IPOPT.py | 27 ++++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py index 0a358c6aedf..3d1d36586e0 100644 --- a/pyomo/contrib/appsi/solvers/wntr.py +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -1,11 +1,8 @@ -from pyomo.contrib.appsi.base import ( - PersistentBase, - PersistentSolver, - SolverConfig, - Results, - TerminationCondition, - PersistentSolutionLoader, -) +from pyomo.solver.base import PersistentSolverBase +from pyomo.solver.util import PersistentSolverUtils +from pyomo.solver.config import SolverConfig, ConfigValue +from pyomo.solver.results import Results, TerminationCondition +from pyomo.solver.solution import PersistentSolutionLoader from pyomo.core.expr.numeric_expr import ( ProductExpression, DivisionExpression, @@ -73,7 +70,7 @@ def __init__(self, solver): self.solution_loader = PersistentSolutionLoader(solver=solver) -class Wntr(PersistentBase, PersistentSolver): +class Wntr(PersistentSolverUtils, PersistentSolverBase): def __init__(self, only_child_vars=True): super().__init__(only_child_vars=only_child_vars) self._config = WntrConfig() diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index 90a92b0de24..c22b0e39857 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -20,7 +20,7 @@ from pyomo.common.errors import PyomoException from pyomo.common.tempfiles import TempfileManager from pyomo.core.base.label import NumericLabeler -from pyomo.repn.plugins.nl_writer import NLWriter, NLWriterInfo +from pyomo.repn.plugins.nl_writer import NLWriter, NLWriterInfo, AMPLRepn from pyomo.solver.base import SolverBase, SymbolMap from pyomo.solver.config import SolverConfig from pyomo.solver.factory import SolverFactory @@ -155,6 +155,8 @@ class IPOPT(SolverBase): def __init__(self, **kwds): self._config = self.CONFIG(kwds) self._writer = NLWriter() + self._writer.config.skip_trivial_constraints = True + self._writer.config.linear_presolve = True self.ipopt_options = self._config.solver_options def available(self): @@ -279,10 +281,10 @@ def solve(self, model, **kwds): ostreams = [ LogStream( - level=self.config.log_level, logger=self.config.solver_output_logger + level=config.log_level, logger=config.solver_output_logger ) ] - if self.config.tee: + if config.tee: ostreams.append(sys.stdout) with TeeStream(*ostreams) as t: process = subprocess.run( @@ -376,6 +378,14 @@ def _parse_solution(self, instream: io.TextIOBase, nl_info: NLWriterInfo): if abs(zu) > abs(rc[v_id][1]): rc[v_id] = (v, zu) + if len(nl_info.eliminated_vars) > 0: + sub_map = {k: v[1] for k, v in sol_data.primals.items()} + for v, v_expr in nl_info.eliminated_vars: + val = evaluate_ampl_repn(v_expr, sub_map) + v_id = id(v) + sub_map[v_id] = val + sol_data.primals[v_id] = (v, val) + res.solution_loader = SolutionLoader( primals=sol_data.primals, duals=sol_data.duals, @@ -384,3 +394,14 @@ def _parse_solution(self, instream: io.TextIOBase, nl_info: NLWriterInfo): ) return res + + +def evaluate_ampl_repn(repn: AMPLRepn, sub_map): + assert not repn.nonlinear + assert repn.nl is None + val = repn.const + if repn.linear is not None: + for v_id, v_coef in repn.linear.items(): + val += v_coef * sub_map[v_id] + val *= repn.mult + return val \ No newline at end of file From 04a8809c19f9f1e6a15ee157eae4a242ab878dbb Mon Sep 17 00:00:00 2001 From: asifhaider <1805112@ugrad.cse.buet.ac.bd> Date: Sun, 3 Dec 2023 06:08:28 +0600 Subject: [PATCH 0538/1204] Fixed Inappropriate Logical Expressions --- pyomo/neos/plugins/NEOS.py | 2 +- pyomo/opt/base/convert.py | 2 +- pyomo/opt/solver/shellcmd.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/neos/plugins/NEOS.py b/pyomo/neos/plugins/NEOS.py index 2d5929fa9a1..85fad42d4b2 100644 --- a/pyomo/neos/plugins/NEOS.py +++ b/pyomo/neos/plugins/NEOS.py @@ -50,7 +50,7 @@ def create_command_line(self, executable, problem_files): logger.info("Solver log file: '%s'" % (self._log_file,)) if self._soln_file is not None: logger.info("Solver solution file: '%s'" % (self._soln_file,)) - if self._problem_files is not []: + if self._problem_files != []: logger.info("Solver problem files: %s" % (self._problem_files,)) return Bunch(cmd="", log_file=self._log_file, env="") diff --git a/pyomo/opt/base/convert.py b/pyomo/opt/base/convert.py index 972239a65cd..8d8bd78e2ee 100644 --- a/pyomo/opt/base/convert.py +++ b/pyomo/opt/base/convert.py @@ -55,7 +55,7 @@ def convert_problem( if os.sep in fname: # pragma:nocover fname = tmp.split(os.sep)[-1] source_ptype = [guess_format(fname)] - if source_ptype is [None]: + if source_ptype == [None]: raise ConverterError("Unknown suffix type: " + tmp) else: source_ptype = args[0].valid_problem_types() diff --git a/pyomo/opt/solver/shellcmd.py b/pyomo/opt/solver/shellcmd.py index aad4298729a..20892000066 100644 --- a/pyomo/opt/solver/shellcmd.py +++ b/pyomo/opt/solver/shellcmd.py @@ -260,7 +260,7 @@ def _apply_solver(self): print("Solver log file: '%s'" % self._log_file) if self._soln_file is not None: print("Solver solution file: '%s'" % self._soln_file) - if self._problem_files is not []: + if self._problem_files != []: print("Solver problem files: %s" % str(self._problem_files)) sys.stdout.flush() From 3f3b3c364fb20c740f83982081d4deccb50ffc2a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Dec 2023 09:44:11 -0700 Subject: [PATCH 0539/1204] Add timing capture and iteration/log parsing --- pyomo/solver/IPOPT.py | 109 +++++++++++++++++++++++++++------------- pyomo/solver/plugins.py | 4 +- pyomo/solver/results.py | 6 ++- 3 files changed, 79 insertions(+), 40 deletions(-) diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/IPOPT.py index 90a92b0de24..5e145a5743f 100644 --- a/pyomo/solver/IPOPT.py +++ b/pyomo/solver/IPOPT.py @@ -11,6 +11,7 @@ import os import subprocess +import datetime import io import sys from typing import Mapping @@ -50,7 +51,7 @@ class SolverError(PyomoException): pass -class IPOPTConfig(SolverConfig): +class ipoptConfig(SolverConfig): def __init__( self, description=None, @@ -84,7 +85,7 @@ def __init__( ) -class IPOPTSolutionLoader(SolutionLoaderBase): +class ipoptSolutionLoader(SolutionLoaderBase): pass @@ -148,9 +149,9 @@ class IPOPTSolutionLoader(SolutionLoaderBase): } -@SolverFactory.register('ipopt_v2', doc='The IPOPT NLP solver (new interface)') -class IPOPT(SolverBase): - CONFIG = IPOPTConfig() +@SolverFactory.register('ipopt_v2', doc='The ipopt NLP solver (new interface)') +class ipopt(SolverBase): + CONFIG = ipoptConfig() def __init__(self, **kwds): self._config = self.CONFIG(kwds) @@ -193,7 +194,7 @@ def _write_options_file(self, ostream: io.TextIOBase, options: Mapping): if k not in ipopt_command_line_options: f.write(str(k) + ' ' + str(val) + '\n') - def _create_command_line(self, basename: str, config: IPOPTConfig): + def _create_command_line(self, basename: str, config: ipoptConfig): cmd = [ str(config.executable), basename + '.nl', @@ -202,8 +203,8 @@ def _create_command_line(self, basename: str, config: IPOPTConfig): ] if 'option_file_name' in config.solver_options: raise ValueError( - 'Use IPOPT.config.temp_dir to specify the name of the options file. ' - 'Do not use IPOPT.config.solver_options["option_file_name"].' + 'Use ipopt.config.temp_dir to specify the name of the options file. ' + 'Do not use ipopt.config.solver_options["option_file_name"].' ) self.ipopt_options = dict(config.solver_options) if config.time_limit is not None and 'max_cpu_time' not in self.ipopt_options: @@ -214,25 +215,15 @@ def _create_command_line(self, basename: str, config: IPOPTConfig): return cmd def solve(self, model, **kwds): + # Begin time tracking + start_timestamp = datetime.datetime.now(datetime.timezone.utc) # Check if solver is available avail = self.available() if not avail: raise SolverError(f'Solver {self.__class__} is not available ({avail}).') # Update configuration options, based on keywords passed to solve - config: IPOPTConfig = self.config(kwds.pop('options', {})) + config: ipoptConfig = self.config(kwds.pop('options', {})) config.set_value(kwds) - # Get a copy of the environment to pass to the subprocess - env = os.environ.copy() - if 'PYOMO_AMPLFUNC' in env: - env['AMPLFUNC'] = "\n".join( - filter( - None, (env.get('AMPLFUNC', None), env.get('PYOMO_AMPLFUNC', None)) - ) - ) - # Need to add check for symbolic_solver_labels; may need to generate up - # to three files for nl, row, col, if ssl == True - # What we have here may or may not work with IPOPT; will find out when - # we try to run it. with TempfileManager.new_context() as tempfile: if config.temp_dir is None: dname = tempfile.mkdtemp() @@ -248,24 +239,30 @@ def solve(self, model, **kwds): with open(basename + '.nl', 'w') as nl_file, open( basename + '.row', 'w' ) as row_file, open(basename + '.col', 'w') as col_file: - self.info = self._writer.write( + nl_info = self._writer.write( model, nl_file, row_file, col_file, symbolic_solver_labels=config.symbolic_solver_labels, ) + # Get a copy of the environment to pass to the subprocess + env = os.environ.copy() + if nl_info.external_function_libraries: + if env.get('AMPLFUNC'): + nl_info.external_function_libraries.append(env.get('AMPLFUNC')) + env['AMPLFUNC'] = "\n".join(nl_info.external_function_libraries) symbol_map = self._symbol_map = SymbolMap() labeler = NumericLabeler('component') - for v in self.info.variables: + for v in nl_info.variables: symbol_map.getSymbol(v, labeler) - for c in self.info.constraints: + for c in nl_info.constraints: symbol_map.getSymbol(c, labeler) with open(basename + '.opt', 'w') as opt_file: self._write_options_file( ostream=opt_file, options=config.solver_options ) - # Call IPOPT - passing the files via the subprocess + # Call ipopt - passing the files via the subprocess cmd = self._create_command_line(basename=basename, config=config) # this seems silly, but we have to give the subprocess slightly longer to finish than @@ -277,13 +274,15 @@ def solve(self, model, **kwds): else: timeout = None - ostreams = [ - LogStream( - level=self.config.log_level, logger=self.config.solver_output_logger - ) - ] - if self.config.tee: + ostreams = [io.StringIO()] + if config.tee: ostreams.append(sys.stdout) + else: + ostreams.append( + LogStream( + level=config.log_level, logger=config.solver_output_logger + ) + ) with TeeStream(*ostreams) as t: process = subprocess.run( cmd, @@ -293,16 +292,19 @@ def solve(self, model, **kwds): stdout=t.STDOUT, stderr=t.STDERR, ) + # This is the stuff we need to parse to get the iterations + # and time + iters, solver_time = self._parse_ipopt_output(ostreams[0]) if process.returncode != 0: results = Results() results.termination_condition = TerminationCondition.error results.solution_loader = SolutionLoader(None, None, None, None) else: - # TODO: Make a context manager out of this and open the file - # to pass to the results, instead of doing this thing. with open(basename + '.sol', 'r') as sol_file: - results = self._parse_solution(sol_file, self.info) + results = self._parse_solution(sol_file, nl_info) + results.iteration_count = iters + results.timing_info.solver_wall_time = solver_time if ( config.raise_exception_on_nonoptimal_result @@ -340,10 +342,10 @@ def solve(self, model, **kwds): if results.solution_status in {SolutionStatus.feasible, SolutionStatus.optimal}: if config.load_solution: - results.incumbent_objective = value(self.info.objectives[0]) + results.incumbent_objective = value(nl_info.objectives[0]) else: results.incumbent_objective = replace_expressions( - self.info.objectives[0].expr, + nl_info.objectives[0].expr, substitution_map={ id(v): val for v, val in results.solution_loader.get_primals().items() @@ -352,8 +354,43 @@ def solve(self, model, **kwds): remove_named_expressions=True, ) + # Capture/record end-time / wall-time + end_timestamp = datetime.datetime.now(datetime.timezone.utc) + results.timing_info.start_timestamp = start_timestamp + results.timing_info.wall_time = ( + end_timestamp - start_timestamp + ).total_seconds() return results + def _parse_ipopt_output(self, stream: io.StringIO): + """ + Parse an IPOPT output file and return: + + * number of iterations + * time in IPOPT + + """ + + iters = None + time = None + # parse the output stream to get the iteration count and solver time + for line in stream.getvalue().splitlines(): + if line.startswith("Number of Iterations....:"): + tokens = line.split() + iters = int(tokens[3]) + elif line.startswith( + "Total CPU secs in IPOPT (w/o function evaluations) =" + ): + tokens = line.split() + time = float(tokens[9]) + elif line.startswith( + "Total CPU secs in NLP function evaluations =" + ): + tokens = line.split() + time += float(tokens[8]) + + return iters, time + def _parse_solution(self, instream: io.TextIOBase, nl_info: NLWriterInfo): suffixes_to_read = ['dual', 'ipopt_zL_out', 'ipopt_zU_out'] res, sol_data = parse_sol_file( diff --git a/pyomo/solver/plugins.py b/pyomo/solver/plugins.py index 2f95ca9f410..54d03eaf74b 100644 --- a/pyomo/solver/plugins.py +++ b/pyomo/solver/plugins.py @@ -11,10 +11,10 @@ from .factory import SolverFactory -from .IPOPT import IPOPT +from .ipopt import ipopt def load(): SolverFactory.register(name='ipopt_v2', doc='The IPOPT NLP solver (new interface)')( - IPOPT + ipopt ) diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index 728e47fc7a1..71e92a1539f 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -228,9 +228,11 @@ def __init__( ) self.timing_info: ConfigDict = self.declare('timing_info', ConfigDict()) - self.timing_info.start_time: datetime = self.timing_info.declare( - 'start_time', ConfigValue(domain=Datetime) + self.timing_info.start_timestamp: datetime = self.timing_info.declare( + 'start_timestamp', ConfigValue(domain=Datetime) ) + # wall_time is the actual standard (until Michael complains) that is + # required for everyone. This is from entry->exit of the solve method. self.timing_info.wall_time: Optional[float] = self.timing_info.declare( 'wall_time', ConfigValue(domain=NonNegativeFloat) ) From 5138ae8c6f7fdcef1ac954db2b617e2417b9cd26 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Dec 2023 09:55:37 -0700 Subject: [PATCH 0540/1204] Change to lowercase ipopt --- pyomo/solver/{IPOPT.py => ipopt.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pyomo/solver/{IPOPT.py => ipopt.py} (100%) diff --git a/pyomo/solver/IPOPT.py b/pyomo/solver/ipopt.py similarity index 100% rename from pyomo/solver/IPOPT.py rename to pyomo/solver/ipopt.py From 5a9e5e598660f99a85adac16e29d7b69ce2c012c Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Dec 2023 09:58:16 -0700 Subject: [PATCH 0541/1204] Blackify --- pyomo/solver/ipopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/solver/ipopt.py b/pyomo/solver/ipopt.py index 6196d230ec3..8f987c0e3c7 100644 --- a/pyomo/solver/ipopt.py +++ b/pyomo/solver/ipopt.py @@ -441,4 +441,4 @@ def evaluate_ampl_repn(repn: AMPLRepn, sub_map): for v_id, v_coef in repn.linear.items(): val += v_coef * sub_map[v_id] val *= repn.mult - return val \ No newline at end of file + return val From a503d45b7989a1c8363a518bbdeeed65a9bb946d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Dec 2023 10:04:02 -0700 Subject: [PATCH 0542/1204] Test file needed updated --- pyomo/solver/tests/solvers/test_ipopt.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/solver/tests/solvers/test_ipopt.py b/pyomo/solver/tests/solvers/test_ipopt.py index 8cf046fcfef..abf7287489f 100644 --- a/pyomo/solver/tests/solvers/test_ipopt.py +++ b/pyomo/solver/tests/solvers/test_ipopt.py @@ -13,12 +13,12 @@ import pyomo.environ as pyo from pyomo.common.fileutils import ExecutableData from pyomo.common.config import ConfigDict -from pyomo.solver.IPOPT import IPOPTConfig +from pyomo.solver.ipopt import ipoptConfig from pyomo.solver.factory import SolverFactory from pyomo.common import unittest -class TestIPOPT(unittest.TestCase): +class TestIpopt(unittest.TestCase): def create_model(self): model = pyo.ConcreteModel() model.x = pyo.Var(initialize=1.5) @@ -30,9 +30,9 @@ def rosenbrock(m): model.obj = pyo.Objective(rule=rosenbrock, sense=pyo.minimize) return model - def test_IPOPT_config(self): + def test_ipopt_config(self): # Test default initialization - config = IPOPTConfig() + config = ipoptConfig() self.assertTrue(config.load_solution) self.assertIsInstance(config.solver_options, ConfigDict) print(type(config.executable)) From d04e1f8d7d5f39143022f62688ff06cbba1f5e30 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Dec 2023 10:10:20 -0700 Subject: [PATCH 0543/1204] Anotther test was incorrect --- pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py index d250923f104..1644eab4008 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py @@ -1,6 +1,6 @@ import pyomo.environ as pe import pyomo.common.unittest as unittest -from pyomo.contrib.appsi.base import TerminationCondition, Results, PersistentSolver +from pyomo.solver.results import TerminationCondition from pyomo.contrib.appsi.solvers.wntr import Wntr, wntr_available import math From d8be7b5294d74cd95e53d3c0e6c6b1f34edbf575 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 4 Dec 2023 10:17:58 -0700 Subject: [PATCH 0544/1204] Clarify what is meant by 'Pyomo sets are 1-based' --- pyomo/core/base/set.py | 34 ++++++++++++++---------------- pyomo/core/tests/unit/test_set.py | 20 +++++++++--------- pyomo/core/tests/unit/test_sets.py | 4 +++- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index 051922c4aaa..a203680dc1a 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -1584,28 +1584,26 @@ def _to_0_based_index(self, item): # implementation does not guarantee that the index is valid (it # could be outside of abs(i) <= len(self)). try: - if item != int(item): - raise IndexError( - "%s indices must be integers, not %s" - % (self.name, type(item).__name__) - ) - item = int(item) + _item = int(item) + if item != _item: + raise IndexError() except: raise IndexError( - "%s indices must be integers, not %s" % (self.name, type(item).__name__) - ) - - if item >= 1: - return item - 1 - elif item < 0: - item += len(self) - if item < 0: - raise IndexError("%s index out of range" % (self.name,)) - return item + f"Set '{self.name}' positional indices must be integers, " + f"not {type(item).__name__}" + ) from None + + if _item >= 1: + return _item - 1 + elif _item < 0: + _item += len(self) + if _item < 0: + raise IndexError(f"{self.name} index out of range") + return _item else: raise IndexError( - "Pyomo Sets are 1-indexed: valid index values for Sets are " - "[1 .. len(Set)] or [-1 .. -len(Set)]" + "Accessing Pyomo Sets by position is 1-based: valid Set positional " + "index values are [1 .. len(Set)] or [-1 .. -len(Set)]" ) diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index 4263bdef153..7be83561806 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -1530,8 +1530,8 @@ def test_ordered_setof(self): self.assertEqual(i[-1], 0) with self.assertRaisesRegex( IndexError, - "valid index values for Sets are " - r"\[1 .. len\(Set\)\] or \[-1 .. -len\(Set\)\]", + "Accessing Pyomo Sets by position is 1-based: valid Set positional " + r"index values are \[1 .. len\(Set\)\] or \[-1 .. -len\(Set\)\]", ): i[0] with self.assertRaisesRegex(IndexError, "OrderedSetOf index out of range"): @@ -1589,8 +1589,8 @@ def test_ordered_setof(self): self.assertEqual(i[-1], 0) with self.assertRaisesRegex( IndexError, - "valid index values for Sets are " - r"\[1 .. len\(Set\)\] or \[-1 .. -len\(Set\)\]", + "Accessing Pyomo Sets by position is 1-based: valid Set positional " + r"index values are \[1 .. len\(Set\)\] or \[-1 .. -len\(Set\)\]", ): i[0] with self.assertRaisesRegex(IndexError, "OrderedSetOf index out of range"): @@ -1752,8 +1752,8 @@ def test_ord_index(self): self.assertEqual(r[i + 1], v) with self.assertRaisesRegex( IndexError, - "valid index values for Sets are " - r"\[1 .. len\(Set\)\] or \[-1 .. -len\(Set\)\]", + "Accessing Pyomo Sets by position is 1-based: valid Set positional " + r"index values are \[1 .. len\(Set\)\] or \[-1 .. -len\(Set\)\]", ): r[0] with self.assertRaisesRegex( @@ -1769,8 +1769,8 @@ def test_ord_index(self): self.assertEqual(r[i + 1], v) with self.assertRaisesRegex( IndexError, - "valid index values for Sets are " - r"\[1 .. len\(Set\)\] or \[-1 .. -len\(Set\)\]", + "Accessing Pyomo Sets by position is 1-based: valid Set positional " + r"index values are \[1 .. len\(Set\)\] or \[-1 .. -len\(Set\)\]", ): r[0] with self.assertRaisesRegex( @@ -4191,10 +4191,10 @@ def test_indexing(self): m.I = [1, 3, 2] self.assertEqual(m.I[2], 3) with self.assertRaisesRegex( - IndexError, "I indices must be integers, not float" + IndexError, "Set 'I' positional indices must be integers, not float" ): m.I[2.5] - with self.assertRaisesRegex(IndexError, "I indices must be integers, not str"): + with self.assertRaisesRegex(IndexError, "Set 'I' positional indices must be integers, not str"): m.I['a'] def test_add_filter_validate(self): diff --git a/pyomo/core/tests/unit/test_sets.py b/pyomo/core/tests/unit/test_sets.py index 47cc14ce181..079054586b2 100644 --- a/pyomo/core/tests/unit/test_sets.py +++ b/pyomo/core/tests/unit/test_sets.py @@ -3395,7 +3395,9 @@ def test_getitem(self): with self.assertRaisesRegex(RuntimeError, ".*before it has been constructed"): a[0] a.construct() - with self.assertRaisesRegex(IndexError, "Pyomo Sets are 1-indexed"): + with self.assertRaisesRegex( + IndexError, "Accessing Pyomo Sets by position is 1-based" + ): a[0] self.assertEqual(a[1], 2) From c5097c06d57c7e7e5766003db01ed4e767b9b523 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 4 Dec 2023 10:18:41 -0700 Subject: [PATCH 0545/1204] Standardize IndexError raised by Set.at() --- pyomo/core/base/set.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index a203680dc1a..f71c08024f7 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -1681,7 +1681,7 @@ def at(self, index): try: return self._ordered_values[i] except IndexError: - raise IndexError("%s index out of range" % (self.name)) + raise IndexError(f"{self.name} index out of range") from None def ord(self, item): """ @@ -2543,7 +2543,7 @@ def at(self, index): try: return self._ref[i] except IndexError: - raise IndexError("%s index out of range" % (self.name)) + raise IndexError(f"{self.name} index out of range") from None def ord(self, item): # The bulk of single-value set members are stored as scalars. @@ -2684,7 +2684,7 @@ def at(self, index): if not idx: return ans idx -= 1 - raise IndexError("%s index out of range" % (self.name,)) + raise IndexError(f"{self.name} index out of range") def ord(self, item): if len(self._ranges) == 1: @@ -3503,7 +3503,7 @@ def at(self, index): if val not in self._sets[0]: idx -= 1 except StopIteration: - raise IndexError("%s index out of range" % (self.name,)) + raise IndexError(f"{self.name} index out of range") from None return val def ord(self, item): @@ -3640,7 +3640,7 @@ def at(self, index): idx -= 1 return next(_iter) except StopIteration: - raise IndexError("%s index out of range" % (self.name,)) + raise IndexError(f"{self.name} index out of range") from None def ord(self, item): """ @@ -3734,7 +3734,7 @@ def at(self, index): idx -= 1 return next(_iter) except StopIteration: - raise IndexError("%s index out of range" % (self.name,)) + raise IndexError(f"{self.name} index out of range") from None def ord(self, item): """ @@ -3844,7 +3844,7 @@ def at(self, index): idx -= 1 return next(_iter) except StopIteration: - raise IndexError("%s index out of range" % (self.name,)) + raise IndexError(f"{self.name} index out of range") from None def ord(self, item): """ @@ -4126,7 +4126,7 @@ def at(self, index): i -= 1 _ord[i], _idx = _idx % _ord[i], _idx // _ord[i] if _idx: - raise IndexError("%s index out of range" % (self.name,)) + raise IndexError(f"{self.name} index out of range") ans = tuple(s.at(i + 1) for s, i in zip(self._sets, _ord)) if FLATTEN_CROSS_PRODUCT and normalize_index.flatten and self.dimen != len(ans): return self._flatten_product(ans) From 6588232f7f93345da5e159a2eddc05d95db347b2 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Dec 2023 10:19:01 -0700 Subject: [PATCH 0546/1204] Remove cmodel extensions --- pyomo/contrib/appsi/solvers/wntr.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py index 3d1d36586e0..5b7f2de8592 100644 --- a/pyomo/contrib/appsi/solvers/wntr.py +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -1,6 +1,6 @@ from pyomo.solver.base import PersistentSolverBase from pyomo.solver.util import PersistentSolverUtils -from pyomo.solver.config import SolverConfig, ConfigValue +from pyomo.solver.config import SolverConfig from pyomo.solver.results import Results, TerminationCondition from pyomo.solver.solution import PersistentSolutionLoader from pyomo.core.expr.numeric_expr import ( @@ -33,7 +33,6 @@ from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler from pyomo.common.dependencies import attempt_import from pyomo.core.staleflag import StaleFlagManager -from pyomo.contrib.appsi.cmodel import cmodel, cmodel_available wntr, wntr_available = attempt_import('wntr') import logging @@ -209,8 +208,6 @@ def set_instance(self, model): ) self._reinit() self._model = model - if self.use_extensions and cmodel_available: - self._expr_types = cmodel.PyomoExprTypes() if self.config.symbolic_solver_labels: self._labeler = TextLabeler() From 77f65969bf0b16baff7989927fa67f9223b95c0f Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Dec 2023 10:30:57 -0700 Subject: [PATCH 0547/1204] More conversion in Wntr needed --- pyomo/contrib/appsi/solvers/wntr.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py index 5b7f2de8592..649b9aa2479 100644 --- a/pyomo/contrib/appsi/solvers/wntr.py +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -1,7 +1,7 @@ from pyomo.solver.base import PersistentSolverBase from pyomo.solver.util import PersistentSolverUtils from pyomo.solver.config import SolverConfig -from pyomo.solver.results import Results, TerminationCondition +from pyomo.solver.results import Results, TerminationCondition, SolutionStatus from pyomo.solver.solution import PersistentSolutionLoader from pyomo.core.expr.numeric_expr import ( ProductExpression, @@ -122,7 +122,7 @@ def _solve(self, timer: HierarchicalTimer): options.update(self.wntr_options) opt = wntr.sim.solvers.NewtonSolver(options) - if self.config.stream_solver: + if self.config.tee: ostream = sys.stdout else: ostream = None @@ -139,13 +139,12 @@ def _solve(self, timer: HierarchicalTimer): tf = time.time() results = WntrResults(self) - results.wallclock_time = tf - t0 + results.timing_info.wall_time = tf - t0 if status == wntr.sim.solvers.SolverStatus.converged: - results.termination_condition = TerminationCondition.optimal + results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied + results.solution_status = SolutionStatus.optimal else: results.termination_condition = TerminationCondition.error - results.best_feasible_objective = None - results.best_objective_bound = None if self.config.load_solution: if status == wntr.sim.solvers.SolverStatus.converged: @@ -157,7 +156,7 @@ def _solve(self, timer: HierarchicalTimer): 'A feasible solution was not found, so no solution can be loaded.' 'Please set opt.config.load_solution=False and check ' 'results.termination_condition and ' - 'results.best_feasible_objective before loading a solution.' + 'results.incumbent_objective before loading a solution.' ) return results From 7f76ff4bf0b1ae62d1166b003e8a1cff9cd13aa6 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Dec 2023 10:33:56 -0700 Subject: [PATCH 0548/1204] Update Results test --- pyomo/solver/tests/test_results.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/solver/tests/test_results.py b/pyomo/solver/tests/test_results.py index 5392c1135f8..bf822594002 100644 --- a/pyomo/solver/tests/test_results.py +++ b/pyomo/solver/tests/test_results.py @@ -99,7 +99,7 @@ def test_uninitialized(self): self.assertIsNone(res.iteration_count) self.assertIsInstance(res.timing_info, ConfigDict) self.assertIsInstance(res.extra_info, ConfigDict) - self.assertIsNone(res.timing_info.start_time) + self.assertIsNone(res.timing_info.start_timestamp) self.assertIsNone(res.timing_info.wall_time) self.assertIsNone(res.timing_info.solver_wall_time) res.solution_loader = solution.SolutionLoader(None, None, None, None) From 09e3fe946f49b92263116413de298817cb88a431 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Dec 2023 10:36:56 -0700 Subject: [PATCH 0549/1204] Blackify --- pyomo/contrib/appsi/solvers/wntr.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py index 649b9aa2479..70af135e681 100644 --- a/pyomo/contrib/appsi/solvers/wntr.py +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -141,7 +141,9 @@ def _solve(self, timer: HierarchicalTimer): results = WntrResults(self) results.timing_info.wall_time = tf - t0 if status == wntr.sim.solvers.SolverStatus.converged: - results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied + results.termination_condition = ( + TerminationCondition.convergenceCriteriaSatisfied + ) results.solution_status = SolutionStatus.optimal else: results.termination_condition = TerminationCondition.error From f26625eaf8cd9b345132d9d2bb106accb820cdce Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Dec 2023 10:43:52 -0700 Subject: [PATCH 0550/1204] wallclock attribute no longer valid --- pyomo/contrib/appsi/solvers/wntr.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py index 70af135e681..aaa130f8631 100644 --- a/pyomo/contrib/appsi/solvers/wntr.py +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -65,7 +65,6 @@ def __init__( class WntrResults(Results): def __init__(self, solver): super().__init__() - self.wallclock_time = None self.solution_loader = PersistentSolutionLoader(solver=solver) From da8da5a7219d3a1f25e4dd05c8462a04078f98e4 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 4 Dec 2023 10:45:25 -0700 Subject: [PATCH 0551/1204] NFC: apply black --- pyomo/core/tests/unit/test_set.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index ed295ef0e1b..d3e5a599f7a 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -2663,12 +2663,16 @@ def test_infinite_setdifference(self): self.assertEqual( list(x.ranges()), - list(RangeSet(ranges=[ - NR(0, 1, 0, (True, False)), - NR(1, 3, 0, (False, False)), - NR(3, 5, 0, (False, False)), - NR(5, 6, 0, (False, True)), - ]).ranges()), + list( + RangeSet( + ranges=[ + NR(0, 1, 0, (True, False)), + NR(1, 3, 0, (False, False)), + NR(3, 5, 0, (False, False)), + NR(5, 6, 0, (False, True)), + ] + ).ranges() + ), ) From 3ad12d62b3c30944c4b69af006fe1a4fe9543da9 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 4 Dec 2023 10:46:41 -0700 Subject: [PATCH 0552/1204] NFC: apply black --- pyomo/core/base/set.py | 8 ++++---- pyomo/core/tests/unit/test_set.py | 4 +++- pyomo/core/tests/unit/test_sets.py | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index f71c08024f7..6dfc3f07427 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -2021,7 +2021,7 @@ def __init__( filter=None, validate=None, name=None, - doc=None + doc=None, ): ... @@ -2859,7 +2859,7 @@ def __init__( filter=None, validate=None, name=None, - doc=None + doc=None, ): ... @@ -2876,7 +2876,7 @@ def __init__( filter=None, validate=None, name=None, - doc=None + doc=None, ): ... @@ -2890,7 +2890,7 @@ def __init__( filter=None, validate=None, name=None, - doc=None + doc=None, ): ... diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index 7be83561806..7b09b3cb948 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -4194,7 +4194,9 @@ def test_indexing(self): IndexError, "Set 'I' positional indices must be integers, not float" ): m.I[2.5] - with self.assertRaisesRegex(IndexError, "Set 'I' positional indices must be integers, not str"): + with self.assertRaisesRegex( + IndexError, "Set 'I' positional indices must be integers, not str" + ): m.I['a'] def test_add_filter_validate(self): diff --git a/pyomo/core/tests/unit/test_sets.py b/pyomo/core/tests/unit/test_sets.py index 079054586b2..90668a28e72 100644 --- a/pyomo/core/tests/unit/test_sets.py +++ b/pyomo/core/tests/unit/test_sets.py @@ -3396,7 +3396,7 @@ def test_getitem(self): a[0] a.construct() with self.assertRaisesRegex( - IndexError, "Accessing Pyomo Sets by position is 1-based" + IndexError, "Accessing Pyomo Sets by position is 1-based" ): a[0] self.assertEqual(a[1], 2) From a2efd8d8931e85644d50ca07163b807c096cde31 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Dec 2023 11:08:57 -0700 Subject: [PATCH 0553/1204] Update TerminationCondition and SolutionStatus checks --- .../solvers/tests/test_wntr_persistent.py | 59 ++++++++++++------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py index 1644eab4008..50058262488 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py @@ -1,6 +1,6 @@ import pyomo.environ as pe import pyomo.common.unittest as unittest -from pyomo.solver.results import TerminationCondition +from pyomo.solver.results import TerminationCondition, SolutionStatus from pyomo.contrib.appsi.solvers.wntr import Wntr, wntr_available import math @@ -18,12 +18,14 @@ def test_param_updates(self): opt = Wntr() opt.wntr_options.update(_default_wntr_options) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 1) m.p.value = 2 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 2) def test_remove_add_constraint(self): @@ -36,7 +38,8 @@ def test_remove_add_constraint(self): opt.config.symbolic_solver_labels = True opt.wntr_options.update(_default_wntr_options) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 0) self.assertAlmostEqual(m.y.value, 1) @@ -45,7 +48,8 @@ def test_remove_add_constraint(self): m.x.value = 0.5 m.y.value = 0.5 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 1) self.assertAlmostEqual(m.y.value, 0) @@ -58,21 +62,24 @@ def test_fixed_var(self): opt = Wntr() opt.wntr_options.update(_default_wntr_options) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 0.5) self.assertAlmostEqual(m.y.value, 0.25) m.x.unfix() m.c2 = pe.Constraint(expr=m.y == pe.exp(m.x)) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 0) self.assertAlmostEqual(m.y.value, 1) m.x.fix(0.5) del m.c2 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 0.5) self.assertAlmostEqual(m.y.value, 0.25) @@ -89,7 +96,8 @@ def test_remove_variables_params(self): opt = Wntr() opt.wntr_options.update(_default_wntr_options) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 1) self.assertAlmostEqual(m.y.value, 1) self.assertAlmostEqual(m.z.value, 0) @@ -100,14 +108,16 @@ def test_remove_variables_params(self): m.z.value = 2 m.px.value = 2 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 2) self.assertAlmostEqual(m.z.value, 2) del m.z m.px.value = 3 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 3) def test_get_primals(self): @@ -120,7 +130,8 @@ def test_get_primals(self): opt.config.load_solution = False opt.wntr_options.update(_default_wntr_options) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, None) self.assertAlmostEqual(m.y.value, None) primals = opt.get_primals() @@ -134,49 +145,57 @@ def test_operators(self): opt = Wntr() opt.wntr_options.update(_default_wntr_options) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 2) del m.c1 m.x.value = 0 m.c1 = pe.Constraint(expr=pe.sin(m.x) == math.sin(math.pi / 4)) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, math.pi / 4) del m.c1 m.c1 = pe.Constraint(expr=pe.cos(m.x) == 0) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, math.pi / 2) del m.c1 m.c1 = pe.Constraint(expr=pe.tan(m.x) == 1) m.x.value = 0 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, math.pi / 4) del m.c1 m.c1 = pe.Constraint(expr=pe.asin(m.x) == math.asin(0.5)) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 0.5) del m.c1 m.c1 = pe.Constraint(expr=pe.acos(m.x) == math.acos(0.6)) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 0.6) del m.c1 m.c1 = pe.Constraint(expr=pe.atan(m.x) == math.atan(0.5)) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 0.5) del m.c1 m.c1 = pe.Constraint(expr=pe.sqrt(m.x) == math.sqrt(0.6)) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 0.6) From 682c8b8aff32599d09d47478fba76c972e8ea706 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Dec 2023 11:10:54 -0700 Subject: [PATCH 0554/1204] Blackify... again --- .../solvers/tests/test_wntr_persistent.py | 76 ++++++++++++++----- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py index 50058262488..971305001a9 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py @@ -18,13 +18,17 @@ def test_param_updates(self): opt = Wntr() opt.wntr_options.update(_default_wntr_options) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 1) m.p.value = 2 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 2) @@ -38,7 +42,9 @@ def test_remove_add_constraint(self): opt.config.symbolic_solver_labels = True opt.wntr_options.update(_default_wntr_options) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 0) self.assertAlmostEqual(m.y.value, 1) @@ -48,7 +54,9 @@ def test_remove_add_constraint(self): m.x.value = 0.5 m.y.value = 0.5 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 1) self.assertAlmostEqual(m.y.value, 0) @@ -62,7 +70,9 @@ def test_fixed_var(self): opt = Wntr() opt.wntr_options.update(_default_wntr_options) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 0.5) self.assertAlmostEqual(m.y.value, 0.25) @@ -70,7 +80,9 @@ def test_fixed_var(self): m.x.unfix() m.c2 = pe.Constraint(expr=m.y == pe.exp(m.x)) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 0) self.assertAlmostEqual(m.y.value, 1) @@ -78,7 +90,9 @@ def test_fixed_var(self): m.x.fix(0.5) del m.c2 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 0.5) self.assertAlmostEqual(m.y.value, 0.25) @@ -96,7 +110,9 @@ def test_remove_variables_params(self): opt = Wntr() opt.wntr_options.update(_default_wntr_options) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 1) self.assertAlmostEqual(m.y.value, 1) @@ -108,7 +124,9 @@ def test_remove_variables_params(self): m.z.value = 2 m.px.value = 2 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 2) self.assertAlmostEqual(m.z.value, 2) @@ -116,7 +134,9 @@ def test_remove_variables_params(self): del m.z m.px.value = 3 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 3) @@ -130,7 +150,9 @@ def test_get_primals(self): opt.config.load_solution = False opt.wntr_options.update(_default_wntr_options) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, None) self.assertAlmostEqual(m.y.value, None) @@ -145,7 +167,9 @@ def test_operators(self): opt = Wntr() opt.wntr_options.update(_default_wntr_options) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 2) @@ -153,14 +177,18 @@ def test_operators(self): m.x.value = 0 m.c1 = pe.Constraint(expr=pe.sin(m.x) == math.sin(math.pi / 4)) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, math.pi / 4) del m.c1 m.c1 = pe.Constraint(expr=pe.cos(m.x) == 0) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, math.pi / 2) @@ -168,34 +196,44 @@ def test_operators(self): m.c1 = pe.Constraint(expr=pe.tan(m.x) == 1) m.x.value = 0 res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, math.pi / 4) del m.c1 m.c1 = pe.Constraint(expr=pe.asin(m.x) == math.asin(0.5)) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 0.5) del m.c1 m.c1 = pe.Constraint(expr=pe.acos(m.x) == math.acos(0.6)) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 0.6) del m.c1 m.c1 = pe.Constraint(expr=pe.atan(m.x) == math.atan(0.5)) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 0.5) del m.c1 m.c1 = pe.Constraint(expr=pe.sqrt(m.x) == math.sqrt(0.6)) res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied) + self.assertEqual( + res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied + ) self.assertEqual(res.solution_status, SolutionStatus.optimal) self.assertAlmostEqual(m.x.value, 0.6) From 39eb268829ebb2184cc8d62d60d16c3acdc9079e Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 5 Dec 2023 14:43:43 -0700 Subject: [PATCH 0555/1204] Apply options files updates --- pyomo/common/tests/test_config.py | 2 +- pyomo/solver/ipopt.py | 126 +++++++++++++++++++++--------- pyomo/solver/results.py | 12 +-- 3 files changed, 92 insertions(+), 48 deletions(-) diff --git a/pyomo/common/tests/test_config.py b/pyomo/common/tests/test_config.py index 9bafd852eb9..bf6786ba2a0 100644 --- a/pyomo/common/tests/test_config.py +++ b/pyomo/common/tests/test_config.py @@ -1473,7 +1473,7 @@ def test_parseDisplay_userdata_add_block_nonDefault(self): self.config.add("bar", ConfigDict(implicit=True)).add("baz", ConfigDict()) test = _display(self.config, 'userdata') sys.stdout.write(test) - self.assertEqual(yaml_load(test), {'bar': {'baz': None}, foo: 0}) + self.assertEqual(yaml_load(test), {'bar': {'baz': None}, 'foo': 0}) @unittest.skipIf(not yaml_available, "Test requires PyYAML") def test_parseDisplay_userdata_add_block(self): diff --git a/pyomo/solver/ipopt.py b/pyomo/solver/ipopt.py index 8f987c0e3c7..fbe7c0f5604 100644 --- a/pyomo/solver/ipopt.py +++ b/pyomo/solver/ipopt.py @@ -14,10 +14,10 @@ import datetime import io import sys -from typing import Mapping +from typing import Mapping, Optional from pyomo.common import Executable -from pyomo.common.config import ConfigValue, NonNegativeInt +from pyomo.common.config import ConfigValue, NonNegativeInt, NonNegativeFloat from pyomo.common.errors import PyomoException from pyomo.common.tempfiles import TempfileManager from pyomo.core.base.label import NumericLabeler @@ -43,7 +43,7 @@ logger = logging.getLogger(__name__) -class SolverError(PyomoException): +class ipoptSolverError(PyomoException): """ General exception to catch solver system errors """ @@ -74,6 +74,7 @@ def __init__( self.save_solver_io: bool = self.declare( 'save_solver_io', ConfigValue(domain=bool, default=False) ) + # TODO: Add in a deprecation here for keepfiles self.temp_dir: str = self.declare( 'temp_dir', ConfigValue(domain=str, default=None) ) @@ -85,6 +86,34 @@ def __init__( ) +class ipoptResults(Results): + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + super().__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, + ) + self.timing_info.no_function_solve_time: Optional[ + float + ] = self.timing_info.declare( + 'no_function_solve_time', ConfigValue(domain=NonNegativeFloat) + ) + self.timing_info.function_solve_time: Optional[ + float + ] = self.timing_info.declare( + 'function_solve_time', ConfigValue(domain=NonNegativeFloat) + ) + + class ipoptSolutionLoader(SolutionLoaderBase): pass @@ -190,30 +219,37 @@ def config(self, val): def symbol_map(self): return self._symbol_map - def _write_options_file(self, ostream: io.TextIOBase, options: Mapping): - f = ostream - for k, val in options.items(): - if k not in ipopt_command_line_options: - f.write(str(k) + ' ' + str(val) + '\n') - - def _create_command_line(self, basename: str, config: ipoptConfig): - cmd = [ - str(config.executable), - basename + '.nl', - '-AMPL', - 'option_file_name=' + basename + '.opt', - ] + def _write_options_file(self, filename: str, options: Mapping): + # First we need to determine if we even need to create a file. + # If options is empty, then we return False + opt_file_exists = False + if not options: + return False + # If it has options in it, parse them and write them to a file. + # If they are command line options, ignore them; they will be + # parsed during _create_command_line + with open(filename + '.opt', 'w') as opt_file: + for k, val in options.items(): + if k not in ipopt_command_line_options: + opt_file_exists = True + opt_file.write(str(k) + ' ' + str(val) + '\n') + return opt_file_exists + + def _create_command_line(self, basename: str, config: ipoptConfig, opt_file: bool): + cmd = [str(config.executable), basename + '.nl', '-AMPL'] + if opt_file: + cmd.append('option_file_name=' + basename + '.opt') if 'option_file_name' in config.solver_options: raise ValueError( - 'Use ipopt.config.temp_dir to specify the name of the options file. ' - 'Do not use ipopt.config.solver_options["option_file_name"].' + 'Pyomo generates the ipopt options file as part of the solve method. ' + 'Add all options to ipopt.config.solver_options instead.' ) self.ipopt_options = dict(config.solver_options) if config.time_limit is not None and 'max_cpu_time' not in self.ipopt_options: self.ipopt_options['max_cpu_time'] = config.time_limit - for k, v in self.ipopt_options.items(): - cmd.append(str(k) + '=' + str(v)) - + for k, val in self.ipopt_options.items(): + if k in ipopt_command_line_options: + cmd.append(str(k) + '=' + str(val)) return cmd def solve(self, model, **kwds): @@ -222,10 +258,13 @@ def solve(self, model, **kwds): # Check if solver is available avail = self.available() if not avail: - raise SolverError(f'Solver {self.__class__} is not available ({avail}).') + raise ipoptSolverError( + f'Solver {self.__class__} is not available ({avail}).' + ) # Update configuration options, based on keywords passed to solve config: ipoptConfig = self.config(kwds.pop('options', {})) config.set_value(kwds) + results = ipoptResults() with TempfileManager.new_context() as tempfile: if config.temp_dir is None: dname = tempfile.mkdtemp() @@ -260,13 +299,15 @@ def solve(self, model, **kwds): symbol_map.getSymbol(v, labeler) for c in nl_info.constraints: symbol_map.getSymbol(c, labeler) - with open(basename + '.opt', 'w') as opt_file: - self._write_options_file( - ostream=opt_file, options=config.solver_options - ) + # Write the opt_file, if there should be one; return a bool to say + # whether or not we have one (so we can correctly build the command line) + opt_file = self._write_options_file( + filename=basename, options=config.solver_options + ) # Call ipopt - passing the files via the subprocess - cmd = self._create_command_line(basename=basename, config=config) - + cmd = self._create_command_line( + basename=basename, config=config, opt_file=opt_file + ) # this seems silly, but we have to give the subprocess slightly longer to finish than # ipopt if config.time_limit is not None: @@ -296,18 +337,19 @@ def solve(self, model, **kwds): ) # This is the stuff we need to parse to get the iterations # and time - iters, solver_time = self._parse_ipopt_output(ostreams[0]) + iters, ipopt_time_nofunc, ipopt_time_func = self._parse_ipopt_output( + ostreams[0] + ) if process.returncode != 0: - results = Results() results.termination_condition = TerminationCondition.error results.solution_loader = SolutionLoader(None, None, None, None) else: with open(basename + '.sol', 'r') as sol_file: - results = self._parse_solution(sol_file, nl_info) + results = self._parse_solution(sol_file, nl_info, results) results.iteration_count = iters - results.timing_info.solver_wall_time = solver_time - + results.timing_info.no_function_solve_time = ipopt_time_nofunc + results.timing_info.function_solve_time = ipopt_time_func if ( config.raise_exception_on_nonoptimal_result and results.solution_status != SolutionStatus.optimal @@ -374,7 +416,8 @@ def _parse_ipopt_output(self, stream: io.StringIO): """ iters = None - time = None + nofunc_time = None + func_time = None # parse the output stream to get the iteration count and solver time for line in stream.getvalue().splitlines(): if line.startswith("Number of Iterations....:"): @@ -384,19 +427,24 @@ def _parse_ipopt_output(self, stream: io.StringIO): "Total CPU secs in IPOPT (w/o function evaluations) =" ): tokens = line.split() - time = float(tokens[9]) + nofunc_time = float(tokens[9]) elif line.startswith( "Total CPU secs in NLP function evaluations =" ): tokens = line.split() - time += float(tokens[8]) + func_time = float(tokens[8]) - return iters, time + return iters, nofunc_time, func_time - def _parse_solution(self, instream: io.TextIOBase, nl_info: NLWriterInfo): + def _parse_solution( + self, instream: io.TextIOBase, nl_info: NLWriterInfo, result: ipoptResults + ): suffixes_to_read = ['dual', 'ipopt_zL_out', 'ipopt_zU_out'] res, sol_data = parse_sol_file( - sol_file=instream, nl_info=nl_info, suffixes_to_read=suffixes_to_read + sol_file=instream, + nl_info=nl_info, + suffixes_to_read=suffixes_to_read, + result=result, ) if res.solution_status == SolutionStatus.noSolution: diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index 71e92a1539f..0aa78bef6bc 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -231,14 +231,9 @@ def __init__( self.timing_info.start_timestamp: datetime = self.timing_info.declare( 'start_timestamp', ConfigValue(domain=Datetime) ) - # wall_time is the actual standard (until Michael complains) that is - # required for everyone. This is from entry->exit of the solve method. self.timing_info.wall_time: Optional[float] = self.timing_info.declare( 'wall_time', ConfigValue(domain=NonNegativeFloat) ) - self.timing_info.solver_wall_time: Optional[float] = self.timing_info.declare( - 'solver_wall_time', ConfigValue(domain=NonNegativeFloat) - ) self.extra_info: ConfigDict = self.declare( 'extra_info', ConfigDict(implicit=True) ) @@ -267,7 +262,10 @@ def __init__(self) -> None: def parse_sol_file( - sol_file: io.TextIOBase, nl_info: NLWriterInfo, suffixes_to_read: Sequence[str] + sol_file: io.TextIOBase, + nl_info: NLWriterInfo, + suffixes_to_read: Sequence[str], + result: Results, ) -> Tuple[Results, SolFileData]: suffixes_to_read = set(suffixes_to_read) sol_data = SolFileData() @@ -275,8 +273,6 @@ def parse_sol_file( # # Some solvers (minto) do not write a message. We will assume # all non-blank lines up the 'Options' line is the message. - result = Results() - # For backwards compatibility and general safety, we will parse all # lines until "Options" appears. Anything before "Options" we will # consider to be the solver message. From 396ebce1e962fe2423e89b6c512aa6fe859317bb Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 5 Dec 2023 14:54:06 -0700 Subject: [PATCH 0556/1204] Fix tests; add TODO notes --- pyomo/solver/ipopt.py | 1 + pyomo/solver/tests/solvers/test_ipopt.py | 9 +++++++++ pyomo/solver/tests/test_results.py | 1 - 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/pyomo/solver/ipopt.py b/pyomo/solver/ipopt.py index fbe7c0f5604..092b279269b 100644 --- a/pyomo/solver/ipopt.py +++ b/pyomo/solver/ipopt.py @@ -186,6 +186,7 @@ def __init__(self, **kwds): self._config = self.CONFIG(kwds) self._writer = NLWriter() self._writer.config.skip_trivial_constraints = True + # TODO: Make this an option; not always turned on self._writer.config.linear_presolve = True self.ipopt_options = self._config.solver_options diff --git a/pyomo/solver/tests/solvers/test_ipopt.py b/pyomo/solver/tests/solvers/test_ipopt.py index abf7287489f..e157321b4cc 100644 --- a/pyomo/solver/tests/solvers/test_ipopt.py +++ b/pyomo/solver/tests/solvers/test_ipopt.py @@ -18,6 +18,15 @@ from pyomo.common import unittest +""" +TODO: + - Test unique configuration options + - Test unique results options + - Ensure that `*.opt` file is only created when needed + - Ensure options are correctly parsing to env or opt file + - Failures at appropriate times +""" + class TestIpopt(unittest.TestCase): def create_model(self): model = pyo.ConcreteModel() diff --git a/pyomo/solver/tests/test_results.py b/pyomo/solver/tests/test_results.py index bf822594002..0c0b4bb18db 100644 --- a/pyomo/solver/tests/test_results.py +++ b/pyomo/solver/tests/test_results.py @@ -101,7 +101,6 @@ def test_uninitialized(self): self.assertIsInstance(res.extra_info, ConfigDict) self.assertIsNone(res.timing_info.start_timestamp) self.assertIsNone(res.timing_info.wall_time) - self.assertIsNone(res.timing_info.solver_wall_time) res.solution_loader = solution.SolutionLoader(None, None, None, None) with self.assertRaisesRegex( From e19d440800260b973847fdc51f5c88ea7ac1dfb3 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 5 Dec 2023 14:56:08 -0700 Subject: [PATCH 0557/1204] Blackify - adding a single empty space --- pyomo/solver/tests/solvers/test_ipopt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/solver/tests/solvers/test_ipopt.py b/pyomo/solver/tests/solvers/test_ipopt.py index e157321b4cc..d9fccbb84fc 100644 --- a/pyomo/solver/tests/solvers/test_ipopt.py +++ b/pyomo/solver/tests/solvers/test_ipopt.py @@ -27,6 +27,7 @@ - Failures at appropriate times """ + class TestIpopt(unittest.TestCase): def create_model(self): model = pyo.ConcreteModel() From eed6c7d6ed29083547a6bd04fc71042a4b79298a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Dec 2023 09:50:14 -0700 Subject: [PATCH 0558/1204] Leverage attempt_import to guard pyutilib tempfile dependency --- pyomo/common/tempfiles.py | 34 +++++++++++++++-------------- pyomo/common/tests/test_tempfile.py | 8 ++----- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/pyomo/common/tempfiles.py b/pyomo/common/tempfiles.py index e981d26d84e..0a38eac28d3 100644 --- a/pyomo/common/tempfiles.py +++ b/pyomo/common/tempfiles.py @@ -22,18 +22,15 @@ import logging import shutil import weakref + +from pyomo.common.dependencies import pyutilib_available from pyomo.common.deprecation import deprecated, deprecation_warning from pyomo.common.errors import TempfileContextError from pyomo.common.multithread import MultiThreadWrapperWithMain -try: - from pyutilib.component.config.tempfiles import TempfileManager as pyutilib_mngr -except ImportError: - pyutilib_mngr = None - deletion_errors_are_fatal = True - logger = logging.getLogger(__name__) +pyutilib_mngr = None class TempfileManagerClass(object): @@ -432,16 +429,21 @@ def _resolve_tempdir(self, dir=None): return self.manager().tempdir elif TempfileManager.main_thread.tempdir is not None: return TempfileManager.main_thread.tempdir - elif pyutilib_mngr is not None and pyutilib_mngr.tempdir is not None: - deprecation_warning( - "The use of the PyUtilib TempfileManager.tempdir " - "to specify the default location for Pyomo " - "temporary files has been deprecated. " - "Please set TempfileManager.tempdir in " - "pyomo.common.tempfiles", - version='5.7.2', - ) - return pyutilib_mngr.tempdir + elif pyutilib_available: + if pyutilib_mngr is None: + from pyutilib.component.config.tempfiles import ( + TempfileManager as pyutilib_mngr, + ) + if pyutilib_mngr.tempdir is not None: + deprecation_warning( + "The use of the PyUtilib TempfileManager.tempdir " + "to specify the default location for Pyomo " + "temporary files has been deprecated. " + "Please set TempfileManager.tempdir in " + "pyomo.common.tempfiles", + version='5.7.2', + ) + return pyutilib_mngr.tempdir return None def _remove_filesystem_object(self, name): diff --git a/pyomo/common/tests/test_tempfile.py b/pyomo/common/tests/test_tempfile.py index b82082ac1af..b549ed14cec 100644 --- a/pyomo/common/tests/test_tempfile.py +++ b/pyomo/common/tests/test_tempfile.py @@ -30,6 +30,7 @@ import pyomo.common.tempfiles as tempfiles +from pyomo.common.dependencies import pyutilib_available from pyomo.common.log import LoggingIntercept from pyomo.common.tempfiles import ( TempfileManager, @@ -37,11 +38,6 @@ TempfileContextError, ) -try: - from pyutilib.component.config.tempfiles import TempfileManager as pyutilib_mngr -except ImportError: - pyutilib_mngr = None - old_tempdir = TempfileManager.tempdir tempdir = None @@ -528,7 +524,7 @@ def test_open_tempfile_windows(self): f.close() os.remove(fname) - @unittest.skipIf(pyutilib_mngr is None, "deprecation test requires pyutilib") + @unittest.skipUnless(pyutilib_available, "deprecation test requires pyutilib") def test_deprecated_tempdir(self): self.TM.push() try: From 40764edf0e27f2ac7c4eabbe12d37b4599e2ea5a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Dec 2023 09:50:27 -0700 Subject: [PATCH 0559/1204] Remove pyutilib as an expected import in pyomo.environ --- pyomo/environ/tests/test_environ.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/environ/tests/test_environ.py b/pyomo/environ/tests/test_environ.py index 27a9f10cc08..02e4d723145 100644 --- a/pyomo/environ/tests/test_environ.py +++ b/pyomo/environ/tests/test_environ.py @@ -168,7 +168,6 @@ def test_tpl_import_time(self): } # Non-standard-library TPLs that Pyomo will load unconditionally ref.add('ply') - ref.add('pyutilib') if numpy_available: ref.add('numpy') diff = set(_[0] for _ in tpl_by_time[-5:]).difference(ref) From 52f39a16ee700c3524df6e9a107dbf2dc792c250 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Dec 2023 10:27:51 -0700 Subject: [PATCH 0560/1204] Resolve conflict between module attributes and imports --- pyomo/common/tempfiles.py | 12 ++++-------- pyomo/common/tests/test_tempfile.py | 6 +++--- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/pyomo/common/tempfiles.py b/pyomo/common/tempfiles.py index 0a38eac28d3..f51fad3f3ac 100644 --- a/pyomo/common/tempfiles.py +++ b/pyomo/common/tempfiles.py @@ -23,14 +23,14 @@ import shutil import weakref -from pyomo.common.dependencies import pyutilib_available +from pyomo.common.dependencies import attempt_import, pyutilib_available from pyomo.common.deprecation import deprecated, deprecation_warning from pyomo.common.errors import TempfileContextError from pyomo.common.multithread import MultiThreadWrapperWithMain deletion_errors_are_fatal = True logger = logging.getLogger(__name__) -pyutilib_mngr = None +pyutilib_tempfiles, _ = attempt_import('pyutilib.component.config.tempfiles') class TempfileManagerClass(object): @@ -430,11 +430,7 @@ def _resolve_tempdir(self, dir=None): elif TempfileManager.main_thread.tempdir is not None: return TempfileManager.main_thread.tempdir elif pyutilib_available: - if pyutilib_mngr is None: - from pyutilib.component.config.tempfiles import ( - TempfileManager as pyutilib_mngr, - ) - if pyutilib_mngr.tempdir is not None: + if pyutilib_tempfiles.TempfileManager.tempdir is not None: deprecation_warning( "The use of the PyUtilib TempfileManager.tempdir " "to specify the default location for Pyomo " @@ -443,7 +439,7 @@ def _resolve_tempdir(self, dir=None): "pyomo.common.tempfiles", version='5.7.2', ) - return pyutilib_mngr.tempdir + return pyutilib_tempfiles.TempfileManager.tempdir return None def _remove_filesystem_object(self, name): diff --git a/pyomo/common/tests/test_tempfile.py b/pyomo/common/tests/test_tempfile.py index b549ed14cec..5e75c55305a 100644 --- a/pyomo/common/tests/test_tempfile.py +++ b/pyomo/common/tests/test_tempfile.py @@ -529,8 +529,8 @@ def test_deprecated_tempdir(self): self.TM.push() try: tmpdir = self.TM.create_tempdir() - _orig = pyutilib_mngr.tempdir - pyutilib_mngr.tempdir = tmpdir + _orig = tempfiles.pyutilib_tempfiles.TempfileManager.tempdir + tempfiles.pyutilib_tempfiles.TempfileManager.tempdir = tmpdir self.TM.tempdir = None with LoggingIntercept() as LOG: @@ -552,7 +552,7 @@ def test_deprecated_tempdir(self): ) finally: self.TM.pop() - pyutilib_mngr.tempdir = _orig + tempfiles.pyutilib_tempfiles.TempfileManager.tempdir = _orig def test_context(self): with self.assertRaisesRegex( From d22946164ff018f41d42d9f554090b46fedac371 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 7 Dec 2023 13:29:39 -0500 Subject: [PATCH 0561/1204] fix greybox cuts bug --- pyomo/contrib/mindtpy/cut_generation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/cut_generation.py b/pyomo/contrib/mindtpy/cut_generation.py index e57cfd2eada..4ee7a6ff07b 100644 --- a/pyomo/contrib/mindtpy/cut_generation.py +++ b/pyomo/contrib/mindtpy/cut_generation.py @@ -210,8 +210,8 @@ def add_oa_cuts_for_grey_box( target_model_grey_box.inputs.values() ) ) + - (output - value(output)) ) - - (output - value(output)) - (slack_var if config.add_slack else 0) <= 0 ) From ab26ce7899452b65d7d8e234be5af127f31090d5 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Dec 2023 11:41:52 -0700 Subject: [PATCH 0562/1204] Make the pyutilib import checker more robust for python3.12 failures --- pyomo/common/dependencies.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index a0717dba883..2ce12dcac0f 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -826,6 +826,17 @@ def _finalize_numpy(np, available): numeric_types.RegisterComplexType(t) +def _pyutilib_importer(): + # On newer Pythons, PyUtilib import will fail, but only if a + # second-level module is imported. We will arbirtarily choose to + # check pyutilib.component (as that is the path exercised by the + # pyomo.common.tempfiles deprecation path) + import pyutilib + import pyutilib.component + + return pyutilib + + dill, dill_available = attempt_import('dill') mpi4py, mpi4py_available = attempt_import('mpi4py') networkx, networkx_available = attempt_import('networkx') @@ -833,7 +844,7 @@ def _finalize_numpy(np, available): pandas, pandas_available = attempt_import('pandas') plotly, plotly_available = attempt_import('plotly') pympler, pympler_available = attempt_import('pympler', callback=_finalize_pympler) -pyutilib, pyutilib_available = attempt_import('pyutilib') +pyutilib, pyutilib_available = attempt_import('pyutilib', importer=_pyutilib_importer) scipy, scipy_available = attempt_import( 'scipy', callback=_finalize_scipy, From 407e3a2fb3cd2c33af6372ad83890be0a81f4628 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Dec 2023 11:52:51 -0700 Subject: [PATCH 0563/1204] NFC: fix typo --- pyomo/common/dependencies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 2ce12dcac0f..7464d632f69 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -828,7 +828,7 @@ def _finalize_numpy(np, available): def _pyutilib_importer(): # On newer Pythons, PyUtilib import will fail, but only if a - # second-level module is imported. We will arbirtarily choose to + # second-level module is imported. We will arbitrarily choose to # check pyutilib.component (as that is the path exercised by the # pyomo.common.tempfiles deprecation path) import pyutilib From a64b96ec5e5720fd159a7a0f2277fc10fc45e92e Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Dec 2023 13:00:19 -0700 Subject: [PATCH 0564/1204] Prevent resolution of pyutilib_available in environ import --- pyomo/dataportal/plugins/__init__.py | 6 +----- pyomo/dataportal/plugins/sheet.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/pyomo/dataportal/plugins/__init__.py b/pyomo/dataportal/plugins/__init__.py index e861233dc01..c3387af9d1e 100644 --- a/pyomo/dataportal/plugins/__init__.py +++ b/pyomo/dataportal/plugins/__init__.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from pyomo.common.dependencies import pyutilib, pyutilib_available - def load(): import pyomo.dataportal.plugins.csv_table @@ -19,6 +17,4 @@ def load(): import pyomo.dataportal.plugins.json_dict import pyomo.dataportal.plugins.text import pyomo.dataportal.plugins.xml_table - - if pyutilib_available: - import pyomo.dataportal.plugins.sheet + import pyomo.dataportal.plugins.sheet diff --git a/pyomo/dataportal/plugins/sheet.py b/pyomo/dataportal/plugins/sheet.py index bc7e4d06952..8672b9917da 100644 --- a/pyomo/dataportal/plugins/sheet.py +++ b/pyomo/dataportal/plugins/sheet.py @@ -18,9 +18,18 @@ # ) from pyomo.dataportal.factory import DataManagerFactory from pyomo.common.errors import ApplicationError -from pyomo.common.dependencies import attempt_import +from pyomo.common.dependencies import attempt_import, importlib, pyutilib -spreadsheet, spreadsheet_available = attempt_import('pyutilib.excel.spreadsheet') + +def _spreadsheet_importer(): + # verify pyutilib imported correctly the first time + pyutilib.component + return importlib.import_module('pyutilib.excel.spreadsheet') + + +spreadsheet, spreadsheet_available = attempt_import( + 'pyutilib.excel.spreadsheet', importer=_spreadsheet_importer +) def _attempt_open_excel(): From 8ad516c7e601fd0516bf860a2ec62a3ef8eed728 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Dec 2023 13:02:16 -0700 Subject: [PATCH 0565/1204] Clean up pyutilib import --- pyomo/common/dependencies.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 7464d632f69..350762bc8ad 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -831,10 +831,8 @@ def _pyutilib_importer(): # second-level module is imported. We will arbitrarily choose to # check pyutilib.component (as that is the path exercised by the # pyomo.common.tempfiles deprecation path) - import pyutilib - import pyutilib.component - - return pyutilib + importlib.import_module('pyutilib.component') + return importlib.import_module('pyutilib') dill, dill_available = attempt_import('dill') From 3d1db1363f5e96069ef5f6deafe458a83f0fb0fe Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 7 Dec 2023 16:21:59 -0500 Subject: [PATCH 0566/1204] redesign calc_jacobians function --- pyomo/contrib/mindtpy/extended_cutting_plane.py | 4 +++- pyomo/contrib/mindtpy/feasibility_pump.py | 4 +++- pyomo/contrib/mindtpy/outer_approximation.py | 4 +++- pyomo/contrib/mindtpy/util.py | 10 +++++----- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/pyomo/contrib/mindtpy/extended_cutting_plane.py b/pyomo/contrib/mindtpy/extended_cutting_plane.py index 3a09af155a0..08c89a4c5f0 100644 --- a/pyomo/contrib/mindtpy/extended_cutting_plane.py +++ b/pyomo/contrib/mindtpy/extended_cutting_plane.py @@ -86,7 +86,9 @@ def check_config(self): def initialize_mip_problem(self): '''Deactivate the nonlinear constraints to create the MIP problem.''' super().initialize_mip_problem() - self.jacobians = calc_jacobians(self.mip, self.config) # preload jacobians + self.jacobians = calc_jacobians( + self.mip, self.config.differentiate_mode + ) # preload jacobians self.mip.MindtPy_utils.cuts.ecp_cuts = ConstraintList( doc='Extended Cutting Planes' ) diff --git a/pyomo/contrib/mindtpy/feasibility_pump.py b/pyomo/contrib/mindtpy/feasibility_pump.py index 990f56b8f93..bf6fb8f84bb 100644 --- a/pyomo/contrib/mindtpy/feasibility_pump.py +++ b/pyomo/contrib/mindtpy/feasibility_pump.py @@ -46,7 +46,9 @@ def check_config(self): def initialize_mip_problem(self): '''Deactivate the nonlinear constraints to create the MIP problem.''' super().initialize_mip_problem() - self.jacobians = calc_jacobians(self.mip, self.config) # preload jacobians + self.jacobians = calc_jacobians( + self.mip, self.config.differentiate_mode + ) # preload jacobians self.mip.MindtPy_utils.cuts.oa_cuts = ConstraintList( doc='Outer approximation cuts' ) diff --git a/pyomo/contrib/mindtpy/outer_approximation.py b/pyomo/contrib/mindtpy/outer_approximation.py index 6cf0b26cb37..4fd140a0bba 100644 --- a/pyomo/contrib/mindtpy/outer_approximation.py +++ b/pyomo/contrib/mindtpy/outer_approximation.py @@ -96,7 +96,9 @@ def check_config(self): def initialize_mip_problem(self): '''Deactivate the nonlinear constraints to create the MIP problem.''' super().initialize_mip_problem() - self.jacobians = calc_jacobians(self.mip, self.config) # preload jacobians + self.jacobians = calc_jacobians( + self.mip, self.config.differentiate_mode + ) # preload jacobians self.mip.MindtPy_utils.cuts.oa_cuts = ConstraintList( doc='Outer approximation cuts' ) diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index ec2829c6a18..5ca4604d37e 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -41,7 +41,7 @@ numpy = attempt_import('numpy')[0] -def calc_jacobians(model, config): +def calc_jacobians(model, differentiate_mode): """Generates a map of jacobians for the variables in the model. This function generates a map of jacobians corresponding to the variables in the @@ -51,15 +51,15 @@ def calc_jacobians(model, config): ---------- model : Pyomo model Target model to calculate jacobian. - config : ConfigBlock - The specific configurations for MindtPy. + differentiate_mode : String + The differentiate mode to calculate Jacobians. """ # Map nonlinear_constraint --> Map( # variable --> jacobian of constraint w.r.t. variable) jacobians = ComponentMap() - if config.differentiate_mode == 'reverse_symbolic': + if differentiate_mode == 'reverse_symbolic': mode = EXPR.differentiate.Modes.reverse_symbolic - elif config.differentiate_mode == 'sympy': + elif differentiate_mode == 'sympy': mode = EXPR.differentiate.Modes.sympy for c in model.MindtPy_utils.nonlinear_constraint_list: vars_in_constr = list(EXPR.identify_variables(c.body)) From 7e694136a8ac8cd197c7b56f2613a40597acbb59 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 7 Dec 2023 16:26:05 -0500 Subject: [PATCH 0567/1204] redesign initialize_feas_subproblem function --- pyomo/contrib/mindtpy/algorithm_base_class.py | 2 +- pyomo/contrib/mindtpy/util.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 78250d1ba59..05f1e4389d3 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -2629,7 +2629,7 @@ def initialize_mip_problem(self): self.fixed_nlp = self.working_model.clone() TransformationFactory('core.fix_integer_vars').apply_to(self.fixed_nlp) - initialize_feas_subproblem(self.fixed_nlp, config) + initialize_feas_subproblem(self.fixed_nlp, config.feasibility_norm) def initialize_subsolvers(self): """Initialize and set options for MIP and NLP subsolvers.""" diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index 5ca4604d37e..551945dfc67 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -70,7 +70,7 @@ def calc_jacobians(model, differentiate_mode): return jacobians -def initialize_feas_subproblem(m, config): +def initialize_feas_subproblem(m, feasibility_norm): """Adds feasibility slack variables according to config.feasibility_norm (given an infeasible problem). Defines the objective function of the feasibility subproblem. @@ -78,14 +78,14 @@ def initialize_feas_subproblem(m, config): ---------- m : Pyomo model The feasbility NLP subproblem. - config : ConfigBlock - The specific configurations for MindtPy. + feasibility_norm : String + The norm used to generate the objective function. """ MindtPy = m.MindtPy_utils # generate new constraints for i, constr in enumerate(MindtPy.nonlinear_constraint_list, 1): if constr.has_ub(): - if config.feasibility_norm in {'L1', 'L2'}: + if feasibility_norm in {'L1', 'L2'}: MindtPy.feas_opt.feas_constraints.add( constr.body - constr.upper <= MindtPy.feas_opt.slack_var[i] ) @@ -94,7 +94,7 @@ def initialize_feas_subproblem(m, config): constr.body - constr.upper <= MindtPy.feas_opt.slack_var ) if constr.has_lb(): - if config.feasibility_norm in {'L1', 'L2'}: + if feasibility_norm in {'L1', 'L2'}: MindtPy.feas_opt.feas_constraints.add( constr.body - constr.lower >= -MindtPy.feas_opt.slack_var[i] ) @@ -103,11 +103,11 @@ def initialize_feas_subproblem(m, config): constr.body - constr.lower >= -MindtPy.feas_opt.slack_var ) # Setup objective function for the feasibility subproblem. - if config.feasibility_norm == 'L1': + if feasibility_norm == 'L1': MindtPy.feas_obj = Objective( expr=sum(s for s in MindtPy.feas_opt.slack_var.values()), sense=minimize ) - elif config.feasibility_norm == 'L2': + elif feasibility_norm == 'L2': MindtPy.feas_obj = Objective( expr=sum(s * s for s in MindtPy.feas_opt.slack_var.values()), sense=minimize ) From c9f788849c0c15c197e85a8ce7e10df52bce2c98 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 7 Dec 2023 16:34:26 -0500 Subject: [PATCH 0568/1204] redesign calc_jacobians function --- pyomo/contrib/mindtpy/extended_cutting_plane.py | 3 ++- pyomo/contrib/mindtpy/feasibility_pump.py | 3 ++- pyomo/contrib/mindtpy/outer_approximation.py | 3 ++- pyomo/contrib/mindtpy/util.py | 10 +++++----- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pyomo/contrib/mindtpy/extended_cutting_plane.py b/pyomo/contrib/mindtpy/extended_cutting_plane.py index 08c89a4c5f0..f5fa205e091 100644 --- a/pyomo/contrib/mindtpy/extended_cutting_plane.py +++ b/pyomo/contrib/mindtpy/extended_cutting_plane.py @@ -87,7 +87,8 @@ def initialize_mip_problem(self): '''Deactivate the nonlinear constraints to create the MIP problem.''' super().initialize_mip_problem() self.jacobians = calc_jacobians( - self.mip, self.config.differentiate_mode + self.mip.MindtPy_utils.nonlinear_constraint_list, + self.config.differentiate_mode, ) # preload jacobians self.mip.MindtPy_utils.cuts.ecp_cuts = ConstraintList( doc='Extended Cutting Planes' diff --git a/pyomo/contrib/mindtpy/feasibility_pump.py b/pyomo/contrib/mindtpy/feasibility_pump.py index bf6fb8f84bb..9d5be89bab5 100644 --- a/pyomo/contrib/mindtpy/feasibility_pump.py +++ b/pyomo/contrib/mindtpy/feasibility_pump.py @@ -47,7 +47,8 @@ def initialize_mip_problem(self): '''Deactivate the nonlinear constraints to create the MIP problem.''' super().initialize_mip_problem() self.jacobians = calc_jacobians( - self.mip, self.config.differentiate_mode + self.mip.MindtPy_utils.nonlinear_constraint_list, + self.config.differentiate_mode, ) # preload jacobians self.mip.MindtPy_utils.cuts.oa_cuts = ConstraintList( doc='Outer approximation cuts' diff --git a/pyomo/contrib/mindtpy/outer_approximation.py b/pyomo/contrib/mindtpy/outer_approximation.py index 4fd140a0bba..6d790ce70d0 100644 --- a/pyomo/contrib/mindtpy/outer_approximation.py +++ b/pyomo/contrib/mindtpy/outer_approximation.py @@ -97,7 +97,8 @@ def initialize_mip_problem(self): '''Deactivate the nonlinear constraints to create the MIP problem.''' super().initialize_mip_problem() self.jacobians = calc_jacobians( - self.mip, self.config.differentiate_mode + self.mip.MindtPy_utils.nonlinear_constraint_list, + self.config.differentiate_mode, ) # preload jacobians self.mip.MindtPy_utils.cuts.oa_cuts = ConstraintList( doc='Outer approximation cuts' diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index 551945dfc67..5845b3047f5 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -41,16 +41,16 @@ numpy = attempt_import('numpy')[0] -def calc_jacobians(model, differentiate_mode): +def calc_jacobians(constraint_list, differentiate_mode): """Generates a map of jacobians for the variables in the model. This function generates a map of jacobians corresponding to the variables in the - model. + constraint list. Parameters ---------- - model : Pyomo model - Target model to calculate jacobian. + constraint_list : List + The list of constraints to calculate Jacobians. differentiate_mode : String The differentiate mode to calculate Jacobians. """ @@ -61,7 +61,7 @@ def calc_jacobians(model, differentiate_mode): mode = EXPR.differentiate.Modes.reverse_symbolic elif differentiate_mode == 'sympy': mode = EXPR.differentiate.Modes.sympy - for c in model.MindtPy_utils.nonlinear_constraint_list: + for c in constraint_list: vars_in_constr = list(EXPR.identify_variables(c.body)) jac_list = EXPR.differentiate(c.body, wrt_list=vars_in_constr, mode=mode) jacobians[c] = ComponentMap( From fd5b4daa672fab7bab4b90cd1bfdc095625f5c79 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Dec 2023 16:38:06 -0700 Subject: [PATCH 0569/1204] Remove distutils dependency in CI harnesses --- .github/workflows/test_branches.yml | 4 ++-- .github/workflows/test_pr_and_main.yml | 4 ++-- .jenkins.sh | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index f3f19b78591..ff24c731d94 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -592,8 +592,8 @@ jobs: echo "COVERAGE_PROCESS_START=$COVERAGE_RC" >> $GITHUB_ENV cp ${GITHUB_WORKSPACE}/.coveragerc ${COVERAGE_RC} echo "data_file=${COVERAGE_BASE}age" >> ${COVERAGE_RC} - SITE_PACKAGES=$($PYTHON_EXE -c "from distutils.sysconfig import \ - get_python_lib; print(get_python_lib())") + SITE_PACKAGES=$($PYTHON_EXE -c \ + "import sysconfig; print(sysconfig.get_path('purelib'))") echo "Python site-packages: $SITE_PACKAGES" echo 'import coverage; coverage.process_startup()' \ > ${SITE_PACKAGES}/run_coverage_at_startup.pth diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 13dc828c639..6e5604bea47 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -622,8 +622,8 @@ jobs: echo "COVERAGE_PROCESS_START=$COVERAGE_RC" >> $GITHUB_ENV cp ${GITHUB_WORKSPACE}/.coveragerc ${COVERAGE_RC} echo "data_file=${COVERAGE_BASE}age" >> ${COVERAGE_RC} - SITE_PACKAGES=$($PYTHON_EXE -c "from distutils.sysconfig import \ - get_python_lib; print(get_python_lib())") + SITE_PACKAGES=$($PYTHON_EXE -c \ + "import sysconfig; print(sysconfig.get_path('purelib'))") echo "Python site-packages: $SITE_PACKAGES" echo 'import coverage; coverage.process_startup()' \ > ${SITE_PACKAGES}/run_coverage_at_startup.pth diff --git a/.jenkins.sh b/.jenkins.sh index f31fef99377..544cb549175 100644 --- a/.jenkins.sh +++ b/.jenkins.sh @@ -77,7 +77,7 @@ if test -z "$MODE" -o "$MODE" == setup; then source python/bin/activate # Because modules set the PYTHONPATH, we need to make sure that the # virtualenv appears first - LOCAL_SITE_PACKAGES=`python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"` + LOCAL_SITE_PACKAGES=`python -c "import sysconfig; print(sysconfig.get_path('purelib'))"` export PYTHONPATH="$LOCAL_SITE_PACKAGES:$PYTHONPATH" # Set up Pyomo checkouts From e3df7bdaf62f5fadd4a76fb4dad9aa4df084ba2f Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Dec 2023 23:13:02 -0700 Subject: [PATCH 0570/1204] Remove distutils references --- pyomo/common/cmake_builder.py | 9 +++------ pyomo/contrib/appsi/build.py | 3 +-- pyomo/contrib/mcpp/build.py | 6 +++--- setup.py | 2 ++ 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/pyomo/common/cmake_builder.py b/pyomo/common/cmake_builder.py index 71358c29fb2..bb612b43b72 100644 --- a/pyomo/common/cmake_builder.py +++ b/pyomo/common/cmake_builder.py @@ -32,11 +32,8 @@ def handleReadonly(function, path, excinfo): def build_cmake_project( targets, package_name=None, description=None, user_args=[], parallel=None ): - # Note: setuptools must be imported before distutils to avoid - # warnings / errors with recent setuptools distributions - from setuptools import Extension - import distutils.core - from distutils.command.build_ext import build_ext + from setuptools import Extension, Distribution + from setuptools.command.build_ext import build_ext class _CMakeBuild(build_ext, object): def run(self): @@ -122,7 +119,7 @@ def __init__(self, target_dir, user_args, parallel): 'ext_modules': ext_modules, 'cmdclass': {'build_ext': _CMakeBuild}, } - dist = distutils.core.Distribution(package_config) + dist = Distribution(package_config) basedir = os.path.abspath(os.path.curdir) try: tmpdir = os.path.abspath(tempfile.mkdtemp()) diff --git a/pyomo/contrib/appsi/build.py b/pyomo/contrib/appsi/build.py index 2a4e7bb785e..2c8d02dd3ac 100644 --- a/pyomo/contrib/appsi/build.py +++ b/pyomo/contrib/appsi/build.py @@ -63,8 +63,7 @@ def get_appsi_extension(in_setup=False, appsi_root=None): def build_appsi(args=[]): print('\n\n**** Building APPSI ****') - import setuptools - from distutils.dist import Distribution + from setuptools import Distribution from pybind11.setup_helpers import build_ext import pybind11.setup_helpers from pyomo.common.envvar import PYOMO_CONFIG_DIR diff --git a/pyomo/contrib/mcpp/build.py b/pyomo/contrib/mcpp/build.py index 95246e5278e..55c893335d2 100644 --- a/pyomo/contrib/mcpp/build.py +++ b/pyomo/contrib/mcpp/build.py @@ -64,8 +64,8 @@ def _generate_configuration(): def build_mcpp(): - import distutils.core - from distutils.command.build_ext import build_ext + from setuptools import Distribution + from setuptools.command.build_ext import build_ext class _BuildWithoutPlatformInfo(build_ext, object): # Python3.x puts platform information into the generated SO file @@ -87,7 +87,7 @@ def get_ext_filename(self, ext_name): print("\n**** Building MCPP library ****") package_config = _generate_configuration() package_config['cmdclass'] = {'build_ext': _BuildWithoutPlatformInfo} - dist = distutils.core.Distribution(package_config) + dist = Distribution(package_config) install_dir = os.path.join(envvar.PYOMO_CONFIG_DIR, 'lib') dist.get_command_obj('install_lib').install_dir = install_dir try: diff --git a/setup.py b/setup.py index b019abe91cb..dae62e72ca0 100644 --- a/setup.py +++ b/setup.py @@ -19,8 +19,10 @@ from setuptools import setup, find_packages, Command try: + # This works beginning in setuptools 40.7.0 (27 Jan 2019) from setuptools import DistutilsOptionError except ImportError: + # Needed for setuptools prior to 40.7.0 from distutils.errors import DistutilsOptionError From eecd1497d43a564c155d17b0d66a423ecd7f09a3 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Sun, 10 Dec 2023 06:26:50 -0700 Subject: [PATCH 0571/1204] Update index.rst --- doc/OnlineDocs/model_debugging/index.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/OnlineDocs/model_debugging/index.rst b/doc/OnlineDocs/model_debugging/index.rst index 9efb89b4ad1..e1dafe45e0a 100644 --- a/doc/OnlineDocs/model_debugging/index.rst +++ b/doc/OnlineDocs/model_debugging/index.rst @@ -7,4 +7,3 @@ Debugging Pyomo Models model_interrogation.rst FAQ.rst getting_help.rst - latex_printing.rst From bf78e46e3aff3bd9a4402bc082d56f959c2e0de7 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 10 Dec 2023 10:14:26 -0700 Subject: [PATCH 0572/1204] Delete Suffix APIs deprecated since Pyomo 4.1.x --- pyomo/core/base/suffix.py | 100 -------------------------------------- 1 file changed, 100 deletions(-) diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index 46a87523001..a056c2bbaef 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -262,13 +262,6 @@ def direction(self, direction): ) self._direction = direction - @deprecated( - 'Suffix.exportEnabled is replaced with Suffix.export_enabled.', - version='4.1.10486', - ) - def exportEnabled(self): - return self.export_enabled() - def export_enabled(self): """ Returns True when this suffix is enabled for export to @@ -276,13 +269,6 @@ def export_enabled(self): """ return bool(self._direction & Suffix.EXPORT) - @deprecated( - 'Suffix.importEnabled is replaced with Suffix.import_enabled.', - version='4.1.10486', - ) - def importEnabled(self): - return self.import_enabled() - def import_enabled(self): """ Returns True when this suffix is enabled for import from @@ -290,13 +276,6 @@ def import_enabled(self): """ return bool(self._direction & Suffix.IMPORT) - @deprecated( - 'Suffix.updateValues is replaced with Suffix.update_values.', - version='4.1.10486', - ) - def updateValues(self, data, expand=True): - return self.update_values(data, expand) - def update_values(self, data, expand=True): """ Updates the suffix data given a list of component,value @@ -316,12 +295,6 @@ def update_values(self, data, expand=True): # As implemented by MutableMapping self.update(data) - @deprecated( - 'Suffix.setValue is replaced with Suffix.set_value.', version='4.1.10486' - ) - def setValue(self, component, value, expand=True): - return self.set_value(component, value, expand) - def set_value(self, component, value, expand=True): """ Sets the value of this suffix on the specified component. @@ -339,13 +312,6 @@ def set_value(self, component, value, expand=True): else: self[component] = value - @deprecated( - 'Suffix.setAllValues is replaced with Suffix.set_all_values.', - version='4.1.10486', - ) - def setAllValues(self, value): - return self.set_all_values(value) - def set_all_values(self, value): """ Sets the value of this suffix on all components. @@ -353,12 +319,6 @@ def set_all_values(self, value): for ndx in self: self[ndx] = value - @deprecated( - 'Suffix.clearValue is replaced with Suffix.clear_value.', version='4.1.10486' - ) - def clearValue(self, component, expand=True): - return self.clear_value(component, expand) - def clear_value(self, component, expand=True): """ Clears suffix information for a component. @@ -375,25 +335,12 @@ def clear_value(self, component, expand=True): except KeyError: pass - @deprecated( - 'Suffix.clearAllValues is replaced with Suffix.clear_all_values.', - version='4.1.10486', - ) - def clearAllValues(self): - return self.clear_all_values() - def clear_all_values(self): """ Clears all suffix data. """ self.clear() - @deprecated( - 'Suffix.setDatatype is replaced with Suffix.set_datatype.', version='4.1.10486' - ) - def setDatatype(self, datatype): - return self.set_datatype(datatype) - def set_datatype(self, datatype): """ Set the suffix datatype. @@ -406,25 +353,12 @@ def set_datatype(self, datatype): ) self._datatype = datatype - @deprecated( - 'Suffix.getDatatype is replaced with Suffix.get_datatype.', version='4.1.10486' - ) - def getDatatype(self): - return self.get_datatype() - def get_datatype(self): """ Return the suffix datatype. """ return self._datatype - @deprecated( - 'Suffix.setDirection is replaced with Suffix.set_direction.', - version='4.1.10486', - ) - def setDirection(self, direction): - return self.set_direction(direction) - def set_direction(self, direction): """ Set the suffix direction. @@ -437,13 +371,6 @@ def set_direction(self, direction): ) self._direction = direction - @deprecated( - 'Suffix.getDirection is replaced with Suffix.get_direction.', - version='4.1.10486', - ) - def getDirection(self): - return self.get_direction() - def get_direction(self): """ Return the suffix direction. @@ -471,33 +398,6 @@ def _pprint(self): lambda k, v: [v], ) - # TODO: delete - @deprecated( - 'Suffix.getValue is replaced with the dict-interface method Suffix.get.', - version='4.1.10486', - ) - def getValue(self, component, *args): - """ - Returns the current value of this suffix for the specified - component. - """ - # As implemented by MutableMapping - return self.get(component, *args) - - # TODO: delete - @deprecated( - 'Suffix.extractValues() is replaced with ' - 'the dict-interface method Suffix.items().', - version='4.1.10486', - ) - def extractValues(self): - """ - Extract all data stored on this Suffix into a list of - component, value tuples. - """ - # As implemented by MutableMapping - return list(self.items()) - # # Override a few methods to make sure the ActiveComponent versions are # called. We can't just switch the inheritance order due to From eb486b6572513aa462ae3d42bbd07b582335954a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 10 Dec 2023 10:15:05 -0700 Subject: [PATCH 0573/1204] Remove unreachable code --- pyomo/core/base/suffix.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index a056c2bbaef..4f1e5e93eb4 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -377,16 +377,6 @@ def get_direction(self): """ return self._direction - def __str__(self): - """ - Return a string representation of the suffix. If the name - attribute is None, then return '' - """ - name = self.name - if name is None: - return '' - return name - def _pprint(self): return ( [ From f503beefda0112ab519b03e3e7c1964a5d2ad2f5 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 10 Dec 2023 11:54:52 -0700 Subject: [PATCH 0574/1204] Convert Suffix direction, datatype to Enums --- pyomo/core/base/suffix.py | 89 +++++++++++----------------- pyomo/core/tests/unit/test_suffix.py | 77 +++++++++--------------- 2 files changed, 64 insertions(+), 102 deletions(-) diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index 4f1e5e93eb4..6d406be6116 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -134,6 +134,22 @@ def suffix_generator(a_block, datatype=False): yield name, suffix +class SuffixDataType(enum.IntEnum): + INT = 0 + FLOAT = 4 + + +class SuffixDirection(enum.IntEnum): + LOCAL = 0 + EXPORT = 1 + IMPORT = 2 + IMPORT_EXPORT = 3 + + +_SuffixDataTypeDomain = In(SuffixDataType) +_SuffixDirectionDomain = In(SuffixDirection) + + @ModelComponentFactory.register("Declare a container for extraneous model data") class Suffix(ComponentMap, ActiveComponent): """A model suffix, representing extraneous model data""" @@ -148,28 +164,17 @@ class Suffix(ComponentMap, ActiveComponent): """ # Suffix Directions: - # If more directions are added be sure to update the error message - # in the setDirection method - # neither sent to solver or received from solver - LOCAL = 0 - # sent to solver or other external location - EXPORT = 1 - # obtained from solver or other external source - IMPORT = 2 - IMPORT_EXPORT = 3 # both - - SuffixDirections = (LOCAL, EXPORT, IMPORT, IMPORT_EXPORT) - SuffixDirectionToStr = { - LOCAL: 'Suffix.LOCAL', - EXPORT: 'Suffix.EXPORT', - IMPORT: 'Suffix.IMPORT', - IMPORT_EXPORT: 'Suffix.IMPORT_EXPORT', - } - # Suffix Datatypes - FLOAT = 4 - INT = 0 - SuffixDatatypes = (FLOAT, INT, None) - SuffixDatatypeToStr = {FLOAT: 'Suffix.FLOAT', INT: 'Suffix.INT', None: str(None)} + # - neither sent to solver or received from solver + LOCAL = SuffixDirection.LOCAL + # - sent to solver or other external location + EXPORT = SuffixDirection.EXPORT + # - obtained from solver or other external source + IMPORT = SuffixDirection.IMPORT + # - both import and export + IMPORT_EXPORT = SuffixDirection.IMPORT_EXPORT + + FLOAT = SuffixDataType.FLOAT + INT = SuffixDataType.INT @overload def __init__( @@ -239,11 +244,8 @@ def datatype(self): @datatype.setter def datatype(self, datatype): """Set the suffix datatype.""" - if datatype not in self.SuffixDatatypeToStr: - raise ValueError( - "Suffix datatype must be one of: %s. \n" - "Value given: %s" % (list(self.SuffixDatatypeToStr.values()), datatype) - ) + if datatype is not None: + datatype = _SuffixDataTypeDomain(datatype) self._datatype = datatype @property @@ -254,12 +256,8 @@ def direction(self): @direction.setter def direction(self, direction): """Set the suffix direction.""" - if direction not in self.SuffixDirectionToStr: - raise ValueError( - "Suffix direction must be one of: %s. \n" - "Value given: %s" - % (list(self.SuffixDirectionToStr.values()), direction) - ) + if direction is not None: + direction = _SuffixDirectionDomain(direction) self._direction = direction def export_enabled(self): @@ -345,44 +343,29 @@ def set_datatype(self, datatype): """ Set the suffix datatype. """ - if datatype not in self.SuffixDatatypes: - raise ValueError( - "Suffix datatype must be one of: %s. \n" - "Value given: %s" - % (list(Suffix.SuffixDatatypeToStr.values()), datatype) - ) - self._datatype = datatype + self.datatype = datatype def get_datatype(self): """ Return the suffix datatype. """ - return self._datatype + return self.datatype def set_direction(self, direction): """ Set the suffix direction. """ - if direction not in self.SuffixDirections: - raise ValueError( - "Suffix direction must be one of: %s. \n" - "Value given: %s" - % (list(self.SuffixDirectionToStr.values()), direction) - ) - self._direction = direction + self.direction = direction def get_direction(self): """ Return the suffix direction. """ - return self._direction + return self.direction def _pprint(self): return ( - [ - ('Direction', self.SuffixDirectionToStr[self._direction]), - ('Datatype', self.SuffixDatatypeToStr[self._datatype]), - ], + [('Direction', str(self._direction)), ('Datatype', str(self._datatype))], ((str(k), v) for k, v in self._dict.values()), ("Value",), lambda k, v: [v], diff --git a/pyomo/core/tests/unit/test_suffix.py b/pyomo/core/tests/unit/test_suffix.py index 131e2054284..ddfa9385f5a 100644 --- a/pyomo/core/tests/unit/test_suffix.py +++ b/pyomo/core/tests/unit/test_suffix.py @@ -55,19 +55,6 @@ def simple_obj_rule(model, i): class TestSuffixMethods(unittest.TestCase): - # test __init__ - def test_init(self): - model = ConcreteModel() - # no keywords - model.junk = Suffix() - model.del_component('junk') - - for direction, datatype in itertools.product( - Suffix.SuffixDirections, Suffix.SuffixDatatypes - ): - model.junk = Suffix(direction=direction, datatype=datatype) - model.del_component('junk') - # test import_enabled def test_import_enabled(self): model = ConcreteModel() @@ -853,45 +840,37 @@ def test_clear_all_values(self): def test_set_datatype_get_datatype(self): model = ConcreteModel() model.junk = Suffix(datatype=Suffix.FLOAT) - self.assertTrue(model.junk.get_datatype() is Suffix.FLOAT) - model.junk.set_datatype(Suffix.INT) - self.assertTrue(model.junk.get_datatype() is Suffix.INT) - model.junk.set_datatype(None) - self.assertTrue(model.junk.get_datatype() is None) - - # test that calling set_datatype with a bad value fails - def test_set_datatype_badvalue(self): - model = ConcreteModel() - model.junk = Suffix() - try: - model.junk.set_datatype(1.0) - except ValueError: - pass - else: - self.fail("Calling set_datatype with a bad type should fail.") + self.assertEqual(model.junk.datatype, Suffix.FLOAT) + model.junk.datatype = Suffix.INT + self.assertEqual(model.junk.datatype, Suffix.INT) + model.junk.datatype = None + self.assertEqual(model.junk.datatype, None) + model.junk.datatype = 'FLOAT' + self.assertEqual(model.junk.datatype, Suffix.FLOAT) + model.junk.datatype = 'INT' + self.assertEqual(model.junk.datatype, Suffix.INT) + model.junk.datatype = 4 + self.assertEqual(model.junk.datatype, Suffix.FLOAT) + model.junk.datatype = 0 + self.assertEqual(model.junk.datatype, Suffix.INT) + + with self.assertRaisesRegex(ValueError, "1.0 is not a valid SuffixDataType"): + model.junk.datatype = 1.0 # test set_direction and get_direction def test_set_direction_get_direction(self): model = ConcreteModel() model.junk = Suffix(direction=Suffix.LOCAL) - self.assertTrue(model.junk.get_direction() is Suffix.LOCAL) - model.junk.set_direction(Suffix.EXPORT) - self.assertTrue(model.junk.get_direction() is Suffix.EXPORT) - model.junk.set_direction(Suffix.IMPORT) - self.assertTrue(model.junk.get_direction() is Suffix.IMPORT) - model.junk.set_direction(Suffix.IMPORT_EXPORT) - self.assertTrue(model.junk.get_direction() is Suffix.IMPORT_EXPORT) + self.assertEqual(model.junk.direction, Suffix.LOCAL) + model.junk.direction = Suffix.EXPORT + self.assertEqual(model.junk.direction, Suffix.EXPORT) + model.junk.direction = Suffix.IMPORT + self.assertEqual(model.junk.direction, Suffix.IMPORT) + model.junk.direction = Suffix.IMPORT_EXPORT + self.assertEqual(model.junk.direction, Suffix.IMPORT_EXPORT) - # test that calling set_direction with a bad value fails - def test_set_direction_badvalue(self): - model = ConcreteModel() - model.junk = Suffix() - try: - model.junk.set_direction('a') - except ValueError: - pass - else: - self.fail("Calling set_datatype with a bad type should fail.") + with self.assertRaisesRegex(ValueError, "'a' is not a valid SuffixDirection"): + model.junk.direction = 'a' # test __str__ def test_str(self): @@ -905,11 +884,11 @@ def test_pprint(self): model.junk = Suffix(direction=Suffix.EXPORT) output = StringIO() model.junk.pprint(ostream=output) - model.junk.set_direction(Suffix.IMPORT) + model.junk.direction = Suffix.IMPORT model.junk.pprint(ostream=output) - model.junk.set_direction(Suffix.LOCAL) + model.junk.direction = Suffix.LOCAL model.junk.pprint(ostream=output) - model.junk.set_direction(Suffix.IMPORT_EXPORT) + model.junk.direction = Suffix.IMPORT_EXPORT model.junk.pprint(ostream=output) model.pprint(ostream=output) From 35d612786436997768bb31f4d892738759485635 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 10 Dec 2023 11:56:40 -0700 Subject: [PATCH 0575/1204] Promote _pop_from_kwargs() utility from IndexComponent to Component --- pyomo/core/base/component.py | 21 +++++++++++++++++++++ pyomo/core/base/indexed_component.py | 21 --------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index a8550f8f469..bb855bd6f8d 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -723,6 +723,27 @@ def get_suffix_value(self, suffix_or_name, default=None): else: return suffix_or_name.get(self, default) + def _pop_from_kwargs(self, name, kwargs, namelist, notset=None): + args = [ + arg + for arg in (kwargs.pop(name, notset) for name in namelist) + if arg is not notset + ] + if len(args) == 1: + return args[0] + elif not args: + return notset + else: + argnames = "%s%s '%s='" % ( + ', '.join("'%s='" % _ for _ in namelist[:-1]), + ',' if len(namelist) > 2 else '', + namelist[-1], + ) + raise ValueError( + "Duplicate initialization: %s() only accepts one of %s" + % (name, argnames) + ) + class ActiveComponent(Component): """A Component that makes semantic sense to activate or deactivate diff --git a/pyomo/core/base/indexed_component.py b/pyomo/core/base/indexed_component.py index b474281f5b9..b1a158b5e18 100644 --- a/pyomo/core/base/indexed_component.py +++ b/pyomo/core/base/indexed_component.py @@ -746,27 +746,6 @@ def __delitem__(self, index): self._data[index]._component = None del self._data[index] - def _pop_from_kwargs(self, name, kwargs, namelist, notset=None): - args = [ - arg - for arg in (kwargs.pop(name, notset) for name in namelist) - if arg is not notset - ] - if len(args) == 1: - return args[0] - elif not args: - return notset - else: - argnames = "%s%s '%s='" % ( - ', '.join("'%s='" % _ for _ in namelist[:-1]), - ',' if len(namelist) > 2 else '', - namelist[-1], - ) - raise ValueError( - "Duplicate initialization: %s() only accepts one of %s" - % (name, argnames) - ) - def _construct_from_rule_using_setitem(self): if self._rule is None: return From 001ca4c45f23d6ce3fd601506566dfd4eb77ff5c Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 10 Dec 2023 11:57:17 -0700 Subject: [PATCH 0576/1204] Remove mostly repeated code --- pyomo/core/base/suffix.py | 135 +++++++++++--------------------------- 1 file changed, 39 insertions(+), 96 deletions(-) diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index 6d406be6116..074d5ad4d2e 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -36,102 +36,45 @@ # - suffix_generator -def active_export_suffix_generator(a_block, datatype=False): - if datatype is False: - for name, suffix in a_block.component_map(Suffix, active=True).items(): - if suffix.export_enabled() is True: - yield name, suffix - else: - for name, suffix in a_block.component_map(Suffix, active=True).items(): - if (suffix.export_enabled() is True) and ( - suffix.get_datatype() is datatype - ): - yield name, suffix - - -def export_suffix_generator(a_block, datatype=False): - if datatype is False: - for name, suffix in a_block.component_map(Suffix).items(): - if suffix.export_enabled() is True: - yield name, suffix - else: - for name, suffix in a_block.component_map(Suffix).items(): - if (suffix.export_enabled() is True) and ( - suffix.get_datatype() is datatype - ): - yield name, suffix - - -def active_import_suffix_generator(a_block, datatype=False): - if datatype is False: - for name, suffix in a_block.component_map(Suffix, active=True).items(): - if suffix.import_enabled() is True: - yield name, suffix - else: - for name, suffix in a_block.component_map(Suffix, active=True).items(): - if (suffix.import_enabled() is True) and ( - suffix.get_datatype() is datatype - ): - yield name, suffix - - -def import_suffix_generator(a_block, datatype=False): - if datatype is False: - for name, suffix in a_block.component_map(Suffix).items(): - if suffix.import_enabled() is True: - yield name, suffix - else: - for name, suffix in a_block.component_map(Suffix).items(): - if (suffix.import_enabled() is True) and ( - suffix.get_datatype() is datatype - ): - yield name, suffix - - -def active_local_suffix_generator(a_block, datatype=False): - if datatype is False: - for name, suffix in a_block.component_map(Suffix, active=True).items(): - if suffix.get_direction() is Suffix.LOCAL: - yield name, suffix - else: - for name, suffix in a_block.component_map(Suffix, active=True).items(): - if (suffix.get_direction() is Suffix.LOCAL) and ( - suffix.get_datatype() is datatype - ): - yield name, suffix - - -def local_suffix_generator(a_block, datatype=False): - if datatype is False: - for name, suffix in a_block.component_map(Suffix).items(): - if suffix.get_direction() is Suffix.LOCAL: - yield name, suffix - else: - for name, suffix in a_block.component_map(Suffix).items(): - if (suffix.get_direction() is Suffix.LOCAL) and ( - suffix.get_datatype() is datatype - ): - yield name, suffix - - -def active_suffix_generator(a_block, datatype=False): - if datatype is False: - for name, suffix in a_block.component_map(Suffix, active=True).items(): - yield name, suffix - else: - for name, suffix in a_block.component_map(Suffix, active=True).items(): - if suffix.get_datatype() is datatype: - yield name, suffix - - -def suffix_generator(a_block, datatype=False): - if datatype is False: - for name, suffix in a_block.component_map(Suffix).items(): - yield name, suffix - else: - for name, suffix in a_block.component_map(Suffix).items(): - if suffix.get_datatype() is datatype: - yield name, suffix +def suffix_generator(a_block, datatype=NOTSET, direction=NOTSET, active=None): + _iter = a_block.component_map(Suffix, active=active).items() + if direction is not NOTSET: + direction = _SuffixDirectionDomain(direction) + if not direction: + _iter = filter(lambda item: item[1].direction == direction, _iter) + else: + _iter = filter(lambda item: item[1].direction & direction, _iter) + if datatype is not NOTSET: + _iter = filter(lambda item: item[1].datatype == datatype, _iter) + return _iter + + +def active_export_suffix_generator(a_block, datatype=NOTSET): + return suffix_generator(a_block, datatype, SuffixDirection.EXPORT, True) + + +def export_suffix_generator(a_block, datatype=NOTSET): + return suffix_generator(a_block, datatype, SuffixDirection.EXPORT) + + +def active_import_suffix_generator(a_block, datatype=NOTSET): + return suffix_generator(a_block, datatype, SuffixDirection.IMPORT, True) + + +def import_suffix_generator(a_block, datatype=NOTSET): + return suffix_generator(a_block, datatype, SuffixDirection.IMPORT) + + +def active_local_suffix_generator(a_block, datatype=NOTSET): + return suffix_generator(a_block, datatype, SuffixDirection.LOCAL, True) + + +def local_suffix_generator(a_block, datatype=NOTSET): + return suffix_generator(a_block, datatype, SuffixDirection.LOCAL) + + +def active_suffix_generator(a_block, datatype=NOTSET): + return suffix_generator(a_block, datatype, active=True) class SuffixDataType(enum.IntEnum): From 3b6cfa44dc5a33bbd250d757a6c6af6a39169b9a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 10 Dec 2023 11:58:16 -0700 Subject: [PATCH 0577/1204] Move to using Initializer() in Suffix --- pyomo/core/base/suffix.py | 43 +++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index 074d5ad4d2e..4f9d299a90a 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -11,15 +11,18 @@ __all__ = ('Suffix', 'active_export_suffix_generator', 'active_import_suffix_generator') +import enum import logging -from pyomo.common.pyomo_typing import overload from pyomo.common.collections import ComponentMap +from pyomo.common.config import In +from pyomo.common.deprecation import deprecated from pyomo.common.log import is_debug_set +from pyomo.common.modeling import NOTSET +from pyomo.common.pyomo_typing import overload from pyomo.common.timing import ConstructionTimer from pyomo.core.base.component import ActiveComponent, ModelComponentFactory - -from pyomo.common.deprecation import deprecated +from pyomo.core.base.initializer import Initializer logger = logging.getLogger('pyomo.core') @@ -132,31 +135,28 @@ def __init__( ): ... - def __init__(self, **kwds): + def __init__(self, **kwargs): # Suffix type information self._direction = None self._datatype = None self._rule = None - # The suffix direction - direction = kwds.pop('direction', Suffix.LOCAL) + # The suffix direction (note the setter performs error chrcking) + self.direction = kwargs.pop('direction', Suffix.LOCAL) - # The suffix datatype - datatype = kwds.pop('datatype', Suffix.FLOAT) + # The suffix datatype (note the setter performs error chrcking) + self.datatype = kwargs.pop('datatype', Suffix.FLOAT) # The suffix construction rule # TODO: deprecate the use of 'rule' - self._rule = kwds.pop('rule', None) - self._rule = kwds.pop('initialize', self._rule) - - # Check that keyword values make sense (these function have - # internal error checking). - self.set_direction(direction) - self.set_datatype(datatype) + self._rule = Initializer( + self._pop_from_kwargs('Suffix', kwargs, ('rule', 'initialize'), None), + treat_sequences_as_mappings=False, + ) # Initialize base classes - kwds.setdefault('ctype', Suffix) - ActiveComponent.__init__(self, **kwds) + kwargs.setdefault('ctype', Suffix) + ActiveComponent.__init__(self, **kwargs) ComponentMap.__init__(self) if self._rule is None: @@ -176,7 +176,14 @@ def construct(self, data=None): self._constructed = True if self._rule is not None: - self.update_values(self._rule(self._parent())) + rule = self._rule + block = self.parent_block() + if rule.contains_indices(): + # The index is coming in externally; we need to validate it + for index in rule.indices(): + self[index] = rule(block, index) + else: + self.update_values(rule(block, None)) timer.report() @property From 6e5f8ee6a26087012beaedc637345997388bdb2e Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 10 Dec 2023 11:58:45 -0700 Subject: [PATCH 0578/1204] Deprecate use of explicit datatype, direction setters and getters --- pyomo/core/base/suffix.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index 4f9d299a90a..6f497b6e5fc 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -289,24 +289,40 @@ def clear_all_values(self): """ self.clear() + @deprecated( + 'Suffix.set_datatype is replaced with the Suffix.datatype property', + version='6.7.1.dev0', + ) def set_datatype(self, datatype): """ Set the suffix datatype. """ self.datatype = datatype + @deprecated( + 'Suffix.get_datatype is replaced with the Suffix.datatype property', + version='6.7.1.dev0', + ) def get_datatype(self): """ Return the suffix datatype. """ return self.datatype + @deprecated( + 'Suffix.set_direction is replaced with the Suffix.direction property', + version='6.7.1.dev0', + ) def set_direction(self, direction): """ Set the suffix direction. """ self.direction = direction + @deprecated( + 'Suffix.set_direction is replaced with the Suffix.direction property', + version='6.7.1.dev0', + ) def get_direction(self): """ Return the suffix direction. From aaf64289b6a62135cbc0c778cb9b36de9fcd65ac Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 10 Dec 2023 11:59:13 -0700 Subject: [PATCH 0579/1204] Remove fragile use of 'is' --- pyomo/core/tests/unit/test_suffix.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/pyomo/core/tests/unit/test_suffix.py b/pyomo/core/tests/unit/test_suffix.py index ddfa9385f5a..6bd14c8f70b 100644 --- a/pyomo/core/tests/unit/test_suffix.py +++ b/pyomo/core/tests/unit/test_suffix.py @@ -58,33 +58,39 @@ class TestSuffixMethods(unittest.TestCase): # test import_enabled def test_import_enabled(self): model = ConcreteModel() + model.test_implicit = Suffix() + self.assertFalse(model.test_implicit.import_enabled()) + model.test_local = Suffix(direction=Suffix.LOCAL) - self.assertTrue(model.test_local.import_enabled() is False) + self.assertFalse(model.test_local.import_enabled()) model.test_out = Suffix(direction=Suffix.IMPORT) - self.assertTrue(model.test_out.import_enabled() is True) + self.assertTrue(model.test_out.import_enabled()) model.test_in = Suffix(direction=Suffix.EXPORT) - self.assertTrue(model.test_in.import_enabled() is False) + self.assertFalse(model.test_in.import_enabled()) model.test_inout = Suffix(direction=Suffix.IMPORT_EXPORT) - self.assertTrue(model.test_inout.import_enabled() is True) + self.assertTrue(model.test_inout.import_enabled()) # test export_enabled def test_export_enabled(self): model = ConcreteModel() + model.test_implicit = Suffix() + self.assertFalse(model.test_implicit.export_enabled()) + model.test_local = Suffix(direction=Suffix.LOCAL) - self.assertTrue(model.test_local.export_enabled() is False) + self.assertFalse(model.test_local.export_enabled()) model.test_out = Suffix(direction=Suffix.IMPORT) - self.assertTrue(model.test_out.export_enabled() is False) + self.assertFalse(model.test_out.export_enabled()) model.test_in = Suffix(direction=Suffix.EXPORT) - self.assertTrue(model.test_in.export_enabled() is True) + self.assertTrue(model.test_in.export_enabled()) model.test_inout = Suffix(direction=Suffix.IMPORT_EXPORT) - self.assertTrue(model.test_inout.export_enabled() is True) + self.assertTrue(model.test_inout.export_enabled()) # test set_value and getValue # and if Var arrays are correctly expanded From a0fce3de856996f04fecc6a30d6f7aaf1f886377 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 10 Dec 2023 12:15:37 -0700 Subject: [PATCH 0580/1204] Test deprecated getters/setters --- pyomo/core/base/suffix.py | 2 +- pyomo/core/tests/unit/test_suffix.py | 33 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index 6f497b6e5fc..25f15b890c3 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -320,7 +320,7 @@ def set_direction(self, direction): self.direction = direction @deprecated( - 'Suffix.set_direction is replaced with the Suffix.direction property', + 'Suffix.get_direction is replaced with the Suffix.direction property', version='6.7.1.dev0', ) def get_direction(self): diff --git a/pyomo/core/tests/unit/test_suffix.py b/pyomo/core/tests/unit/test_suffix.py index 6bd14c8f70b..c5b80a0cedd 100644 --- a/pyomo/core/tests/unit/test_suffix.py +++ b/pyomo/core/tests/unit/test_suffix.py @@ -20,6 +20,7 @@ currdir = dirname(abspath(__file__)) + os.sep import pyomo.common.unittest as unittest +from pyomo.common.log import LoggingIntercept from pyomo.core.base.suffix import ( active_export_suffix_generator, export_suffix_generator, @@ -860,6 +861,22 @@ def test_set_datatype_get_datatype(self): model.junk.datatype = 0 self.assertEqual(model.junk.datatype, Suffix.INT) + with LoggingIntercept() as LOG: + model.junk.set_datatype(None) + self.assertEqual(model.junk.datatype, None) + self.assertRegex( + LOG.getvalue().replace("\n", " "), + "^DEPRECATED: Suffix.set_datatype is replaced with the Suffix.datatype property", + ) + + model.junk.datatype = 'FLOAT' + with LoggingIntercept() as LOG: + self.assertEqual(model.junk.get_datatype(), Suffix.FLOAT) + self.assertRegex( + LOG.getvalue().replace("\n", " "), + "^DEPRECATED: Suffix.get_datatype is replaced with the Suffix.datatype property", + ) + with self.assertRaisesRegex(ValueError, "1.0 is not a valid SuffixDataType"): model.junk.datatype = 1.0 @@ -875,6 +892,22 @@ def test_set_direction_get_direction(self): model.junk.direction = Suffix.IMPORT_EXPORT self.assertEqual(model.junk.direction, Suffix.IMPORT_EXPORT) + with LoggingIntercept() as LOG: + model.junk.set_direction(None) + self.assertEqual(model.junk.direction, None) + self.assertRegex( + LOG.getvalue().replace("\n", " "), + "^DEPRECATED: Suffix.set_direction is replaced with the Suffix.direction property", + ) + + model.junk.direction = 'IMPORT' + with LoggingIntercept() as LOG: + self.assertEqual(model.junk.get_direction(), Suffix.IMPORT) + self.assertRegex( + LOG.getvalue().replace("\n", " "), + "^DEPRECATED: Suffix.get_direction is replaced with the Suffix.direction property", + ) + with self.assertRaisesRegex(ValueError, "'a' is not a valid SuffixDirection"): model.junk.direction = 'a' From 1a3f9407e90a0ec9fda00c53fc4e3fad23b9fdd3 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 10 Dec 2023 12:15:58 -0700 Subject: [PATCH 0581/1204] NFC: documentation --- pyomo/core/base/suffix.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index 25f15b890c3..70141353a16 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -81,11 +81,37 @@ def active_suffix_generator(a_block, datatype=NOTSET): class SuffixDataType(enum.IntEnum): + """Suffix data types + + AMPL only supports two data types for Suffixes: int and float. The + numeric values here are specific to the NL file format and should + not be changed without checking/updating the NL writer. + + """ + INT = 0 FLOAT = 4 class SuffixDirection(enum.IntEnum): + """Suffix data flow definition. + + This identifies if the specific Suffix is to be sent to the solver, + read from the solver output, both, or neither: + + - LOCAL: Suffix is local to Pyomo and should not be sent to or read + from the solver. + + - EXPORT: Suffix should be sent tot he solver as supplemental model + information. + + - IMPORT: Suffix values will be returned from the solver and should + be read from the solver output. + + - IMPORT_EXPORT: The Suffix is both an EXPORT and IMPORT suffix. + + """ + LOCAL = 0 EXPORT = 1 IMPORT = 2 @@ -109,6 +135,11 @@ class Suffix(ComponentMap, ActiveComponent): suffix. """ + # + # The following local (class) aliases are provided for backwards + # compatibility + # + # Suffix Directions: # - neither sent to solver or received from solver LOCAL = SuffixDirection.LOCAL From 8da7dbda8249514d9d70442779716ed5bf7fc09e Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 10 Dec 2023 12:37:05 -0700 Subject: [PATCH 0582/1204] Test (and fix) Suffix initialization from rule --- pyomo/core/base/suffix.py | 3 +- pyomo/core/tests/unit/test_suffix.py | 79 ++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index 70141353a16..fb5de94381c 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -183,6 +183,7 @@ def __init__(self, **kwargs): self._rule = Initializer( self._pop_from_kwargs('Suffix', kwargs, ('rule', 'initialize'), None), treat_sequences_as_mappings=False, + allow_generators=True, ) # Initialize base classes @@ -212,7 +213,7 @@ def construct(self, data=None): if rule.contains_indices(): # The index is coming in externally; we need to validate it for index in rule.indices(): - self[index] = rule(block, index) + self.set_value(index, rule(block, index)) else: self.update_values(rule(block, None)) timer.report() diff --git a/pyomo/core/tests/unit/test_suffix.py b/pyomo/core/tests/unit/test_suffix.py index c5b80a0cedd..70f71002c9b 100644 --- a/pyomo/core/tests/unit/test_suffix.py +++ b/pyomo/core/tests/unit/test_suffix.py @@ -20,6 +20,7 @@ currdir = dirname(abspath(__file__)) + os.sep import pyomo.common.unittest as unittest +from pyomo.common.collections import ComponentMap from pyomo.common.log import LoggingIntercept from pyomo.core.base.suffix import ( active_export_suffix_generator, @@ -56,6 +57,84 @@ def simple_obj_rule(model, i): class TestSuffixMethods(unittest.TestCase): + def test_suffix_rule(self): + m = ConcreteModel() + m.I = Set(initialize=[1, 2, 3]) + m.x = Var(m.I) + m.y = Var(m.I) + m.c = Constraint(m.I, rule=lambda m, i: m.x[i] >= i) + m.d = Constraint(m.I, rule=lambda m, i: m.x[i] <= -i) + + _dict = {m.c[1]: 10, m.c[2]: 20, m.c[3]: 30, m.d: 100} + m.suffix_dict = Suffix(initialize=_dict) + self.assertEqual(len(m.suffix_dict), 6) + self.assertEqual(m.suffix_dict[m.c[1]], 10) + self.assertEqual(m.suffix_dict[m.c[2]], 20) + self.assertEqual(m.suffix_dict[m.c[3]], 30) + self.assertEqual(m.suffix_dict[m.d[1]], 100) + self.assertEqual(m.suffix_dict[m.d[2]], 100) + self.assertEqual(m.suffix_dict[m.d[3]], 100) + + # check double-construction + _dict[m.c[1]] = 1000 + m.suffix_dict.construct() + self.assertEqual(len(m.suffix_dict), 6) + self.assertEqual(m.suffix_dict[m.c[1]], 10) + + m.suffix_cmap = Suffix( + initialize=ComponentMap( + [(m.x[1], 10), (m.x[2], 20), (m.x[3], 30), (m.y, 100)] + ) + ) + self.assertEqual(len(m.suffix_dict), 6) + self.assertEqual(m.suffix_cmap[m.x[1]], 10) + self.assertEqual(m.suffix_cmap[m.x[2]], 20) + self.assertEqual(m.suffix_cmap[m.x[3]], 30) + self.assertEqual(m.suffix_cmap[m.y[1]], 100) + self.assertEqual(m.suffix_cmap[m.y[2]], 100) + self.assertEqual(m.suffix_cmap[m.y[3]], 100) + + m.suffix_list = Suffix( + initialize=[(m.x[1], 10), (m.x[2], 20), (m.x[3], 30), (m.y, 100)] + ) + self.assertEqual(len(m.suffix_dict), 6) + self.assertEqual(m.suffix_list[m.x[1]], 10) + self.assertEqual(m.suffix_list[m.x[2]], 20) + self.assertEqual(m.suffix_list[m.x[3]], 30) + self.assertEqual(m.suffix_list[m.y[1]], 100) + self.assertEqual(m.suffix_list[m.y[2]], 100) + self.assertEqual(m.suffix_list[m.y[3]], 100) + + def gen_init(): + yield (m.x[1], 10) + yield (m.x[2], 20) + yield (m.x[3], 30) + yield (m.y, 100) + + m.suffix_generator = Suffix(initialize=gen_init()) + self.assertEqual(len(m.suffix_dict), 6) + self.assertEqual(m.suffix_generator[m.x[1]], 10) + self.assertEqual(m.suffix_generator[m.x[2]], 20) + self.assertEqual(m.suffix_generator[m.x[3]], 30) + self.assertEqual(m.suffix_generator[m.y[1]], 100) + self.assertEqual(m.suffix_generator[m.y[2]], 100) + self.assertEqual(m.suffix_generator[m.y[3]], 100) + + def genfcn_init(m, i): + yield (m.x[1], 10) + yield (m.x[2], 20) + yield (m.x[3], 30) + yield (m.y, 100) + + m.suffix_generator_fcn = Suffix(initialize=genfcn_init) + self.assertEqual(len(m.suffix_dict), 6) + self.assertEqual(m.suffix_generator_fcn[m.x[1]], 10) + self.assertEqual(m.suffix_generator_fcn[m.x[2]], 20) + self.assertEqual(m.suffix_generator_fcn[m.x[3]], 30) + self.assertEqual(m.suffix_generator_fcn[m.y[1]], 100) + self.assertEqual(m.suffix_generator_fcn[m.y[2]], 100) + self.assertEqual(m.suffix_generator_fcn[m.y[3]], 100) + # test import_enabled def test_import_enabled(self): model = ConcreteModel() From 857105a01706237efb233d531ba5c5f2c7e070f4 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Mon, 11 Dec 2023 17:05:03 -0700 Subject: [PATCH 0583/1204] fix documentation of linear_only option --- pyomo/contrib/incidence_analysis/config.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/config.py b/pyomo/contrib/incidence_analysis/config.py index 56841617cac..a107792a9cd 100644 --- a/pyomo/contrib/incidence_analysis/config.py +++ b/pyomo/contrib/incidence_analysis/config.py @@ -39,11 +39,10 @@ class IncidenceMethod(enum.Enum): _linear_only = ConfigValue( default=False, domain=bool, - description="Identify variables that participate linearly", + description="Identify only variables that participate linearly", doc=( "Flag indicating whether only variables that participate linearly should" - " be included. Note that these are included even if they participate" - " nonlinearly as well." + " be included." ), ) @@ -61,8 +60,7 @@ class IncidenceMethod(enum.Enum): - ``include_fixed`` -- Flag indicating whether fixed variables should be included in the incidence graph - ``linear_only`` -- Flag indicating whether only variables that participate linearly - should be included. Note that these are included even if they participate - nonlinearly as well + should be included. - ``method`` -- Method used to identify incident variables. Must be a value of the ``IncidenceMethod`` enum. From c571f5c2d1952db77ea980d472a90d64463a355f Mon Sep 17 00:00:00 2001 From: robbybp Date: Tue, 12 Dec 2023 08:19:04 -0700 Subject: [PATCH 0584/1204] initial implementation of identify-via-amplrepn --- pyomo/contrib/incidence_analysis/incidence.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/pyomo/contrib/incidence_analysis/incidence.py b/pyomo/contrib/incidence_analysis/incidence.py index 1852cf75648..974153984d2 100644 --- a/pyomo/contrib/incidence_analysis/incidence.py +++ b/pyomo/contrib/incidence_analysis/incidence.py @@ -16,6 +16,8 @@ from pyomo.core.expr.visitor import identify_variables from pyomo.core.expr.numvalue import value as pyo_value from pyomo.repn import generate_standard_repn +from pyomo.repn.nl_writer import AMPLRepnVisitor, AMPLRepn, text_nl_template +from pyomo.repn.util import FileDeterminism, FileDeterminism_to_SortComponents from pyomo.util.subsystems import TemporarySubsystemManager from pyomo.contrib.incidence_analysis.config import IncidenceMethod, IncidenceConfig @@ -74,6 +76,45 @@ def _get_incident_via_standard_repn(expr, include_fixed, linear_only): return unique_variables +def _get_incident_via_amplrepn(expr, linear_only): + subexpression_cache = {} + subexpression_order = [] + external_functions = {} + var_map = {} + used_named_expressions = set() + symbolic_solver_labels = False + export_defined_variabels = True + sorter = FileDeterminism_to_SortComponents(FileDeterminism.ORDERED) + visitor = AMPLRepnVisitor( + text_nl_template, + subexpression_cache, + subexpression_order, + external_functions, + var_map, + used_named_expressions, + symbolic_solver_labels, + export_defined_variables, + sorter, + ) + AMPLRepn.ActiveVisitor = visitor + try: + repn = visitor.walk_expression((expr, None, 0, 1.0)) + finally: + AMPLRepn.ActiveVisitor = None + + nonlinear_vars = [var_map[v_id] for v_id in repn.nonlinear[1]] + nonlinear_vid_set = set(repn.nonlinear[1]) + linear_only_vars = [ + var_map[v_id] for v_id, coef in repn.linear.items() + if coef != 0.0 and v_id not in nonlinear_vid_set + ] + if linear_only: + return linear_only_vars + else: + variables = linear_only_vars + nonlinear_vars + return variables + + def get_incident_variables(expr, **kwds): """Get variables that participate in an expression From 07c65e940a32a5fae3c6509c4a055c28ef4b146b Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Tue, 12 Dec 2023 09:53:14 -0700 Subject: [PATCH 0585/1204] add IncidenceMethod.ampl_repn option --- pyomo/contrib/incidence_analysis/config.py | 3 +++ pyomo/contrib/incidence_analysis/incidence.py | 17 ++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/config.py b/pyomo/contrib/incidence_analysis/config.py index 56841617cac..60acc53abfc 100644 --- a/pyomo/contrib/incidence_analysis/config.py +++ b/pyomo/contrib/incidence_analysis/config.py @@ -24,6 +24,9 @@ class IncidenceMethod(enum.Enum): standard_repn = 1 """Use ``pyomo.repn.standard_repn.generate_standard_repn``""" + ampl_repn = 2 + """Use ``pyomo.repn.plugins.nl_writer.AMPLRepnVisitor``""" + _include_fixed = ConfigValue( default=False, diff --git a/pyomo/contrib/incidence_analysis/incidence.py b/pyomo/contrib/incidence_analysis/incidence.py index 974153984d2..f16b248463c 100644 --- a/pyomo/contrib/incidence_analysis/incidence.py +++ b/pyomo/contrib/incidence_analysis/incidence.py @@ -16,7 +16,7 @@ from pyomo.core.expr.visitor import identify_variables from pyomo.core.expr.numvalue import value as pyo_value from pyomo.repn import generate_standard_repn -from pyomo.repn.nl_writer import AMPLRepnVisitor, AMPLRepn, text_nl_template +from pyomo.repn.plugins.nl_writer import AMPLRepnVisitor, AMPLRepn, text_nl_template from pyomo.repn.util import FileDeterminism, FileDeterminism_to_SortComponents from pyomo.util.subsystems import TemporarySubsystemManager from pyomo.contrib.incidence_analysis.config import IncidenceMethod, IncidenceConfig @@ -76,14 +76,14 @@ def _get_incident_via_standard_repn(expr, include_fixed, linear_only): return unique_variables -def _get_incident_via_amplrepn(expr, linear_only): +def _get_incident_via_ampl_repn(expr, linear_only): subexpression_cache = {} subexpression_order = [] external_functions = {} var_map = {} used_named_expressions = set() symbolic_solver_labels = False - export_defined_variabels = True + export_defined_variables = True sorter = FileDeterminism_to_SortComponents(FileDeterminism.ORDERED) visitor = AMPLRepnVisitor( text_nl_template, @@ -102,8 +102,9 @@ def _get_incident_via_amplrepn(expr, linear_only): finally: AMPLRepn.ActiveVisitor = None - nonlinear_vars = [var_map[v_id] for v_id in repn.nonlinear[1]] - nonlinear_vid_set = set(repn.nonlinear[1]) + nonlinear_var_ids = [] if repn.nonlinear is None else repn.nonlinear[1] + nonlinear_vars = [var_map[v_id] for v_id in nonlinear_var_ids] + nonlinear_vid_set = set(nonlinear_var_ids) linear_only_vars = [ var_map[v_id] for v_id, coef in repn.linear.items() if coef != 0.0 and v_id not in nonlinear_vid_set @@ -161,10 +162,16 @@ def get_incident_variables(expr, **kwds): raise RuntimeError( "linear_only=True is not supported when using identify_variables" ) + if include_fixed and method is IncidenceMethod.ampl_repn: + raise RuntimeError( + "include_fixed=True is not supported when using ampl_repn" + ) if method is IncidenceMethod.identify_variables: return _get_incident_via_identify_variables(expr, include_fixed) elif method is IncidenceMethod.standard_repn: return _get_incident_via_standard_repn(expr, include_fixed, linear_only) + elif method is IncidenceMethod.ampl_repn: + return _get_incident_via_ampl_repn(expr, linear_only) else: raise ValueError( f"Unrecognized value {method} for the method used to identify incident" From 5c88a9794ad2d05595eb849d53af3d18a50747ab Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Tue, 12 Dec 2023 09:53:57 -0700 Subject: [PATCH 0586/1204] refactor tests and test ampl_repn option --- .../tests/test_incidence.py | 124 ++++++++++++------ 1 file changed, 83 insertions(+), 41 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/tests/test_incidence.py b/pyomo/contrib/incidence_analysis/tests/test_incidence.py index 7f57dd904a7..e37e4f97691 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_incidence.py +++ b/pyomo/contrib/incidence_analysis/tests/test_incidence.py @@ -63,37 +63,37 @@ def test_incidence_with_fixed_variable(self): var_set = ComponentSet(variables) self.assertEqual(var_set, ComponentSet([m.x[1], m.x[3]])) - def test_incidence_with_mutable_parameter(self): + +class _TestIncidenceLinearOnly(object): + def _get_incident_variables(self, expr): + raise NotImplementedError( + "_TestIncidenceLinearOnly should not be used directly" + ) + + def test_linear_only(self): m = pyo.ConcreteModel() m.x = pyo.Var([1, 2, 3]) - m.p = pyo.Param(mutable=True, initialize=None) - expr = m.x[1] + m.p * m.x[1] * m.x[2] + m.x[1] * pyo.exp(m.x[3]) - variables = self._get_incident_variables(expr) - self.assertEqual(ComponentSet(variables), ComponentSet(m.x[:])) + expr = 2 * m.x[1] + 4 * m.x[2] * m.x[1] - m.x[1] * pyo.exp(m.x[3]) + variables = self._get_incident_variables(expr, linear_only=True) + self.assertEqual(len(variables), 0) -class TestIncidenceStandardRepn(unittest.TestCase, _TestIncidence): - def _get_incident_variables(self, expr, **kwds): - method = IncidenceMethod.standard_repn - return get_incident_variables(expr, method=method, **kwds) + expr = 2 * m.x[1] + 2 * m.x[2] * m.x[3] + 3 * m.x[2] + variables = self._get_incident_variables(expr, linear_only=True) + self.assertEqual(ComponentSet(variables), ComponentSet([m.x[1]])) - def test_assumed_standard_repn_behavior(self): - m = pyo.ConcreteModel() - m.x = pyo.Var([1, 2]) - m.p = pyo.Param(initialize=0.0) + m.x[3].fix(2.5) + expr = 2 * m.x[1] + 2 * m.x[2] * m.x[3] + 3 * m.x[2] + variables = self._get_incident_variables(expr, linear_only=True) + self.assertEqual(ComponentSet(variables), ComponentSet([m.x[1], m.x[2]])) - # We rely on variables with constant coefficients of zero not appearing - # in the standard repn (as opposed to appearing with explicit - # coefficients of zero). - expr = m.x[1] + 0 * m.x[2] - repn = generate_standard_repn(expr) - self.assertEqual(len(repn.linear_vars), 1) - self.assertIs(repn.linear_vars[0], m.x[1]) - expr = m.p * m.x[1] + m.x[2] - repn = generate_standard_repn(expr) - self.assertEqual(len(repn.linear_vars), 1) - self.assertIs(repn.linear_vars[0], m.x[2]) +class _TestIncidenceLinearCancellation(object): + """Tests for methods that perform linear cancellation""" + def _get_incident_variables(self, expr): + raise NotImplementedError( + "_TestIncidenceLinearCancellation should not be used directly" + ) def test_zero_coef(self): m = pyo.ConcreteModel() @@ -113,23 +113,6 @@ def test_variable_minus_itself(self): var_set = ComponentSet(variables) self.assertEqual(var_set, ComponentSet([m.x[2], m.x[3]])) - def test_linear_only(self): - m = pyo.ConcreteModel() - m.x = pyo.Var([1, 2, 3]) - - expr = 2 * m.x[1] + 4 * m.x[2] * m.x[1] - m.x[1] * pyo.exp(m.x[3]) - variables = self._get_incident_variables(expr, linear_only=True) - self.assertEqual(len(variables), 0) - - expr = 2 * m.x[1] + 2 * m.x[2] * m.x[3] + 3 * m.x[2] - variables = self._get_incident_variables(expr, linear_only=True) - self.assertEqual(ComponentSet(variables), ComponentSet([m.x[1]])) - - m.x[3].fix(2.5) - expr = 2 * m.x[1] + 2 * m.x[2] * m.x[3] + 3 * m.x[2] - variables = self._get_incident_variables(expr, linear_only=True) - self.assertEqual(ComponentSet(variables), ComponentSet([m.x[1], m.x[2]])) - def test_fixed_zero_linear_coefficient(self): m = pyo.ConcreteModel() m.x = pyo.Var([1, 2, 3]) @@ -148,6 +131,9 @@ def test_fixed_zero_linear_coefficient(self): variables = self._get_incident_variables(expr) self.assertEqual(ComponentSet(variables), ComponentSet([m.x[1], m.x[2]])) + # NOTE: This test assumes that all methods that support linear cancellation + # accept a linear_only argument. If this changes, this test wil need to be + # moved. def test_fixed_zero_coefficient_linear_only(self): m = pyo.ConcreteModel() m.x = pyo.Var([1, 2, 3]) @@ -159,6 +145,35 @@ def test_fixed_zero_coefficient_linear_only(self): self.assertEqual(len(variables), 1) self.assertIs(variables[0], m.x[3]) + +class TestIncidenceStandardRepn( + unittest.TestCase, + _TestIncidence, + _TestIncidenceLinearOnly, + _TestIncidenceLinearCancellation, +): + def _get_incident_variables(self, expr, **kwds): + method = IncidenceMethod.standard_repn + return get_incident_variables(expr, method=method, **kwds) + + def test_assumed_standard_repn_behavior(self): + m = pyo.ConcreteModel() + m.x = pyo.Var([1, 2]) + m.p = pyo.Param(initialize=0.0) + + # We rely on variables with constant coefficients of zero not appearing + # in the standard repn (as opposed to appearing with explicit + # coefficients of zero). + expr = m.x[1] + 0 * m.x[2] + repn = generate_standard_repn(expr) + self.assertEqual(len(repn.linear_vars), 1) + self.assertIs(repn.linear_vars[0], m.x[1]) + + expr = m.p * m.x[1] + m.x[2] + repn = generate_standard_repn(expr) + self.assertEqual(len(repn.linear_vars), 1) + self.assertIs(repn.linear_vars[0], m.x[2]) + def test_fixed_none_linear_coefficient(self): m = pyo.ConcreteModel() m.x = pyo.Var([1, 2, 3]) @@ -168,6 +183,14 @@ def test_fixed_none_linear_coefficient(self): variables = self._get_incident_variables(expr) self.assertEqual(ComponentSet(variables), ComponentSet([m.x[1], m.x[2]])) + def test_incidence_with_mutable_parameter(self): + m = pyo.ConcreteModel() + m.x = pyo.Var([1, 2, 3]) + m.p = pyo.Param(mutable=True, initialize=None) + expr = m.x[1] + m.p * m.x[1] * m.x[2] + m.x[1] * pyo.exp(m.x[3]) + variables = self._get_incident_variables(expr) + self.assertEqual(ComponentSet(variables), ComponentSet(m.x[:])) + class TestIncidenceIdentifyVariables(unittest.TestCase, _TestIncidence): def _get_incident_variables(self, expr, **kwds): @@ -192,6 +215,25 @@ def test_variable_minus_itself(self): var_set = ComponentSet(variables) self.assertEqual(var_set, ComponentSet(m.x[:])) + def test_incidence_with_mutable_parameter(self): + m = pyo.ConcreteModel() + m.x = pyo.Var([1, 2, 3]) + m.p = pyo.Param(mutable=True, initialize=None) + expr = m.x[1] + m.p * m.x[1] * m.x[2] + m.x[1] * pyo.exp(m.x[3]) + variables = self._get_incident_variables(expr) + self.assertEqual(ComponentSet(variables), ComponentSet(m.x[:])) + + +class TestIncidenceAmplRepn( + unittest.TestCase, + _TestIncidence, + _TestIncidenceLinearOnly, + _TestIncidenceLinearCancellation, +): + def _get_incident_variables(self, expr, **kwds): + method = IncidenceMethod.ampl_repn + return get_incident_variables(expr, method=method, **kwds) + if __name__ == "__main__": unittest.main() From 515f7e59705ec6b95f6784f42a69ed2b1517161f Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Tue, 12 Dec 2023 12:09:44 -0700 Subject: [PATCH 0587/1204] apply black --- pyomo/contrib/incidence_analysis/incidence.py | 9 ++++----- pyomo/contrib/incidence_analysis/tests/test_incidence.py | 1 + 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/incidence.py b/pyomo/contrib/incidence_analysis/incidence.py index f16b248463c..5ac7b49fa1f 100644 --- a/pyomo/contrib/incidence_analysis/incidence.py +++ b/pyomo/contrib/incidence_analysis/incidence.py @@ -103,10 +103,11 @@ def _get_incident_via_ampl_repn(expr, linear_only): AMPLRepn.ActiveVisitor = None nonlinear_var_ids = [] if repn.nonlinear is None else repn.nonlinear[1] - nonlinear_vars = [var_map[v_id] for v_id in nonlinear_var_ids] + nonlinear_vars = [var_map[v_id] for v_id in nonlinear_var_ids] nonlinear_vid_set = set(nonlinear_var_ids) linear_only_vars = [ - var_map[v_id] for v_id, coef in repn.linear.items() + var_map[v_id] + for v_id, coef in repn.linear.items() if coef != 0.0 and v_id not in nonlinear_vid_set ] if linear_only: @@ -163,9 +164,7 @@ def get_incident_variables(expr, **kwds): "linear_only=True is not supported when using identify_variables" ) if include_fixed and method is IncidenceMethod.ampl_repn: - raise RuntimeError( - "include_fixed=True is not supported when using ampl_repn" - ) + raise RuntimeError("include_fixed=True is not supported when using ampl_repn") if method is IncidenceMethod.identify_variables: return _get_incident_via_identify_variables(expr, include_fixed) elif method is IncidenceMethod.standard_repn: diff --git a/pyomo/contrib/incidence_analysis/tests/test_incidence.py b/pyomo/contrib/incidence_analysis/tests/test_incidence.py index e37e4f97691..bcf867c619a 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_incidence.py +++ b/pyomo/contrib/incidence_analysis/tests/test_incidence.py @@ -90,6 +90,7 @@ def test_linear_only(self): class _TestIncidenceLinearCancellation(object): """Tests for methods that perform linear cancellation""" + def _get_incident_variables(self, expr): raise NotImplementedError( "_TestIncidenceLinearCancellation should not be used directly" From 8d5c737f551b3a513e13499957cc20dba86ec771 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Tue, 12 Dec 2023 12:10:58 -0700 Subject: [PATCH 0588/1204] add docstring to TestLinearOnly helper class --- pyomo/contrib/incidence_analysis/tests/test_incidence.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/contrib/incidence_analysis/tests/test_incidence.py b/pyomo/contrib/incidence_analysis/tests/test_incidence.py index bcf867c619a..78493ecc651 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_incidence.py +++ b/pyomo/contrib/incidence_analysis/tests/test_incidence.py @@ -65,6 +65,8 @@ def test_incidence_with_fixed_variable(self): class _TestIncidenceLinearOnly(object): + """Tests for methods that support linear_only""" + def _get_incident_variables(self, expr): raise NotImplementedError( "_TestIncidenceLinearOnly should not be used directly" From 45eb8616c67058b895ce49ebca9aa61877731976 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Tue, 12 Dec 2023 12:12:35 -0700 Subject: [PATCH 0589/1204] fix typo --- pyomo/contrib/incidence_analysis/tests/test_incidence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/incidence_analysis/tests/test_incidence.py b/pyomo/contrib/incidence_analysis/tests/test_incidence.py index 78493ecc651..87a9178dc1a 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_incidence.py +++ b/pyomo/contrib/incidence_analysis/tests/test_incidence.py @@ -135,7 +135,7 @@ def test_fixed_zero_linear_coefficient(self): self.assertEqual(ComponentSet(variables), ComponentSet([m.x[1], m.x[2]])) # NOTE: This test assumes that all methods that support linear cancellation - # accept a linear_only argument. If this changes, this test wil need to be + # accept a linear_only argument. If this changes, this test will need to be # moved. def test_fixed_zero_coefficient_linear_only(self): m = pyo.ConcreteModel() From 682e054a217cbc37a8c444efd38d2df491603cd3 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Tue, 12 Dec 2023 13:48:44 -0700 Subject: [PATCH 0590/1204] set export_defined_variables=False and add TODO comment about exploiting this later --- pyomo/contrib/incidence_analysis/incidence.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/incidence_analysis/incidence.py b/pyomo/contrib/incidence_analysis/incidence.py index 5ac7b49fa1f..b2cb23dc8c7 100644 --- a/pyomo/contrib/incidence_analysis/incidence.py +++ b/pyomo/contrib/incidence_analysis/incidence.py @@ -83,7 +83,10 @@ def _get_incident_via_ampl_repn(expr, linear_only): var_map = {} used_named_expressions = set() symbolic_solver_labels = False - export_defined_variables = True + # TODO: Explore potential performance benefit of exporting defined variables. + # This likely only shows up if we can preserve the subexpression cache across + # multiple constraint expressions. + export_defined_variables = False sorter = FileDeterminism_to_SortComponents(FileDeterminism.ORDERED) visitor = AMPLRepnVisitor( text_nl_template, From c8ed1cda1a562788348157e72f90e103421af5d7 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Tue, 12 Dec 2023 13:53:09 -0700 Subject: [PATCH 0591/1204] add test that uses named expression --- pyomo/contrib/incidence_analysis/tests/test_incidence.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pyomo/contrib/incidence_analysis/tests/test_incidence.py b/pyomo/contrib/incidence_analysis/tests/test_incidence.py index 87a9178dc1a..2354b0efc39 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_incidence.py +++ b/pyomo/contrib/incidence_analysis/tests/test_incidence.py @@ -63,6 +63,15 @@ def test_incidence_with_fixed_variable(self): var_set = ComponentSet(variables) self.assertEqual(var_set, ComponentSet([m.x[1], m.x[3]])) + def test_incidence_with_named_expression(self): + m = pyo.ConcreteModel() + m.x = pyo.Var([1, 2, 3]) + m.subexpr = pyo.Expression(pyo.Integers) + m.subexpr[1] = m.x[1] * pyo.exp(m.x[3]) + expr = m.x[1] + m.x[1] * m.x[2] + m.subexpr[1] + variables = self._get_incident_variables(expr) + self.assertEqual(ComponentSet(variables), ComponentSet(m.x[:])) + class _TestIncidenceLinearOnly(object): """Tests for methods that support linear_only""" From ef37f10c80dbccf14d0dc82926d0353d6cb98172 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 13 Dec 2023 06:28:06 -0700 Subject: [PATCH 0592/1204] Update pprint of Suffix enums --- .../pyomobook/pyomo-components-ch/suffix_declaration.txt | 6 +++--- pyomo/core/base/suffix.py | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/examples/pyomobook/pyomo-components-ch/suffix_declaration.txt b/examples/pyomobook/pyomo-components-ch/suffix_declaration.txt index d5e38a44dcc..e0237e3ef46 100644 --- a/examples/pyomobook/pyomo-components-ch/suffix_declaration.txt +++ b/examples/pyomobook/pyomo-components-ch/suffix_declaration.txt @@ -1,9 +1,9 @@ *** suffixsimple *** 2 Suffix Declarations - dual : Direction=Suffix.IMPORT_EXPORT, Datatype=Suffix.FLOAT + dual : Direction=IMPORT_EXPORT, Datatype=FLOAT Key : Value - priority : Direction=Suffix.EXPORT, Datatype=Suffix.INT + priority : Direction=EXPORT, Datatype=INT Key : Value 2 Declarations: priority dual @@ -16,7 +16,7 @@ Not constructed 1 Suffix Declarations - foo : Direction=Suffix.LOCAL, Datatype=Suffix.FLOAT + foo : Direction=LOCAL, Datatype=FLOAT Not constructed 3 Declarations: x c foo diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index fb5de94381c..0c3ba6741f0 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -363,7 +363,10 @@ def get_direction(self): def _pprint(self): return ( - [('Direction', str(self._direction)), ('Datatype', str(self._datatype))], + [ + ('Direction', str(self._direction.name)), + ('Datatype', str(self._datatype.name)), + ], ((str(k), v) for k, v in self._dict.values()), ("Value",), lambda k, v: [v], From 73ab378a5e7bbc502cc31d7a29c6951cae5bc3b8 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 13 Dec 2023 08:22:03 -0700 Subject: [PATCH 0593/1204] Improve robustness, performance of ComponentMap, COmponentSet.__eq__ --- pyomo/common/collections/component_map.py | 25 +++++++++++++------ pyomo/common/collections/component_set.py | 8 +++--- .../tests/unit/kernel/test_component_map.py | 14 +++++++++++ 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/pyomo/common/collections/component_map.py b/pyomo/common/collections/component_map.py index 47b88ce5914..4bb9e88e9af 100644 --- a/pyomo/common/collections/component_map.py +++ b/pyomo/common/collections/component_map.py @@ -98,16 +98,25 @@ def update(self, *args, **kwargs): return self._dict.update(args[0]._dict) return super().update(*args, **kwargs) - # We want to avoid generating Pyomo expressions due to - # comparison of values, so we convert both objects to a - # plain dictionary mapping key->(type(val), id(val)) and - # compare that instead. + # We want to avoid generating Pyomo expressions due to comparing the + # keys, so look up each entry from other in this dict. def __eq__(self, other): - if not isinstance(other, collections_Mapping): + if self is other: + return True + if not isinstance(other, collections_Mapping) or len(self) != len(other): return False - return {(type(key), id(key)): val for key, val in self.items()} == { - (type(key), id(key)): val for key, val in other.items() - } + # Note we have already verified the dicts are the same size + for key, val in other.items(): + other_id = id(key) + if other_id not in self._dict: + return False + self_val = self._dict[other_id][1] + # Note: check "is" first to help avoid creation of Pyomo + # expressions (for the case that the values contain the same + # pyomo component) + if self_val is not val and self_val != val: + return False + return True def __ne__(self, other): return not (self == other) diff --git a/pyomo/common/collections/component_set.py b/pyomo/common/collections/component_set.py index 0b16acd00be..ad13baced44 100644 --- a/pyomo/common/collections/component_set.py +++ b/pyomo/common/collections/component_set.py @@ -104,11 +104,11 @@ def discard(self, val): # plain dictionary mapping key->(type(val), id(val)) and # compare that instead. def __eq__(self, other): - if not isinstance(other, collections_Set): + if self is other: + return True + if not isinstance(other, collections_Set) or len(self) != len(other): return False - return set((type(val), id(val)) for val in self) == set( - (type(val), id(val)) for val in other - ) + return all(id(key) in self._data for key in other) def __ne__(self, other): return not (self == other) diff --git a/pyomo/core/tests/unit/kernel/test_component_map.py b/pyomo/core/tests/unit/kernel/test_component_map.py index 64ba700895e..6d19743c3fe 100644 --- a/pyomo/core/tests/unit/kernel/test_component_map.py +++ b/pyomo/core/tests/unit/kernel/test_component_map.py @@ -234,6 +234,20 @@ def test_eq(self): self.assertTrue(cmap1 != cmap2) self.assertNotEqual(cmap1, cmap2) + cmap2 = ComponentMap(self._components) + o = objective() + cmap1[o] = 10 + cmap2[o] = 10 + self.assertEqual(cmap1, cmap2) + cmap2[o] = 20 + self.assertNotEqual(cmap1, cmap2) + cmap2[o] = 10 + self.assertEqual(cmap1, cmap2) + del cmap2[o] + self.assertNotEqual(cmap1, cmap2) + cmap2[objective()] = 10 + self.assertNotEqual(cmap1, cmap2) + if __name__ == "__main__": unittest.main() From 098c7856e3d48f21c0f88b834649c96c9da8e79a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 13 Dec 2023 08:51:18 -0700 Subject: [PATCH 0594/1204] None is not a valid Suffix direction --- pyomo/core/base/suffix.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index 0c3ba6741f0..d218eef4234 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -238,9 +238,7 @@ def direction(self): @direction.setter def direction(self, direction): """Set the suffix direction.""" - if direction is not None: - direction = _SuffixDirectionDomain(direction) - self._direction = direction + self._direction = _SuffixDirectionDomain(direction) def export_enabled(self): """ @@ -365,7 +363,7 @@ def _pprint(self): return ( [ ('Direction', str(self._direction.name)), - ('Datatype', str(self._datatype.name)), + ('Datatype', getattr(self._datatype, 'name', 'None')), ], ((str(k), v) for k, v in self._dict.values()), ("Value",), From ed5dac8788b80d646211aeaeceaa87dc6e881337 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 13 Dec 2023 08:51:46 -0700 Subject: [PATCH 0595/1204] NFC: comments, logging --- pyomo/core/base/suffix.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index d218eef4234..2e0e6191f75 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -136,8 +136,8 @@ class Suffix(ComponentMap, ActiveComponent): """ # - # The following local (class) aliases are provided for backwards - # compatibility + # The following local (class) aliases are provided for convenience + # and backwards compatibility with The Book, 3rd ed # # Suffix Directions: @@ -199,7 +199,7 @@ def construct(self, data=None): Constructs this component, applying rule if it exists. """ if is_debug_set(logger): - logger.debug("Constructing suffix %s", self.name) + logger.debug("Constructing Suffix '%s'", self.name) if self._constructed is True: return From 9274444c2e2fd0f60d78dd89a01d5f9e034c56f9 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 13 Dec 2023 08:52:19 -0700 Subject: [PATCH 0596/1204] Minor Suffix performance improvements --- pyomo/core/base/suffix.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index 2e0e6191f75..713bcbf086a 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -209,13 +209,15 @@ def construct(self, data=None): if self._rule is not None: rule = self._rule - block = self.parent_block() if rule.contains_indices(): - # The index is coming in externally; we need to validate it + # The rule contains explicit indices (e.g., is a dict). + # Iterate over the indices, expand them, and store the + # result + block = self.parent_block() for index in rule.indices(): - self.set_value(index, rule(block, index)) + self.set_value(index, rule(block, index), expand=True) else: - self.update_values(rule(block, None)) + self.update_values(rule(self.parent_block(), None), expand=True) timer.report() @property @@ -303,15 +305,9 @@ def clear_value(self, component, expand=True): """ if expand and component.is_indexed(): for component_ in component.values(): - try: - del self[component_] - except KeyError: - pass + self.pop(component_, None) else: - try: - del self[component] - except KeyError: - pass + self.pop(component, None) def clear_all_values(self): """ From 5f00df14887caf6b91d0473d495ca81ee7cfad35 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 13 Dec 2023 08:52:39 -0700 Subject: [PATCH 0597/1204] Remove unneeded method overrides --- pyomo/core/base/suffix.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index 713bcbf086a..b21c9cce4e8 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -378,18 +378,6 @@ def pprint(self, *args, **kwds): def __str__(self): return ActiveComponent.__str__(self) - # - # Override NotImplementedError messages on ComponentMap base class - # - - def __eq__(self, other): - """Not implemented.""" - raise NotImplementedError("Suffix components are not comparable") - - def __ne__(self, other): - """Not implemented.""" - raise NotImplementedError("Suffix components are not comparable") - class SuffixFinder(object): def __init__(self, name, default=None): From e2268f273e844c06b4dc530163fa8bba0168735a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 13 Dec 2023 08:52:57 -0700 Subject: [PATCH 0598/1204] Additional Suffix testing --- pyomo/core/tests/unit/test_suffix.py | 148 ++++++++++++++++++++++++--- 1 file changed, 133 insertions(+), 15 deletions(-) diff --git a/pyomo/core/tests/unit/test_suffix.py b/pyomo/core/tests/unit/test_suffix.py index 70f71002c9b..9a5a900a2fd 100644 --- a/pyomo/core/tests/unit/test_suffix.py +++ b/pyomo/core/tests/unit/test_suffix.py @@ -14,6 +14,7 @@ import os import itertools +import logging import pickle from os.path import abspath, dirname @@ -57,6 +58,23 @@ def simple_obj_rule(model, i): class TestSuffixMethods(unittest.TestCase): + def test_suffix_debug(self): + with LoggingIntercept(level=logging.DEBUG) as OUT: + m = ConcreteModel() + m.s = Suffix() + m.foo = Suffix(rule=[]) + print(OUT.getvalue()) + self.assertEqual( + OUT.getvalue(), + "Constructing ConcreteModel 'ConcreteModel', from data=None\n" + "Constructing Suffix 'Suffix'\n" + "Constructing Suffix 'foo' on [Model] from data=None\n" + "Constructing Suffix 'foo'\n" + "Constructed component ''[Model].foo'':\n" + "foo : Direction=LOCAL, Datatype=FLOAT\n" + " Key : Value\n\n", + ) + def test_suffix_rule(self): m = ConcreteModel() m.I = Set(initialize=[1, 2, 3]) @@ -846,20 +864,33 @@ def test_set_all_values3(self): self.assertEqual(model.z[1].get_suffix_value(model.junk), 3.0) # test update_values - def test_update_values1(self): + def test_update_values(self): model = ConcreteModel() model.junk = Suffix() model.x = Var() model.y = Var() - model.z = Var() + model.z = Var([1, 2]) model.junk.set_value(model.x, 0.0) self.assertEqual(model.junk.get(model.x), 0.0) self.assertEqual(model.junk.get(model.y), None) self.assertEqual(model.junk.get(model.z), None) + self.assertEqual(model.junk.get(model.z[1]), None) + self.assertEqual(model.junk.get(model.z[2]), None) model.junk.update_values([(model.x, 1.0), (model.y, 2.0), (model.z, 3.0)]) self.assertEqual(model.junk.get(model.x), 1.0) self.assertEqual(model.junk.get(model.y), 2.0) + self.assertEqual(model.junk.get(model.z), None) + self.assertEqual(model.junk.get(model.z[1]), 3.0) + self.assertEqual(model.junk.get(model.z[2]), 3.0) + model.junk.clear() + model.junk.update_values( + [(model.x, 1.0), (model.y, 2.0), (model.z, 3.0)], expand=False + ) + self.assertEqual(model.junk.get(model.x), 1.0) + self.assertEqual(model.junk.get(model.y), 2.0) self.assertEqual(model.junk.get(model.z), 3.0) + self.assertEqual(model.junk.get(model.z[1]), None) + self.assertEqual(model.junk.get(model.z[2]), None) # test clear_value def test_clear_value(self): @@ -875,26 +906,66 @@ def test_clear_value(self): model.junk.set_value(model.z, 2.0) model.junk.set_value(model.z[1], 4.0) - self.assertTrue(model.junk.get(model.x) == -1.0) - self.assertTrue(model.junk.get(model.y) == None) - self.assertTrue(model.junk.get(model.y[1]) == -2.0) + self.assertEqual(model.junk.get(model.x), -1.0) + self.assertEqual(model.junk.get(model.y), None) + self.assertEqual(model.junk.get(model.y[1]), -2.0) self.assertEqual(model.junk.get(model.y[2]), 1.0) self.assertEqual(model.junk.get(model.z), None) self.assertEqual(model.junk.get(model.z[2]), 2.0) self.assertEqual(model.junk.get(model.z[1]), 4.0) model.junk.clear_value(model.y) + + self.assertEqual(model.junk.get(model.x), -1.0) + self.assertEqual(model.junk.get(model.y), None) + self.assertEqual(model.junk.get(model.y[1]), None) + self.assertEqual(model.junk.get(model.y[2]), None) + self.assertEqual(model.junk.get(model.z), None) + self.assertEqual(model.junk.get(model.z[2]), 2.0) + self.assertEqual(model.junk.get(model.z[1]), 4.0) + model.junk.clear_value(model.x) + + self.assertEqual(model.junk.get(model.x), None) + self.assertEqual(model.junk.get(model.y), None) + self.assertEqual(model.junk.get(model.y[1]), None) + self.assertEqual(model.junk.get(model.y[2]), None) + self.assertEqual(model.junk.get(model.z), None) + self.assertEqual(model.junk.get(model.z[2]), 2.0) + self.assertEqual(model.junk.get(model.z[1]), 4.0) + + # Clearing a scalar that is not there does not raise an error + model.junk.clear_value(model.x) + + self.assertEqual(model.junk.get(model.x), None) + self.assertEqual(model.junk.get(model.y), None) + self.assertEqual(model.junk.get(model.y[1]), None) + self.assertEqual(model.junk.get(model.y[2]), None) + self.assertEqual(model.junk.get(model.z), None) + self.assertEqual(model.junk.get(model.z[2]), 2.0) + self.assertEqual(model.junk.get(model.z[1]), 4.0) + model.junk.clear_value(model.z[1]) - self.assertTrue(model.junk.get(model.x) is None) - self.assertTrue(model.junk.get(model.y) is None) - self.assertTrue(model.junk.get(model.y[1]) is None) + self.assertEqual(model.junk.get(model.x), None) + self.assertEqual(model.junk.get(model.y), None) + self.assertEqual(model.junk.get(model.y[1]), None) self.assertEqual(model.junk.get(model.y[2]), None) self.assertEqual(model.junk.get(model.z), None) self.assertEqual(model.junk.get(model.z[2]), 2.0) self.assertEqual(model.junk.get(model.z[1]), None) + # Clearing an indexed component with missing indices does not raise an error + model.junk.clear_value(model.z) + + self.assertEqual(model.junk.get(model.x), None) + self.assertEqual(model.junk.get(model.y), None) + self.assertEqual(model.junk.get(model.y[1]), None) + self.assertEqual(model.junk.get(model.y[2]), None) + self.assertEqual(model.junk.get(model.z), None) + self.assertEqual(model.junk.get(model.z[2]), None) + self.assertEqual(model.junk.get(model.z[1]), None) + # test clear_value no args def test_clear_all_values(self): model = ConcreteModel() @@ -945,7 +1016,8 @@ def test_set_datatype_get_datatype(self): self.assertEqual(model.junk.datatype, None) self.assertRegex( LOG.getvalue().replace("\n", " "), - "^DEPRECATED: Suffix.set_datatype is replaced with the Suffix.datatype property", + "^DEPRECATED: Suffix.set_datatype is replaced with the " + "Suffix.datatype property", ) model.junk.datatype = 'FLOAT' @@ -953,7 +1025,8 @@ def test_set_datatype_get_datatype(self): self.assertEqual(model.junk.get_datatype(), Suffix.FLOAT) self.assertRegex( LOG.getvalue().replace("\n", " "), - "^DEPRECATED: Suffix.get_datatype is replaced with the Suffix.datatype property", + "^DEPRECATED: Suffix.get_datatype is replaced with the " + "Suffix.datatype property", ) with self.assertRaisesRegex(ValueError, "1.0 is not a valid SuffixDataType"): @@ -972,11 +1045,12 @@ def test_set_direction_get_direction(self): self.assertEqual(model.junk.direction, Suffix.IMPORT_EXPORT) with LoggingIntercept() as LOG: - model.junk.set_direction(None) - self.assertEqual(model.junk.direction, None) + model.junk.set_direction(1) + self.assertEqual(model.junk.direction, Suffix.EXPORT) self.assertRegex( LOG.getvalue().replace("\n", " "), - "^DEPRECATED: Suffix.set_direction is replaced with the Suffix.direction property", + "^DEPRECATED: Suffix.set_direction is replaced with the " + "Suffix.direction property", ) model.junk.direction = 'IMPORT' @@ -984,11 +1058,15 @@ def test_set_direction_get_direction(self): self.assertEqual(model.junk.get_direction(), Suffix.IMPORT) self.assertRegex( LOG.getvalue().replace("\n", " "), - "^DEPRECATED: Suffix.get_direction is replaced with the Suffix.direction property", + "^DEPRECATED: Suffix.get_direction is replaced with the " + "Suffix.direction property", ) with self.assertRaisesRegex(ValueError, "'a' is not a valid SuffixDirection"): model.junk.direction = 'a' + # None is allowed for datatype, but not direction + with self.assertRaisesRegex(ValueError, "None is not a valid SuffixDirection"): + model.junk.direction = None # test __str__ def test_str(self): @@ -1002,13 +1080,44 @@ def test_pprint(self): model.junk = Suffix(direction=Suffix.EXPORT) output = StringIO() model.junk.pprint(ostream=output) + self.assertEqual( + output.getvalue(), + "junk : Direction=EXPORT, Datatype=FLOAT\n Key : Value\n", + ) model.junk.direction = Suffix.IMPORT + output = StringIO() model.junk.pprint(ostream=output) + self.assertEqual( + output.getvalue(), + "junk : Direction=IMPORT, Datatype=FLOAT\n Key : Value\n", + ) model.junk.direction = Suffix.LOCAL + model.junk.datatype = None + output = StringIO() model.junk.pprint(ostream=output) + self.assertEqual( + output.getvalue(), + "junk : Direction=LOCAL, Datatype=None\n Key : Value\n", + ) model.junk.direction = Suffix.IMPORT_EXPORT + model.junk.datatype = Suffix.INT + output = StringIO() model.junk.pprint(ostream=output) + self.assertEqual( + output.getvalue(), + "junk : Direction=IMPORT_EXPORT, Datatype=INT\n Key : Value\n", + ) + output = StringIO() model.pprint(ostream=output) + self.assertEqual( + output.getvalue(), + """1 Suffix Declarations + junk : Direction=IMPORT_EXPORT, Datatype=INT + Key : Value + +1 Declarations: junk +""", + ) # test pprint(verbose=True) def test_pprint_verbose(self): @@ -1026,7 +1135,16 @@ def test_pprint_verbose(self): output = StringIO() model.junk.pprint(ostream=output, verbose=True) - model.pprint(ostream=output, verbose=True) + self.assertEqual( + output.getvalue(), + """junk : Direction=LOCAL, Datatype=FLOAT + Key : Value + s.B[1] : 2.0 + s.B[2] : 3.0 + s.B[3] : 1.0 + s.b : 3.0 +""", + ) def test_active_export_suffix_generator(self): model = ConcreteModel() From 6675566fe7f4c7bf19832a5a72c09e7235cb6c8e Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Wed, 13 Dec 2023 13:19:16 -0700 Subject: [PATCH 0599/1204] re-use visitor when iterating over constraints --- pyomo/contrib/incidence_analysis/incidence.py | 61 ++++++++++--------- pyomo/contrib/incidence_analysis/interface.py | 39 ++++++++++-- 2 files changed, 67 insertions(+), 33 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/incidence.py b/pyomo/contrib/incidence_analysis/incidence.py index b2cb23dc8c7..7632f81e38a 100644 --- a/pyomo/contrib/incidence_analysis/incidence.py +++ b/pyomo/contrib/incidence_analysis/incidence.py @@ -76,34 +76,38 @@ def _get_incident_via_standard_repn(expr, include_fixed, linear_only): return unique_variables -def _get_incident_via_ampl_repn(expr, linear_only): - subexpression_cache = {} - subexpression_order = [] - external_functions = {} - var_map = {} - used_named_expressions = set() - symbolic_solver_labels = False - # TODO: Explore potential performance benefit of exporting defined variables. - # This likely only shows up if we can preserve the subexpression cache across - # multiple constraint expressions. - export_defined_variables = False - sorter = FileDeterminism_to_SortComponents(FileDeterminism.ORDERED) - visitor = AMPLRepnVisitor( - text_nl_template, - subexpression_cache, - subexpression_order, - external_functions, - var_map, - used_named_expressions, - symbolic_solver_labels, - export_defined_variables, - sorter, - ) - AMPLRepn.ActiveVisitor = visitor - try: +def _get_incident_via_ampl_repn(expr, linear_only, visitor=None): + if visitor is None: + subexpression_cache = {} + subexpression_order = [] + external_functions = {} + var_map = {} + used_named_expressions = set() + symbolic_solver_labels = False + # TODO: Explore potential performance benefit of exporting defined variables. + # This likely only shows up if we can preserve the subexpression cache across + # multiple constraint expressions. + export_defined_variables = False + sorter = FileDeterminism_to_SortComponents(FileDeterminism.ORDERED) + visitor = AMPLRepnVisitor( + text_nl_template, + subexpression_cache, + subexpression_order, + external_functions, + var_map, + used_named_expressions, + symbolic_solver_labels, + export_defined_variables, + sorter, + ) + AMPLRepn.ActiveVisitor = visitor + try: + repn = visitor.walk_expression((expr, None, 0, 1.0)) + finally: + AMPLRepn.ActiveVisitor = None + else: + var_map = visitor.var_map repn = visitor.walk_expression((expr, None, 0, 1.0)) - finally: - AMPLRepn.ActiveVisitor = None nonlinear_var_ids = [] if repn.nonlinear is None else repn.nonlinear[1] nonlinear_vars = [var_map[v_id] for v_id in nonlinear_var_ids] @@ -158,6 +162,7 @@ def get_incident_variables(expr, **kwds): ['x[1]', 'x[2]'] """ + visitor = kwds.pop("visitor", None) config = IncidenceConfig(kwds) method = config.method include_fixed = config.include_fixed @@ -173,7 +178,7 @@ def get_incident_variables(expr, **kwds): elif method is IncidenceMethod.standard_repn: return _get_incident_via_standard_repn(expr, include_fixed, linear_only) elif method is IncidenceMethod.ampl_repn: - return _get_incident_via_ampl_repn(expr, linear_only) + return _get_incident_via_ampl_repn(expr, linear_only, visitor=visitor) else: raise ValueError( f"Unrecognized value {method} for the method used to identify incident" diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index e922551c6a4..60e77d26f7a 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -29,7 +29,7 @@ plotly, ) from pyomo.common.deprecation import deprecated -from pyomo.contrib.incidence_analysis.config import IncidenceConfig +from pyomo.contrib.incidence_analysis.config import IncidenceConfig, IncidenceMethod from pyomo.contrib.incidence_analysis.matching import maximum_matching from pyomo.contrib.incidence_analysis.connected import get_independent_submatrices from pyomo.contrib.incidence_analysis.triangularize import ( @@ -45,6 +45,8 @@ ) from pyomo.contrib.incidence_analysis.incidence import get_incident_variables from pyomo.contrib.pynumero.asl import AmplInterface +from pyomo.repn.plugins.nl_writer import AMPLRepnVisitor, AMPLRepn, text_nl_template +from pyomo.repn.util import FileDeterminism, FileDeterminism_to_SortComponents pyomo_nlp, pyomo_nlp_available = attempt_import( 'pyomo.contrib.pynumero.interfaces.pyomo_nlp' @@ -99,10 +101,37 @@ def get_bipartite_incidence_graph(variables, constraints, **kwds): graph.add_nodes_from(range(M), bipartite=0) graph.add_nodes_from(range(M, M + N), bipartite=1) var_node_map = ComponentMap((v, M + i) for i, v in enumerate(variables)) - for i, con in enumerate(constraints): - for var in get_incident_variables(con.body, **config): - if var in var_node_map: - graph.add_edge(i, var_node_map[var]) + + if config.method == IncidenceMethod.ampl_repn: + subexpression_cache = {} + subexpression_order = [] + external_functions = {} + used_named_expressions = set() + symbolic_solver_labels = False + export_defined_variables = False + sorter = FileDeterminism_to_SortComponents(FileDeterminism.ORDERED) + visitor = AMPLRepnVisitor( + text_nl_template, + subexpression_cache, + subexpression_order, + external_functions, + var_map, + used_named_expressions, + symbolic_solver_labels, + export_defined_variables, + sorter, + ) + else: + visitor = None + + AMPLRepn.ActiveVisitor = visitor + try: + for i, con in enumerate(constraints): + for var in get_incident_variables(con.body, visitor=visitor, **config): + if var in var_node_map: + graph.add_edge(i, var_node_map[var]) + finally: + AMPLRepn.ActiveVisitor = None return graph From bcd2435ebca336996893bfcb1243a54f3055d7f0 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Wed, 13 Dec 2023 13:40:40 -0700 Subject: [PATCH 0600/1204] add IncidenceMethod.standard_repn_compute_values option --- pyomo/contrib/incidence_analysis/config.py | 5 + pyomo/contrib/incidence_analysis/incidence.py | 16 ++- .../tests/test_incidence.py | 132 ++++++++++++------ 3 files changed, 111 insertions(+), 42 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/config.py b/pyomo/contrib/incidence_analysis/config.py index a107792a9cd..62856047121 100644 --- a/pyomo/contrib/incidence_analysis/config.py +++ b/pyomo/contrib/incidence_analysis/config.py @@ -24,6 +24,11 @@ class IncidenceMethod(enum.Enum): standard_repn = 1 """Use ``pyomo.repn.standard_repn.generate_standard_repn``""" + standard_repn_compute_values = 2 + """Use ``pyomo.repn.standard_repn.generate_standard_repn`` with + ``compute_values=True`` + """ + _include_fixed = ConfigValue( default=False, diff --git a/pyomo/contrib/incidence_analysis/incidence.py b/pyomo/contrib/incidence_analysis/incidence.py index 1852cf75648..b8dcd27c685 100644 --- a/pyomo/contrib/incidence_analysis/incidence.py +++ b/pyomo/contrib/incidence_analysis/incidence.py @@ -29,7 +29,9 @@ def _get_incident_via_identify_variables(expr, include_fixed): return list(identify_variables(expr, include_fixed=include_fixed)) -def _get_incident_via_standard_repn(expr, include_fixed, linear_only): +def _get_incident_via_standard_repn( + expr, include_fixed, linear_only, compute_values=False +): if include_fixed: to_unfix = [ var for var in identify_variables(expr, include_fixed=True) if var.fixed @@ -39,7 +41,9 @@ def _get_incident_via_standard_repn(expr, include_fixed, linear_only): context = nullcontext() with context: - repn = generate_standard_repn(expr, compute_values=False, quadratic=False) + repn = generate_standard_repn( + expr, compute_values=compute_values, quadratic=False + ) linear_vars = [] # Check coefficients to make sure we don't include linear variables with @@ -123,7 +127,13 @@ def get_incident_variables(expr, **kwds): if method is IncidenceMethod.identify_variables: return _get_incident_via_identify_variables(expr, include_fixed) elif method is IncidenceMethod.standard_repn: - return _get_incident_via_standard_repn(expr, include_fixed, linear_only) + return _get_incident_via_standard_repn( + expr, include_fixed, linear_only, compute_values=False + ) + elif method is IncidenceMethod.standard_repn_compute_values: + return _get_incident_via_standard_repn( + expr, include_fixed, linear_only, compute_values=True + ) else: raise ValueError( f"Unrecognized value {method} for the method used to identify incident" diff --git a/pyomo/contrib/incidence_analysis/tests/test_incidence.py b/pyomo/contrib/incidence_analysis/tests/test_incidence.py index 7f57dd904a7..b1a8ef1b14c 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_incidence.py +++ b/pyomo/contrib/incidence_analysis/tests/test_incidence.py @@ -56,44 +56,56 @@ def test_basic_incidence(self): def test_incidence_with_fixed_variable(self): m = pyo.ConcreteModel() - m.x = pyo.Var([1, 2, 3]) + m.x = pyo.Var([1, 2, 3], initialize=1.0) expr = m.x[1] + m.x[1] * m.x[2] + m.x[1] * pyo.exp(m.x[3]) m.x[2].fix() variables = self._get_incident_variables(expr) var_set = ComponentSet(variables) self.assertEqual(var_set, ComponentSet([m.x[1], m.x[3]])) - def test_incidence_with_mutable_parameter(self): + def test_incidence_with_named_expression(self): m = pyo.ConcreteModel() m.x = pyo.Var([1, 2, 3]) - m.p = pyo.Param(mutable=True, initialize=None) - expr = m.x[1] + m.p * m.x[1] * m.x[2] + m.x[1] * pyo.exp(m.x[3]) + m.subexpr = pyo.Expression(pyo.Integers) + m.subexpr[1] = m.x[1] * pyo.exp(m.x[3]) + expr = m.x[1] + m.x[1] * m.x[2] + m.subexpr[1] variables = self._get_incident_variables(expr) self.assertEqual(ComponentSet(variables), ComponentSet(m.x[:])) -class TestIncidenceStandardRepn(unittest.TestCase, _TestIncidence): - def _get_incident_variables(self, expr, **kwds): - method = IncidenceMethod.standard_repn - return get_incident_variables(expr, method=method, **kwds) +class _TestIncidenceLinearOnly(object): + """Tests for methods that support linear_only""" - def test_assumed_standard_repn_behavior(self): + def _get_incident_variables(self, expr): + raise NotImplementedError( + "_TestIncidenceLinearOnly should not be used directly" + ) + + def test_linear_only(self): m = pyo.ConcreteModel() - m.x = pyo.Var([1, 2]) - m.p = pyo.Param(initialize=0.0) + m.x = pyo.Var([1, 2, 3]) - # We rely on variables with constant coefficients of zero not appearing - # in the standard repn (as opposed to appearing with explicit - # coefficients of zero). - expr = m.x[1] + 0 * m.x[2] - repn = generate_standard_repn(expr) - self.assertEqual(len(repn.linear_vars), 1) - self.assertIs(repn.linear_vars[0], m.x[1]) + expr = 2 * m.x[1] + 4 * m.x[2] * m.x[1] - m.x[1] * pyo.exp(m.x[3]) + variables = self._get_incident_variables(expr, linear_only=True) + self.assertEqual(len(variables), 0) - expr = m.p * m.x[1] + m.x[2] - repn = generate_standard_repn(expr) - self.assertEqual(len(repn.linear_vars), 1) - self.assertIs(repn.linear_vars[0], m.x[2]) + expr = 2 * m.x[1] + 2 * m.x[2] * m.x[3] + 3 * m.x[2] + variables = self._get_incident_variables(expr, linear_only=True) + self.assertEqual(ComponentSet(variables), ComponentSet([m.x[1]])) + + m.x[3].fix(2.5) + expr = 2 * m.x[1] + 2 * m.x[2] * m.x[3] + 3 * m.x[2] + variables = self._get_incident_variables(expr, linear_only=True) + self.assertEqual(ComponentSet(variables), ComponentSet([m.x[1], m.x[2]])) + + +class _TestIncidenceLinearCancellation(object): + """Tests for methods that perform linear cancellation""" + + def _get_incident_variables(self, expr): + raise NotImplementedError( + "_TestIncidenceLinearCancellation should not be used directly" + ) def test_zero_coef(self): m = pyo.ConcreteModel() @@ -113,23 +125,6 @@ def test_variable_minus_itself(self): var_set = ComponentSet(variables) self.assertEqual(var_set, ComponentSet([m.x[2], m.x[3]])) - def test_linear_only(self): - m = pyo.ConcreteModel() - m.x = pyo.Var([1, 2, 3]) - - expr = 2 * m.x[1] + 4 * m.x[2] * m.x[1] - m.x[1] * pyo.exp(m.x[3]) - variables = self._get_incident_variables(expr, linear_only=True) - self.assertEqual(len(variables), 0) - - expr = 2 * m.x[1] + 2 * m.x[2] * m.x[3] + 3 * m.x[2] - variables = self._get_incident_variables(expr, linear_only=True) - self.assertEqual(ComponentSet(variables), ComponentSet([m.x[1]])) - - m.x[3].fix(2.5) - expr = 2 * m.x[1] + 2 * m.x[2] * m.x[3] + 3 * m.x[2] - variables = self._get_incident_variables(expr, linear_only=True) - self.assertEqual(ComponentSet(variables), ComponentSet([m.x[1], m.x[2]])) - def test_fixed_zero_linear_coefficient(self): m = pyo.ConcreteModel() m.x = pyo.Var([1, 2, 3]) @@ -148,6 +143,9 @@ def test_fixed_zero_linear_coefficient(self): variables = self._get_incident_variables(expr) self.assertEqual(ComponentSet(variables), ComponentSet([m.x[1], m.x[2]])) + # NOTE: This test assumes that all methods that support linear cancellation + # accept a linear_only argument. If this changes, this test will need to be + # moved. def test_fixed_zero_coefficient_linear_only(self): m = pyo.ConcreteModel() m.x = pyo.Var([1, 2, 3]) @@ -159,6 +157,35 @@ def test_fixed_zero_coefficient_linear_only(self): self.assertEqual(len(variables), 1) self.assertIs(variables[0], m.x[3]) + +class TestIncidenceStandardRepn( + unittest.TestCase, + _TestIncidence, + _TestIncidenceLinearOnly, + _TestIncidenceLinearCancellation, +): + def _get_incident_variables(self, expr, **kwds): + method = IncidenceMethod.standard_repn + return get_incident_variables(expr, method=method, **kwds) + + def test_assumed_standard_repn_behavior(self): + m = pyo.ConcreteModel() + m.x = pyo.Var([1, 2]) + m.p = pyo.Param(initialize=0.0) + + # We rely on variables with constant coefficients of zero not appearing + # in the standard repn (as opposed to appearing with explicit + # coefficients of zero). + expr = m.x[1] + 0 * m.x[2] + repn = generate_standard_repn(expr) + self.assertEqual(len(repn.linear_vars), 1) + self.assertIs(repn.linear_vars[0], m.x[1]) + + expr = m.p * m.x[1] + m.x[2] + repn = generate_standard_repn(expr) + self.assertEqual(len(repn.linear_vars), 1) + self.assertIs(repn.linear_vars[0], m.x[2]) + def test_fixed_none_linear_coefficient(self): m = pyo.ConcreteModel() m.x = pyo.Var([1, 2, 3]) @@ -168,6 +195,14 @@ def test_fixed_none_linear_coefficient(self): variables = self._get_incident_variables(expr) self.assertEqual(ComponentSet(variables), ComponentSet([m.x[1], m.x[2]])) + def test_incidence_with_mutable_parameter(self): + m = pyo.ConcreteModel() + m.x = pyo.Var([1, 2, 3]) + m.p = pyo.Param(mutable=True, initialize=None) + expr = m.x[1] + m.p * m.x[1] * m.x[2] + m.x[1] * pyo.exp(m.x[3]) + variables = self._get_incident_variables(expr) + self.assertEqual(ComponentSet(variables), ComponentSet(m.x[:])) + class TestIncidenceIdentifyVariables(unittest.TestCase, _TestIncidence): def _get_incident_variables(self, expr, **kwds): @@ -192,6 +227,25 @@ def test_variable_minus_itself(self): var_set = ComponentSet(variables) self.assertEqual(var_set, ComponentSet(m.x[:])) + def test_incidence_with_mutable_parameter(self): + m = pyo.ConcreteModel() + m.x = pyo.Var([1, 2, 3]) + m.p = pyo.Param(mutable=True, initialize=None) + expr = m.x[1] + m.p * m.x[1] * m.x[2] + m.x[1] * pyo.exp(m.x[3]) + variables = self._get_incident_variables(expr) + self.assertEqual(ComponentSet(variables), ComponentSet(m.x[:])) + + +class TestIncidenceStandardRepnComputeValues( + unittest.TestCase, + _TestIncidence, + _TestIncidenceLinearOnly, + _TestIncidenceLinearCancellation, +): + def _get_incident_variables(self, expr, **kwds): + method = IncidenceMethod.standard_repn_compute_values + return get_incident_variables(expr, method=method, **kwds) + if __name__ == "__main__": unittest.main() From ecaf0530ba1c85f75afd82a3662abe639d1bf8bf Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Wed, 13 Dec 2023 13:59:26 -0700 Subject: [PATCH 0601/1204] re-add var_map local variable --- pyomo/contrib/incidence_analysis/interface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 60e77d26f7a..726398f7750 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -106,6 +106,7 @@ def get_bipartite_incidence_graph(variables, constraints, **kwds): subexpression_cache = {} subexpression_order = [] external_functions = {} + var_map = {} used_named_expressions = set() symbolic_solver_labels = False export_defined_variables = False From 9236f4f1d1d0e8b49c7cbb3da37135ab0a2ad8e8 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Wed, 13 Dec 2023 13:59:50 -0700 Subject: [PATCH 0602/1204] filter duplicates from list of nonlinear vars --- pyomo/contrib/incidence_analysis/incidence.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/incidence.py b/pyomo/contrib/incidence_analysis/incidence.py index 7632f81e38a..feb8689a7c3 100644 --- a/pyomo/contrib/incidence_analysis/incidence.py +++ b/pyomo/contrib/incidence_analysis/incidence.py @@ -110,12 +110,18 @@ def _get_incident_via_ampl_repn(expr, linear_only, visitor=None): repn = visitor.walk_expression((expr, None, 0, 1.0)) nonlinear_var_ids = [] if repn.nonlinear is None else repn.nonlinear[1] - nonlinear_vars = [var_map[v_id] for v_id in nonlinear_var_ids] - nonlinear_vid_set = set(nonlinear_var_ids) + nonlinear_var_id_set = set() + unique_nonlinear_var_ids = [] + for v_id in nonlinear_var_ids: + if v_id not in nonlinear_var_id_set: + nonlinear_var_id_set.add(v_id) + unique_nonlinear_var_ids.append(v_id) + + nonlinear_vars = [var_map[v_id] for v_id in unique_nonlinear_var_ids] linear_only_vars = [ var_map[v_id] for v_id, coef in repn.linear.items() - if coef != 0.0 and v_id not in nonlinear_vid_set + if coef != 0.0 and v_id not in nonlinear_var_id_set ] if linear_only: return linear_only_vars From c04264005f6c7729b7a2054e8c7a96c389f13ef8 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Wed, 13 Dec 2023 14:31:33 -0700 Subject: [PATCH 0603/1204] re-use visitor in _generate_variables_in_constraints --- pyomo/contrib/incidence_analysis/interface.py | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 726398f7750..ce5f4780210 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -194,12 +194,45 @@ def extract_bipartite_subgraph(graph, nodes0, nodes1): def _generate_variables_in_constraints(constraints, **kwds): config = IncidenceConfig(kwds) - known_vars = ComponentSet() - for con in constraints: - for var in get_incident_variables(con.body, **config): - if var not in known_vars: - known_vars.add(var) - yield var + + if config.method == IncidenceMethod.ampl_repn: + subexpression_cache = {} + subexpression_order = [] + external_functions = {} + var_map = {} + used_named_expressions = set() + symbolic_solver_labels = False + export_defined_variables = False + sorter = FileDeterminism_to_SortComponents(FileDeterminism.ORDERED) + visitor = AMPLRepnVisitor( + text_nl_template, + subexpression_cache, + subexpression_order, + external_functions, + var_map, + used_named_expressions, + symbolic_solver_labels, + export_defined_variables, + sorter, + ) + else: + visitor = None + + AMPLRepn.ActiveVisitor = visitor + try: + known_vars = ComponentSet() + for con in constraints: + for var in get_incident_variables(con.body, visitor=visitor, **config): + if var not in known_vars: + known_vars.add(var) + yield var + finally: + # NOTE: I believe this is only guaranteed to be called when the + # generator is garbage collected. This could lead to some nasty + # bug where ActiveVisitor is set for longer than we intend. + # TODO: Convert this into a function. (or yield from variables + # after this try/finally. + AMPLRepn.ActiveVisitor = None def get_structural_incidence_matrix(variables, constraints, **kwds): From 5dc5cd54847930c8bc28820db1cf81c6405d4474 Mon Sep 17 00:00:00 2001 From: jialuw96 Date: Wed, 13 Dec 2023 20:00:54 -0500 Subject: [PATCH 0604/1204] update doc --- doc/OnlineDocs/contributed_packages/doe/doe.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/OnlineDocs/contributed_packages/doe/doe.rst b/doc/OnlineDocs/contributed_packages/doe/doe.rst index 354a9916e9b..8c22ff7370d 100644 --- a/doc/OnlineDocs/contributed_packages/doe/doe.rst +++ b/doc/OnlineDocs/contributed_packages/doe/doe.rst @@ -266,7 +266,7 @@ It allows users to define any number of design decisions. Heatmaps can be drawn The function ``run_grid_search`` enumerates over the design space, each MBDoE problem accomplished by ``compute_FIM`` method. Therefore, ``run_grid_search`` supports only two modes: ``sequential_finite`` and ``direct_kaug``. -.. literalinclude:: ../../../../pyomo/contrib/doe/examples/reactor_compute_FIM.py +.. literalinclude:: ../../../../pyomo/contrib/doe/examples/reactor_grid_search.py :language: python :pyobject: main @@ -284,7 +284,7 @@ Pyomo.DoE accomplishes gradient-based optimization with the ``stochastic_program This function solves twice: It solves the square version of the MBDoE problem first, and then unfixes the design variables as degree of freedoms and solves again. In this way the optimization problem can be well initialized. -.. literalinclude:: ../../../../pyomo/contrib/doe/examples/reactor_compute_FIM.py +.. literalinclude:: ../../../../pyomo/contrib/doe/examples/reactor_optimize_doe.py :language: python :pyobject: main From a167c4e2568ea4e30bed4ce19d1565816ad71f3a Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 14 Dec 2023 13:40:10 -0700 Subject: [PATCH 0605/1204] Removing unused import --- pyomo/gdp/plugins/multiple_bigm.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index 18f159c7ca2..6a45c9ebc73 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -31,7 +31,6 @@ NonNegativeIntegers, Objective, Param, - RangeSet, Set, SetOf, SortComponents, From 78b225807637e5db3f9fa8bf1aef7aee934996e1 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 14 Dec 2023 13:58:42 -0700 Subject: [PATCH 0606/1204] Fixing a silly indentation bug that happens when there are empty constraint containers on Disjuncts --- pyomo/gdp/plugins/multiple_bigm.py | 4 +-- pyomo/gdp/tests/test_mbigm.py | 57 +++++++++++++++++++++--------- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index 6a45c9ebc73..e66dcb3bb88 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -426,8 +426,8 @@ def _transform_constraint(self, obj, disjunct, active_disjuncts, Ms): constraintMap, ) - # deactivate now that we have transformed - c.deactivate() + # deactivate now that we have transformed + c.deactivate() def _transform_bound_constraints(self, active_disjuncts, transBlock, Ms): # first we're just going to find all of them diff --git a/pyomo/gdp/tests/test_mbigm.py b/pyomo/gdp/tests/test_mbigm.py index f067e1da5af..7ab34153468 100644 --- a/pyomo/gdp/tests/test_mbigm.py +++ b/pyomo/gdp/tests/test_mbigm.py @@ -49,8 +49,24 @@ ) exdir = normpath(join(PYOMO_ROOT_DIR, 'examples', 'gdp')) +class CommonTests(unittest.TestCase): + def check_pretty_bound_constraints(self, cons, var, bounds, lb): + self.assertEqual(value(cons.upper), 0) + self.assertIsNone(cons.lower) + repn = generate_standard_repn(cons.body) + self.assertTrue(repn.is_linear()) + self.assertEqual(len(repn.linear_vars), len(bounds) + 1) + self.assertEqual(repn.constant, 0) + if lb: + check_linear_coef(self, repn, var, -1) + for disj, bnd in bounds.items(): + check_linear_coef(self, repn, disj.binary_indicator_var, bnd) + else: + check_linear_coef(self, repn, var, 1) + for disj, bnd in bounds.items(): + check_linear_coef(self, repn, disj.binary_indicator_var, -bnd) -class LinearModelDecisionTreeExample(unittest.TestCase): +class LinearModelDecisionTreeExample(CommonTests): def make_model(self): m = ConcreteModel() m.x1 = Var(bounds=(-10, 10)) @@ -381,22 +397,6 @@ def test_algebraic_constraints(self): check_linear_coef(self, repn, m.d3.binary_indicator_var, 1) check_obj_in_active_tree(self, xor) - def check_pretty_bound_constraints(self, cons, var, bounds, lb): - self.assertEqual(value(cons.upper), 0) - self.assertIsNone(cons.lower) - repn = generate_standard_repn(cons.body) - self.assertTrue(repn.is_linear()) - self.assertEqual(len(repn.linear_vars), len(bounds) + 1) - self.assertEqual(repn.constant, 0) - if lb: - check_linear_coef(self, repn, var, -1) - for disj, bnd in bounds.items(): - check_linear_coef(self, repn, disj.binary_indicator_var, bnd) - else: - check_linear_coef(self, repn, var, 1) - for disj, bnd in bounds.items(): - check_linear_coef(self, repn, disj.binary_indicator_var, -bnd) - def test_bounds_constraints_correct(self): m = self.make_model() @@ -876,6 +876,29 @@ class NestedDisjunctsInFlatGDP(unittest.TestCase): def test_declare_disjuncts_in_disjunction_rule(self): check_nested_disjuncts_in_flat_gdp(self, 'bigm') +class IndexedDisjunctiveConstraints(CommonTests): + def test_empty_constraint_container_on_Disjunct(self): + m = ConcreteModel() + m.d = Disjunct() + m.e = Disjunct() + m.d.c = Constraint(['s', 'i', 'l', 'L', 'y']) + m.x = Var(bounds=(2, 3)) + m.e.c = Constraint(expr=m.x == 2.7) + m.disjunction = Disjunction(expr=[m.d, m.e]) + + mbm = TransformationFactory('gdp.mbigm') + mbm.apply_to(m) + + cons = mbm.get_transformed_constraints(m.e.c) + self.assertEqual(len(cons), 2) + self.check_pretty_bound_constraints( + cons[0], m.x, {m.d: 2, m.e: 2.7}, lb=True + + ) + self.check_pretty_bound_constraints( + cons[1], m.x, {m.d: 3, m.e: 2.7}, lb=False + ) + @unittest.skipUnless(gurobi_available, "Gurobi is not available") class IndexedDisjunction(unittest.TestCase): From 7fe251e05a6a7a333d29d80a49ccef21312de7e4 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 14 Dec 2023 14:00:44 -0700 Subject: [PATCH 0607/1204] Taking out the Suffix paranoia in mbigm--it can just ignore Suffixes --- pyomo/gdp/plugins/multiple_bigm.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index e66dcb3bb88..b2f4b5f6e12 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -34,7 +34,6 @@ Set, SetOf, SortComponents, - Suffix, value, Var, ) @@ -200,7 +199,6 @@ class MultipleBigMTransformation(GDP_to_MIP_Transformation, _BigM_MixIn): def __init__(self): super().__init__(logger) - self.handlers[Suffix] = self._warn_for_active_suffix self._arg_list = {} self._set_up_expr_bound_visitor() @@ -345,13 +343,6 @@ def _transform_disjunct(self, obj, transBlock, active_disjuncts, Ms): # deactivate disjunct so writers can be happy obj._deactivate_without_fixing_indicator() - def _warn_for_active_suffix(self, obj, disjunct, active_disjuncts, Ms): - raise GDP_Error( - "Found active Suffix '{0}' on Disjunct '{1}'. " - "The multiple bigM transformation does not currently " - "support Suffixes.".format(obj.name, disjunct.name) - ) - def _transform_constraint(self, obj, disjunct, active_disjuncts, Ms): # we will put a new transformed constraint on the relaxation block. relaxationBlock = disjunct._transformation_block() From dbabb67fe65294088f1fe959b7b1203882374ca1 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 14 Dec 2023 14:30:08 -0700 Subject: [PATCH 0608/1204] Revert "Taking out the Suffix paranoia in mbigm--it can just ignore Suffixes" This reverts commit 7fe251e05a6a7a333d29d80a49ccef21312de7e4. --- pyomo/gdp/plugins/multiple_bigm.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index b2f4b5f6e12..e66dcb3bb88 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -34,6 +34,7 @@ Set, SetOf, SortComponents, + Suffix, value, Var, ) @@ -199,6 +200,7 @@ class MultipleBigMTransformation(GDP_to_MIP_Transformation, _BigM_MixIn): def __init__(self): super().__init__(logger) + self.handlers[Suffix] = self._warn_for_active_suffix self._arg_list = {} self._set_up_expr_bound_visitor() @@ -343,6 +345,13 @@ def _transform_disjunct(self, obj, transBlock, active_disjuncts, Ms): # deactivate disjunct so writers can be happy obj._deactivate_without_fixing_indicator() + def _warn_for_active_suffix(self, obj, disjunct, active_disjuncts, Ms): + raise GDP_Error( + "Found active Suffix '{0}' on Disjunct '{1}'. " + "The multiple bigM transformation does not currently " + "support Suffixes.".format(obj.name, disjunct.name) + ) + def _transform_constraint(self, obj, disjunct, active_disjuncts, Ms): # we will put a new transformed constraint on the relaxation block. relaxationBlock = disjunct._transformation_block() From 1f9b20f95a57c0d8106cf40ebd50237746d5a584 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 15 Dec 2023 15:28:29 -0500 Subject: [PATCH 0609/1204] Exclude fixed vars from state vars --- pyomo/contrib/pyros/pyros.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 5b37b114722..b5d77d74a6e 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -990,13 +990,14 @@ def solve( # === Move bounds on control variables to explicit ineq constraints wm_util = model_data.working_model - # === Assuming all other Var objects in the model are state variables + # === Every non-fixed variable that is neither first-stage + # nor second-stage is taken to be a state variable fsv = ComponentSet(model_data.working_model.util.first_stage_variables) ssv = ComponentSet(model_data.working_model.util.second_stage_variables) sv = ComponentSet() model_data.working_model.util.state_vars = [] for v in model_data.working_model.component_data_objects(Var): - if v not in fsv and v not in ssv and v not in sv: + if not v.fixed and v not in fsv | ssv | sv: model_data.working_model.util.state_vars.append(v) sv.add(v) From 802e4c7b9d0a609bbb26d0f4b57589f1ac5720d5 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 15 Dec 2023 16:03:15 -0500 Subject: [PATCH 0610/1204] Update separation problem initialization --- .../pyros/separation_problem_methods.py | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index 61f347b418d..b4bbd00259a 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -882,13 +882,40 @@ def initialize_separation(perf_con_to_maximize, model_data, config): discrete geometry (as there is no master model block corresponding to any of the remaining discrete scenarios against which we separate). + + This method assumes that the master model has only one block + per iteration. """ - # initialize to values from nominal block if nominal objective. - # else, initialize to values from latest block added to master - if config.objective_focus == ObjectiveType.nominal: - block_num = 0 - else: - block_num = model_data.iteration + def eval_master_violation(block_idx): + """ + Evaluate violation of `perf_con` by variables of + specified master block. + """ + new_con_map = ( + model_data + .separation_model + .util + .map_new_constraint_list_to_original_con + ) + in_new_cons = perf_con_to_maximize in new_con_map + if in_new_cons: + sep_con = new_con_map[perf_con_to_maximize] + else: + sep_con = perf_con_to_maximize + master_con = ( + model_data.master_model.scenarios[block_idx, 0].find_component( + sep_con, + ) + ) + return value(master_con) + + # initialize from master block with max violation of the + # performance constraint of interest. This gives the best known + # feasible solution (for case of non-discrete uncertainty sets). + block_num = max( + range(model_data.iteration + 1), + key=eval_master_violation, + ) master_blk = model_data.master_model.scenarios[block_num, 0] master_blks = list(model_data.master_model.scenarios.values()) From e8860b7553acacfa07209ba1a2143874896a34af Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 15 Dec 2023 16:50:33 -0500 Subject: [PATCH 0611/1204] Update master problem initialization --- pyomo/contrib/pyros/master_problem_methods.py | 74 +++++++------------ .../contrib/pyros/pyros_algorithm_methods.py | 8 ++ 2 files changed, 34 insertions(+), 48 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index dc4b6b957bb..628b79ef9a2 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -102,6 +102,11 @@ def construct_master_feasibility_problem(model_data, config): Slack variable model. """ + # clone master model. current state: + # - variables for all but newest block are set to values from + # master solution from previous iteration + # - variables for newest block are set to values from separation + # solution chosen in previous iteration model = model_data.master_model.clone() # obtain mapping from master problem to master feasibility @@ -123,53 +128,26 @@ def construct_master_feasibility_problem(model_data, config): obj.deactivate() iteration = model_data.iteration - # first stage vars are already initialized appropriately. - # initialize second-stage DOF variables using DR equation expressions - if model.scenarios[iteration, 0].util.second_stage_variables: - for blk in model.scenarios[iteration, :]: - for eq in blk.util.decision_rule_eqns: - vars_in_dr_eq = ComponentSet(identify_variables(eq.body)) - ssv_set = ComponentSet(blk.util.second_stage_variables) - - # get second-stage var in DR eqn. should only be one var - ssv_in_dr_eq = [var for var in vars_in_dr_eq if var in ssv_set][0] - - # update var value for initialization - # fine since DR eqns are f(d) - z == 0 (not z - f(d) == 0) - ssv_in_dr_eq.set_value(0) - ssv_in_dr_eq.set_value(value(eq.body)) - - # initialize state vars to previous master solution values - if iteration != 0: - stvar_map = get_state_vars(model, [iteration, iteration - 1]) - for current, prev in zip(stvar_map[iteration], stvar_map[iteration - 1]): - current.set_value(value(prev)) - - # constraints to which slacks should be added - # (all the constraints for the current iteration, except the DR eqns) + # add slacks only to inequality constraints for the newest + # master block. these should be the only constraints which + # may have been violated by the previous master and separation + # solution(s) targets = [] for blk in model.scenarios[iteration, :]: - if blk.util.second_stage_variables: - dr_eqs = blk.util.decision_rule_eqns - else: - dr_eqs = list() - - targets.extend( - [ - con - for con in blk.component_data_objects( - Constraint, active=True, descend_into=True - ) - if con not in dr_eqs - ] - ) + targets.extend([ + con + for con in blk.component_data_objects( + Constraint, active=True, descend_into=True + ) + if not con.equality + ]) - # retain original constraint exprs (for slack initialization and scaling) + # retain original constraint expressions + # (for slack initialization and scaling) pre_slack_con_exprs = ComponentMap((con, con.body - con.upper) for con in targets) # add slack variables and objective - # inequalities g(v) <= b become g(v) -- s^-<= b - # equalities h(v) == b become h(v) -- s^- + s^+ == b + # inequalities g(v) <= b become g(v) - s^- <= b TransformationFactory("core.add_slack_variables").apply_to(model, targets=targets) slack_vars = ComponentSet( model._core_add_slack_variables.component_data_objects(Var, descend_into=True) @@ -177,8 +155,8 @@ def construct_master_feasibility_problem(model_data, config): # initialize and scale slack variables for con in pre_slack_con_exprs: - # obtain slack vars in updated constraints - # and their coefficients (+/-1) in the constraint expression + # get mapping from slack variables to their (linear) + # coefficients (+/-1) in the updated constraint expressions repn = generate_standard_repn(con.body) slack_var_coef_map = ComponentMap() for idx in range(len(repn.linear_vars)): @@ -187,19 +165,19 @@ def construct_master_feasibility_problem(model_data, config): slack_var_coef_map[var] = repn.linear_coefs[idx] slack_substitution_map = dict() - for slack_var in slack_var_coef_map: - # coefficient determines whether the slack is a +ve or -ve slack + # coefficient determines whether the slack + # is a +ve or -ve slack if slack_var_coef_map[slack_var] == -1: con_slack = max(0, value(pre_slack_con_exprs[con])) else: con_slack = max(0, -value(pre_slack_con_exprs[con])) - # initialize slack var, evaluate scaling coefficient - scaling_coeff = 1 + # initialize slack variable, evaluate scaling coefficient slack_var.set_value(con_slack) + scaling_coeff = 1 - # update expression replacement map + # update expression replacement map for slack scaling slack_substitution_map[id(slack_var)] = scaling_coeff * slack_var # finally, scale slack(s) diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index af7a91d21a4..9f78a4c18ec 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -886,6 +886,14 @@ def ROSolver_iterative_solve(model_data, config): np.array([pt for pt in separation_data.points_added_to_master]) ) + # initialize second-stage and state variables + # for new master block to separation + # solution chosen by heuristic. consequently, + # equality constraints should all be satisfied (up to tolerances). + for var, val in separation_results.violating_separation_variable_values.items(): + master_var = master_data.master_model.scenarios[k + 1, 0].find_component(var) + master_var.set_value(val) + k += 1 iter_log_record.log(config.progress_logger.info) From a582c9219bd1121ec3305fb96383bed1040a88f2 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 16 Dec 2023 00:06:18 -0500 Subject: [PATCH 0612/1204] Simplify DR component declaration routines --- pyomo/contrib/pyros/tests/test_grcs.py | 238 ++++++++++----------- pyomo/contrib/pyros/util.py | 278 ++++++++++--------------- 2 files changed, 220 insertions(+), 296 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 46af1277ba5..0365cde1a48 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -243,79 +243,108 @@ class testAddDecisionRuleVars(unittest.TestCase): depends on the number of control variables in the model and the number of uncertain parameters in the model. ''' - @unittest.skipIf(not scipy_available, 'Scipy is not available.') - def test_add_decision_rule_vars_positive_case(self): - ''' - Testing whether the correct number of decision rule variables is created in each DR type case - ''' + def make_simple_test_model(self): + """ + Make simple test model for DR variable + declaration testing. + """ m = ConcreteModel() - m.p1 = Param(initialize=0, mutable=True) - m.p2 = Param(initialize=0, mutable=True) - m.z1 = Var(initialize=0) - m.z2 = Var(initialize=0) - m.working_model = ConcreteModel() - m.working_model.util = Block() + # uncertain parameters + m.p = Param(range(3), initialize=0, mutable=True) - m.working_model.util.second_stage_variables = [m.z1, m.z2] - m.working_model.util.uncertain_params = [m.p1, m.p2] - m.working_model.util.first_stage_variables = [] + # second-stage variables + m.z = Var([0, 1], initialize=0) - m.working_model.util.first_stage_variables = [] - config = Block() - config.decision_rule_order = 0 + # util block + m.util = Block() + m.util.first_stage_variables = [] + m.util.second_stage_variables = list(m.z.values()) + m.util.uncertain_params = list(m.p.values()) - add_decision_rule_variables(model_data=m, config=config) + return m - self.assertEqual( - len(m.working_model.util.first_stage_variables), - len(m.working_model.util.second_stage_variables), - msg="For static approximation decision rule the number of decision rule variables" - "added to the list of design variables should equal the number of control variables.", - ) + @unittest.skipIf(not scipy_available, 'Scipy is not available.') + def test_correct_num_dr_vars_static(self): + """ + Test DR variable setup routines declare the correct + number of DR coefficient variables, static DR case. + """ + model_data = ROSolveResults() + model_data.working_model = m = self.make_simple_test_model() - m.working_model.util.first_stage_variables = [] + config = Bunch() + config.decision_rule_order = 0 - m.working_model.del_component(m.working_model.decision_rule_var_0) - m.working_model.del_component(m.working_model.decision_rule_var_1) + add_decision_rule_variables(model_data=model_data, config=config) - config.decision_rule_order = 1 + for indexed_dr_var in m.util.decision_rule_vars: + self.assertEqual( + len(indexed_dr_var), + 1, + msg=( + "Number of decision rule coefficient variables " + f"in indexed Var object {indexed_dr_var.name!r}" + "does not match correct value." + ), + ) - add_decision_rule_variables(m, config=config) + @unittest.skipIf(not scipy_available, 'Scipy is not available.') + def test_correct_num_dr_vars_affine(self): + """ + Test DR variable setup routines declare the correct + number of DR coefficient variables, affine DR case. + """ + model_data = ROSolveResults() + model_data.working_model = m = self.make_simple_test_model() - self.assertEqual( - len(m.working_model.util.first_stage_variables), - len(m.working_model.util.second_stage_variables) - * (1 + len(m.working_model.util.uncertain_params)), - msg="For affine decision rule the number of decision rule variables add to the " - "list of design variables should equal the number of control variables" - "multiplied by the number of uncertain parameters plus 1.", - ) + config = Bunch() + config.decision_rule_order = 1 - m.working_model.util.first_stage_variables = [] + add_decision_rule_variables(model_data=model_data, config=config) - m.working_model.del_component(m.working_model.decision_rule_var_0) - m.working_model.del_component(m.working_model.decision_rule_var_1) - m.working_model.del_component(m.working_model.decision_rule_var_0_index) - m.working_model.del_component(m.working_model.decision_rule_var_1_index) + for indexed_dr_var in m.util.decision_rule_vars: + self.assertEqual( + len(indexed_dr_var), + 1 + len(m.util.uncertain_params), + msg=( + "Number of decision rule coefficient variables " + f"in indexed Var object {indexed_dr_var.name!r}" + "does not match correct value." + ), + ) + + @unittest.skipIf(not scipy_available, 'Scipy is not available.') + def test_correct_num_dr_vars_quadratic(self): + """ + Test DR variable setup routines declare the correct + number of DR coefficient variables, quadratic DR case. + """ + model_data = ROSolveResults() + model_data.working_model = m = self.make_simple_test_model() + config = Bunch() config.decision_rule_order = 2 - add_decision_rule_variables(m, config=config) + add_decision_rule_variables(model_data=model_data, config=config) - self.assertEqual( - len(m.working_model.util.first_stage_variables), - len(m.working_model.util.second_stage_variables) - * int( - 2 * len(m.working_model.util.uncertain_params) - + sp.special.comb(N=len(m.working_model.util.uncertain_params), k=2) - + 1 - ), - msg="For quadratic decision rule the number of decision rule variables add to the " - "list of design variables should equal the number of control variables" - "multiplied by 2 time the number of uncertain parameters plus all 2-combinations" - "of uncertain parameters plus 1.", + num_params = len(m.util.uncertain_params) + correct_num_dr_vars = ( + 1 # static term + + num_params # affine terms + + sp.special.comb(num_params, 2, repetition=True, exact=True) + # quadratic terms ) + for indexed_dr_var in m.util.decision_rule_vars: + self.assertEqual( + len(indexed_dr_var), + correct_num_dr_vars, + msg=( + "Number of decision rule coefficient variables " + f"in indexed Var object {indexed_dr_var.name!r}" + "does not match correct value." + ), + ) class testAddDecisionRuleConstraints(unittest.TestCase): @@ -325,92 +354,45 @@ class testAddDecisionRuleConstraints(unittest.TestCase): to the number of control variables. These constraints should reference the uncertain parameters and unique decision rule variables per control variable. ''' + def test_num_dr_eqns_added_correct(self): + """ + Check that number of DR equality constraints added + by constraint declaration routines matches the number + of second-stage variables in the model. + """ + model_data = ROSolveResults() + model_data.working_model = m = ConcreteModel() - def test_correct_number_of_decision_rule_constraints(self): - ''' - Number of decision rule constraints added to the model should equal number of control variables in - list "second_stage_variables". - ''' - m = ConcreteModel() + # uncertain parameters m.p1 = Param(initialize=0, mutable=True) m.p2 = Param(initialize=0, mutable=True) + + # second-stage variables m.z1 = Var(initialize=0) m.z2 = Var(initialize=0) - m.working_model = ConcreteModel() - m.working_model.util = Block() + # add util block + m.util = Block() + m.util.uncertain_params = [m.p1, m.p2] + m.util.second_stage_variables = [m.z1, m.z2] # === Decision rule vars have been added - m.working_model.decision_rule_var_0 = Var(initialize=0) - m.working_model.decision_rule_var_1 = Var(initialize=0) - - m.working_model.util.second_stage_variables = [m.z1, m.z2] - m.working_model.util.uncertain_params = [m.p1, m.p2] + m.decision_rule_var_0 = Var([0], initialize=0) + m.decision_rule_var_1 = Var([0], initialize=0) + m.util.decision_rule_vars = [ + m.decision_rule_var_0, + m.decision_rule_var_1, + ] - decision_rule_cons = [] - config = Block() + # set up simple config-like object + config = Bunch() config.decision_rule_order = 0 - add_decision_rule_constraints(model_data=m, config=config) - - for c in m.working_model.component_data_objects(Constraint, descend_into=True): - if "decision_rule_eqn_" in c.name: - decision_rule_cons.append(c) - m.working_model.del_component(c) - - self.assertEqual( - len(decision_rule_cons), - len(m.working_model.util.second_stage_variables), - msg="The number of decision rule constraints added to model should equal" - "the number of control variables in the model.", - ) - - decision_rule_cons = [] - config.decision_rule_order = 1 - - # === Decision rule vars have been added - m.working_model.del_component(m.working_model.decision_rule_var_0) - m.working_model.del_component(m.working_model.decision_rule_var_1) - - m.working_model.decision_rule_var_0 = Var([0, 1, 2], initialize=0) - m.working_model.decision_rule_var_1 = Var([0, 1, 2], initialize=0) - - add_decision_rule_constraints(model_data=m, config=config) - - for c in m.working_model.component_data_objects(Constraint, descend_into=True): - if "decision_rule_eqn_" in c.name: - decision_rule_cons.append(c) - m.working_model.del_component(c) - - self.assertEqual( - len(decision_rule_cons), - len(m.working_model.util.second_stage_variables), - msg="The number of decision rule constraints added to model should equal" - "the number of control variables in the model.", - ) - - decision_rule_cons = [] - config.decision_rule_order = 2 - - # === Decision rule vars have been added - m.working_model.del_component(m.working_model.decision_rule_var_0) - m.working_model.del_component(m.working_model.decision_rule_var_1) - m.working_model.del_component(m.working_model.decision_rule_var_0_index) - m.working_model.del_component(m.working_model.decision_rule_var_1_index) - - m.working_model.decision_rule_var_0 = Var([0, 1, 2, 3, 4, 5], initialize=0) - m.working_model.decision_rule_var_1 = Var([0, 1, 2, 3, 4, 5], initialize=0) - - add_decision_rule_constraints(model_data=m, config=config) - - for c in m.working_model.component_data_objects(Constraint, descend_into=True): - if "decision_rule_eqn_" in c.name: - decision_rule_cons.append(c) - m.working_model.del_component(c) + add_decision_rule_constraints(model_data=model_data, config=config) self.assertEqual( - len(decision_rule_cons), - len(m.working_model.util.second_stage_variables), + len(m.util.decision_rule_eqns), + len(m.util.second_stage_variables), msg="The number of decision rule constraints added to model should equal" "the number of control variables in the model.", ) diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index 19f178c70f6..263363a4200 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -3,7 +3,7 @@ ''' import copy from enum import Enum, auto -from pyomo.common.collections import ComponentSet +from pyomo.common.collections import ComponentSet, ComponentMap from pyomo.common.modeling import unique_component_name from pyomo.core.base import ( Constraint, @@ -17,6 +17,7 @@ Block, Param, ) +from pyomo.core.util import prod from pyomo.core.base.var import IndexedVar from pyomo.core.base.set_types import Reals from pyomo.opt import TerminationCondition as tc @@ -43,6 +44,7 @@ import math from pyomo.common.timing import HierarchicalTimer from pyomo.common.log import Preformatted +from scipy.special import comb # Tolerances used in the code @@ -1275,97 +1277,59 @@ def selective_clone(block, first_stage_vars): def add_decision_rule_variables(model_data, config): - ''' - Function to add decision rule (DR) variables to the working model. DR variables become first-stage design - variables which do not get copied at each iteration. Currently support static_approx (no DR), affine DR, - and quadratic DR. - :param model_data: the data container for the working model - :param config: the config block - :return: - ''' + """ + Add variables for polynomial decision rules to the working + model. + + Parameters + ---------- + model_data : ROSolveResults + Model data. + config : config_dict + PyROS solver options. + + Note + ---- + Decision rule variables are considered first-stage decision + variables which do not get copied at each iteration. + PyROS currently supports static (zeroth order), + affine (first-order), and quadratic DR. + """ second_stage_variables = model_data.working_model.util.second_stage_variables first_stage_variables = model_data.working_model.util.first_stage_variables - uncertain_params = model_data.working_model.util.uncertain_params decision_rule_vars = [] + + # since DR expression is a general polynomial in the uncertain + # parameters, the exact number of DR variables per second-stage + # variable depends on DR order and uncertainty set dimension degree = config.decision_rule_order - bounds = (None, None) - if degree == 0: - for i in range(len(second_stage_variables)): - model_data.working_model.add_component( - "decision_rule_var_" + str(i), - Var( - initialize=value(second_stage_variables[i], exception=False), - bounds=bounds, - domain=Reals, - ), - ) - first_stage_variables.extend( - getattr( - model_data.working_model, "decision_rule_var_" + str(i) - ).values() - ) - decision_rule_vars.append( - getattr(model_data.working_model, "decision_rule_var_" + str(i)) - ) - elif degree == 1: - for i in range(len(second_stage_variables)): - index_set = list(range(len(uncertain_params) + 1)) - model_data.working_model.add_component( - "decision_rule_var_" + str(i), - Var(index_set, initialize=0, bounds=bounds, domain=Reals), - ) - # === For affine drs, the [0]th constant term is initialized to the control variable values, all other terms are initialized to 0 - getattr(model_data.working_model, "decision_rule_var_" + str(i))[ - 0 - ].set_value( - value(second_stage_variables[i], exception=False), skip_validation=True - ) - first_stage_variables.extend( - list( - getattr( - model_data.working_model, "decision_rule_var_" + str(i) - ).values() - ) - ) - decision_rule_vars.append( - getattr(model_data.working_model, "decision_rule_var_" + str(i)) - ) - elif degree == 2 or degree == 3 or degree == 4: - for i in range(len(second_stage_variables)): - num_vars = int(sp.special.comb(N=len(uncertain_params) + degree, k=degree)) - dict_init = {} - for r in range(num_vars): - if r == 0: - dict_init.update( - {r: value(second_stage_variables[i], exception=False)} - ) - else: - dict_init.update({r: 0}) - model_data.working_model.add_component( - "decision_rule_var_" + str(i), - Var( - list(range(num_vars)), - initialize=dict_init, - bounds=bounds, - domain=Reals, - ), - ) - first_stage_variables.extend( - list( - getattr( - model_data.working_model, "decision_rule_var_" + str(i) - ).values() - ) - ) - decision_rule_vars.append( - getattr(model_data.working_model, "decision_rule_var_" + str(i)) - ) - else: - raise ValueError( - "Decision rule order " - + str(config.decision_rule_order) - + " is not yet supported. PyROS supports polynomials of degree 0 (static approximation), 1, 2." + num_uncertain_params = len(model_data.working_model.util.uncertain_params) + num_dr_vars = comb( + N=num_uncertain_params + degree, + k=degree, + exact=True, + repetition=False, + ) + + for idx, ss_var in enumerate(second_stage_variables): + # declare DR coefficients for current second-stage + # variable + indexed_dr_var = Var( + range(num_dr_vars), + initialize=0, + bounds=(None, None), + domain=Reals, ) + model_data.working_model.add_component( + f"decision_rule_var_{idx}", + indexed_dr_var, + ) + indexed_dr_var[0].set_value(value(ss_var, exception=False)) + + # update attributes + first_stage_variables.extend(indexed_dr_var.values()) + decision_rule_vars.append(indexed_dr_var) + model_data.working_model.util.decision_rule_vars = decision_rule_vars @@ -1401,94 +1365,72 @@ def sort_partitioned_powers(powers_list): def add_decision_rule_constraints(model_data, config): - ''' - Function to add the defining Constraint relationships for the decision rules to the working model. - :param model_data: model data container object - :param config: the config object - :return: - ''' + """ + Add decision rule equality constraints to the working model. + + Parameters + ---------- + model_data : ROSolveResults + Model data. + config : ConfigDict + PyROS solver options. + """ second_stage_variables = model_data.working_model.util.second_stage_variables uncertain_params = model_data.working_model.util.uncertain_params decision_rule_eqns = [] + decision_rule_vars_list = model_data.working_model.util.decision_rule_vars degree = config.decision_rule_order - if degree == 0: - for i in range(len(second_stage_variables)): - model_data.working_model.add_component( - "decision_rule_eqn_" + str(i), - Constraint( - expr=getattr( - model_data.working_model, "decision_rule_var_" + str(i) - ) - == second_stage_variables[i] - ), - ) - decision_rule_eqns.append( - getattr(model_data.working_model, "decision_rule_eqn_" + str(i)) - ) - elif degree == 1: - for i in range(len(second_stage_variables)): - expr = 0 - for j in range( - len(getattr(model_data.working_model, "decision_rule_var_" + str(i))) - ): - if j == 0: - expr += getattr( - model_data.working_model, "decision_rule_var_" + str(i) - )[j] - else: - expr += ( - getattr( - model_data.working_model, "decision_rule_var_" + str(i) - )[j] - * uncertain_params[j - 1] - ) - model_data.working_model.add_component( - "decision_rule_eqn_" + str(i), - Constraint(expr=expr == second_stage_variables[i]), - ) - decision_rule_eqns.append( - getattr(model_data.working_model, "decision_rule_eqn_" + str(i)) - ) - elif degree >= 2: - # Using bars and stars groupings of variable powers, construct x1^a * .... * xn^b terms for all c <= a+...+b = degree - all_powers = [] - for n in range(1, degree + 1): - all_powers.append( - sort_partitioned_powers( - list(partition_powers(n, len(uncertain_params))) - ) - ) - for i in range(len(second_stage_variables)): - Z = list( - z - for z in getattr( - model_data.working_model, "decision_rule_var_" + str(i) - ).values() - ) - e = Z.pop(0) - for degree_param_powers in all_powers: - for param_powers in degree_param_powers: - product = 1 - for idx, power in enumerate(param_powers): - if power == 0: - pass - else: - product = product * uncertain_params[idx] ** power - e += Z.pop(0) * product - model_data.working_model.add_component( - "decision_rule_eqn_" + str(i), - Constraint(expr=e == second_stage_variables[i]), - ) - decision_rule_eqns.append( - getattr(model_data.working_model, "decision_rule_eqn_" + str(i)) + + # keeping track of degree of monomial in which each + # DR coefficient participates will be useful for later + dr_var_to_exponent_map = ComponentMap() + + # set up uncertain parameter combinations for + # construction of the monomials of the DR expressions + monomial_param_combos = [] + for power in range(degree + 1): + power_combos = it.combinations_with_replacement(uncertain_params, power) + monomial_param_combos.extend(power_combos) + + # now construct DR equations and declare them on the working model + second_stage_dr_var_zip = zip( + second_stage_variables, + decision_rule_vars_list, + ) + for idx, (ss_var, indexed_dr_var) in enumerate(second_stage_dr_var_zip): + # for each DR equation, the number of coefficients should match + # the number of monomial terms exactly + if len(monomial_param_combos) != len(indexed_dr_var.index_set()): + raise ValueError( + f"Mismatch between number of DR coefficient variables " + "and number of DR monomials for equation of second-stage " + f"variable {ss_var.name!r} " + f"({len(indexed_dr_var.index_set())}!= {len(monomial_param_combos)})" ) - if len(Z) != 0: - raise RuntimeError( - "Construction of the decision rule functions did not work correctly! " - "Did not use all coefficient terms." - ) + + # construct the DR polynomial + dr_expression = 0 + for dr_var, param_combo in zip(indexed_dr_var.values(), monomial_param_combos): + dr_expression += dr_var * prod(param_combo) + + # map decision rule var to degree (exponent) of the + # associated monomial with respect to the uncertain params + dr_var_to_exponent_map[dr_var] = len(param_combo) + + # declare constraint on model + dr_eqn = Constraint(expr=dr_expression - ss_var == 0) + model_data.working_model.add_component( + f"decision_rule_eqn_{idx}", + dr_eqn, + ) + + # append to list of DR equality constraints + decision_rule_eqns.append(dr_eqn) + + # finally, add attributes to util block model_data.working_model.util.decision_rule_eqns = decision_rule_eqns + model_data.working_model.util.dr_var_to_exponent_map = dr_var_to_exponent_map def identify_objective_functions(model, objective): From 4118302a61c40734a8175e810e51c137ab8bb2df Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 16 Dec 2023 01:29:21 -0500 Subject: [PATCH 0613/1204] Refactor DR efficiency and DR polishing routines --- pyomo/contrib/pyros/master_problem_methods.py | 440 ++++++++++-------- pyomo/contrib/pyros/tests/test_grcs.py | 5 +- pyomo/contrib/pyros/util.py | 29 ++ 3 files changed, 266 insertions(+), 208 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index 628b79ef9a2..b97b218fbfa 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -36,7 +36,7 @@ from pyomo.common.modeling import unique_component_name from pyomo.common.timing import TicTocTimer -from pyomo.contrib.pyros.util import TIC_TOC_SOLVE_TIME_ATTR +from pyomo.contrib.pyros.util import TIC_TOC_SOLVE_TIME_ATTR, enforce_dr_degree def initial_construct_master(model_data): @@ -285,11 +285,10 @@ def solve_master_feasibility_problem(model_data, config): return results -def minimize_dr_vars(model_data, config): +def construct_dr_polishing_problem(model_data, config): """ - Polish the PyROS decision rule determined for the most - recently solved master problem by minimizing the collective - L1 norm of the vector of all decision rule variables. + Construct DR polishing problem from most recently added + master problem. Parameters ---------- @@ -300,174 +299,171 @@ def minimize_dr_vars(model_data, config): Returns ------- - results : SolverResults - Subordinate solver results for the polishing problem. - polishing_successful : bool - True if polishing model was solved to acceptable level, - False otherwise. + polishing_model : ConcreteModel + Polishing model. + + Note + ---- + Polishing problem is to minimize the L1-norm of the vector of + all decision rule polynomial terms, subject to the original + master problem constraints, with all first-stage variables + (including epigraph) fixed. Optimality of the polished + DR with respect to the master objective is also enforced. """ - # config.progress_logger.info("Executing decision rule variable polishing solve.") - model = model_data.master_model - polishing_model = model.clone() + # clone master problem + master_model = model_data.master_model + polishing_model = master_model.clone() + nominal_polishing_block = polishing_model.scenarios[0, 0] + + # fix first-stage variables (including epigraph, where applicable) + decision_rule_var_set = ComponentSet( + var + for indexed_dr_var in nominal_polishing_block.util.decision_rule_vars + for var in indexed_dr_var.values() + ) + first_stage_vars = nominal_polishing_block.util.first_stage_variables + for var in first_stage_vars: + if var not in decision_rule_var_set: + var.fix() - first_stage_variables = polishing_model.scenarios[0, 0].util.first_stage_variables - decision_rule_vars = polishing_model.scenarios[0, 0].util.decision_rule_vars + # ensure master optimality constraint enforced + if config.objective_focus == ObjectiveType.worst_case: + polishing_model.zeta.fix() + else: + optimal_master_obj_value = value(polishing_model.obj) + polishing_model.nominal_optimality_con = Constraint( + expr=( + nominal_polishing_block.first_stage_objective + + nominal_polishing_block.second_stage_objective + <= optimal_master_obj_value + ), + ) + # deactivate master problem objective polishing_model.obj.deactivate() - index_set = decision_rule_vars[0].index_set() - polishing_model.tau_vars = [] - # ========== - for idx in range(len(decision_rule_vars)): - polishing_model.scenarios[0, 0].add_component( - "polishing_var_" + str(idx), - Var(index_set, initialize=1e6, domain=NonNegativeReals), + + decision_rule_vars = nominal_polishing_block.util.decision_rule_vars + nominal_polishing_block.util.polishing_vars = polishing_vars = [] + for idx, indexed_dr_var in enumerate(decision_rule_vars): + # declare auxiliary 'polishing' variables. + # these are meant to represent the absolute values + # of the terms of DR polynomial + indexed_polishing_var = Var( + list(indexed_dr_var.keys()), + domain=NonNegativeReals, ) - polishing_model.tau_vars.append( - getattr(polishing_model.scenarios[0, 0], "polishing_var_" + str(idx)) + nominal_polishing_block.add_component( + unique_component_name( + nominal_polishing_block, + f"dr_polishing_var_{idx}", + ), + indexed_polishing_var, + ) + polishing_vars.append(indexed_polishing_var) + + dr_eq_var_zip = zip( + nominal_polishing_block.util.decision_rule_eqns, + polishing_vars, + nominal_polishing_block.util.second_stage_variables, + ) + nominal_polishing_block.util.polishing_abs_val_lb_cons = all_lb_cons = [] + nominal_polishing_block.util.polishing_abs_val_ub_cons = all_ub_cons = [] + for idx, (dr_eq, indexed_polishing_var, ss_var) in enumerate(dr_eq_var_zip): + # set up absolute value constraint components + polishing_absolute_value_lb_cons = Constraint( + indexed_polishing_var.index_set(), ) - # ========== - this_iter = polishing_model.scenarios[max(polishing_model.scenarios.keys())[0], 0] - nom_block = polishing_model.scenarios[0, 0] - if config.objective_focus == ObjectiveType.nominal: - obj_val = value( - this_iter.second_stage_objective + this_iter.first_stage_objective + polishing_absolute_value_ub_cons = Constraint( + indexed_polishing_var.index_set(), ) - polishing_model.scenarios[0, 0].polishing_constraint = Constraint( - expr=obj_val - >= nom_block.second_stage_objective + nom_block.first_stage_objective + + # add constraints to polishing model + nominal_polishing_block.add_component( + unique_component_name( + polishing_model, + f"polishing_abs_val_lb_con_{idx}", + ), + polishing_absolute_value_lb_cons, ) - elif config.objective_focus == ObjectiveType.worst_case: - polishing_model.zeta.fix() # Searching equivalent optimal solutions given optimal zeta - - # === Make absolute value constraints on polishing_vars - polishing_model.scenarios[ - 0, 0 - ].util.absolute_var_constraints = cons = ConstraintList() - uncertain_params = nom_block.util.uncertain_params - if config.decision_rule_order == 1: - for i, tau in enumerate(polishing_model.tau_vars): - for j in range(len(this_iter.util.decision_rule_vars[i])): - if j == 0: - cons.add(-tau[j] <= this_iter.util.decision_rule_vars[i][j]) - cons.add(this_iter.util.decision_rule_vars[i][j] <= tau[j]) - else: - cons.add( - -tau[j] - <= this_iter.util.decision_rule_vars[i][j] - * uncertain_params[j - 1] - ) - cons.add( - this_iter.util.decision_rule_vars[i][j] - * uncertain_params[j - 1] - <= tau[j] - ) - elif config.decision_rule_order == 2: - l = list(range(len(uncertain_params))) - index_pairs = list(it.combinations(l, 2)) - for i, tau in enumerate(polishing_model.tau_vars): - Z = this_iter.util.decision_rule_vars[i] - indices = list(k for k in range(len(Z))) - for r in indices: - if r == 0: - cons.add(-tau[r] <= Z[r]) - cons.add(Z[r] <= tau[r]) - elif r <= len(uncertain_params) and r > 0: - cons.add(-tau[r] <= Z[r] * uncertain_params[r - 1]) - cons.add(Z[r] * uncertain_params[r - 1] <= tau[r]) - elif r <= len(indices) - len(uncertain_params) - 1 and r > len( - uncertain_params - ): - cons.add( - -tau[r] - <= Z[r] - * uncertain_params[ - index_pairs[r - len(uncertain_params) - 1][0] - ] - * uncertain_params[ - index_pairs[r - len(uncertain_params) - 1][1] - ] - ) - cons.add( - Z[r] - * uncertain_params[ - index_pairs[r - len(uncertain_params) - 1][0] - ] - * uncertain_params[ - index_pairs[r - len(uncertain_params) - 1][1] - ] - <= tau[r] - ) - elif r > len(indices) - len(uncertain_params) - 1: - cons.add( - -tau[r] - <= Z[r] - * uncertain_params[ - r - len(index_pairs) - len(uncertain_params) - 1 - ] - ** 2 - ) - cons.add( - Z[r] - * uncertain_params[ - r - len(index_pairs) - len(uncertain_params) - 1 - ] - ** 2 - <= tau[r] - ) - else: - raise NotImplementedError( - "Decision rule variable polishing has not been generalized to decision_rule_order " - + str(config.decision_rule_order) - + "." + nominal_polishing_block.add_component( + unique_component_name( + polishing_model, + f"polishing_abs_val_ub_con_{idx}", + ), + polishing_absolute_value_ub_cons, ) - polishing_model.scenarios[0, 0].polishing_obj = Objective( + # update list of absolute value cons + all_lb_cons.append(polishing_absolute_value_lb_cons) + all_ub_cons.append(polishing_absolute_value_ub_cons) + + # get monomials; ensure second-stage variable term excluded + dr_expr_terms = dr_eq.body.args[:-1] + + for dr_eq_term in dr_expr_terms: + dr_var_in_term = dr_eq_term.args[-1] + dr_var_in_term_idx = dr_var_in_term.index() + + # get corresponding polishing variable + polishing_var = indexed_polishing_var[dr_var_in_term_idx] + + # add polishing constraints + polishing_absolute_value_lb_cons[dr_var_in_term_idx] = ( + -polishing_var - dr_eq_term <= 0 + ) + polishing_absolute_value_ub_cons[dr_var_in_term_idx] = ( + dr_eq_term - polishing_var <= 0 + ) + + # if DR var is fixed, then fix corresponding polishing + # variable, and deactivate the absolute value constraints + if dr_var_in_term.fixed: + polishing_var.fix() + polishing_absolute_value_lb_cons[dr_var_in_term_idx].deactivate() + polishing_absolute_value_ub_cons[dr_var_in_term_idx].deactivate() + + # initialize polishing variable to absolute value of + # the DR term. polishing constraints should now be + # satisfied (to equality) at the initial point + polishing_var.set_value(abs(value(dr_eq_term))) + + # polishing problem objective is taken to be 1-norm + # of DR monomials, or equivalently, sum of the polishing + # variables. + polishing_model.polishing_obj = Objective( expr=sum( - sum(tau[j] for j in tau.index_set()) for tau in polishing_model.tau_vars + sum(polishing_var.values()) + for polishing_var in polishing_vars ) ) - # === Fix design - for d in first_stage_variables: - d.fix() - - # === Unfix DR vars - num_dr_vars = len( - model.scenarios[0, 0].util.decision_rule_vars[0] - ) # there is at least one dr var - num_uncertain_params = len(config.uncertain_params) - - if model.const_efficiency_applied: - for d in decision_rule_vars: - for i in range(1, num_dr_vars): - d[i].fix(0) - d[0].unfix() - elif model.linear_efficiency_applied: - for d in decision_rule_vars: - d.unfix() - for i in range(num_uncertain_params + 1, num_dr_vars): - d[i].fix(0) - else: - for d in decision_rule_vars: - d.unfix() - - # === Unfix all control var values - for block in polishing_model.scenarios.values(): - for c in block.util.second_stage_variables: - c.unfix() - if model.const_efficiency_applied: - for d in block.util.decision_rule_vars: - for i in range(1, num_dr_vars): - d[i].fix(0) - d[0].unfix() - elif model.linear_efficiency_applied: - for d in block.util.decision_rule_vars: - d.unfix() - for i in range(num_uncertain_params + 1, num_dr_vars): - d[i].fix(0) - else: - for d in block.util.decision_rule_vars: - d.unfix() + return polishing_model + + +def minimize_dr_vars(model_data, config): + """ + Polish decision rule of most recent master problem solution. + + Parameters + ---------- + model_data : MasterProblemData + Master problem data. + config : ConfigDict + PyROS solver settings. + + Returns + ------- + results : SolverResults + Subordinate solver results for the polishing problem. + polishing_successful : bool + True if polishing model was solved to acceptable level, + False otherwise. + """ + # create polishing NLP + polishing_model = construct_dr_polishing_problem( + model_data=model_data, + config=config, + ) if config.solve_master_globally: solver = config.global_solver @@ -476,11 +472,10 @@ def minimize_dr_vars(model_data, config): config.progress_logger.debug("Solving DR polishing problem") - # NOTE: this objective evalaution may not be accurate, due - # to the current initialization scheme for the auxiliary - # variables. new initialization will be implemented in the - # near future. - polishing_obj = polishing_model.scenarios[0, 0].polishing_obj + # polishing objective should be consistent with value of sum + # of absolute values of polynomial DR terms provided + # auxiliary variables initialized correctly + polishing_obj = polishing_model.polishing_obj config.progress_logger.debug(f" Initial DR norm: {value(polishing_obj)}") # === Solve the polishing model @@ -511,14 +506,14 @@ def minimize_dr_vars(model_data, config): # purposes config.progress_logger.debug(" Done solving DR polishing problem") config.progress_logger.debug( - f" Solve time: {getattr(results.solver, TIC_TOC_SOLVE_TIME_ATTR)} s" + f" Termination condition: {results.solver.termination_condition} " ) config.progress_logger.debug( - f" Termination status: {results.solver.termination_condition} " + f" Solve time: {getattr(results.solver, TIC_TOC_SOLVE_TIME_ATTR)} s" ) # === Process solution by termination condition - acceptable = {tc.globallyOptimal, tc.optimal, tc.locallyOptimal, tc.feasible} + acceptable = {tc.globallyOptimal, tc.optimal, tc.locallyOptimal} if results.solver.termination_condition not in acceptable: # continue with "unpolished" master model solution config.progress_logger.warning( @@ -534,6 +529,7 @@ def minimize_dr_vars(model_data, config): # update master model second-stage, state, and decision rule # variables to polishing model solution polishing_model.solutions.load_from(results) + for idx, blk in model_data.master_model.scenarios.items(): ssv_zip = zip( blk.util.second_stage_variables, @@ -557,29 +553,32 @@ def minimize_dr_vars(model_data, config): mvar.set_value(value(pvar), skip_validation=True) config.progress_logger.debug(f" Optimized DR norm: {value(polishing_obj)}") - config.progress_logger.debug(" Polished Master objective:") + config.progress_logger.debug(" Polished master objective:") - # print master solution + # print breakdown of objective value of polished master solution if config.objective_focus == ObjectiveType.worst_case: - worst_blk_idx = max( + eval_obj_blk_idx = max( model_data.master_model.scenarios.keys(), key=lambda idx: value( model_data.master_model.scenarios[idx].second_stage_objective ), ) else: - worst_blk_idx = (0, 0) + eval_obj_blk_idx = (0, 0) # debugging: summarize objective breakdown - worst_master_blk = model_data.master_model.scenarios[worst_blk_idx] + eval_obj_blk = model_data.master_model.scenarios[eval_obj_blk_idx] config.progress_logger.debug( - " First-stage objective: " f"{value(worst_master_blk.first_stage_objective)}" + " First-stage objective: " + f"{value(eval_obj_blk.first_stage_objective)}" ) config.progress_logger.debug( - " Second-stage objective: " f"{value(worst_master_blk.second_stage_objective)}" + " Second-stage objective: " + f"{value(eval_obj_blk.second_stage_objective)}" ) polished_master_obj = value( - worst_master_blk.first_stage_objective + worst_master_blk.second_stage_objective + eval_obj_blk.first_stage_objective + + eval_obj_blk.second_stage_objective ) config.progress_logger.debug(f" Objective: {polished_master_obj}") @@ -629,37 +628,64 @@ def add_scenario_to_master(model_data, violations): return -def higher_order_decision_rule_efficiency(config, model_data): - # === Efficiencies for decision rules - # if iteration <= |q| then all d^n where n > 1 are fixed to 0 - # if iteration == 0, all d^n, n > 0 are fixed to 0 - # These efficiencies should be carried through as d* to polishing - nlp_model = model_data.master_model - if config.decision_rule_order != None and len(config.second_stage_variables) > 0: - # Ensure all are unfixed unless next conditions are met... - for dr_var in nlp_model.scenarios[0, 0].util.decision_rule_vars: - dr_var.unfix() - num_dr_vars = len( - nlp_model.scenarios[0, 0].util.decision_rule_vars[0] - ) # there is at least one dr var - num_uncertain_params = len(config.uncertain_params) - nlp_model.const_efficiency_applied = False - nlp_model.linear_efficiency_applied = False - if model_data.iteration == 0: - nlp_model.const_efficiency_applied = True - for dr_var in nlp_model.scenarios[0, 0].util.decision_rule_vars: - for i in range(1, num_dr_vars): - dr_var[i].fix(0) - elif ( - model_data.iteration <= num_uncertain_params - and config.decision_rule_order > 1 - ): - # Only applied in DR order > 1 case - for dr_var in nlp_model.scenarios[0, 0].util.decision_rule_vars: - for i in range(num_uncertain_params + 1, num_dr_vars): - nlp_model.linear_efficiency_applied = True - dr_var[i].fix(0) - return +def get_master_dr_degree(model_data, config): + """ + Determine DR polynomial degree to enforce based on + the iteration number. + + Currently, the degree is set to: + + - 0 if iteration number is 0 + - min(1, config.decision_rule_order) if iteration number + otherwise does not exceed number of uncertain parameters + - min(2, config.decision_rule_order) otherwise. + + Parameters + ---------- + model_data : MasterProblemData + Master problem data. + config : ConfigDict + PyROS solver options. + + Returns + ------- + int + DR order, or polynomial degree, to enforce. + """ + if model_data.iteration == 0: + return 0 + elif model_data.iteration <= len(config.uncertain_params): + return min(1, config.decision_rule_order) + else: + return min(2, config.decision_rule_order) + + +def higher_order_decision_rule_efficiency(model_data, config): + """ + Enforce DR coefficient variable efficiencies for + master problem-like formulation. + + Parameters + ---------- + model_data : MasterProblemData + Master problem data. + config : ConfigDict + PyROS solver options. + + Note + ---- + The DR coefficient variable efficiencies consist of + setting the degree of the DR polynomial expressions + by fixing the appropriate variables to 0. The degree + to be set depends on the iteration number; + see ``get_master_dr_degree``. + """ + order_to_enforce = get_master_dr_degree(model_data, config) + enforce_dr_degree( + blk=model_data.master_model.scenarios[0, 0], + config=config, + degree=order_to_enforce, + ) def solver_call_master(model_data, config, solver, solve_data): @@ -695,7 +721,7 @@ def solver_call_master(model_data, config, solver, solve_data): else: solvers = [solver] + config.backup_local_solvers - higher_order_decision_rule_efficiency(config, model_data) + higher_order_decision_rule_efficiency(model_data=model_data, config=config) solve_mode = "global" if config.solve_master_globally else "local" config.progress_logger.debug("Solving master problem") diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 0365cde1a48..444b641dcb6 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -5,7 +5,7 @@ import pyomo.common.unittest as unittest from pyomo.common.log import LoggingIntercept -from pyomo.common.collections import ComponentSet +from pyomo.common.collections import ComponentSet, ComponentMap from pyomo.common.config import ConfigBlock, ConfigValue from pyomo.core.base.set_types import NonNegativeIntegers from pyomo.core.expr import identify_variables, identify_mutable_parameters @@ -3554,6 +3554,9 @@ def test_solve_master(self): master_data.master_model.scenarios[0, 0].second_stage_objective = Expression( expr=master_data.master_model.scenarios[0, 0].x ) + master_data.master_model.scenarios[0, 0].util.dr_var_to_exponent_map = ( + ComponentMap() + ) master_data.iteration = 0 master_data.timing = TimingData() diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index 263363a4200..4f17fcd3585 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -1433,6 +1433,35 @@ def add_decision_rule_constraints(model_data, config): model_data.working_model.util.dr_var_to_exponent_map = dr_var_to_exponent_map +def enforce_dr_degree(blk, config, degree): + """ + Make decision rule polynomials of a given degree + by fixing value of the appropriate subset of the decision + rule coefficients to 0. + + Parameters + ---------- + blk : ScalarBlock + Working model, or master problem block. + config : ConfigDict + PyROS solver options. + degree : int + Degree of the DR polynomials that is to be enforced. + """ + second_stage_vars = blk.util.second_stage_variables + indexed_dr_vars = blk.util.decision_rule_vars + dr_var_to_exponent_map = blk.util.dr_var_to_exponent_map + + for ss_var, indexed_dr_var in zip(second_stage_vars, indexed_dr_vars): + for dr_var in indexed_dr_var.values(): + dr_var_degree = dr_var_to_exponent_map[dr_var] + + if dr_var_degree > degree: + dr_var.fix(0) + else: + dr_var.unfix() + + def identify_objective_functions(model, objective): """ Identify the first and second-stage portions of an Objective From 42f929b115b9fccf36ab51c1e2e0fccc2a5221ff Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 16 Dec 2023 01:35:14 -0500 Subject: [PATCH 0614/1204] Fix logging of master problem objective value --- pyomo/contrib/pyros/master_problem_methods.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index b97b218fbfa..7fd87dfd955 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -816,17 +816,28 @@ def solver_call_master(model_data, config, solver, solve_data): ) # debugging: log breakdown of master objective + if config.objective_focus == ObjectiveType.worst_case: + eval_obj_blk_idx = max( + nlp_model.scenarios.keys(), + key=lambda idx: value( + nlp_model.scenarios[idx].second_stage_objective + ), + ) + else: + eval_obj_blk_idx = (0, 0) + + eval_obj_blk = nlp_model.scenarios[eval_obj_blk_idx] config.progress_logger.debug(" Optimized master objective breakdown:") config.progress_logger.debug( - f" First-stage objective: {master_soln.first_stage_objective}" + f" First-stage objective: {value(eval_obj_blk.first_stage_objective)}" ) config.progress_logger.debug( - f" Second-stage objective: {master_soln.second_stage_objective}" + f" Second-stage objective: {value(eval_obj_blk.second_stage_objective)}" ) master_obj = ( - master_soln.first_stage_objective + master_soln.second_stage_objective + eval_obj_blk.first_stage_objective + eval_obj_blk.second_stage_objective ) - config.progress_logger.debug(f" Objective: {master_obj}") + config.progress_logger.debug(f" Objective: {value(master_obj)}") config.progress_logger.debug( f" Termination condition: {results.solver.termination_condition}" ) From 4c35d803c6a9faa0d7e075470f4367700eb72667 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 16 Dec 2023 19:09:54 -0500 Subject: [PATCH 0615/1204] Apply black --- pyomo/contrib/pyros/master_problem_methods.py | 61 +++++++------------ .../contrib/pyros/pyros_algorithm_methods.py | 4 +- .../pyros/separation_problem_methods.py | 17 ++---- pyomo/contrib/pyros/tests/test_grcs.py | 12 ++-- pyomo/contrib/pyros/util.py | 23 ++----- 5 files changed, 39 insertions(+), 78 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index 7fd87dfd955..5477fcc5048 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -134,13 +134,15 @@ def construct_master_feasibility_problem(model_data, config): # solution(s) targets = [] for blk in model.scenarios[iteration, :]: - targets.extend([ - con - for con in blk.component_data_objects( - Constraint, active=True, descend_into=True - ) - if not con.equality - ]) + targets.extend( + [ + con + for con in blk.component_data_objects( + Constraint, active=True, descend_into=True + ) + if not con.equality + ] + ) # retain original constraint expressions # (for slack initialization and scaling) @@ -336,7 +338,7 @@ def construct_dr_polishing_problem(model_data, config): nominal_polishing_block.first_stage_objective + nominal_polishing_block.second_stage_objective <= optimal_master_obj_value - ), + ) ) # deactivate master problem objective @@ -349,14 +351,10 @@ def construct_dr_polishing_problem(model_data, config): # these are meant to represent the absolute values # of the terms of DR polynomial indexed_polishing_var = Var( - list(indexed_dr_var.keys()), - domain=NonNegativeReals, + list(indexed_dr_var.keys()), domain=NonNegativeReals ) nominal_polishing_block.add_component( - unique_component_name( - nominal_polishing_block, - f"dr_polishing_var_{idx}", - ), + unique_component_name(nominal_polishing_block, f"dr_polishing_var_{idx}"), indexed_polishing_var, ) polishing_vars.append(indexed_polishing_var) @@ -370,26 +368,16 @@ def construct_dr_polishing_problem(model_data, config): nominal_polishing_block.util.polishing_abs_val_ub_cons = all_ub_cons = [] for idx, (dr_eq, indexed_polishing_var, ss_var) in enumerate(dr_eq_var_zip): # set up absolute value constraint components - polishing_absolute_value_lb_cons = Constraint( - indexed_polishing_var.index_set(), - ) - polishing_absolute_value_ub_cons = Constraint( - indexed_polishing_var.index_set(), - ) + polishing_absolute_value_lb_cons = Constraint(indexed_polishing_var.index_set()) + polishing_absolute_value_ub_cons = Constraint(indexed_polishing_var.index_set()) # add constraints to polishing model nominal_polishing_block.add_component( - unique_component_name( - polishing_model, - f"polishing_abs_val_lb_con_{idx}", - ), + unique_component_name(polishing_model, f"polishing_abs_val_lb_con_{idx}"), polishing_absolute_value_lb_cons, ) nominal_polishing_block.add_component( - unique_component_name( - polishing_model, - f"polishing_abs_val_ub_con_{idx}", - ), + unique_component_name(polishing_model, f"polishing_abs_val_ub_con_{idx}"), polishing_absolute_value_ub_cons, ) @@ -431,10 +419,7 @@ def construct_dr_polishing_problem(model_data, config): # of DR monomials, or equivalently, sum of the polishing # variables. polishing_model.polishing_obj = Objective( - expr=sum( - sum(polishing_var.values()) - for polishing_var in polishing_vars - ) + expr=sum(sum(polishing_var.values()) for polishing_var in polishing_vars) ) return polishing_model @@ -461,8 +446,7 @@ def minimize_dr_vars(model_data, config): """ # create polishing NLP polishing_model = construct_dr_polishing_problem( - model_data=model_data, - config=config, + model_data=model_data, config=config ) if config.solve_master_globally: @@ -569,16 +553,13 @@ def minimize_dr_vars(model_data, config): # debugging: summarize objective breakdown eval_obj_blk = model_data.master_model.scenarios[eval_obj_blk_idx] config.progress_logger.debug( - " First-stage objective: " - f"{value(eval_obj_blk.first_stage_objective)}" + " First-stage objective: " f"{value(eval_obj_blk.first_stage_objective)}" ) config.progress_logger.debug( - " Second-stage objective: " - f"{value(eval_obj_blk.second_stage_objective)}" + " Second-stage objective: " f"{value(eval_obj_blk.second_stage_objective)}" ) polished_master_obj = value( - eval_obj_blk.first_stage_objective - + eval_obj_blk.second_stage_objective + eval_obj_blk.first_stage_objective + eval_obj_blk.second_stage_objective ) config.progress_logger.debug(f" Objective: {polished_master_obj}") diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index 9f78a4c18ec..38f675e64a5 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -891,7 +891,9 @@ def ROSolver_iterative_solve(model_data, config): # solution chosen by heuristic. consequently, # equality constraints should all be satisfied (up to tolerances). for var, val in separation_results.violating_separation_variable_values.items(): - master_var = master_data.master_model.scenarios[k + 1, 0].find_component(var) + master_var = master_data.master_model.scenarios[k + 1, 0].find_component( + var + ) master_var.set_value(val) k += 1 diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index b4bbd00259a..240291f5375 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -886,36 +886,29 @@ def initialize_separation(perf_con_to_maximize, model_data, config): This method assumes that the master model has only one block per iteration. """ + def eval_master_violation(block_idx): """ Evaluate violation of `perf_con` by variables of specified master block. """ new_con_map = ( - model_data - .separation_model - .util - .map_new_constraint_list_to_original_con + model_data.separation_model.util.map_new_constraint_list_to_original_con ) in_new_cons = perf_con_to_maximize in new_con_map if in_new_cons: sep_con = new_con_map[perf_con_to_maximize] else: sep_con = perf_con_to_maximize - master_con = ( - model_data.master_model.scenarios[block_idx, 0].find_component( - sep_con, - ) + master_con = model_data.master_model.scenarios[block_idx, 0].find_component( + sep_con ) return value(master_con) # initialize from master block with max violation of the # performance constraint of interest. This gives the best known # feasible solution (for case of non-discrete uncertainty sets). - block_num = max( - range(model_data.iteration + 1), - key=eval_master_violation, - ) + block_num = max(range(model_data.iteration + 1), key=eval_master_violation) master_blk = model_data.master_model.scenarios[block_num, 0] master_blks = list(model_data.master_model.scenarios.values()) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 444b641dcb6..b74ef1f2405 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -354,6 +354,7 @@ class testAddDecisionRuleConstraints(unittest.TestCase): to the number of control variables. These constraints should reference the uncertain parameters and unique decision rule variables per control variable. ''' + def test_num_dr_eqns_added_correct(self): """ Check that number of DR equality constraints added @@ -379,10 +380,7 @@ def test_num_dr_eqns_added_correct(self): # === Decision rule vars have been added m.decision_rule_var_0 = Var([0], initialize=0) m.decision_rule_var_1 = Var([0], initialize=0) - m.util.decision_rule_vars = [ - m.decision_rule_var_0, - m.decision_rule_var_1, - ] + m.util.decision_rule_vars = [m.decision_rule_var_0, m.decision_rule_var_1] # set up simple config-like object config = Bunch() @@ -3554,9 +3552,9 @@ def test_solve_master(self): master_data.master_model.scenarios[0, 0].second_stage_objective = Expression( expr=master_data.master_model.scenarios[0, 0].x ) - master_data.master_model.scenarios[0, 0].util.dr_var_to_exponent_map = ( - ComponentMap() - ) + master_data.master_model.scenarios[ + 0, 0 + ].util.dr_var_to_exponent_map = ComponentMap() master_data.iteration = 0 master_data.timing = TimingData() diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index 4f17fcd3585..a20b541b633 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -1305,24 +1305,17 @@ def add_decision_rule_variables(model_data, config): degree = config.decision_rule_order num_uncertain_params = len(model_data.working_model.util.uncertain_params) num_dr_vars = comb( - N=num_uncertain_params + degree, - k=degree, - exact=True, - repetition=False, + N=num_uncertain_params + degree, k=degree, exact=True, repetition=False ) for idx, ss_var in enumerate(second_stage_variables): # declare DR coefficients for current second-stage # variable indexed_dr_var = Var( - range(num_dr_vars), - initialize=0, - bounds=(None, None), - domain=Reals, + range(num_dr_vars), initialize=0, bounds=(None, None), domain=Reals ) model_data.working_model.add_component( - f"decision_rule_var_{idx}", - indexed_dr_var, + f"decision_rule_var_{idx}", indexed_dr_var ) indexed_dr_var[0].set_value(value(ss_var, exception=False)) @@ -1394,10 +1387,7 @@ def add_decision_rule_constraints(model_data, config): monomial_param_combos.extend(power_combos) # now construct DR equations and declare them on the working model - second_stage_dr_var_zip = zip( - second_stage_variables, - decision_rule_vars_list, - ) + second_stage_dr_var_zip = zip(second_stage_variables, decision_rule_vars_list) for idx, (ss_var, indexed_dr_var) in enumerate(second_stage_dr_var_zip): # for each DR equation, the number of coefficients should match # the number of monomial terms exactly @@ -1420,10 +1410,7 @@ def add_decision_rule_constraints(model_data, config): # declare constraint on model dr_eqn = Constraint(expr=dr_expression - ss_var == 0) - model_data.working_model.add_component( - f"decision_rule_eqn_{idx}", - dr_eqn, - ) + model_data.working_model.add_component(f"decision_rule_eqn_{idx}", dr_eqn) # append to list of DR equality constraints decision_rule_eqns.append(dr_eqn) From deea1074e63a63e3f34b66a62096d279cc90ddec Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 17 Dec 2023 00:21:31 -0500 Subject: [PATCH 0616/1204] Update DR monomial mismatch exception message --- pyomo/contrib/pyros/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index a20b541b633..d060acb4c63 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -1394,8 +1394,8 @@ def add_decision_rule_constraints(model_data, config): if len(monomial_param_combos) != len(indexed_dr_var.index_set()): raise ValueError( f"Mismatch between number of DR coefficient variables " - "and number of DR monomials for equation of second-stage " - f"variable {ss_var.name!r} " + f"and number of DR monomials for DR equation index {idx}, " + f"corresponding to second-stage variable {ss_var.name!r}. " f"({len(indexed_dr_var.index_set())}!= {len(monomial_param_combos)})" ) From 3062b08482ddb65bcb6096cb543b055d71dc63a3 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 17 Dec 2023 01:28:03 -0500 Subject: [PATCH 0617/1204] Update version number, changelog --- pyomo/contrib/pyros/CHANGELOG.txt | 12 ++++++++++++ pyomo/contrib/pyros/pyros.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/CHANGELOG.txt b/pyomo/contrib/pyros/CHANGELOG.txt index 7977c37fb95..2a0b782a9b4 100644 --- a/pyomo/contrib/pyros/CHANGELOG.txt +++ b/pyomo/contrib/pyros/CHANGELOG.txt @@ -2,6 +2,18 @@ PyROS CHANGELOG =============== +------------------------------------------------------------------------------- +PyROS 1.2.9 12 Oct 2023 +------------------------------------------------------------------------------- +- Fix DR polishing optimality constraint for case of nominal objective focus +- Use previous separation solution to initialize second-stage and state + variables of new master block; simplify the master feasibility problem +- Use best known solution from master to initialize separation problems + per performance constraint +- Refactor DR variable and constraint declaration routines. +- Refactor DR polishing routine; initialize auxiliary variables + to values they are meant to represent + ------------------------------------------------------------------------------- PyROS 1.2.8 12 Oct 2023 ------------------------------------------------------------------------------- diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index b5d77d74a6e..829184fc70c 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -49,7 +49,7 @@ from datetime import datetime -__version__ = "1.2.8" +__version__ = "1.2.9" default_pyros_solver_logger = setup_pyros_logger() From 0723b66f043dc8a1c563c48c3c3701e039f38491 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 17 Dec 2023 01:29:50 -0500 Subject: [PATCH 0618/1204] Update online docs log output example --- doc/OnlineDocs/contributed_packages/pyros.rst | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/doc/OnlineDocs/contributed_packages/pyros.rst b/doc/OnlineDocs/contributed_packages/pyros.rst index 133258fb9b8..9538c03329e 100644 --- a/doc/OnlineDocs/contributed_packages/pyros.rst +++ b/doc/OnlineDocs/contributed_packages/pyros.rst @@ -854,10 +854,10 @@ Observe that the log contains the following information: :linenos: ============================================================================== - PyROS: The Pyomo Robust Optimization Solver, v1.2.8. + PyROS: The Pyomo Robust Optimization Solver, v1.2.9. Pyomo version: 6.7.0 Commit hash: unknown - Invoked at UTC 2023-11-03T04:27:42.954101 + Invoked at UTC 2023-12-16T00:00:00.000000 Developed by: Natalie M. Isenberg (1), Jason A. F. Sherman (1), John D. Siirola (2), Chrysanthos E. Gounaris (1) @@ -914,14 +914,11 @@ Observe that the log contains the following information: ------------------------------------------------------------------------------ Itn Objective 1-Stg Shift 2-Stg Shift #CViol Max Viol Wall Time (s) ------------------------------------------------------------------------------ - 0 3.5838e+07 - - 5 1.8832e+04 1.555 - 1 3.5838e+07 2.2045e-12 2.7854e-12 7 3.7766e+04 2.991 - 2 3.6116e+07 1.2324e-01 3.9256e-01 8 1.3466e+06 4.881 - 3 3.6285e+07 5.1968e-01 4.5604e-01 6 4.8734e+03 6.908 - 4 3.6285e+07 2.6524e-13 1.3909e-13 1 3.5036e+01 9.176 - 5 3.6285e+07 2.0167e-13 5.4357e-02 6 2.9103e+00 11.457 - 6 3.6285e+07 2.2335e-12 1.2150e-01 5 4.1726e-01 13.868 - 7 3.6285e+07 2.2340e-12 1.1422e-01 0 9.3279e-10g 20.917 + 0 3.5838e+07 - - 5 1.8832e+04 1.741 + 1 3.5838e+07 3.5184e-15 3.9404e-15 10 4.2516e+06 3.766 + 2 3.5993e+07 1.8105e-01 7.1406e-01 13 5.2004e+06 6.288 + 3 3.6285e+07 5.1968e-01 7.7753e-01 4 1.7892e+04 8.247 + 4 3.6285e+07 9.1166e-13 1.9702e-15 0 7.1157e-10g 11.456 ------------------------------------------------------------------------------ Robust optimal solution identified. ------------------------------------------------------------------------------ @@ -929,22 +926,22 @@ Observe that the log contains the following information: Identifier ncalls cumtime percall % ----------------------------------------------------------- - main 1 20.918 20.918 100.0 + main 1 11.457 11.457 100.0 ------------------------------------------------------ - dr_polishing 7 1.472 0.210 7.0 - global_separation 47 1.239 0.026 5.9 - local_separation 376 9.244 0.025 44.2 - master 8 5.259 0.657 25.1 - master_feasibility 7 0.486 0.069 2.3 - preprocessing 1 0.403 0.403 1.9 - other n/a 2.815 n/a 13.5 + dr_polishing 4 0.682 0.171 6.0 + global_separation 47 1.109 0.024 9.7 + local_separation 235 5.810 0.025 50.7 + master 5 1.353 0.271 11.8 + master_feasibility 4 0.247 0.062 2.2 + preprocessing 1 0.429 0.429 3.7 + other n/a 1.828 n/a 16.0 ====================================================== =========================================================== ------------------------------------------------------------------------------ Termination stats: - Iterations : 8 - Solve time (wall s) : 20.918 + Iterations : 5 + Solve time (wall s) : 11.457 Final objective value : 3.6285e+07 Termination condition : pyrosTerminationCondition.robust_optimal ------------------------------------------------------------------------------ From a839e4e37dca30ca5864dba8b574ce4b465cefbe Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 17 Dec 2023 01:58:37 -0500 Subject: [PATCH 0619/1204] Update docs usage example results table --- doc/OnlineDocs/contributed_packages/pyros.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/OnlineDocs/contributed_packages/pyros.rst b/doc/OnlineDocs/contributed_packages/pyros.rst index 9538c03329e..3ff1bfccf0e 100644 --- a/doc/OnlineDocs/contributed_packages/pyros.rst +++ b/doc/OnlineDocs/contributed_packages/pyros.rst @@ -723,11 +723,11 @@ For this example, we obtain the following price of robustness results: +==========================================+==============================+=============================+ | 0.00 | 35,837,659.18 | 0.00 % | +------------------------------------------+------------------------------+-----------------------------+ - | 0.10 | 36,135,191.59 | 0.82 % | + | 0.10 | 36,135,182.66 | 0.83 % | +------------------------------------------+------------------------------+-----------------------------+ - | 0.20 | 36,437,979.81 | 1.64 % | + | 0.20 | 36,437,979.81 | 1.68 % | +------------------------------------------+------------------------------+-----------------------------+ - | 0.30 | 43,478,190.92 | 17.57 % | + | 0.30 | 43,478,190.91 | 21.32 % | +------------------------------------------+------------------------------+-----------------------------+ | 0.40 | ``robust_infeasible`` | :math:`\text{-----}` | +------------------------------------------+------------------------------+-----------------------------+ From 3943607a27fc3e9a6d02eb615af6c621c75f1519 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 17 Dec 2023 17:12:17 -0500 Subject: [PATCH 0620/1204] Update DR test class docstrings --- pyomo/contrib/pyros/tests/test_grcs.py | 27 +++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index b74ef1f2405..7ee1bb0c17c 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -237,11 +237,14 @@ def test_cloning_positive_case(self): class testAddDecisionRuleVars(unittest.TestCase): - ''' - Testing the method to add decision rule variables to a Pyomo model. This function should add decision rule - variables to the list of first_stage_variables in a model object. The number of decision rule variables added - depends on the number of control variables in the model and the number of uncertain parameters in the model. - ''' + """ + Test method for adding decision rule variables to working model. + The number of decision rule variables per control variable + should depend on: + + - the number of uncertain parameters in the model + - the decision rule order specified by the user. + """ def make_simple_test_model(self): """ @@ -348,12 +351,14 @@ def test_correct_num_dr_vars_quadratic(self): class testAddDecisionRuleConstraints(unittest.TestCase): - ''' - Testing the addition of decision rule constraints functionally relating second-stage (control) variables to - uncertain parameters and decision rule variables. This method should add constraints to the model object equal - to the number of control variables. These constraints should reference the uncertain parameters and unique - decision rule variables per control variable. - ''' + """ + Test method for adding decision rule equality constraints + to the working model. There should be as many decision + rule equality constraints as there are second-stage + variables, and each constraint should relate a second-stage + variable to the uncertain parameters and corresponding + decision rule variables. + """ def test_num_dr_eqns_added_correct(self): """ From a174c0d5b39afd2168a920b760237908834850ce Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 17 Dec 2023 17:13:50 -0500 Subject: [PATCH 0621/1204] Add scipy available check to DR test --- pyomo/contrib/pyros/tests/test_grcs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 7ee1bb0c17c..3657565e0ca 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -360,6 +360,7 @@ class testAddDecisionRuleConstraints(unittest.TestCase): decision rule variables. """ + @unittest.skipIf(not scipy_available, 'Scipy is not available.') def test_num_dr_eqns_added_correct(self): """ Check that number of DR equality constraints added From 32b39a6cdd34555acc4d27389440df4f17e6d29e Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 17 Dec 2023 17:31:30 -0500 Subject: [PATCH 0622/1204] Add extra step to DR variable declaration tests --- pyomo/contrib/pyros/tests/test_grcs.py | 62 ++++++++++++++++++++------ 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 3657565e0ca..b79ed64552c 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -292,6 +292,15 @@ def test_correct_num_dr_vars_static(self): ), ) + self.assertEqual( + len(ComponentSet(m.util.decision_rule_vars)), + len(m.util.second_stage_variables), + msg=( + "Number of unique indexed DR variable components should equal " + "number of second-stage variables." + ) + ) + @unittest.skipIf(not scipy_available, 'Scipy is not available.') def test_correct_num_dr_vars_affine(self): """ @@ -317,6 +326,15 @@ def test_correct_num_dr_vars_affine(self): ), ) + self.assertEqual( + len(ComponentSet(m.util.decision_rule_vars)), + len(m.util.second_stage_variables), + msg=( + "Number of unique indexed DR variable components should equal " + "number of second-stage variables." + ) + ) + @unittest.skipIf(not scipy_available, 'Scipy is not available.') def test_correct_num_dr_vars_quadratic(self): """ @@ -349,6 +367,15 @@ def test_correct_num_dr_vars_quadratic(self): ), ) + self.assertEqual( + len(ComponentSet(m.util.decision_rule_vars)), + len(m.util.second_stage_variables), + msg=( + "Number of unique indexed DR variable components should equal " + "number of second-stage variables." + ) + ) + class testAddDecisionRuleConstraints(unittest.TestCase): """ @@ -360,28 +387,35 @@ class testAddDecisionRuleConstraints(unittest.TestCase): decision rule variables. """ - @unittest.skipIf(not scipy_available, 'Scipy is not available.') - def test_num_dr_eqns_added_correct(self): + def make_simple_test_model(self): """ - Check that number of DR equality constraints added - by constraint declaration routines matches the number - of second-stage variables in the model. + Make simple model for DR constraint testing. """ - model_data = ROSolveResults() - model_data.working_model = m = ConcreteModel() + m = ConcreteModel() # uncertain parameters - m.p1 = Param(initialize=0, mutable=True) - m.p2 = Param(initialize=0, mutable=True) + m.p = Param(range(3), initialize=0, mutable=True) # second-stage variables - m.z1 = Var(initialize=0) - m.z2 = Var(initialize=0) + m.z = Var([0, 1], initialize=0) - # add util block + # util block m.util = Block() - m.util.uncertain_params = [m.p1, m.p2] - m.util.second_stage_variables = [m.z1, m.z2] + m.util.first_stage_variables = [] + m.util.second_stage_variables = list(m.z.values()) + m.util.uncertain_params = list(m.p.values()) + + return m + + @unittest.skipIf(not scipy_available, 'Scipy is not available.') + def test_num_dr_eqns_added_correct(self): + """ + Check that number of DR equality constraints added + by constraint declaration routines matches the number + of second-stage variables in the model. + """ + model_data = ROSolveResults() + model_data.working_model = m = self.make_simple_test_model() # === Decision rule vars have been added m.decision_rule_var_0 = Var([0], initialize=0) From 6c6a4d543a61653116800abb5659495224cde0cf Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 17 Dec 2023 17:36:01 -0500 Subject: [PATCH 0623/1204] Remove star import of uncertainty sets module --- pyomo/contrib/pyros/tests/test_grcs.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index b79ed64552c..63657ec1acc 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -27,8 +27,21 @@ from pyomo.contrib.pyros.util import identify_objective_functions from pyomo.common.collections import Bunch import time +import math from pyomo.contrib.pyros.util import time_code -from pyomo.contrib.pyros.uncertainty_sets import * +from pyomo.contrib.pyros.uncertainty_sets import ( + UncertaintySet, + BoxSet, + CardinalitySet, + BudgetSet, + FactorModelSet, + PolyhedralSet, + EllipsoidalSet, + AxisAlignedEllipsoidalSet, + IntersectionSet, + DiscreteScenarioSet, + Geometry, +) from pyomo.contrib.pyros.master_problem_methods import ( add_scenario_to_master, initial_construct_master, @@ -48,6 +61,11 @@ Solution, ) from pyomo.environ import ( + Reals, + Set, + Block, + ConstraintList, + ConcreteModel, Constraint, Expression, Objective, @@ -60,6 +78,8 @@ sin, sqrt, value, + maximize, + minimize, ) import logging From 559e94186d68d15a97cd8d11f967743846558f9e Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 17 Dec 2023 19:18:45 -0500 Subject: [PATCH 0624/1204] Test form of decision rule equations --- pyomo/contrib/pyros/tests/test_grcs.py | 158 ++++++++++++++++++++++++- 1 file changed, 157 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 63657ec1acc..00824578f50 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -8,7 +8,12 @@ from pyomo.common.collections import ComponentSet, ComponentMap from pyomo.common.config import ConfigBlock, ConfigValue from pyomo.core.base.set_types import NonNegativeIntegers -from pyomo.core.expr import identify_variables, identify_mutable_parameters +from pyomo.core.expr import ( + identify_variables, + identify_mutable_parameters, + MonomialTermExpression, + SumExpression, +) from pyomo.contrib.pyros.util import ( selective_clone, add_decision_rule_variables, @@ -82,6 +87,7 @@ minimize, ) import logging +from itertools import chain logger = logging.getLogger(__name__) @@ -455,6 +461,156 @@ def test_num_dr_eqns_added_correct(self): "the number of control variables in the model.", ) + @unittest.skipIf(not scipy_available, 'Scipy is not available.') + def test_dr_eqns_form_correct(self): + """ + Check that form of decision rule equality constraints + is as expected. + + Decision rule equations should be of the standard form: + (sum of DR monomial terms) - (second-stage variable) == 0 + where each monomial term should be of form: + (product of uncertain parameters) * (decision rule variable) + + This test checks that the equality constraints are of this + standard form. + """ + # set up simple model data like object + model_data = ROSolveResults() + model_data.working_model = m = self.make_simple_test_model() + + # set up simple config-like object + config = Bunch() + config.decision_rule_order = 2 + + # add DR variables and constraints + add_decision_rule_variables(model_data, config) + add_decision_rule_constraints(model_data, config) + + # DR polynomial terms and order in which they should + # appear depends on number of uncertain parameters + # and order in which the parameters are listed. + # so uncertain parameters participating in each term + # of the monomial is known, and listed out here. + dr_monomial_param_combos = [ + (1,), + (m.p[0],), + (m.p[1],), + (m.p[2],), + (m.p[0], m.p[0]), + (m.p[0], m.p[1]), + (m.p[0], m.p[2]), + (m.p[1], m.p[1]), + (m.p[1], m.p[2]), + (m.p[2], m.p[2]), + ] + + dr_zip = zip( + m.util.second_stage_variables, + m.util.decision_rule_vars, + m.util.decision_rule_eqns, + ) + for ss_var, indexed_dr_var, dr_eq in dr_zip: + dr_eq_terms = dr_eq.body.args + + # check constraint body is sum expression + self.assertTrue( + isinstance(dr_eq.body, SumExpression), + msg=( + f"Body of DR constraint {dr_eq.name!r} is not of type " + f"{SumExpression.__name__}." + ), + ) + + # ensure DR equation has correct number of (additive) terms + self.assertEqual( + len(dr_eq_terms), + len(dr_monomial_param_combos) + 1, + msg=( + "Number of additive terms in the DR expression of " + f"DR constraint with name {dr_eq.name!r} does not match " + "expected value." + ), + ) + + # check last term is negative of second-stage variable + second_stage_var_term = dr_eq_terms[-1] + last_term_is_neg_ss_var = ( + isinstance(second_stage_var_term, MonomialTermExpression) + and (second_stage_var_term.args[0] == -1) + and (second_stage_var_term.args[1] is ss_var) + and len(second_stage_var_term.args) == 2 + ) + self.assertTrue( + last_term_is_neg_ss_var, + msg=( + "Last argument of last term in second-stage variable" + f"term of DR constraint with name {dr_eq.name!r} " + "is not the negative corresponding second-stage variable " + f"{ss_var.name!r}" + ), + ) + + # now we check the other terms. + # these should comprise the DR polynomial expression + dr_polynomial_terms = dr_eq_terms[:-1] + dr_polynomial_zip = zip( + dr_polynomial_terms, + indexed_dr_var.values(), + dr_monomial_param_combos, + ) + for idx, (term, dr_var, param_combo) in enumerate(dr_polynomial_zip): + # term should be a monomial expression of form + # (uncertain parameter product) * (decision rule variable) + # so length of expression object should be 2 + self.assertEqual( + len(term.args), + 2, + msg=( + f"Length of `args` attribute of term {str(term)} " + f"of DR equation {dr_eq.name!r} is not as expected. " + f"Args: {term.args}" + ) + ) + + # check that uncertain parameters participating in + # the monomial are as expected + param_product_multiplicand = term.args[0] + if idx == 0: + # static DR term + param_combo_found_in_term = (param_product_multiplicand,) + param_names = (str(param) for param in param_combo) + elif len(param_combo) == 1: + # affine DR terms + param_combo_found_in_term = (param_product_multiplicand,) + param_names = (param.name for param in param_combo) + else: + # higher-order DR terms + param_combo_found_in_term = param_product_multiplicand.args + param_names = (param.name for param in param_combo) + + self.assertEqual( + param_combo_found_in_term, + param_combo, + msg=( + f"All but last multiplicand of DR monomial {str(term)} " + f"is not the uncertain parameter tuple " + f"({', '.join(param_names)})." + ), + ) + + # check that DR variable participating in the monomial + # is as expected + dr_var_multiplicand = term.args[1] + self.assertIs( + dr_var_multiplicand, + dr_var, + msg=( + f"Last multiplicand of DR monomial {str(term)} " + f"is not the DR variable {dr_var.name!r}." + ), + ) + class testModelIsValid(unittest.TestCase): def test_model_is_valid_via_possible_inputs(self): From f5c771d4441ec5c621148b0d77600e7f4f591ee7 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 17 Dec 2023 19:19:25 -0500 Subject: [PATCH 0625/1204] Apply black --- pyomo/contrib/pyros/tests/test_grcs.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 00824578f50..05cbdb849f4 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -324,7 +324,7 @@ def test_correct_num_dr_vars_static(self): msg=( "Number of unique indexed DR variable components should equal " "number of second-stage variables." - ) + ), ) @unittest.skipIf(not scipy_available, 'Scipy is not available.') @@ -358,7 +358,7 @@ def test_correct_num_dr_vars_affine(self): msg=( "Number of unique indexed DR variable components should equal " "number of second-stage variables." - ) + ), ) @unittest.skipIf(not scipy_available, 'Scipy is not available.') @@ -399,7 +399,7 @@ def test_correct_num_dr_vars_quadratic(self): msg=( "Number of unique indexed DR variable components should equal " "number of second-stage variables." - ) + ), ) @@ -555,9 +555,7 @@ def test_dr_eqns_form_correct(self): # these should comprise the DR polynomial expression dr_polynomial_terms = dr_eq_terms[:-1] dr_polynomial_zip = zip( - dr_polynomial_terms, - indexed_dr_var.values(), - dr_monomial_param_combos, + dr_polynomial_terms, indexed_dr_var.values(), dr_monomial_param_combos ) for idx, (term, dr_var, param_combo) in enumerate(dr_polynomial_zip): # term should be a monomial expression of form @@ -570,7 +568,7 @@ def test_dr_eqns_form_correct(self): f"Length of `args` attribute of term {str(term)} " f"of DR equation {dr_eq.name!r} is not as expected. " f"Args: {term.args}" - ) + ), ) # check that uncertain parameters participating in From 5ecc917c08d21550de75a7f02b06c6ee6b686317 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 17 Dec 2023 19:52:52 -0500 Subject: [PATCH 0626/1204] Add comments on DR variable initialization --- pyomo/contrib/pyros/util.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index d060acb4c63..d0cc3ef4f9d 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -1309,14 +1309,18 @@ def add_decision_rule_variables(model_data, config): ) for idx, ss_var in enumerate(second_stage_variables): - # declare DR coefficients for current second-stage - # variable + # declare DR coefficients for current second-stage variable indexed_dr_var = Var( range(num_dr_vars), initialize=0, bounds=(None, None), domain=Reals ) model_data.working_model.add_component( f"decision_rule_var_{idx}", indexed_dr_var ) + + # index 0 entry of the IndexedVar is the static + # DR term. initialize to user-provided value of + # the corresponding second-stage variable. + # all other entries remain initialized to 0. indexed_dr_var[0].set_value(value(ss_var, exception=False)) # update attributes From 942af3721b7ba8039d7660b10282da840cf2b711 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 17 Dec 2023 20:19:44 -0500 Subject: [PATCH 0627/1204] Fix imports and remove unused methods --- pyomo/contrib/pyros/util.py | 36 +----------------------------------- 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index d0cc3ef4f9d..6aa6ed61936 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -22,7 +22,6 @@ from pyomo.core.base.set_types import Reals from pyomo.opt import TerminationCondition as tc from pyomo.core.expr import value -import pyomo.core.expr as EXPR from pyomo.core.expr.numeric_expr import NPV_MaxExpression, NPV_MinExpression from pyomo.repn.standard_repn import generate_standard_repn from pyomo.core.expr.visitor import ( @@ -40,11 +39,9 @@ import timeit from contextlib import contextmanager import logging -from pprint import pprint import math from pyomo.common.timing import HierarchicalTimer from pyomo.common.log import Preformatted -from scipy.special import comb # Tolerances used in the code @@ -1304,7 +1301,7 @@ def add_decision_rule_variables(model_data, config): # variable depends on DR order and uncertainty set dimension degree = config.decision_rule_order num_uncertain_params = len(model_data.working_model.util.uncertain_params) - num_dr_vars = comb( + num_dr_vars = sp.special.comb( N=num_uncertain_params + degree, k=degree, exact=True, repetition=False ) @@ -1330,37 +1327,6 @@ def add_decision_rule_variables(model_data, config): model_data.working_model.util.decision_rule_vars = decision_rule_vars -def partition_powers(n, v): - """Partition a total degree n across v variables - - This is an implementation of the "stars and bars" algorithm from - combinatorial mathematics. - - This partitions a "total integer degree" of n across v variables - such that each variable gets an integer degree >= 0. You can think - of this as dividing a set of n+v things into v groupings, with the - power for each v_i being 1 less than the number of things in the - i'th group (because the v is part of the group). It is therefore - sufficient to just get the v-1 starting points chosen from a list of - indices n+v long (the first starting point is fixed to be 0). - - """ - for starts in it.combinations(range(1, n + v), v - 1): - # add the initial starting point to the beginning and the total - # number of objects (degree counters and variables) to the end - # of the list. The degree for each variable is 1 less than the - # difference of sequential starting points (to account for the - # variable itself) - starts = (0,) + starts + (n + v,) - yield [starts[i + 1] - starts[i] - 1 for i in range(v)] - - -def sort_partitioned_powers(powers_list): - powers_list = sorted(powers_list, reverse=True) - powers_list = sorted(powers_list, key=lambda elem: max(elem)) - return powers_list - - def add_decision_rule_constraints(model_data, config): """ Add decision rule equality constraints to the working model. From 2c865d6b1c1c8122fa55c0b65830e9e092eecb13 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Dec 2023 09:41:01 -0700 Subject: [PATCH 0628/1204] Declare AbstractSuffix (with disabled public API) --- pyomo/core/base/suffix.py | 36 ++++++++++++++++++++++++++-- pyomo/core/tests/unit/test_suffix.py | 2 +- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index b21c9cce4e8..49bac51e903 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -22,10 +22,23 @@ from pyomo.common.pyomo_typing import overload from pyomo.common.timing import ConstructionTimer from pyomo.core.base.component import ActiveComponent, ModelComponentFactory +from pyomo.core.base.disable_methods import disable_methods from pyomo.core.base.initializer import Initializer logger = logging.getLogger('pyomo.core') +_SUFFIX_API = ( + ('__contains__', 'test membership in'), + ('__iter__', 'iterate over'), + '__getitem__', + '__setitem__', + 'set_value', + 'set_all_values', + 'clear_value', + 'clear_all_values', + 'update_values', +) + # A list of convenient suffix generators, including: # - active_export_suffix_generator # **(used by problem writers) @@ -153,6 +166,20 @@ class Suffix(ComponentMap, ActiveComponent): FLOAT = SuffixDataType.FLOAT INT = SuffixDataType.INT + def __new__(cls, *args, **kwargs): + if cls is not Suffix: + return super().__new__(cls) + return super().__new__(AbstractSuffix) + + def __setstate__(self, state): + super().__setstate__(state) + # As the concrete class *is* the "Suffix" base class, the normal + # implementation of deepcopy (through get/setstate) will create + # the new Suffix, and __new__ will map it to AbstractSuffix. We + # need to map constructed Suffixes back to Suffix: + if self._constructed and self.__class__ is AbstractSuffix: + self.__class__ = Suffix + @overload def __init__( self, @@ -162,7 +189,7 @@ def __init__( initialize=None, rule=None, name=None, - doc=None + doc=None, ): ... @@ -199,7 +226,7 @@ def construct(self, data=None): Constructs this component, applying rule if it exists. """ if is_debug_set(logger): - logger.debug("Constructing Suffix '%s'", self.name) + logger.debug(f"Constructing %s '%s'", self.__class__.__name__, self.name) if self._constructed is True: return @@ -379,6 +406,11 @@ def __str__(self): return ActiveComponent.__str__(self) +@disable_methods(_SUFFIX_API) +class AbstractSuffix(Suffix): + pass + + class SuffixFinder(object): def __init__(self, name, default=None): """This provides an efficient utility for finding suffix values on a diff --git a/pyomo/core/tests/unit/test_suffix.py b/pyomo/core/tests/unit/test_suffix.py index 9a5a900a2fd..1ec1af9d919 100644 --- a/pyomo/core/tests/unit/test_suffix.py +++ b/pyomo/core/tests/unit/test_suffix.py @@ -68,7 +68,7 @@ def test_suffix_debug(self): OUT.getvalue(), "Constructing ConcreteModel 'ConcreteModel', from data=None\n" "Constructing Suffix 'Suffix'\n" - "Constructing Suffix 'foo' on [Model] from data=None\n" + "Constructing AbstractSuffix 'foo' on [Model] from data=None\n" "Constructing Suffix 'foo'\n" "Constructed component ''[Model].foo'':\n" "foo : Direction=LOCAL, Datatype=FLOAT\n" From 3d84f38d0f3aacbaf8a7f021bb5fdb436af6c699 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Mon, 18 Dec 2023 14:57:52 -0700 Subject: [PATCH 0629/1204] Testing that we log a more polite warning about BigM Suffixes, though we don't yet if they are on the model --- pyomo/gdp/plugins/multiple_bigm.py | 12 +++++++----- pyomo/gdp/tests/test_mbigm.py | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index e66dcb3bb88..d40486406ab 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -346,11 +346,13 @@ def _transform_disjunct(self, obj, transBlock, active_disjuncts, Ms): obj._deactivate_without_fixing_indicator() def _warn_for_active_suffix(self, obj, disjunct, active_disjuncts, Ms): - raise GDP_Error( - "Found active Suffix '{0}' on Disjunct '{1}'. " - "The multiple bigM transformation does not currently " - "support Suffixes.".format(obj.name, disjunct.name) - ) + if obj.name == 'BigM': + logger.warning( + "Found active 'BigM' Suffix on '{0}'. " + "The multiple bigM transformation does not currently " + "support specifying M's with Suffixes and is ignoring " + "this Suffix.".format(disjunct.name) + ) def _transform_constraint(self, obj, disjunct, active_disjuncts, Ms): # we will put a new transformed constraint on the relaxation block. diff --git a/pyomo/gdp/tests/test_mbigm.py b/pyomo/gdp/tests/test_mbigm.py index 7ab34153468..d2c49958df6 100644 --- a/pyomo/gdp/tests/test_mbigm.py +++ b/pyomo/gdp/tests/test_mbigm.py @@ -494,6 +494,24 @@ def test_Ms_specified_as_args_honored(self): cons[1], m.x2, m.d2, m.disjunction, {m.d1: 3, m.d3: 110}, upper=10 ) + def test_log_warning_for_bigm_suffixes(self): + m = self.make_model() + m.BigM = Suffix(direction=Suffix.LOCAL) + m.BigM[m.d2.x2_bounds] = (-100, 100) + + out = StringIO() + with LoggingIntercept(out, 'pyomo.gdp.mbigm'): + TransformationFactory('gdp.mbigm').apply_to(m) + + warnings = out.getvalue() + self.assertIn( + "Found active 'BigM' Suffix on 'unknown'. " + "The multiple bigM transformation does not currently " + "support specifying M's with Suffixes and is ignoring " + "this Suffix.", + warnings, + ) + # TODO: If Suffixes allow tuple keys then we can support them and it will # look something like this: # def test_Ms_specified_as_suffixes_honored(self): From bafe936a17ba632aae1b0ddc9e661e4c12ff72e4 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Mon, 18 Dec 2023 15:43:13 -0700 Subject: [PATCH 0630/1204] Changing my mind on the warning for Suffix issue again. --- pyomo/gdp/plugins/multiple_bigm.py | 10 ---------- pyomo/gdp/tests/test_mbigm.py | 18 ------------------ 2 files changed, 28 deletions(-) diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index d40486406ab..2fa26479908 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -200,7 +200,6 @@ class MultipleBigMTransformation(GDP_to_MIP_Transformation, _BigM_MixIn): def __init__(self): super().__init__(logger) - self.handlers[Suffix] = self._warn_for_active_suffix self._arg_list = {} self._set_up_expr_bound_visitor() @@ -345,15 +344,6 @@ def _transform_disjunct(self, obj, transBlock, active_disjuncts, Ms): # deactivate disjunct so writers can be happy obj._deactivate_without_fixing_indicator() - def _warn_for_active_suffix(self, obj, disjunct, active_disjuncts, Ms): - if obj.name == 'BigM': - logger.warning( - "Found active 'BigM' Suffix on '{0}'. " - "The multiple bigM transformation does not currently " - "support specifying M's with Suffixes and is ignoring " - "this Suffix.".format(disjunct.name) - ) - def _transform_constraint(self, obj, disjunct, active_disjuncts, Ms): # we will put a new transformed constraint on the relaxation block. relaxationBlock = disjunct._transformation_block() diff --git a/pyomo/gdp/tests/test_mbigm.py b/pyomo/gdp/tests/test_mbigm.py index d2c49958df6..46b57d3256d 100644 --- a/pyomo/gdp/tests/test_mbigm.py +++ b/pyomo/gdp/tests/test_mbigm.py @@ -493,24 +493,6 @@ def test_Ms_specified_as_args_honored(self): self.check_untightened_bounds_constraint( cons[1], m.x2, m.d2, m.disjunction, {m.d1: 3, m.d3: 110}, upper=10 ) - - def test_log_warning_for_bigm_suffixes(self): - m = self.make_model() - m.BigM = Suffix(direction=Suffix.LOCAL) - m.BigM[m.d2.x2_bounds] = (-100, 100) - - out = StringIO() - with LoggingIntercept(out, 'pyomo.gdp.mbigm'): - TransformationFactory('gdp.mbigm').apply_to(m) - - warnings = out.getvalue() - self.assertIn( - "Found active 'BigM' Suffix on 'unknown'. " - "The multiple bigM transformation does not currently " - "support specifying M's with Suffixes and is ignoring " - "this Suffix.", - warnings, - ) # TODO: If Suffixes allow tuple keys then we can support them and it will # look something like this: From 3ef0e62b4f984fdce46b644165d1017a0307d2bf Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Mon, 18 Dec 2023 15:48:33 -0700 Subject: [PATCH 0631/1204] Adding a warning about silently ignoring BigM Suffix in GDP docs, to assuage my guilt --- doc/OnlineDocs/modeling_extensions/gdp/solving.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/OnlineDocs/modeling_extensions/gdp/solving.rst b/doc/OnlineDocs/modeling_extensions/gdp/solving.rst index 2f3076862e6..9fea90ebf5f 100644 --- a/doc/OnlineDocs/modeling_extensions/gdp/solving.rst +++ b/doc/OnlineDocs/modeling_extensions/gdp/solving.rst @@ -140,6 +140,10 @@ For example, to apply the transformation and store the M values, use: From the Pyomo command line, include the ``--transform pyomo.gdp.mbigm`` option. +.. warning:: + The Multiple Big-M transformation does not currently support Suffixes and will + ignore "BigM" Suffixes. + Hull Reformulation (HR) ^^^^^^^^^^^^^^^^^^^^^^^ From 5d7b74036d188845e3653c848f179acb2d44936f Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 19 Dec 2023 08:53:59 -0700 Subject: [PATCH 0632/1204] Begin cleaning up documentation; move tests to more logical configuration --- pyomo/solver/base.py | 67 ++++++++++++++----- pyomo/solver/tests/{ => unit}/test_base.py | 0 pyomo/solver/tests/{ => unit}/test_config.py | 0 pyomo/solver/tests/{ => unit}/test_results.py | 0 .../solver/tests/{ => unit}/test_solution.py | 0 pyomo/solver/tests/{ => unit}/test_util.py | 0 6 files changed, 52 insertions(+), 15 deletions(-) rename pyomo/solver/tests/{ => unit}/test_base.py (100%) rename pyomo/solver/tests/{ => unit}/test_config.py (100%) rename pyomo/solver/tests/{ => unit}/test_results.py (100%) rename pyomo/solver/tests/{ => unit}/test_solution.py (100%) rename pyomo/solver/tests/{ => unit}/test_util.py (100%) diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index d4b46ebe5d4..fc361bdaf5e 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -37,6 +37,18 @@ class SolverBase(abc.ABC): + """ + Base class upon which direct solver interfaces can be built. + + This base class contains the required methods for all direct solvers: + - available: Determines whether the solver is able to be run, combining + both whether it can be found on the system and if the license is valid. + - config: The configuration method for solver objects. + - solve: The main method of every solver + - version: The version of the solver + - is_persistent: Set to false for all direct solvers. + """ + # # Support "with" statements. Forgetting to call deactivate # on Plugins is a common source of memory leaks @@ -45,9 +57,14 @@ def __enter__(self): return self def __exit__(self, t, v, traceback): - pass + """Exit statement - enables `with` statements.""" class Availability(enum.IntEnum): + """ + Class to capture different statuses in which a solver can exist in + order to record its availability for use. + """ + FullLicense = 2 LimitedLicense = 1 NotFound = 0 @@ -56,7 +73,7 @@ class Availability(enum.IntEnum): NeedsCompiledExtension = -3 def __bool__(self): - return self._value_ > 0 + return self.real > 0 def __format__(self, format_spec): # We want general formatting of this Enum to return the @@ -93,7 +110,6 @@ def solve( results: Results A results object """ - pass @abc.abstractmethod def available(self): @@ -120,7 +136,6 @@ def available(self): be True if the solver is runable at all and False otherwise. """ - pass @abc.abstractmethod def version(self) -> Tuple: @@ -143,7 +158,6 @@ def config(self): An object for configuring pyomo solve options such as the time limit. These options are mostly independent of the solver. """ - pass def is_persistent(self): """ @@ -156,7 +170,21 @@ def is_persistent(self): class PersistentSolverBase(SolverBase): + """ + Base class upon which persistent solvers can be built. This inherits the + methods from the direct solver base and adds those methods that are necessary + for persistent solvers. + + Example usage can be seen in solvers within APPSI. + """ + def is_persistent(self): + """ + Returns + ------- + is_persistent: bool + True if the solver is a persistent solver. + """ return True def load_vars( @@ -179,7 +207,20 @@ def load_vars( def get_primals( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None ) -> Mapping[_GeneralVarData, float]: - pass + """ + Get mapping of variables to primals. + + Parameters + ---------- + vars_to_load : Optional[Sequence[_GeneralVarData]], optional + Which vars to be populated into the map. The default is None. + + Returns + ------- + Mapping[_GeneralVarData, float] + A map of variables to primals. + + """ def get_duals( self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None @@ -198,9 +239,6 @@ def get_duals( duals: dict Maps constraints to dual values """ - raise NotImplementedError( - '{0} does not support the get_duals method'.format(type(self)) - ) def get_slacks( self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None @@ -217,9 +255,6 @@ def get_slacks( slacks: dict Maps constraints to slack values """ - raise NotImplementedError( - '{0} does not support the get_slacks method'.format(type(self)) - ) def get_reduced_costs( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None @@ -236,9 +271,6 @@ def get_reduced_costs( reduced_costs: ComponentMap Maps variable to reduced cost """ - raise NotImplementedError( - '{0} does not support the get_reduced_costs method'.format(type(self)) - ) @property @abc.abstractmethod @@ -295,6 +327,11 @@ def update_params(self): class LegacySolverInterface: + """ + Class to map the new solver interface features into the legacy solver + interface. Necessary for backwards compatibility. + """ + def solve( self, model: _BlockData, diff --git a/pyomo/solver/tests/test_base.py b/pyomo/solver/tests/unit/test_base.py similarity index 100% rename from pyomo/solver/tests/test_base.py rename to pyomo/solver/tests/unit/test_base.py diff --git a/pyomo/solver/tests/test_config.py b/pyomo/solver/tests/unit/test_config.py similarity index 100% rename from pyomo/solver/tests/test_config.py rename to pyomo/solver/tests/unit/test_config.py diff --git a/pyomo/solver/tests/test_results.py b/pyomo/solver/tests/unit/test_results.py similarity index 100% rename from pyomo/solver/tests/test_results.py rename to pyomo/solver/tests/unit/test_results.py diff --git a/pyomo/solver/tests/test_solution.py b/pyomo/solver/tests/unit/test_solution.py similarity index 100% rename from pyomo/solver/tests/test_solution.py rename to pyomo/solver/tests/unit/test_solution.py diff --git a/pyomo/solver/tests/test_util.py b/pyomo/solver/tests/unit/test_util.py similarity index 100% rename from pyomo/solver/tests/test_util.py rename to pyomo/solver/tests/unit/test_util.py From 2dee6b7fee8cf394c1502b83e4989217ad95413f Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 19 Dec 2023 09:29:04 -0700 Subject: [PATCH 0633/1204] Reinstantiate NotImplementedErrors for PersistentBase; update tests --- pyomo/solver/base.py | 106 +++++++++++++++++++++------ pyomo/solver/tests/unit/test_base.py | 4 +- 2 files changed, 88 insertions(+), 22 deletions(-) diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index fc361bdaf5e..c025e2028ce 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -73,7 +73,7 @@ class Availability(enum.IntEnum): NeedsCompiledExtension = -3 def __bool__(self): - return self.real > 0 + return self._value_ > 0 def __format__(self, format_spec): # We want general formatting of this Enum to return the @@ -219,8 +219,10 @@ def get_primals( ------- Mapping[_GeneralVarData, float] A map of variables to primals. - """ + raise NotImplementedError( + '{0} does not support the get_primals method'.format(type(self)) + ) def get_duals( self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None @@ -239,6 +241,9 @@ def get_duals( duals: dict Maps constraints to dual values """ + raise NotImplementedError( + '{0} does not support the get_duals method'.format(type(self)) + ) def get_slacks( self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None @@ -255,6 +260,9 @@ def get_slacks( slacks: dict Maps constraints to slack values """ + raise NotImplementedError( + '{0} does not support the get_slacks method'.format(type(self)) + ) def get_reduced_costs( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None @@ -271,59 +279,88 @@ def get_reduced_costs( reduced_costs: ComponentMap Maps variable to reduced cost """ + raise NotImplementedError( + '{0} does not support the get_reduced_costs method'.format(type(self)) + ) @property @abc.abstractmethod def update_config(self) -> UpdateConfig: - pass + """ + Updates the solver config + """ @abc.abstractmethod def set_instance(self, model): - pass + """ + Set an instance of the model + """ @abc.abstractmethod def add_variables(self, variables: List[_GeneralVarData]): - pass + """ + Add variables to the model + """ @abc.abstractmethod def add_params(self, params: List[_ParamData]): - pass + """ + Add parameters to the model + """ @abc.abstractmethod def add_constraints(self, cons: List[_GeneralConstraintData]): - pass + """ + Add constraints to the model + """ @abc.abstractmethod def add_block(self, block: _BlockData): - pass + """ + Add a block to the model + """ @abc.abstractmethod def remove_variables(self, variables: List[_GeneralVarData]): - pass + """ + Remove variables from the model + """ @abc.abstractmethod def remove_params(self, params: List[_ParamData]): - pass + """ + Remove parameters from the model + """ @abc.abstractmethod def remove_constraints(self, cons: List[_GeneralConstraintData]): - pass + """ + Remove constraints from the model + """ @abc.abstractmethod def remove_block(self, block: _BlockData): - pass + """ + Remove a block from the model + """ @abc.abstractmethod def set_objective(self, obj: _GeneralObjectiveData): - pass + """ + Set current objective for the model + """ @abc.abstractmethod def update_variables(self, variables: List[_GeneralVarData]): - pass + """ + Update variables on the model + """ @abc.abstractmethod def update_params(self): - pass + """ + Update parameters on the model + """ class LegacySolverInterface: @@ -332,6 +369,10 @@ class LegacySolverInterface: interface. Necessary for backwards compatibility. """ + def __init__(self): + self.original_config = self.config + self.config = self.config() + def solve( self, model: _BlockData, @@ -347,7 +388,15 @@ def solve( keepfiles: bool = False, symbolic_solver_labels: bool = False, ): - original_config = self.config + """ + Solve method: maps new solve method style to backwards compatible version. + + Returns + ------- + legacy_results + Legacy results object + + """ self.config = self.config() self.config.tee = tee self.config.load_solution = load_solutions @@ -401,10 +450,10 @@ def solve( legacy_soln.gap = None symbol_map = SymbolMap() - symbol_map.byObject = dict(self.symbol_map.byObject) - symbol_map.bySymbol = dict(self.symbol_map.bySymbol) - symbol_map.aliases = dict(self.symbol_map.aliases) - symbol_map.default_labeler = self.symbol_map.default_labeler + symbol_map.byObject = dict(symbol_map.byObject) + symbol_map.bySymbol = dict(symbol_map.bySymbol) + symbol_map.aliases = dict(symbol_map.aliases) + symbol_map.default_labeler = symbol_map.default_labeler model.solutions.add_symbol_map(symbol_map) legacy_results._smap_id = id(symbol_map) @@ -439,12 +488,16 @@ def solve( if delete_legacy_soln: legacy_results.solution.delete(0) - self.config = original_config + self.config = self.original_config self.options = original_options return legacy_results def available(self, exception_flag=True): + """ + Returns a bool determining whether the requested solver is available + on the system. + """ ans = super().available() if exception_flag and not ans: raise ApplicationError(f'Solver {self.__class__} is not available ({ans}).') @@ -468,6 +521,14 @@ def license_is_valid(self) -> bool: @property def options(self): + """ + Read the options for the dictated solver. + + NOTE: Only the set of solvers for which the LegacySolverInterface is compatible + are accounted for within this property. + Not all solvers are currently covered by this backwards compatibility + class. + """ for solver_name in ['gurobi', 'ipopt', 'cplex', 'cbc', 'highs']: if hasattr(self, solver_name + '_options'): return getattr(self, solver_name + '_options') @@ -475,6 +536,9 @@ def options(self): @options.setter def options(self, val): + """ + Set the options for the dictated solver. + """ found = False for solver_name in ['gurobi', 'ipopt', 'cplex', 'cbc', 'highs']: if hasattr(self, solver_name + '_options'): diff --git a/pyomo/solver/tests/unit/test_base.py b/pyomo/solver/tests/unit/test_base.py index d8084e9b5b7..b501f8d3dd3 100644 --- a/pyomo/solver/tests/unit/test_base.py +++ b/pyomo/solver/tests/unit/test_base.py @@ -63,7 +63,6 @@ def test_abstract_member_list(self): def test_persistent_solver_base(self): self.instance = base.PersistentSolverBase() self.assertTrue(self.instance.is_persistent()) - self.assertEqual(self.instance.get_primals(), None) self.assertEqual(self.instance.update_config, None) self.assertEqual(self.instance.set_instance(None), None) self.assertEqual(self.instance.add_variables(None), None) @@ -78,6 +77,9 @@ def test_persistent_solver_base(self): self.assertEqual(self.instance.update_variables(None), None) self.assertEqual(self.instance.update_params(), None) + with self.assertRaises(NotImplementedError): + self.instance.get_primals() + with self.assertRaises(NotImplementedError): self.instance.get_duals() From f0c0a07e42e05733d90d41b92db89f5e05194bd1 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 19 Dec 2023 09:36:09 -0700 Subject: [PATCH 0634/1204] Revert config changes --- pyomo/solver/base.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index c025e2028ce..1d459450bab 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -369,10 +369,6 @@ class LegacySolverInterface: interface. Necessary for backwards compatibility. """ - def __init__(self): - self.original_config = self.config - self.config = self.config() - def solve( self, model: _BlockData, @@ -397,6 +393,7 @@ def solve( Legacy results object """ + original_config = self.config self.config = self.config() self.config.tee = tee self.config.load_solution = load_solutions @@ -488,7 +485,7 @@ def solve( if delete_legacy_soln: legacy_results.solution.delete(0) - self.config = self.original_config + self.config = original_config self.options = original_options return legacy_results From 35e5ff0d5033ccb50b0e38a33c3f3d91c26626de Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 19 Dec 2023 10:57:46 -0700 Subject: [PATCH 0635/1204] bug fix --- pyomo/solver/util.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyomo/solver/util.py b/pyomo/solver/util.py index ec59f7e80f7..c0c99a00747 100644 --- a/pyomo/solver/util.py +++ b/pyomo/solver/util.py @@ -173,7 +173,7 @@ def update_config(self, val: UpdateConfig): def set_instance(self, model): saved_update_config = self.update_config - self.__init__() + self.__init__(only_child_vars=self._only_child_vars) self.update_config = saved_update_config self._model = model self.add_block(model) @@ -632,17 +632,17 @@ def update(self, timer: HierarchicalTimer = None): vars_to_update = [] for v in vars_to_check: _v, lb, ub, fixed, domain_interval, value = self._vars[id(v)] - if lb is not v._lb: - vars_to_update.append(v) - elif ub is not v._ub: - vars_to_update.append(v) - elif (fixed is not v.fixed) or (fixed and (value != v.value)): + if (fixed != v.fixed) or (fixed and (value != v.value)): vars_to_update.append(v) if self.update_config.treat_fixed_vars_as_params: for c in self._referenced_variables[id(v)][0]: cons_to_remove_and_add[c] = None if self._referenced_variables[id(v)][2] is not None: need_to_set_objective = True + elif lb is not v._lb: + vars_to_update.append(v) + elif ub is not v._ub: + vars_to_update.append(v) elif domain_interval != v.domain.get_interval(): vars_to_update.append(v) self.update_variables(vars_to_update) From 84896f39fd1dead0be2afda4e0894358c732786c Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 19 Dec 2023 11:18:02 -0700 Subject: [PATCH 0636/1204] Add descriptions; incorporate missing option into ipopt --- pyomo/solver/config.py | 69 +++++++++++++++++++++++++---------------- pyomo/solver/ipopt.py | 15 ++++++--- pyomo/solver/results.py | 6 ++++ 3 files changed, 60 insertions(+), 30 deletions(-) diff --git a/pyomo/solver/config.py b/pyomo/solver/config.py index 551f59ccd9a..54a497cee0c 100644 --- a/pyomo/solver/config.py +++ b/pyomo/solver/config.py @@ -21,24 +21,7 @@ class SolverConfig(ConfigDict): """ - Attributes - ---------- - time_limit: float - sent to solver - Time limit for the solver - tee: bool - If True, then the solver log goes to stdout - load_solution: bool - wrapper - If False, then the values of the primal variables will not be - loaded into the model - symbolic_solver_labels: bool - sent to solver - If True, the names given to the solver will reflect the names - of the pyomo components. Cannot be changed after set_instance - is called. - report_timing: bool - wrapper - If True, then some timing information will be printed at the - end of the solve. - threads: integer - sent to solver - Number of threads to be used by a solver. + Base config values for all solver interfaces """ def __init__( @@ -57,28 +40,62 @@ def __init__( visibility=visibility, ) - self.tee: bool = self.declare('tee', ConfigValue(domain=bool, default=False)) + self.tee: bool = self.declare( + 'tee', + ConfigValue( + domain=bool, + default=False, + description="If True, the solver log prints to stdout.", + ), + ) self.load_solution: bool = self.declare( - 'load_solution', ConfigValue(domain=bool, default=True) + 'load_solution', + ConfigValue( + domain=bool, + default=True, + description="If True, the values of the primal variables will be loaded into the model.", + ), ) self.raise_exception_on_nonoptimal_result: bool = self.declare( 'raise_exception_on_nonoptimal_result', - ConfigValue(domain=bool, default=True), + ConfigValue( + domain=bool, + default=True, + description="If False, the `solve` method will continue processing even if the returned result is nonoptimal.", + ), ) self.symbolic_solver_labels: bool = self.declare( - 'symbolic_solver_labels', ConfigValue(domain=bool, default=False) + 'symbolic_solver_labels', + ConfigValue( + domain=bool, + default=False, + description="If True, the names given to the solver will reflect the names of the Pyomo components. Cannot be changed after set_instance is called.", + ), ) self.report_timing: bool = self.declare( - 'report_timing', ConfigValue(domain=bool, default=False) + 'report_timing', + ConfigValue( + domain=bool, + default=False, + description="If True, timing information will be printed at the end of a solve call.", + ), ) self.threads: Optional[int] = self.declare( - 'threads', ConfigValue(domain=NonNegativeInt) + 'threads', + ConfigValue( + domain=NonNegativeInt, + description="Number of threads to be used by a solver.", + ), ) self.time_limit: Optional[float] = self.declare( - 'time_limit', ConfigValue(domain=NonNegativeFloat) + 'time_limit', + ConfigValue( + domain=NonNegativeFloat, description="Time limit applied to the solver." + ), ) self.solver_options: ConfigDict = self.declare( - 'solver_options', ConfigDict(implicit=True) + 'solver_options', + ConfigDict(implicit=True, description="Options to pass to the solver."), ) diff --git a/pyomo/solver/ipopt.py b/pyomo/solver/ipopt.py index 092b279269b..68ba4989ff4 100644 --- a/pyomo/solver/ipopt.py +++ b/pyomo/solver/ipopt.py @@ -48,8 +48,6 @@ class ipoptSolverError(PyomoException): General exception to catch solver system errors """ - pass - class ipoptConfig(SolverConfig): def __init__( @@ -84,6 +82,9 @@ def __init__( self.log_level = self.declare( 'log_level', ConfigValue(domain=NonNegativeInt, default=logging.INFO) ) + self.presolve: bool = self.declare( + 'presolve', ConfigValue(domain=bool, default=True) + ) class ipoptResults(Results): @@ -186,8 +187,6 @@ def __init__(self, **kwds): self._config = self.CONFIG(kwds) self._writer = NLWriter() self._writer.config.skip_trivial_constraints = True - # TODO: Make this an option; not always turned on - self._writer.config.linear_presolve = True self.ipopt_options = self._config.solver_options def available(self): @@ -265,6 +264,12 @@ def solve(self, model, **kwds): # Update configuration options, based on keywords passed to solve config: ipoptConfig = self.config(kwds.pop('options', {})) config.set_value(kwds) + self._writer.config.linear_presolve = config.presolve + if config.threads: + logger.log( + logging.INFO, + msg="The `threads` option was utilized, but this has not yet been implemented for {self.__class__}.", + ) results = ipoptResults() with TempfileManager.new_context() as tempfile: if config.temp_dir is None: @@ -405,6 +410,8 @@ def solve(self, model, **kwds): results.timing_info.wall_time = ( end_timestamp - start_timestamp ).total_seconds() + if config.report_timing: + results.report_timing() return results def _parse_ipopt_output(self, stream: io.StringIO): diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index 0aa78bef6bc..ae909986ed0 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -246,6 +246,12 @@ def __str__(self): s += 'objective_bound: ' + str(self.objective_bound) return s + def report_timing(self): + print('Timing Information: ') + print('-' * 50) + self.timing_info.display() + print('-' * 50) + class ResultsReader: pass From c114ee3b991540485bd868415ce3a9ed25e73e7b Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 19 Dec 2023 16:17:37 -0700 Subject: [PATCH 0637/1204] Bug fix: Case where there is no active objective --- pyomo/contrib/trustregion/TRF.py | 2 +- pyomo/core/base/PyomoModel.py | 4 ++-- pyomo/solver/base.py | 17 +++++++++-------- pyomo/solver/ipopt.py | 10 +++++++++- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/pyomo/contrib/trustregion/TRF.py b/pyomo/contrib/trustregion/TRF.py index 45e60df7658..24254599609 100644 --- a/pyomo/contrib/trustregion/TRF.py +++ b/pyomo/contrib/trustregion/TRF.py @@ -35,7 +35,7 @@ logger = logging.getLogger('pyomo.contrib.trustregion') -__version__ = '0.2.0' +__version__ = (0, 2, 0) def trust_region_method(model, decision_variables, ext_fcn_surrogate_map_rule, config): diff --git a/pyomo/core/base/PyomoModel.py b/pyomo/core/base/PyomoModel.py index 6aacabeb183..44bc5302217 100644 --- a/pyomo/core/base/PyomoModel.py +++ b/pyomo/core/base/PyomoModel.py @@ -789,7 +789,7 @@ def _load_model_data(self, modeldata, namespaces, **kwds): profile_memory = kwds.get('profile_memory', 0) if profile_memory >= 2 and pympler_available: - mem_used = pympler.muppy.get_size(muppy.get_objects()) + mem_used = pympler.muppy.get_size(pympler.muppy.get_objects()) print("") print( " Total memory = %d bytes prior to model " @@ -798,7 +798,7 @@ def _load_model_data(self, modeldata, namespaces, **kwds): if profile_memory >= 3: gc.collect() - mem_used = pympler.muppy.get_size(muppy.get_objects()) + mem_used = pympler.muppy.get_size(pympler.muppy.get_objects()) print( " Total memory = %d bytes prior to model " "construction (after garbage collection)" % mem_used diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 1d459450bab..72f63e0a1a0 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -430,14 +430,15 @@ def solve( legacy_results.solver.termination_message = str(results.termination_condition) obj = get_objective(model) - legacy_results.problem.sense = obj.sense - - if obj.sense == minimize: - legacy_results.problem.lower_bound = results.objective_bound - legacy_results.problem.upper_bound = results.incumbent_objective - else: - legacy_results.problem.upper_bound = results.objective_bound - legacy_results.problem.lower_bound = results.incumbent_objective + if obj: + legacy_results.problem.sense = obj.sense + + if obj.sense == minimize: + legacy_results.problem.lower_bound = results.objective_bound + legacy_results.problem.upper_bound = results.incumbent_objective + else: + legacy_results.problem.upper_bound = results.objective_bound + legacy_results.problem.lower_bound = results.incumbent_objective if ( results.incumbent_objective is not None and results.objective_bound is not None diff --git a/pyomo/solver/ipopt.py b/pyomo/solver/ipopt.py index 68ba4989ff4..e85b726ba9b 100644 --- a/pyomo/solver/ipopt.py +++ b/pyomo/solver/ipopt.py @@ -20,6 +20,7 @@ from pyomo.common.config import ConfigValue, NonNegativeInt, NonNegativeFloat from pyomo.common.errors import PyomoException from pyomo.common.tempfiles import TempfileManager +from pyomo.core.base import Objective from pyomo.core.base.label import NumericLabeler from pyomo.repn.plugins.nl_writer import NLWriter, NLWriterInfo, AMPLRepn from pyomo.solver.base import SolverBase, SymbolMap @@ -390,7 +391,14 @@ def solve(self, model, **kwds): ): model.rc.update(results.solution_loader.get_reduced_costs()) - if results.solution_status in {SolutionStatus.feasible, SolutionStatus.optimal}: + if results.solution_status in { + SolutionStatus.feasible, + SolutionStatus.optimal, + } and len( + list( + model.component_data_objects(Objective, descend_into=True, active=True) + ) + ): if config.load_solution: results.incumbent_objective = value(nl_info.objectives[0]) else: From 13631448a72c4678de81838a86b73cd6a8142f6d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 20 Dec 2023 08:00:59 -0700 Subject: [PATCH 0638/1204] Bug fix: bcannot convert obj to bool --- pyomo/solver/base.py | 6 +++++- pyomo/solver/results.py | 2 -- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 72f63e0a1a0..48adc44c4d7 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -369,6 +369,10 @@ class LegacySolverInterface: interface. Necessary for backwards compatibility. """ + def set_config(self, config): + # TODO: Make a mapping from new config -> old config + pass + def solve( self, model: _BlockData, @@ -430,7 +434,7 @@ def solve( legacy_results.solver.termination_message = str(results.termination_condition) obj = get_objective(model) - if obj: + if len(list(obj)) > 0: legacy_results.problem.sense = obj.sense if obj.sense == minimize: diff --git a/pyomo/solver/results.py b/pyomo/solver/results.py index ae909986ed0..e99db52073b 100644 --- a/pyomo/solver/results.py +++ b/pyomo/solver/results.py @@ -40,8 +40,6 @@ class SolverResultsError(PyomoException): General exception to catch solver system errors """ - pass - class TerminationCondition(enum.Enum): """ From 6ed1f3ffc0ed08ae3dcf81b23831014ce999bfa1 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Dec 2023 11:37:25 -0700 Subject: [PATCH 0639/1204] NFC: fix comment typos --- pyomo/common/collections/component_map.py | 2 +- pyomo/core/base/suffix.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/common/collections/component_map.py b/pyomo/common/collections/component_map.py index 4bb9e88e9af..41796876d7c 100644 --- a/pyomo/common/collections/component_map.py +++ b/pyomo/common/collections/component_map.py @@ -113,7 +113,7 @@ def __eq__(self, other): self_val = self._dict[other_id][1] # Note: check "is" first to help avoid creation of Pyomo # expressions (for the case that the values contain the same - # pyomo component) + # Pyomo component) if self_val is not val and self_val != val: return False return True diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index 49bac51e903..30d79d57b8c 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -199,10 +199,10 @@ def __init__(self, **kwargs): self._datatype = None self._rule = None - # The suffix direction (note the setter performs error chrcking) + # The suffix direction (note the setter performs error checking) self.direction = kwargs.pop('direction', Suffix.LOCAL) - # The suffix datatype (note the setter performs error chrcking) + # The suffix datatype (note the setter performs error checking) self.datatype = kwargs.pop('datatype', Suffix.FLOAT) # The suffix construction rule From d5a2fba9cece7982d551be89e70737355e954ba2 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 20 Dec 2023 15:17:59 -0700 Subject: [PATCH 0640/1204] Fix f-string warning --- pyomo/solver/ipopt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/solver/ipopt.py b/pyomo/solver/ipopt.py index e85b726ba9b..1b4c0eb36cb 100644 --- a/pyomo/solver/ipopt.py +++ b/pyomo/solver/ipopt.py @@ -268,8 +268,8 @@ def solve(self, model, **kwds): self._writer.config.linear_presolve = config.presolve if config.threads: logger.log( - logging.INFO, - msg="The `threads` option was utilized, but this has not yet been implemented for {self.__class__}.", + logging.WARNING, + msg=f"The `threads` option was specified, but this has not yet been implemented for {self.__class__}.", ) results = ipoptResults() with TempfileManager.new_context() as tempfile: From 428f17f85e54462edc73d18618fad94512dbc94b Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 21 Dec 2023 13:40:57 -0700 Subject: [PATCH 0641/1204] Adding proper handling for when a bigm solve comes back infeasible --- pyomo/gdp/plugins/multiple_bigm.py | 30 ++++++++++++--- pyomo/gdp/tests/test_mbigm.py | 59 ++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index 2fa26479908..f363922d539 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -627,8 +627,15 @@ def _calculate_missing_M_values( if lower_M is None: scratch.obj.expr = constraint.body - constraint.lower scratch.obj.sense = minimize - results = self._config.solver.solve(other_disjunct) - if ( + results = self._config.solver.solve(other_disjunct, + load_solutions=False) + if (results.solver.termination_condition is + TerminationCondition.infeasible): + logger.debug("Disjunct '%s' is infeasible, deactivating." + % other_disjunct.name) + other_disjunct.deactivate() + lower_M = 0 + elif ( results.solver.termination_condition is not TerminationCondition.optimal ): @@ -638,14 +645,23 @@ def _calculate_missing_M_values( "Disjunct '%s' is selected." % (constraint.name, disjunct.name, other_disjunct.name) ) - lower_M = value(scratch.obj.expr) + else: + other_disjunct.solutions.load_from(results) + lower_M = value(scratch.obj.expr) if constraint.upper is not None and upper_M is None: # last resort: calculate if upper_M is None: scratch.obj.expr = constraint.body - constraint.upper scratch.obj.sense = maximize - results = self._config.solver.solve(other_disjunct) - if ( + results = self._config.solver.solve(other_disjunct, + load_solutions=False) + if (results.solver.termination_condition is + TerminationCondition.infeasible): + logger.debug("Disjunct '%s' is infeasible, deactivating." + % other_disjunct.name) + other_disjunct.deactivate() + upper_M = 0 + elif ( results.solver.termination_condition is not TerminationCondition.optimal ): @@ -655,7 +671,9 @@ def _calculate_missing_M_values( "Disjunct '%s' is selected." % (constraint.name, disjunct.name, other_disjunct.name) ) - upper_M = value(scratch.obj.expr) + else: + other_disjunct.solutions.load_from(results) + upper_M = value(scratch.obj.expr) arg_Ms[constraint, other_disjunct] = (lower_M, upper_M) transBlock._mbm_values[constraint, other_disjunct] = (lower_M, upper_M) diff --git a/pyomo/gdp/tests/test_mbigm.py b/pyomo/gdp/tests/test_mbigm.py index 46b57d3256d..8d7c2456e3b 100644 --- a/pyomo/gdp/tests/test_mbigm.py +++ b/pyomo/gdp/tests/test_mbigm.py @@ -10,6 +10,7 @@ # ___________________________________________________________________________ from io import StringIO +import logging from os.path import join, normpath import pickle @@ -953,3 +954,61 @@ def test_two_term_indexed_disjunction(self): self.assertEqual(len(cons_again), 2) self.assertIs(cons_again[0], cons[0]) self.assertIs(cons_again[1], cons[1]) + +class EdgeCases(unittest.TestCase): + @unittest.skipUnless(gurobi_available, "Gurobi is not available") + def test_calculate_Ms_infeasible_Disjunct(self): + m = ConcreteModel() + m.x = Var(bounds=(1, 12)) + m.y = Var(bounds=(19, 22)) + m.disjunction = Disjunction(expr=[ + [m.x >= 3 + m.y, m.y == 19.75], # infeasible given bounds + [m.y >= 21 + m.x], # unique solution + [m.x == m.y - 9], # x in interval [10, 12] + ]) + + out = StringIO() + mbm = TransformationFactory('gdp.mbigm') + with LoggingIntercept(out, 'pyomo.gdp.mbigm', logging.DEBUG): + mbm.apply_to(m, reduce_bound_constraints=False) + + # We mentioned the infeasibility at the DEBUG level + self.assertIn( + r"Disjunct 'disjunction_disjuncts[0]' is infeasible, deactivating", + out.getvalue().strip(), + ) + + # We just fixed the infeasible by to False + self.assertFalse(m.disjunction.disjuncts[0].active) + self.assertTrue(m.disjunction.disjuncts[0].indicator_var.fixed) + self.assertFalse(value(m.disjunction.disjuncts[0].indicator_var)) + + # the remaining constraints are transformed correctly. + cons = mbm.get_transformed_constraints( + m.disjunction.disjuncts[1].constraint[1]) + self.assertEqual(len(cons), 1) + assertExpressionsEqual( + self, + cons[0].expr, + 21 + m.x - m.y <= 0*m.disjunction.disjuncts[0].binary_indicator_var + + 12.0*m.disjunction.disjuncts[2].binary_indicator_var + ) + + cons = mbm.get_transformed_constraints( + m.disjunction.disjuncts[2].constraint[1]) + self.assertEqual(len(cons), 2) + print(cons[0].expr) + print(cons[1].expr) + assertExpressionsEqual( + self, + cons[0].expr, + 0.0*m.disjunction_disjuncts[0].binary_indicator_var - + 12.0*m.disjunction_disjuncts[1].binary_indicator_var <= m.x - (m.y - 9) + ) + assertExpressionsEqual( + self, + cons[1].expr, + m.x - (m.y - 9) <= 0.0*m.disjunction_disjuncts[0].binary_indicator_var - + 12.0*m.disjunction_disjuncts[1].binary_indicator_var + ) + From 4f0cc01c5872a2089d6dc1bb9a7500a1a3434d1e Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 21 Dec 2023 13:42:26 -0700 Subject: [PATCH 0642/1204] NFC: Adding whitespace --- pyomo/gdp/plugins/multiple_bigm.py | 34 +++++++++++------- pyomo/gdp/tests/test_mbigm.py | 55 +++++++++++++++--------------- 2 files changed, 50 insertions(+), 39 deletions(-) diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index f363922d539..48ec1177fe5 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -627,12 +627,17 @@ def _calculate_missing_M_values( if lower_M is None: scratch.obj.expr = constraint.body - constraint.lower scratch.obj.sense = minimize - results = self._config.solver.solve(other_disjunct, - load_solutions=False) - if (results.solver.termination_condition is - TerminationCondition.infeasible): - logger.debug("Disjunct '%s' is infeasible, deactivating." - % other_disjunct.name) + results = self._config.solver.solve( + other_disjunct, load_solutions=False + ) + if ( + results.solver.termination_condition + is TerminationCondition.infeasible + ): + logger.debug( + "Disjunct '%s' is infeasible, deactivating." + % other_disjunct.name + ) other_disjunct.deactivate() lower_M = 0 elif ( @@ -653,12 +658,17 @@ def _calculate_missing_M_values( if upper_M is None: scratch.obj.expr = constraint.body - constraint.upper scratch.obj.sense = maximize - results = self._config.solver.solve(other_disjunct, - load_solutions=False) - if (results.solver.termination_condition is - TerminationCondition.infeasible): - logger.debug("Disjunct '%s' is infeasible, deactivating." - % other_disjunct.name) + results = self._config.solver.solve( + other_disjunct, load_solutions=False + ) + if ( + results.solver.termination_condition + is TerminationCondition.infeasible + ): + logger.debug( + "Disjunct '%s' is infeasible, deactivating." + % other_disjunct.name + ) other_disjunct.deactivate() upper_M = 0 elif ( diff --git a/pyomo/gdp/tests/test_mbigm.py b/pyomo/gdp/tests/test_mbigm.py index 8d7c2456e3b..0cdf004a445 100644 --- a/pyomo/gdp/tests/test_mbigm.py +++ b/pyomo/gdp/tests/test_mbigm.py @@ -50,6 +50,7 @@ ) exdir = normpath(join(PYOMO_ROOT_DIR, 'examples', 'gdp')) + class CommonTests(unittest.TestCase): def check_pretty_bound_constraints(self, cons, var, bounds, lb): self.assertEqual(value(cons.upper), 0) @@ -67,6 +68,7 @@ def check_pretty_bound_constraints(self, cons, var, bounds, lb): for disj, bnd in bounds.items(): check_linear_coef(self, repn, disj.binary_indicator_var, -bnd) + class LinearModelDecisionTreeExample(CommonTests): def make_model(self): m = ConcreteModel() @@ -494,7 +496,7 @@ def test_Ms_specified_as_args_honored(self): self.check_untightened_bounds_constraint( cons[1], m.x2, m.d2, m.disjunction, {m.d1: 3, m.d3: 110}, upper=10 ) - + # TODO: If Suffixes allow tuple keys then we can support them and it will # look something like this: # def test_Ms_specified_as_suffixes_honored(self): @@ -877,6 +879,7 @@ class NestedDisjunctsInFlatGDP(unittest.TestCase): def test_declare_disjuncts_in_disjunction_rule(self): check_nested_disjuncts_in_flat_gdp(self, 'bigm') + class IndexedDisjunctiveConstraints(CommonTests): def test_empty_constraint_container_on_Disjunct(self): m = ConcreteModel() @@ -889,17 +892,12 @@ def test_empty_constraint_container_on_Disjunct(self): mbm = TransformationFactory('gdp.mbigm') mbm.apply_to(m) - + cons = mbm.get_transformed_constraints(m.e.c) self.assertEqual(len(cons), 2) - self.check_pretty_bound_constraints( - cons[0], m.x, {m.d: 2, m.e: 2.7}, lb=True - - ) - self.check_pretty_bound_constraints( - cons[1], m.x, {m.d: 3, m.e: 2.7}, lb=False - ) - + self.check_pretty_bound_constraints(cons[0], m.x, {m.d: 2, m.e: 2.7}, lb=True) + self.check_pretty_bound_constraints(cons[1], m.x, {m.d: 3, m.e: 2.7}, lb=False) + @unittest.skipUnless(gurobi_available, "Gurobi is not available") class IndexedDisjunction(unittest.TestCase): @@ -955,17 +953,20 @@ def test_two_term_indexed_disjunction(self): self.assertIs(cons_again[0], cons[0]) self.assertIs(cons_again[1], cons[1]) + class EdgeCases(unittest.TestCase): @unittest.skipUnless(gurobi_available, "Gurobi is not available") def test_calculate_Ms_infeasible_Disjunct(self): m = ConcreteModel() m.x = Var(bounds=(1, 12)) m.y = Var(bounds=(19, 22)) - m.disjunction = Disjunction(expr=[ - [m.x >= 3 + m.y, m.y == 19.75], # infeasible given bounds - [m.y >= 21 + m.x], # unique solution - [m.x == m.y - 9], # x in interval [10, 12] - ]) + m.disjunction = Disjunction( + expr=[ + [m.x >= 3 + m.y, m.y == 19.75], # infeasible given bounds + [m.y >= 21 + m.x], # unique solution + [m.x == m.y - 9], # x in interval [10, 12] + ] + ) out = StringIO() mbm = TransformationFactory('gdp.mbigm') @@ -982,33 +983,33 @@ def test_calculate_Ms_infeasible_Disjunct(self): self.assertFalse(m.disjunction.disjuncts[0].active) self.assertTrue(m.disjunction.disjuncts[0].indicator_var.fixed) self.assertFalse(value(m.disjunction.disjuncts[0].indicator_var)) - + # the remaining constraints are transformed correctly. - cons = mbm.get_transformed_constraints( - m.disjunction.disjuncts[1].constraint[1]) + cons = mbm.get_transformed_constraints(m.disjunction.disjuncts[1].constraint[1]) self.assertEqual(len(cons), 1) assertExpressionsEqual( self, cons[0].expr, - 21 + m.x - m.y <= 0*m.disjunction.disjuncts[0].binary_indicator_var + - 12.0*m.disjunction.disjuncts[2].binary_indicator_var + 21 + m.x - m.y + <= 0 * m.disjunction.disjuncts[0].binary_indicator_var + + 12.0 * m.disjunction.disjuncts[2].binary_indicator_var, ) - cons = mbm.get_transformed_constraints( - m.disjunction.disjuncts[2].constraint[1]) + cons = mbm.get_transformed_constraints(m.disjunction.disjuncts[2].constraint[1]) self.assertEqual(len(cons), 2) print(cons[0].expr) print(cons[1].expr) assertExpressionsEqual( self, cons[0].expr, - 0.0*m.disjunction_disjuncts[0].binary_indicator_var - - 12.0*m.disjunction_disjuncts[1].binary_indicator_var <= m.x - (m.y - 9) + 0.0 * m.disjunction_disjuncts[0].binary_indicator_var + - 12.0 * m.disjunction_disjuncts[1].binary_indicator_var + <= m.x - (m.y - 9), ) assertExpressionsEqual( self, cons[1].expr, - m.x - (m.y - 9) <= 0.0*m.disjunction_disjuncts[0].binary_indicator_var - - 12.0*m.disjunction_disjuncts[1].binary_indicator_var + m.x - (m.y - 9) + <= 0.0 * m.disjunction_disjuncts[0].binary_indicator_var + - 12.0 * m.disjunction_disjuncts[1].binary_indicator_var, ) - From b68cfe2c9061a6d305726612bf4037cc9b040ea8 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 21 Dec 2023 15:49:00 -0700 Subject: [PATCH 0643/1204] Updating baselines to reflect support for anonymous sets --- .../contributed_packages/mpc/overview.rst | 2 +- doc/OnlineDocs/contributed_packages/pyros.rst | 2 +- .../modeling_extensions/gdp/modeling.rst | 2 +- .../working_abstractmodels/data/raw_dicts.rst | 9 +-- examples/pyomo/tutorials/data.out | 32 ++------ examples/pyomo/tutorials/excel.out | 32 ++------ examples/pyomo/tutorials/param.out | 19 ++--- examples/pyomo/tutorials/set.out | 34 ++------ examples/pyomo/tutorials/table.out | 32 ++------ examples/pyomobook/blocks-ch/blocks_gen.txt | 76 +++++++----------- .../blocks-ch/lotsizing_uncertain.txt | 29 ++----- examples/pyomobook/dae-ch/path_constraint.txt | 7 +- .../optimization-ch/ConcHLinScript.txt | 4 +- .../pyomobook/optimization-ch/ConcreteH.txt | 9 +-- .../optimization-ch/ConcreteHLinear.txt | 9 +-- .../overview-ch/wl_concrete_script.txt | 2 +- examples/pyomobook/overview-ch/wl_excel.txt | 2 +- examples/pyomobook/overview-ch/wl_list.txt | 30 ++----- .../pyomo-components-ch/con_declaration.txt | 46 +++-------- .../pyomo-components-ch/examples.txt | 9 +-- .../pyomo-components-ch/expr_declaration.txt | 14 +--- .../pyomo-components-ch/obj_declaration.txt | 10 +-- .../pyomo-components-ch/param_declaration.txt | 9 +-- .../param_initialization.txt | 24 ++---- .../pyomo-components-ch/set_declaration.txt | 14 +--- .../set_initialization.txt | 19 ++--- .../pyomobook/scripts-ch/warehouse_cuts.txt | 12 +-- .../pyomobook/scripts-ch/warehouse_script.txt | 40 ++-------- pyomo/contrib/pyros/tests/test_grcs.py | 4 - pyomo/core/base/reference.py | 8 +- pyomo/core/tests/unit/test_block.py | 11 +-- pyomo/core/tests/unit/test_componentuid.py | 16 +--- pyomo/core/tests/unit/test_connector.py | 32 ++++---- pyomo/core/tests/unit/test_expr5.txt | 9 +-- pyomo/core/tests/unit/test_expression.py | 12 +-- pyomo/core/tests/unit/test_reference.py | 11 ++- pyomo/core/tests/unit/test_set.py | 78 ++++++++----------- pyomo/core/tests/unit/test_visitor.py | 1 - pyomo/core/tests/unit/varpprint.txt | 14 +--- pyomo/dae/tests/test_diffvar.py | 1 - pyomo/mpec/tests/cov2_None.txt | 2 +- pyomo/mpec/tests/cov2_mpec.nl.txt | 9 +-- .../tests/cov2_mpec.simple_disjunction.txt | 2 +- .../mpec/tests/cov2_mpec.simple_nonlinear.txt | 2 +- pyomo/mpec/tests/cov2_mpec.standard_form.txt | 2 +- pyomo/mpec/tests/list1_None.txt | 2 +- pyomo/mpec/tests/list1_mpec.nl.txt | 9 +-- .../tests/list1_mpec.simple_disjunction.txt | 2 +- .../tests/list1_mpec.simple_nonlinear.txt | 2 +- pyomo/mpec/tests/list1_mpec.standard_form.txt | 2 +- pyomo/mpec/tests/list2_None.txt | 2 +- pyomo/mpec/tests/list2_mpec.nl.txt | 9 +-- .../tests/list2_mpec.simple_disjunction.txt | 2 +- .../tests/list2_mpec.simple_nonlinear.txt | 2 +- pyomo/mpec/tests/list2_mpec.standard_form.txt | 2 +- pyomo/mpec/tests/list5_None.txt | 2 +- pyomo/mpec/tests/list5_mpec.nl.txt | 9 +-- .../tests/list5_mpec.simple_disjunction.txt | 2 +- .../tests/list5_mpec.simple_nonlinear.txt | 2 +- pyomo/mpec/tests/list5_mpec.standard_form.txt | 2 +- pyomo/mpec/tests/t10_None.txt | 2 +- pyomo/mpec/tests/t10_mpec.nl.txt | 9 +-- .../tests/t10_mpec.simple_disjunction.txt | 2 +- .../mpec/tests/t10_mpec.simple_nonlinear.txt | 2 +- pyomo/mpec/tests/t10_mpec.standard_form.txt | 2 +- pyomo/mpec/tests/t13_None.txt | 2 +- pyomo/mpec/tests/t13_mpec.nl.txt | 9 +-- .../tests/t13_mpec.simple_disjunction.txt | 2 +- .../mpec/tests/t13_mpec.simple_nonlinear.txt | 2 +- pyomo/mpec/tests/t13_mpec.standard_form.txt | 2 +- pyomo/network/tests/test_arc.py | 20 ++--- 71 files changed, 262 insertions(+), 586 deletions(-) diff --git a/doc/OnlineDocs/contributed_packages/mpc/overview.rst b/doc/OnlineDocs/contributed_packages/mpc/overview.rst index f5dbe85e523..f3bc7504b59 100644 --- a/doc/OnlineDocs/contributed_packages/mpc/overview.rst +++ b/doc/OnlineDocs/contributed_packages/mpc/overview.rst @@ -189,7 +189,7 @@ a tracking cost expression. >>> m.setpoint_idx = var_set >>> m.tracking_cost = tr_cost >>> m.tracking_cost.pprint() - tracking_cost : Size=6, Index=tracking_cost_index + tracking_cost : Size=6, Index=setpoint_idx*time Key : Expression (0, 0) : (var[0,A] - 0.5)**2 (0, 1) : (var[1,A] - 0.5)**2 diff --git a/doc/OnlineDocs/contributed_packages/pyros.rst b/doc/OnlineDocs/contributed_packages/pyros.rst index 133258fb9b8..f8aa7e36e37 100644 --- a/doc/OnlineDocs/contributed_packages/pyros.rst +++ b/doc/OnlineDocs/contributed_packages/pyros.rst @@ -539,7 +539,7 @@ correspond to first-stage degrees of freedom. ... load_solution=False, ... ) ============================================================================== - PyROS: The Pyomo Robust Optimization Solver. + PyROS: The Pyomo Robust Optimization Solver... ... ------------------------------------------------------------------------------ Robust optimal solution identified. diff --git a/doc/OnlineDocs/modeling_extensions/gdp/modeling.rst b/doc/OnlineDocs/modeling_extensions/gdp/modeling.rst index b70e37d5935..996ebcb0366 100644 --- a/doc/OnlineDocs/modeling_extensions/gdp/modeling.rst +++ b/doc/OnlineDocs/modeling_extensions/gdp/modeling.rst @@ -166,7 +166,7 @@ Usage: >>> TransformationFactory('core.logical_to_linear').apply_to(m) >>> # constraint auto-generated by transformation >>> m.logic_to_linear.transformed_constraints.pprint() - transformed_constraints : Size=1, Index=logic_to_linear.transformed_constraints_index, Active=True + transformed_constraints : Size=1, Index={1}, Active=True Key : Lower : Body : Upper : Active 1 : 3.0 : Y_asbinary[1] + Y_asbinary[2] + Y_asbinary[3] + Y_asbinary[4] : +Inf : True diff --git a/doc/OnlineDocs/working_abstractmodels/data/raw_dicts.rst b/doc/OnlineDocs/working_abstractmodels/data/raw_dicts.rst index e10042b3ceb..f78e349c28b 100644 --- a/doc/OnlineDocs/working_abstractmodels/data/raw_dicts.rst +++ b/doc/OnlineDocs/working_abstractmodels/data/raw_dicts.rst @@ -28,13 +28,10 @@ components, the required data dictionary maps the implicit index ... }} >>> i = m.create_instance(data) >>> i.pprint() - 2 Set Declarations + 1 Set Declarations I : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {1, 2, 3} - r_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : I*I : 9 : {(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3), (3, 1), (3, 2), (3, 3)} 3 Param Declarations p : Size=1, Index=None, Domain=Any, Default=None, Mutable=False @@ -45,12 +42,12 @@ components, the required data dictionary maps the implicit index 1 : 10 2 : 20 3 : 30 - r : Size=9, Index=r_index, Domain=Any, Default=0, Mutable=False + r : Size=9, Index=I*I, Domain=Any, Default=0, Mutable=False Key : Value (1, 1) : 110 (1, 2) : 120 (2, 3) : 230 - 5 Declarations: I p q r_index r + 4 Declarations: I p q r diff --git a/examples/pyomo/tutorials/data.out b/examples/pyomo/tutorials/data.out index d1353f87858..7dce6012e2f 100644 --- a/examples/pyomo/tutorials/data.out +++ b/examples/pyomo/tutorials/data.out @@ -1,4 +1,4 @@ -20 Set Declarations +14 Set Declarations A : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {'A1', 'A2', 'A3'} @@ -9,30 +9,18 @@ Key : Dimen : Domain : Size : Members None : 2 : A*B : 9 : {('A1', 1), ('A1', 2), ('A1', 3), ('A2', 1), ('A2', 2), ('A2', 3), ('A3', 1), ('A3', 2), ('A3', 3)} D : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 2 : D_domain : 3 : {('A1', 1), ('A2', 2), ('A3', 3)} - D_domain : Size=1, Index=None, Ordered=True Key : Dimen : Domain : Size : Members - None : 2 : A*B : 9 : {('A1', 1), ('A1', 2), ('A1', 3), ('A2', 1), ('A2', 2), ('A2', 3), ('A3', 1), ('A3', 2), ('A3', 3)} + None : 2 : A*B : 3 : {('A1', 1), ('A2', 2), ('A3', 3)} E : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 3 : E_domain : 6 : {('A1', 1, 'A1'), ('A1', 1, 'A2'), ('A2', 2, 'A2'), ('A2', 2, 'A3'), ('A3', 3, 'A1'), ('A3', 3, 'A3')} - E_domain : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 3 : E_domain_index_0*A : 27 : {('A1', 1, 'A1'), ('A1', 1, 'A2'), ('A1', 1, 'A3'), ('A1', 2, 'A1'), ('A1', 2, 'A2'), ('A1', 2, 'A3'), ('A1', 3, 'A1'), ('A1', 3, 'A2'), ('A1', 3, 'A3'), ('A2', 1, 'A1'), ('A2', 1, 'A2'), ('A2', 1, 'A3'), ('A2', 2, 'A1'), ('A2', 2, 'A2'), ('A2', 2, 'A3'), ('A2', 3, 'A1'), ('A2', 3, 'A2'), ('A2', 3, 'A3'), ('A3', 1, 'A1'), ('A3', 1, 'A2'), ('A3', 1, 'A3'), ('A3', 2, 'A1'), ('A3', 2, 'A2'), ('A3', 2, 'A3'), ('A3', 3, 'A1'), ('A3', 3, 'A2'), ('A3', 3, 'A3')} - E_domain_index_0 : Size=1, Index=None, Ordered=True Key : Dimen : Domain : Size : Members - None : 2 : A*B : 9 : {('A1', 1), ('A1', 2), ('A1', 3), ('A2', 1), ('A2', 2), ('A2', 3), ('A3', 1), ('A3', 2), ('A3', 3)} + None : 3 : A*B*A : 6 : {('A1', 1, 'A1'), ('A1', 1, 'A2'), ('A2', 2, 'A2'), ('A2', 2, 'A3'), ('A3', 3, 'A1'), ('A3', 3, 'A3')} F : Size=3, Index=A, Ordered=Insertion Key : Dimen : Domain : Size : Members A1 : 1 : Any : 3 : {1, 3, 5} A2 : 1 : Any : 3 : {2, 4, 6} A3 : 1 : Any : 3 : {3, 5, 7} - G : Size=0, Index=G_index, Ordered=Insertion + G : Size=0, Index=A*B, Ordered=Insertion Key : Dimen : Domain : Size : Members - G_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*B : 9 : {('A1', 1), ('A1', 2), ('A1', 3), ('A2', 1), ('A2', 2), ('A2', 3), ('A3', 1), ('A3', 2), ('A3', 3)} H : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {'H1', 'H2', 'H3'} @@ -45,12 +33,6 @@ K : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 2 : Any : 3 : {('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')} - T_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*I : 12 : {('A1', 'I1'), ('A1', 'I2'), ('A1', 'I3'), ('A1', 'I4'), ('A2', 'I1'), ('A2', 'I2'), ('A2', 'I3'), ('A2', 'I4'), ('A3', 'I1'), ('A3', 'I2'), ('A3', 'I3'), ('A3', 'I4')} - U_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : I*A : 12 : {('I1', 'A1'), ('I1', 'A2'), ('I1', 'A3'), ('I2', 'A1'), ('I2', 'A2'), ('I2', 'A3'), ('I3', 'A1'), ('I3', 'A2'), ('I3', 'A3'), ('I4', 'A1'), ('I4', 'A2'), ('I4', 'A3')} x : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {'A1', 'A2', 'A3'} @@ -116,7 +98,7 @@ Key : Value A1 : 3.3 A3 : 3.5 - T : Size=12, Index=T_index, Domain=Any, Default=None, Mutable=False + T : Size=12, Index=A*I, Domain=Any, Default=None, Mutable=False Key : Value ('A1', 'I1') : 1.3 ('A1', 'I2') : 1.4 @@ -130,7 +112,7 @@ ('A3', 'I2') : 3.4 ('A3', 'I3') : 3.5 ('A3', 'I4') : 3.6 - U : Size=12, Index=U_index, Domain=Any, Default=None, Mutable=False + U : Size=12, Index=I*A, Domain=Any, Default=None, Mutable=False Key : Value ('I1', 'A1') : 1.3 ('I1', 'A2') : 2.3 @@ -166,4 +148,4 @@ Key : Value None : 2 -38 Declarations: A B C D_domain D E_domain_index_0 E_domain E F G_index G H I J K Z ZZ Y X W U_index U T_index T S R Q P PP O z y x M N MM MMM NNN +32 Declarations: A B C D E F G H I J K Z ZZ Y X W U T S R Q P PP O z y x M N MM MMM NNN diff --git a/examples/pyomo/tutorials/excel.out b/examples/pyomo/tutorials/excel.out index 5064d4fa511..5e30827f7ae 100644 --- a/examples/pyomo/tutorials/excel.out +++ b/examples/pyomo/tutorials/excel.out @@ -1,4 +1,4 @@ -16 Set Declarations +10 Set Declarations A : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {'A1', 'A2', 'A3'} @@ -9,27 +9,15 @@ Key : Dimen : Domain : Size : Members None : 2 : A*B : 9 : {('A1', 1.0), ('A1', 2.0), ('A1', 3.0), ('A2', 1.0), ('A2', 2.0), ('A2', 3.0), ('A3', 1.0), ('A3', 2.0), ('A3', 3.0)} D : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 2 : D_domain : 3 : {('A1', 1.0), ('A2', 2.0), ('A3', 3.0)} - D_domain : Size=1, Index=None, Ordered=True Key : Dimen : Domain : Size : Members - None : 2 : A*B : 9 : {('A1', 1.0), ('A1', 2.0), ('A1', 3.0), ('A2', 1.0), ('A2', 2.0), ('A2', 3.0), ('A3', 1.0), ('A3', 2.0), ('A3', 3.0)} + None : 2 : A*B : 3 : {('A1', 1.0), ('A2', 2.0), ('A3', 3.0)} E : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 3 : E_domain : 6 : {('A1', 1.0, 'A1'), ('A1', 1.0, 'A2'), ('A2', 2.0, 'A2'), ('A2', 2.0, 'A3'), ('A3', 3.0, 'A1'), ('A3', 3.0, 'A3')} - E_domain : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 3 : E_domain_index_0*A : 27 : {('A1', 1.0, 'A1'), ('A1', 1.0, 'A2'), ('A1', 1.0, 'A3'), ('A1', 2.0, 'A1'), ('A1', 2.0, 'A2'), ('A1', 2.0, 'A3'), ('A1', 3.0, 'A1'), ('A1', 3.0, 'A2'), ('A1', 3.0, 'A3'), ('A2', 1.0, 'A1'), ('A2', 1.0, 'A2'), ('A2', 1.0, 'A3'), ('A2', 2.0, 'A1'), ('A2', 2.0, 'A2'), ('A2', 2.0, 'A3'), ('A2', 3.0, 'A1'), ('A2', 3.0, 'A2'), ('A2', 3.0, 'A3'), ('A3', 1.0, 'A1'), ('A3', 1.0, 'A2'), ('A3', 1.0, 'A3'), ('A3', 2.0, 'A1'), ('A3', 2.0, 'A2'), ('A3', 2.0, 'A3'), ('A3', 3.0, 'A1'), ('A3', 3.0, 'A2'), ('A3', 3.0, 'A3')} - E_domain_index_0 : Size=1, Index=None, Ordered=True Key : Dimen : Domain : Size : Members - None : 2 : A*B : 9 : {('A1', 1.0), ('A1', 2.0), ('A1', 3.0), ('A2', 1.0), ('A2', 2.0), ('A2', 3.0), ('A3', 1.0), ('A3', 2.0), ('A3', 3.0)} + None : 3 : A*B : 6 : {('A1', 1.0, 'A1'), ('A1', 1.0, 'A2'), ('A2', 2.0, 'A2'), ('A2', 2.0, 'A3'), ('A3', 3.0, 'A1'), ('A3', 3.0, 'A3')} F : Size=0, Index=A, Ordered=Insertion Key : Dimen : Domain : Size : Members - G : Size=0, Index=G_index, Ordered=Insertion + G : Size=0, Index=A*B, Ordered=Insertion Key : Dimen : Domain : Size : Members - G_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*B : 9 : {('A1', 1.0), ('A1', 2.0), ('A1', 3.0), ('A2', 1.0), ('A2', 2.0), ('A2', 3.0), ('A3', 1.0), ('A3', 2.0), ('A3', 3.0)} H : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {'H1', 'H2', 'H3'} @@ -39,12 +27,6 @@ J : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 2 : Any : 3 : {('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')} - T_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*I : 12 : {('A1', 'I1'), ('A1', 'I2'), ('A1', 'I3'), ('A1', 'I4'), ('A2', 'I1'), ('A2', 'I2'), ('A2', 'I3'), ('A2', 'I4'), ('A3', 'I1'), ('A3', 'I2'), ('A3', 'I3'), ('A3', 'I4')} - U_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : I*A : 12 : {('I1', 'A1'), ('I1', 'A2'), ('I1', 'A3'), ('I2', 'A1'), ('I2', 'A2'), ('I2', 'A3'), ('I3', 'A1'), ('I3', 'A2'), ('I3', 'A3'), ('I4', 'A1'), ('I4', 'A2'), ('I4', 'A3')} 12 Param Declarations O : Size=3, Index=J, Domain=Reals, Default=None, Mutable=False @@ -76,7 +58,7 @@ Key : Value A1 : 3.3 A3 : 3.5 - T : Size=12, Index=T_index, Domain=Any, Default=None, Mutable=False + T : Size=12, Index=A*I, Domain=Any, Default=None, Mutable=False Key : Value ('A1', 'I1') : 1.3 ('A1', 'I2') : 1.4 @@ -90,7 +72,7 @@ ('A3', 'I2') : 3.4 ('A3', 'I3') : 3.5 ('A3', 'I4') : 3.6 - U : Size=12, Index=U_index, Domain=Any, Default=None, Mutable=False + U : Size=12, Index=I*A, Domain=Any, Default=None, Mutable=False Key : Value ('I1', 'A1') : 1.3 ('I1', 'A2') : 2.3 @@ -123,4 +105,4 @@ Key : Value None : 1.01 -28 Declarations: A B C D_domain D E_domain_index_0 E_domain E F G_index G H I J Z Y X W U_index U T_index T S R Q P PP O +22 Declarations: A B C D E F G H I J Z Y X W U T S R Q P PP O diff --git a/examples/pyomo/tutorials/param.out b/examples/pyomo/tutorials/param.out index 57e6a752ea5..ea258f5b493 100644 --- a/examples/pyomo/tutorials/param.out +++ b/examples/pyomo/tutorials/param.out @@ -1,22 +1,13 @@ -5 Set Declarations +2 Set Declarations A : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 4 : {2, 4, 6, 8} B : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {1, 2, 3} - R_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*B : 12 : {(2, 1), (2, 2), (2, 3), (4, 1), (4, 2), (4, 3), (6, 1), (6, 2), (6, 3), (8, 1), (8, 2), (8, 3)} - W_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*B : 12 : {(2, 1), (2, 2), (2, 3), (4, 1), (4, 2), (4, 3), (6, 1), (6, 2), (6, 3), (8, 1), (8, 2), (8, 3)} - X_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*B : 12 : {(2, 1), (2, 2), (2, 3), (4, 1), (4, 2), (4, 3), (6, 1), (6, 2), (6, 3), (8, 1), (8, 2), (8, 3)} 9 Param Declarations - R : Size=12, Index=R_index, Domain=Any, Default=99.0, Mutable=False + R : Size=12, Index=A*B, Domain=Any, Default=99.0, Mutable=False Key : Value (2, 1) : 1 (2, 2) : 1 @@ -35,7 +26,7 @@ 1 : 1 2 : 2 3 : 9 - W : Size=12, Index=W_index, Domain=Any, Default=None, Mutable=False + W : Size=12, Index=A*B, Domain=Any, Default=None, Mutable=False Key : Value (2, 1) : 2 (2, 2) : 4 @@ -49,7 +40,7 @@ (8, 1) : 8 (8, 2) : 16 (8, 3) : 24 - X : Size=12, Index=X_index, Domain=Any, Default=None, Mutable=False + X : Size=12, Index=A*B, Domain=Any, Default=None, Mutable=False Key : Value (2, 1) : 1.3 (2, 2) : 1.4 @@ -73,4 +64,4 @@ Key : Value None : 1.1 -14 Declarations: A B Z Y X_index X W_index W V U T S R_index R +11 Declarations: A B Z Y X W V U T S R diff --git a/examples/pyomo/tutorials/set.out b/examples/pyomo/tutorials/set.out index b01b666c012..818977f6155 100644 --- a/examples/pyomo/tutorials/set.out +++ b/examples/pyomo/tutorials/set.out @@ -1,15 +1,12 @@ -28 Set Declarations +23 Set Declarations A : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {1, 2, 3} B : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 4 : {2, 3, 4, 5} - C : Size=0, Index=C_index, Ordered=Insertion + C : Size=0, Index=A*B, Ordered=Insertion Key : Dimen : Domain : Size : Members - C_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*B : 12 : {(1, 2), (1, 3), (1, 4), (1, 5), (2, 2), (2, 3), (2, 4), (2, 5), (3, 2), (3, 3), (3, 4), (3, 5)} D : Size=1, Index=None, Ordered=True Key : Dimen : Domain : Size : Members None : 1 : A | B : 5 : {1, 2, 3, 4, 5} @@ -26,15 +23,9 @@ Key : Dimen : Domain : Size : Members None : 2 : A*B : 12 : {(1, 2), (1, 3), (1, 4), (1, 5), (2, 2), (2, 3), (2, 4), (2, 5), (3, 2), (3, 3), (3, 4), (3, 5)} Hsub : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 2 : Hsub_domain : 3 : {(1, 2), (1, 3), (3, 3)} - Hsub_domain : Size=1, Index=None, Ordered=True Key : Dimen : Domain : Size : Members - None : 2 : A*B : 12 : {(1, 2), (1, 3), (1, 4), (1, 5), (2, 2), (2, 3), (2, 4), (2, 5), (3, 2), (3, 3), (3, 4), (3, 5)} + None : 2 : A*B : 3 : {(1, 2), (1, 3), (3, 3)} I : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 2 : I_domain : 12 : {(1, 2), (1, 3), (1, 4), (1, 5), (2, 2), (2, 3), (2, 4), (2, 5), (3, 2), (3, 3), (3, 4), (3, 5)} - I_domain : Size=1, Index=None, Ordered=True Key : Dimen : Domain : Size : Members None : 2 : A*B : 12 : {(1, 2), (1, 3), (1, 4), (1, 5), (2, 2), (2, 3), (2, 4), (2, 5), (3, 2), (3, 3), (3, 4), (3, 5)} J : Size=1, Index=None, Ordered=Insertion @@ -53,15 +44,12 @@ Key : Dimen : Domain : Size : Members None : 1 : Any : 2 : {1, 3} N : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 2 : N_domain : 0 : {} - N_domain : Size=1, Index=None, Ordered=True Key : Dimen : Domain : Size : Members - None : 2 : A*B : 12 : {(1, 2), (1, 3), (1, 4), (1, 5), (2, 2), (2, 3), (2, 4), (2, 5), (3, 2), (3, 3), (3, 4), (3, 5)} + None : 2 : A*B : 0 : {} O : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : -- : Any : 0 : {} - P : Size=16, Index=P_index, Ordered=Insertion + P : Size=16, Index=B*B, Ordered=Insertion Key : Dimen : Domain : Size : Members (2, 2) : 1 : Any : 4 : {0, 1, 2, 3} (2, 3) : 1 : Any : 6 : {0, 1, 2, 3, 4, 5} @@ -79,9 +67,6 @@ (5, 3) : 1 : Any : 15 : {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14} (5, 4) : 1 : Any : 20 : {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19} (5, 5) : 1 : Any : 25 : {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24} - P_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : B*B : 16 : {(2, 2), (2, 3), (2, 4), (2, 5), (3, 2), (3, 3), (3, 4), (3, 5), (4, 2), (4, 3), (4, 4), (4, 5), (5, 2), (5, 3), (5, 4), (5, 5)} R : Size=3, Index=B, Ordered=Insertion Key : Dimen : Domain : Size : Members 2 : 1 : Any : 3 : {1, 3, 5} @@ -98,16 +83,11 @@ U : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 5 : {1, 2, 6, 24, 120} - V : Size=4, Index=V_index, Ordered=Insertion + V : Size=4, Index=[1:4], Ordered=Insertion Key : Dimen : Domain : Size : Members 1 : 1 : Any : 5 : {1, 2, 3, 4, 5} 2 : 1 : Any : 5 : {1, 3, 5, 7, 9} 3 : 1 : Any : 5 : {1, 4, 7, 10, 13} 4 : 1 : Any : 5 : {1, 5, 9, 13, 17} -1 RangeSet Declarations - V_index : Dimen=1, Size=4, Bounds=(1, 4) - Key : Finite : Members - None : True : [1:4] - -29 Declarations: A B C_index C D E F G H Hsub_domain Hsub I_domain I J K K_2 L M N_domain N O P_index P R S T U V_index V +23 Declarations: A B C D E F G H Hsub I J K K_2 L M N O P R S T U V diff --git a/examples/pyomo/tutorials/table.out b/examples/pyomo/tutorials/table.out index 1eba28afd19..75e2b0aee33 100644 --- a/examples/pyomo/tutorials/table.out +++ b/examples/pyomo/tutorials/table.out @@ -1,4 +1,4 @@ -16 Set Declarations +10 Set Declarations A : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {'A1', 'A2', 'A3'} @@ -9,27 +9,15 @@ Key : Dimen : Domain : Size : Members None : 2 : A*B : 9 : {('A1', 1), ('A1', 2), ('A1', 3), ('A2', 1), ('A2', 2), ('A2', 3), ('A3', 1), ('A3', 2), ('A3', 3)} D : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 2 : D_domain : 3 : {('A1', 1), ('A2', 2), ('A3', 3)} - D_domain : Size=1, Index=None, Ordered=True Key : Dimen : Domain : Size : Members - None : 2 : A*B : 9 : {('A1', 1), ('A1', 2), ('A1', 3), ('A2', 1), ('A2', 2), ('A2', 3), ('A3', 1), ('A3', 2), ('A3', 3)} + None : 2 : A*B : 3 : {('A1', 1), ('A2', 2), ('A3', 3)} E : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 3 : E_domain : 6 : {('A1', 1, 'A1'), ('A1', 1, 'A2'), ('A2', 2, 'A2'), ('A2', 2, 'A3'), ('A3', 3, 'A1'), ('A3', 3, 'A3')} - E_domain : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 3 : E_domain_index_0*A : 27 : {('A1', 1, 'A1'), ('A1', 1, 'A2'), ('A1', 1, 'A3'), ('A1', 2, 'A1'), ('A1', 2, 'A2'), ('A1', 2, 'A3'), ('A1', 3, 'A1'), ('A1', 3, 'A2'), ('A1', 3, 'A3'), ('A2', 1, 'A1'), ('A2', 1, 'A2'), ('A2', 1, 'A3'), ('A2', 2, 'A1'), ('A2', 2, 'A2'), ('A2', 2, 'A3'), ('A2', 3, 'A1'), ('A2', 3, 'A2'), ('A2', 3, 'A3'), ('A3', 1, 'A1'), ('A3', 1, 'A2'), ('A3', 1, 'A3'), ('A3', 2, 'A1'), ('A3', 2, 'A2'), ('A3', 2, 'A3'), ('A3', 3, 'A1'), ('A3', 3, 'A2'), ('A3', 3, 'A3')} - E_domain_index_0 : Size=1, Index=None, Ordered=True Key : Dimen : Domain : Size : Members - None : 2 : A*B : 9 : {('A1', 1), ('A1', 2), ('A1', 3), ('A2', 1), ('A2', 2), ('A2', 3), ('A3', 1), ('A3', 2), ('A3', 3)} + None : 3 : A*B*A : 6 : {('A1', 1, 'A1'), ('A1', 1, 'A2'), ('A2', 2, 'A2'), ('A2', 2, 'A3'), ('A3', 3, 'A1'), ('A3', 3, 'A3')} F : Size=0, Index=A, Ordered=Insertion Key : Dimen : Domain : Size : Members - G : Size=0, Index=G_index, Ordered=Insertion + G : Size=0, Index=A*B, Ordered=Insertion Key : Dimen : Domain : Size : Members - G_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*B : 9 : {('A1', 1), ('A1', 2), ('A1', 3), ('A2', 1), ('A2', 2), ('A2', 3), ('A3', 1), ('A3', 2), ('A3', 3)} H : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {'H1', 'H2', 'H3'} @@ -39,12 +27,6 @@ J : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 2 : Any : 3 : {('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')} - T_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*I : 12 : {('A1', 'I1'), ('A1', 'I2'), ('A1', 'I3'), ('A1', 'I4'), ('A2', 'I1'), ('A2', 'I2'), ('A2', 'I3'), ('A2', 'I4'), ('A3', 'I1'), ('A3', 'I2'), ('A3', 'I3'), ('A3', 'I4')} - U_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : I*A : 12 : {('I1', 'A1'), ('I1', 'A2'), ('I1', 'A3'), ('I2', 'A1'), ('I2', 'A2'), ('I2', 'A3'), ('I3', 'A1'), ('I3', 'A2'), ('I3', 'A3'), ('I4', 'A1'), ('I4', 'A2'), ('I4', 'A3')} 12 Param Declarations O : Size=3, Index=J, Domain=Reals, Default=None, Mutable=False @@ -76,7 +58,7 @@ Key : Value A1 : 3.3 A3 : 3.5 - T : Size=12, Index=T_index, Domain=Any, Default=None, Mutable=False + T : Size=12, Index=A*I, Domain=Any, Default=None, Mutable=False Key : Value ('A1', 'I1') : 1.3 ('A1', 'I2') : 1.4 @@ -90,7 +72,7 @@ ('A3', 'I2') : 3.4 ('A3', 'I3') : 3.5 ('A3', 'I4') : 3.6 - U : Size=12, Index=U_index, Domain=Any, Default=None, Mutable=False + U : Size=12, Index=I*A, Domain=Any, Default=None, Mutable=False Key : Value ('I1', 'A1') : 1.3 ('I1', 'A2') : 2.3 @@ -123,4 +105,4 @@ Key : Value None : 1.01 -28 Declarations: A B C D_domain D E_domain_index_0 E_domain E F G_index G H I J Z Y X W U_index U T_index T S R Q P PP O +22 Declarations: A B C D E F G H I J Z Y X W U T S R Q P PP O diff --git a/examples/pyomobook/blocks-ch/blocks_gen.txt b/examples/pyomobook/blocks-ch/blocks_gen.txt index 63d634b3b95..1636f7e4590 100644 --- a/examples/pyomobook/blocks-ch/blocks_gen.txt +++ b/examples/pyomobook/blocks-ch/blocks_gen.txt @@ -9,13 +9,8 @@ 1 Block Declarations Generator : Size=2, Index=GEN_UNITS, Active=True Generator[G_EAST] : Active=True - 1 Set Declarations - CostCoef_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 2 : {1, 2} - 3 Param Declarations - CostCoef : Size=0, Index=Generator[G_EAST].CostCoef_index, Domain=Any, Default=None, Mutable=False + CostCoef : Size=0, Index={1, 2}, Domain=Any, Default=None, Mutable=False Key : Value MaxPower : Size=1, Index=None, Domain=NonNegativeReals, Default=None, Mutable=False Key : Value @@ -27,11 +22,11 @@ 2 Var Declarations Power : Size=5, Index=TIME Key : Lower : Value : Upper : Fixed : Stale : Domain - 0 : 0 : 120.0 : 500 : False : False : Reals - 1 : 0 : 145.0 : 500 : False : False : Reals - 2 : 0 : 119.0 : 500 : False : False : Reals - 3 : 0 : 42.0 : 500 : False : False : Reals - 4 : 0 : 190.0 : 500 : False : False : Reals + 0 : 0 : 120.0 : 500.0 : False : False : Reals + 1 : 0 : 145.0 : 500.0 : False : False : Reals + 2 : 0 : 119.0 : 500.0 : False : False : Reals + 3 : 0 : 42.0 : 500.0 : False : False : Reals + 4 : 0 : 190.0 : 500.0 : False : False : Reals UnitOn : Size=5, Index=TIME Key : Lower : Value : Upper : Fixed : Stale : Domain 0 : 0 : None : 1 : False : True : Binary @@ -57,15 +52,10 @@ 3 : -50.0 : Generator[G_EAST].Power[3] - Generator[G_EAST].Power[2] : Generator[G_EAST].RampLimit : True 4 : -50.0 : Generator[G_EAST].Power[4] - Generator[G_EAST].Power[3] : Generator[G_EAST].RampLimit : True - 8 Declarations: MaxPower RampLimit Power UnitOn limit_ramp CostCoef_index CostCoef Cost + 7 Declarations: MaxPower RampLimit Power UnitOn limit_ramp CostCoef Cost Generator[G_MAIN] : Active=True - 1 Set Declarations - CostCoef_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 2 : {1, 2} - 3 Param Declarations - CostCoef : Size=0, Index=Generator[G_MAIN].CostCoef_index, Domain=Any, Default=None, Mutable=False + CostCoef : Size=0, Index={1, 2}, Domain=Any, Default=None, Mutable=False Key : Value MaxPower : Size=1, Index=None, Domain=NonNegativeReals, Default=None, Mutable=False Key : Value @@ -77,11 +67,11 @@ 2 Var Declarations Power : Size=5, Index=TIME Key : Lower : Value : Upper : Fixed : Stale : Domain - 0 : 0 : 120.0 : 500 : False : False : Reals - 1 : 0 : 145.0 : 500 : False : False : Reals - 2 : 0 : 119.0 : 500 : False : False : Reals - 3 : 0 : 42.0 : 500 : False : False : Reals - 4 : 0 : 190.0 : 500 : False : False : Reals + 0 : 0 : 120.0 : 500.0 : False : False : Reals + 1 : 0 : 145.0 : 500.0 : False : False : Reals + 2 : 0 : 119.0 : 500.0 : False : False : Reals + 3 : 0 : 42.0 : 500.0 : False : False : Reals + 4 : 0 : 190.0 : 500.0 : False : False : Reals UnitOn : Size=5, Index=TIME Key : Lower : Value : Upper : Fixed : Stale : Domain 0 : 0 : None : 1 : False : True : Binary @@ -107,7 +97,7 @@ 3 : -50.0 : Generator[G_MAIN].Power[3] - Generator[G_MAIN].Power[2] : Generator[G_MAIN].RampLimit : True 4 : -50.0 : Generator[G_MAIN].Power[4] - Generator[G_MAIN].Power[3] : Generator[G_MAIN].RampLimit : True - 8 Declarations: MaxPower RampLimit Power UnitOn limit_ramp CostCoef_index CostCoef Cost + 7 Declarations: MaxPower RampLimit Power UnitOn limit_ramp CostCoef Cost 3 Declarations: TIME GEN_UNITS Generator 2 Set Declarations @@ -121,13 +111,8 @@ 1 Block Declarations Generator : Size=2, Index=GEN_UNITS, Active=True Generator[G_EAST] : Active=True - 1 Set Declarations - CostCoef_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 2 : {1, 2} - 3 Param Declarations - CostCoef : Size=0, Index=Generator[G_EAST].CostCoef_index, Domain=Any, Default=None, Mutable=False + CostCoef : Size=0, Index={1, 2}, Domain=Any, Default=None, Mutable=False Key : Value MaxPower : Size=1, Index=None, Domain=NonNegativeReals, Default=None, Mutable=False Key : Value @@ -139,11 +124,11 @@ 2 Var Declarations Power : Size=5, Index=TIME Key : Lower : Value : Upper : Fixed : Stale : Domain - 0 : 0 : 120.0 : 500 : False : False : Reals - 1 : 0 : 145.0 : 500 : False : False : Reals - 2 : 0 : 119.0 : 500 : False : False : Reals - 3 : 0 : 42.0 : 500 : False : False : Reals - 4 : 0 : 190.0 : 500 : False : False : Reals + 0 : 0 : 120.0 : 500.0 : False : False : Reals + 1 : 0 : 145.0 : 500.0 : False : False : Reals + 2 : 0 : 119.0 : 500.0 : False : False : Reals + 3 : 0 : 42.0 : 500.0 : False : False : Reals + 4 : 0 : 190.0 : 500.0 : False : False : Reals UnitOn : Size=5, Index=TIME Key : Lower : Value : Upper : Fixed : Stale : Domain 0 : 0 : None : 1 : False : True : Binary @@ -169,15 +154,10 @@ 3 : -50.0 : Generator[G_EAST].Power[3] - Generator[G_EAST].Power[2] : Generator[G_EAST].RampLimit : True 4 : -50.0 : Generator[G_EAST].Power[4] - Generator[G_EAST].Power[3] : Generator[G_EAST].RampLimit : True - 8 Declarations: MaxPower RampLimit Power UnitOn limit_ramp CostCoef_index CostCoef Cost + 7 Declarations: MaxPower RampLimit Power UnitOn limit_ramp CostCoef Cost Generator[G_MAIN] : Active=True - 1 Set Declarations - CostCoef_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 2 : {1, 2} - 3 Param Declarations - CostCoef : Size=0, Index=Generator[G_MAIN].CostCoef_index, Domain=Any, Default=None, Mutable=False + CostCoef : Size=0, Index={1, 2}, Domain=Any, Default=None, Mutable=False Key : Value MaxPower : Size=1, Index=None, Domain=NonNegativeReals, Default=None, Mutable=False Key : Value @@ -189,11 +169,11 @@ 2 Var Declarations Power : Size=5, Index=TIME Key : Lower : Value : Upper : Fixed : Stale : Domain - 0 : 0 : 120.0 : 500 : False : False : Reals - 1 : 0 : 145.0 : 500 : False : False : Reals - 2 : 0 : 119.0 : 500 : False : False : Reals - 3 : 0 : 42.0 : 500 : False : False : Reals - 4 : 0 : 190.0 : 500 : False : False : Reals + 0 : 0 : 120.0 : 500.0 : False : False : Reals + 1 : 0 : 145.0 : 500.0 : False : False : Reals + 2 : 0 : 119.0 : 500.0 : False : False : Reals + 3 : 0 : 42.0 : 500.0 : False : False : Reals + 4 : 0 : 190.0 : 500.0 : False : False : Reals UnitOn : Size=5, Index=TIME Key : Lower : Value : Upper : Fixed : Stale : Domain 0 : 0 : None : 1 : False : True : Binary @@ -219,7 +199,7 @@ 3 : -50.0 : Generator[G_MAIN].Power[3] - Generator[G_MAIN].Power[2] : Generator[G_MAIN].RampLimit : True 4 : -50.0 : Generator[G_MAIN].Power[4] - Generator[G_MAIN].Power[3] : Generator[G_MAIN].RampLimit : True - 8 Declarations: MaxPower RampLimit Power UnitOn limit_ramp CostCoef_index CostCoef Cost + 7 Declarations: MaxPower RampLimit Power UnitOn limit_ramp CostCoef Cost 3 Declarations: TIME GEN_UNITS Generator Generator[G_MAIN].Power[4] = 190.0 diff --git a/examples/pyomobook/blocks-ch/lotsizing_uncertain.txt b/examples/pyomobook/blocks-ch/lotsizing_uncertain.txt index db9eee79cc3..08f92ae9262 100644 --- a/examples/pyomobook/blocks-ch/lotsizing_uncertain.txt +++ b/examples/pyomobook/blocks-ch/lotsizing_uncertain.txt @@ -1,20 +1,3 @@ -5 Set Declarations - i_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : T*S : 25 : {(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5), (4, 1), (4, 2), (4, 3), (4, 4), (4, 5), (5, 1), (5, 2), (5, 3), (5, 4), (5, 5)} - i_neg_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : T*S : 25 : {(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5), (4, 1), (4, 2), (4, 3), (4, 4), (4, 5), (5, 1), (5, 2), (5, 3), (5, 4), (5, 5)} - i_pos_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : T*S : 25 : {(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5), (4, 1), (4, 2), (4, 3), (4, 4), (4, 5), (5, 1), (5, 2), (5, 3), (5, 4), (5, 5)} - x_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : T*S : 25 : {(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5), (4, 1), (4, 2), (4, 3), (4, 4), (4, 5), (5, 1), (5, 2), (5, 3), (5, 4), (5, 5)} - y_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : T*S : 25 : {(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5), (4, 1), (4, 2), (4, 3), (4, 4), (4, 5), (5, 1), (5, 2), (5, 3), (5, 4), (5, 5)} - 2 RangeSet Declarations S : Dimen=1, Size=5, Bounds=(1, 5) Key : Finite : Members @@ -24,7 +7,7 @@ None : True : [1:5] 5 Var Declarations - i : Size=25, Index=i_index + i : Size=25, Index=T*S Key : Lower : Value : Upper : Fixed : Stale : Domain (1, 1) : None : None : None : False : True : Reals (1, 2) : None : None : None : False : True : Reals @@ -51,7 +34,7 @@ (5, 3) : None : None : None : False : True : Reals (5, 4) : None : None : None : False : True : Reals (5, 5) : None : None : None : False : True : Reals - i_neg : Size=25, Index=i_neg_index + i_neg : Size=25, Index=T*S Key : Lower : Value : Upper : Fixed : Stale : Domain (1, 1) : 0 : None : None : False : True : NonNegativeReals (1, 2) : 0 : None : None : False : True : NonNegativeReals @@ -78,7 +61,7 @@ (5, 3) : 0 : None : None : False : True : NonNegativeReals (5, 4) : 0 : None : None : False : True : NonNegativeReals (5, 5) : 0 : None : None : False : True : NonNegativeReals - i_pos : Size=25, Index=i_pos_index + i_pos : Size=25, Index=T*S Key : Lower : Value : Upper : Fixed : Stale : Domain (1, 1) : 0 : None : None : False : True : NonNegativeReals (1, 2) : 0 : None : None : False : True : NonNegativeReals @@ -105,7 +88,7 @@ (5, 3) : 0 : None : None : False : True : NonNegativeReals (5, 4) : 0 : None : None : False : True : NonNegativeReals (5, 5) : 0 : None : None : False : True : NonNegativeReals - x : Size=25, Index=x_index + x : Size=25, Index=T*S Key : Lower : Value : Upper : Fixed : Stale : Domain (1, 1) : 0 : None : None : False : True : NonNegativeReals (1, 2) : 0 : None : None : False : True : NonNegativeReals @@ -132,7 +115,7 @@ (5, 3) : 0 : None : None : False : True : NonNegativeReals (5, 4) : 0 : None : None : False : True : NonNegativeReals (5, 5) : 0 : None : None : False : True : NonNegativeReals - y : Size=25, Index=y_index + y : Size=25, Index=T*S Key : Lower : Value : Upper : Fixed : Stale : Domain (1, 1) : 0 : None : 1 : False : True : Binary (1, 2) : 0 : None : 1 : False : True : Binary @@ -160,4 +143,4 @@ (5, 4) : 0 : None : 1 : False : True : Binary (5, 5) : 0 : None : 1 : False : True : Binary -12 Declarations: T S y_index y x_index x i_index i i_pos_index i_pos i_neg_index i_neg +7 Declarations: T S y x i i_pos i_neg diff --git a/examples/pyomobook/dae-ch/path_constraint.txt b/examples/pyomobook/dae-ch/path_constraint.txt index 421692b33e9..97e56ab8816 100644 --- a/examples/pyomobook/dae-ch/path_constraint.txt +++ b/examples/pyomobook/dae-ch/path_constraint.txt @@ -1,8 +1,3 @@ -1 RangeSet Declarations - t_domain : Dimen=1, Size=Inf, Bounds=(0, 1) - Key : Finite : Members - None : False : [0..1] - 1 Param Declarations tf : Size=1, Index=None, Domain=Any, Default=None, Mutable=False Key : Value @@ -68,4 +63,4 @@ 0 : None : None : None : False : True : Reals 1 : None : None : None : False : True : Reals -15 Declarations: tf t_domain t u x1 x2 x3 dx1 dx2 dx3 x1dotcon x2dotcon x3dotcon obj con +14 Declarations: tf t u x1 x2 x3 dx1 dx2 dx3 x1dotcon x2dotcon x3dotcon obj con diff --git a/examples/pyomobook/optimization-ch/ConcHLinScript.txt b/examples/pyomobook/optimization-ch/ConcHLinScript.txt index c04591c94dc..0d34868ed99 100644 --- a/examples/pyomobook/optimization-ch/ConcHLinScript.txt +++ b/examples/pyomobook/optimization-ch/ConcHLinScript.txt @@ -1,7 +1,7 @@ Model 'Linear (H)' Variables: - x : Size=2, Index=x_index + x : Size=2, Index={I_C_Scoops, Peanuts} Key : Lower : Value : Upper : Fixed : Stale : Domain I_C_Scoops : 0 : 0.0 : 100 : False : False : Reals Peanuts : 0 : 40.6 : 40.6 : False : False : Reals @@ -9,7 +9,7 @@ Model 'Linear (H)' Objectives: z : Size=1, Index=None, Active=True Key : Active : Value - None : True : 3.83388751715 + None : True : 3.8338875171467763 Constraints: budgetconstr : Size=1 diff --git a/examples/pyomobook/optimization-ch/ConcreteH.txt b/examples/pyomobook/optimization-ch/ConcreteH.txt index 5e669ff71e0..04bbbdab857 100644 --- a/examples/pyomobook/optimization-ch/ConcreteH.txt +++ b/examples/pyomobook/optimization-ch/ConcreteH.txt @@ -1,10 +1,5 @@ -1 Set Declarations - x_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 2 : {'I_C_Scoops', 'Peanuts'} - 1 Var Declarations - x : Size=2, Index=x_index + x : Size=2, Index={I_C_Scoops, Peanuts} Key : Lower : Value : Upper : Fixed : Stale : Domain I_C_Scoops : 0 : None : 100 : False : True : Reals Peanuts : 0 : None : 40.6 : False : True : Reals @@ -19,4 +14,4 @@ Key : Lower : Body : Upper : Active None : -Inf : 3.14*x[I_C_Scoops] + 0.2718*x[Peanuts] : 12.0 : True -4 Declarations: x_index x z budgetconstr +3 Declarations: x z budgetconstr diff --git a/examples/pyomobook/optimization-ch/ConcreteHLinear.txt b/examples/pyomobook/optimization-ch/ConcreteHLinear.txt index 2e778c2bd1b..7f19aca87ec 100644 --- a/examples/pyomobook/optimization-ch/ConcreteHLinear.txt +++ b/examples/pyomobook/optimization-ch/ConcreteHLinear.txt @@ -1,10 +1,5 @@ -1 Set Declarations - x_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 2 : {'I_C_Scoops', 'Peanuts'} - 1 Var Declarations - x : Size=2, Index=x_index + x : Size=2, Index={I_C_Scoops, Peanuts} Key : Lower : Value : Upper : Fixed : Stale : Domain I_C_Scoops : 0 : None : 100 : False : True : Reals Peanuts : 0 : None : 40.6 : False : True : Reals @@ -19,4 +14,4 @@ Key : Lower : Body : Upper : Active None : -Inf : 3.14*x[I_C_Scoops] + 0.2718*x[Peanuts] : 12.0 : True -4 Declarations: x_index x z budgetconstr +3 Declarations: x z budgetconstr diff --git a/examples/pyomobook/overview-ch/wl_concrete_script.txt b/examples/pyomobook/overview-ch/wl_concrete_script.txt index dae31e1a035..165289552d3 100644 --- a/examples/pyomobook/overview-ch/wl_concrete_script.txt +++ b/examples/pyomobook/overview-ch/wl_concrete_script.txt @@ -1,4 +1,4 @@ -y : Size=3, Index=y_index +y : Size=3, Index={Harlingen, Memphis, Ashland} Key : Lower : Value : Upper : Fixed : Stale : Domain Ashland : 0 : 1.0 : 1 : False : False : Binary Harlingen : 0 : 1.0 : 1 : False : False : Binary diff --git a/examples/pyomobook/overview-ch/wl_excel.txt b/examples/pyomobook/overview-ch/wl_excel.txt index dae31e1a035..165289552d3 100644 --- a/examples/pyomobook/overview-ch/wl_excel.txt +++ b/examples/pyomobook/overview-ch/wl_excel.txt @@ -1,4 +1,4 @@ -y : Size=3, Index=y_index +y : Size=3, Index={Harlingen, Memphis, Ashland} Key : Lower : Value : Upper : Fixed : Stale : Domain Ashland : 0 : 1.0 : 1 : False : False : Binary Harlingen : 0 : 1.0 : 1 : False : False : Binary diff --git a/examples/pyomobook/overview-ch/wl_list.txt b/examples/pyomobook/overview-ch/wl_list.txt index 2054efe153d..c0d44f1a0c9 100644 --- a/examples/pyomobook/overview-ch/wl_list.txt +++ b/examples/pyomobook/overview-ch/wl_list.txt @@ -1,25 +1,5 @@ -6 Set Declarations - demand_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 4 : {1, 2, 3, 4} - warehouse_active_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 12 : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12} - x_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : x_index_0*x_index_1 : 12 : {('Harlingen', 'NYC'), ('Harlingen', 'LA'), ('Harlingen', 'Chicago'), ('Harlingen', 'Houston'), ('Memphis', 'NYC'), ('Memphis', 'LA'), ('Memphis', 'Chicago'), ('Memphis', 'Houston'), ('Ashland', 'NYC'), ('Ashland', 'LA'), ('Ashland', 'Chicago'), ('Ashland', 'Houston')} - x_index_0 : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {'Harlingen', 'Memphis', 'Ashland'} - x_index_1 : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 4 : {'NYC', 'LA', 'Chicago', 'Houston'} - y_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {'Harlingen', 'Memphis', 'Ashland'} - 2 Var Declarations - x : Size=12, Index=x_index + x : Size=12, Index={Harlingen, Memphis, Ashland}*{NYC, LA, Chicago, Houston} Key : Lower : Value : Upper : Fixed : Stale : Domain ('Ashland', 'Chicago') : 0 : None : 1 : False : True : Reals ('Ashland', 'Houston') : 0 : None : 1 : False : True : Reals @@ -33,7 +13,7 @@ ('Memphis', 'Houston') : 0 : None : 1 : False : True : Reals ('Memphis', 'LA') : 0 : None : 1 : False : True : Reals ('Memphis', 'NYC') : 0 : None : 1 : False : True : Reals - y : Size=3, Index=y_index + y : Size=3, Index={Harlingen, Memphis, Ashland} Key : Lower : Value : Upper : Fixed : Stale : Domain Ashland : 0 : None : 1 : False : True : Binary Harlingen : 0 : None : 1 : False : True : Binary @@ -45,7 +25,7 @@ None : True : minimize : 1956*x[Harlingen,NYC] + 1606*x[Harlingen,LA] + 1410*x[Harlingen,Chicago] + 330*x[Harlingen,Houston] + 1096*x[Memphis,NYC] + 1792*x[Memphis,LA] + 531*x[Memphis,Chicago] + 567*x[Memphis,Houston] + 485*x[Ashland,NYC] + 2322*x[Ashland,LA] + 324*x[Ashland,Chicago] + 1236*x[Ashland,Houston] 3 Constraint Declarations - demand : Size=4, Index=demand_index, Active=True + demand : Size=4, Index={1, 2, 3, 4}, Active=True Key : Lower : Body : Upper : Active 1 : 1.0 : x[Harlingen,NYC] + x[Memphis,NYC] + x[Ashland,NYC] : 1.0 : True 2 : 1.0 : x[Harlingen,LA] + x[Memphis,LA] + x[Ashland,LA] : 1.0 : True @@ -54,7 +34,7 @@ num_warehouses : Size=1, Index=None, Active=True Key : Lower : Body : Upper : Active None : -Inf : y[Harlingen] + y[Memphis] + y[Ashland] : 2.0 : True - warehouse_active : Size=12, Index=warehouse_active_index, Active=True + warehouse_active : Size=12, Index={1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, Active=True Key : Lower : Body : Upper : Active 1 : -Inf : x[Harlingen,NYC] - y[Harlingen] : 0.0 : True 2 : -Inf : x[Harlingen,LA] - y[Harlingen] : 0.0 : True @@ -69,4 +49,4 @@ 11 : -Inf : x[Ashland,Chicago] - y[Ashland] : 0.0 : True 12 : -Inf : x[Ashland,Houston] - y[Ashland] : 0.0 : True -12 Declarations: x_index_0 x_index_1 x_index x y_index y obj demand_index demand warehouse_active_index warehouse_active num_warehouses +6 Declarations: x y obj demand warehouse_active num_warehouses diff --git a/examples/pyomobook/pyomo-components-ch/con_declaration.txt b/examples/pyomobook/pyomo-components-ch/con_declaration.txt index 019cd448eb0..b4709bd5490 100644 --- a/examples/pyomobook/pyomo-components-ch/con_declaration.txt +++ b/examples/pyomobook/pyomo-components-ch/con_declaration.txt @@ -1,10 +1,5 @@ -1 Set Declarations - x_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 2 : {1, 2} - 1 Var Declarations - x : Size=2, Index=x_index + x : Size=2, Index={1, 2} Key : Lower : Value : Upper : Fixed : Stale : Domain 1 : None : 1.0 : None : False : False : Reals 2 : None : 1.0 : None : False : False : Reals @@ -14,14 +9,9 @@ Key : Lower : Body : Upper : Active None : -Inf : x[2] - x[1] : 7.5 : True -3 Declarations: x_index x diff -1 Set Declarations - x_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 2 : {1, 2} - +2 Declarations: x diff 1 Var Declarations - x : Size=2, Index=x_index + x : Size=2, Index={1, 2} Key : Lower : Value : Upper : Fixed : Stale : Domain 1 : None : 1.0 : None : False : False : Reals 2 : None : 1.0 : None : False : False : Reals @@ -31,40 +21,24 @@ Key : Lower : Body : Upper : Active None : -Inf : x[2] - x[1] : 7.5 : True -3 Declarations: x_index x diff -2 Set Declarations - CoverConstr_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {1, 2, 3} - y_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {1, 2, 3} - +2 Declarations: x diff 1 Var Declarations - y : Size=3, Index=y_index + y : Size=3, Index={1, 2, 3} Key : Lower : Value : Upper : Fixed : Stale : Domain 1 : 0 : 0.0 : None : False : False : NonNegativeReals 2 : 0 : 0.0 : None : False : False : NonNegativeReals 3 : 0 : 0.0 : None : False : False : NonNegativeReals 1 Constraint Declarations - CoverConstr : Size=3, Index=CoverConstr_index, Active=True + CoverConstr : Size=3, Index={1, 2, 3}, Active=True Key : Lower : Body : Upper : Active 1 : 1.0 : y[1] : +Inf : True 2 : 2.9 : 3.1*y[2] : +Inf : True 3 : 3.1 : 4.5*y[3] : +Inf : True -4 Declarations: y_index y CoverConstr_index CoverConstr -2 Set Declarations - Pred_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 5 : {1, 2, 3, 4, 5} - StartTime_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 5 : {1, 2, 3, 4, 5} - +2 Declarations: y CoverConstr 1 Var Declarations - StartTime : Size=5, Index=StartTime_index + StartTime : Size=5, Index={1, 2, 3, 4, 5} Key : Lower : Value : Upper : Fixed : Stale : Domain 1 : None : 1.0 : None : False : False : Reals 2 : None : 1.0 : None : False : False : Reals @@ -73,14 +47,14 @@ 5 : None : 1.0 : None : False : False : Reals 1 Constraint Declarations - Pred : Size=4, Index=Pred_index, Active=True + Pred : Size=4, Index={1, 2, 3, 4, 5}, Active=True Key : Lower : Body : Upper : Active 1 : -Inf : StartTime[1] - StartTime[2] : 0.0 : True 2 : -Inf : StartTime[2] - StartTime[3] : 0.0 : True 3 : -Inf : StartTime[3] - StartTime[4] : 0.0 : True 4 : -Inf : StartTime[4] - StartTime[5] : 0.0 : True -4 Declarations: StartTime_index StartTime Pred_index Pred +2 Declarations: StartTime Pred 0.0 inf 7.5 diff --git a/examples/pyomobook/pyomo-components-ch/examples.txt b/examples/pyomobook/pyomo-components-ch/examples.txt index 635b988cbcd..27ea1ba130b 100644 --- a/examples/pyomobook/pyomo-components-ch/examples.txt +++ b/examples/pyomobook/pyomo-components-ch/examples.txt @@ -1,20 +1,17 @@ indexed1 -3 Set Declarations +2 Set Declarations A : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {1, 2, 3} B : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 2 : {'Q', 'R'} - y_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*B : 6 : {(1, 'Q'), (1, 'R'), (2, 'Q'), (2, 'R'), (3, 'Q'), (3, 'R')} 2 Var Declarations x : Size=1, Index=None Key : Lower : Value : Upper : Fixed : Stale : Domain None : None : None : None : False : True : Reals - y : Size=6, Index=y_index + y : Size=6, Index=A*B Key : Lower : Value : Upper : Fixed : Stale : Domain (1, 'Q') : None : None : None : False : True : Reals (1, 'R') : None : None : None : False : True : Reals @@ -38,4 +35,4 @@ indexed1 2 : -Inf : 2*x : 0.0 : True 3 : -Inf : 3*x : 0.0 : True -8 Declarations: A B x y_index y o c d +7 Declarations: A B x y o c d diff --git a/examples/pyomobook/pyomo-components-ch/expr_declaration.txt b/examples/pyomobook/pyomo-components-ch/expr_declaration.txt index 66c99f6502a..86e0feac27f 100644 --- a/examples/pyomobook/pyomo-components-ch/expr_declaration.txt +++ b/examples/pyomobook/pyomo-components-ch/expr_declaration.txt @@ -18,28 +18,20 @@ None : x + 2 3 Declarations: x e1 e2 -2 Set Declarations - e_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {1, 2, 3} - x_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {1, 2, 3} - 1 Var Declarations - x : Size=3, Index=x_index + x : Size=3, Index={1, 2, 3} Key : Lower : Value : Upper : Fixed : Stale : Domain 1 : None : None : None : False : True : Reals 2 : None : None : None : False : True : Reals 3 : None : None : None : False : True : Reals 1 Expression Declarations - e : Size=2, Index=e_index + e : Size=2, Index={1, 2, 3} Key : Expression 2 : x[2]**2 3 : x[3]**2 -4 Declarations: x_index x e_index e +2 Declarations: x e 1 Var Declarations x : Size=1, Index=None Key : Lower : Value : Upper : Fixed : Stale : Domain diff --git a/examples/pyomobook/pyomo-components-ch/obj_declaration.txt b/examples/pyomobook/pyomo-components-ch/obj_declaration.txt index e43134b8d92..607586a1fb3 100644 --- a/examples/pyomobook/pyomo-components-ch/obj_declaration.txt +++ b/examples/pyomobook/pyomo-components-ch/obj_declaration.txt @@ -14,7 +14,7 @@ declexprrule Model unknown Variables: - x : Size=2, Index=x_index + x : Size=2, Index={1, 2} Key : Lower : Value : Upper : Fixed : Stale : Domain 1 : None : 1.0 : None : False : False : Reals 2 : None : 1.0 : None : False : False : Reals @@ -34,19 +34,19 @@ declskip Model unknown Variables: - x : Size=3, Index=x_index + x : Size=3, Index={Q, R, S} Key : Lower : Value : Upper : Fixed : Stale : Domain Q : None : 1.0 : None : False : False : Reals R : None : 1.0 : None : False : False : Reals S : None : 1.0 : None : False : False : Reals Objectives: - d : Size=3, Index=d_index, Active=True + d : Size=3, Index={Q, R, S}, Active=True Key : Active : Value Q : True : 1.0 R : True : 1.0 S : True : 1.0 - e : Size=2, Index=e_index, Active=True + e : Size=2, Index={Q, R, S}, Active=True Key : Active : Value Q : True : 1.0 S : True : 1.0 @@ -60,7 +60,7 @@ x[Q] + 2*x[R] Model unknown Variables: - x : Size=2, Index=x_index + x : Size=2, Index={Q, R} Key : Lower : Value : Upper : Fixed : Stale : Domain Q : None : 1.5 : None : False : False : Reals R : None : 2.5 : None : False : False : Reals diff --git a/examples/pyomobook/pyomo-components-ch/param_declaration.txt b/examples/pyomobook/pyomo-components-ch/param_declaration.txt index 9b8ce9cacdb..8c8a49eedc6 100644 --- a/examples/pyomobook/pyomo-components-ch/param_declaration.txt +++ b/examples/pyomobook/pyomo-components-ch/param_declaration.txt @@ -1,16 +1,13 @@ -3 Set Declarations +2 Set Declarations A : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {1, 2, 3} B : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 2 : {'A', 'B'} - T_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*B : 6 : {(1, 'A'), (1, 'B'), (2, 'A'), (2, 'B'), (3, 'A'), (3, 'B')} 3 Param Declarations - T : Size=3, Index=T_index, Domain=Any, Default=None, Mutable=False + T : Size=3, Index=A*B, Domain=Any, Default=None, Mutable=False Key : Value (1, 'A') : 10 (2, 'B') : 20 @@ -24,4 +21,4 @@ Key : Value None : 32 -6 Declarations: Z A B U T_index T +5 Declarations: Z A B U T diff --git a/examples/pyomobook/pyomo-components-ch/param_initialization.txt b/examples/pyomobook/pyomo-components-ch/param_initialization.txt index d1ac6aba989..e0bcdf11a71 100644 --- a/examples/pyomobook/pyomo-components-ch/param_initialization.txt +++ b/examples/pyomobook/pyomo-components-ch/param_initialization.txt @@ -1,27 +1,15 @@ -6 Set Declarations +2 Set Declarations A : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {1, 2, 3} B : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {1, 2, 3} - T_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*B : 9 : {(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3), (3, 1), (3, 2), (3, 3)} - U_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*A : 9 : {(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3), (3, 1), (3, 2), (3, 3)} - XX_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*A : 9 : {(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3), (3, 1), (3, 2), (3, 3)} - X_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*A : 9 : {(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3), (3, 1), (3, 2), (3, 3)} 5 Param Declarations - T : Size=0, Index=T_index, Domain=Any, Default=None, Mutable=False + T : Size=0, Index=A*B, Domain=Any, Default=None, Mutable=False Key : Value - U : Size=9, Index=U_index, Domain=Any, Default=0, Mutable=False + U : Size=9, Index=A*A, Domain=Any, Default=0, Mutable=False Key : Value (1, 1) : 10 (2, 2) : 20 @@ -30,7 +18,7 @@ Key : Value 1 : 10 3 : 30 - X : Size=9, Index=X_index, Domain=Any, Default=None, Mutable=False + X : Size=9, Index=A*A, Domain=Any, Default=None, Mutable=False Key : Value (1, 1) : 1 (1, 2) : 2 @@ -41,7 +29,7 @@ (3, 1) : 3 (3, 2) : 6 (3, 3) : 9 - XX : Size=9, Index=XX_index, Domain=Any, Default=None, Mutable=False + XX : Size=9, Index=A*A, Domain=Any, Default=None, Mutable=False Key : Value (1, 1) : 1 (1, 2) : 2 @@ -53,7 +41,7 @@ (3, 2) : 8 (3, 3) : 14 -11 Declarations: A X_index X XX_index XX B W U_index U T_index T +7 Declarations: A X XX B W U T 2 3 False diff --git a/examples/pyomobook/pyomo-components-ch/set_declaration.txt b/examples/pyomobook/pyomo-components-ch/set_declaration.txt index bdbb7376de4..a588e5601b6 100644 --- a/examples/pyomobook/pyomo-components-ch/set_declaration.txt +++ b/examples/pyomobook/pyomo-components-ch/set_declaration.txt @@ -5,22 +5,16 @@ 1 Declarations: A 0 Declarations: -4 Set Declarations - E : Size=1, Index=E_index, Ordered=Insertion +2 Set Declarations + E : Size=1, Index={1, 2, 3}, Ordered=Insertion Key : Dimen : Domain : Size : Members 2 : 1 : Any : 3 : {21, 22, 23} - E_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {1, 2, 3} - F : Size=2, Index=F_index, Ordered=Insertion + F : Size=2, Index={1, 2, 3}, Ordered=Insertion Key : Dimen : Domain : Size : Members 1 : 1 : Any : 3 : {11, 12, 13} 3 : 1 : Any : 3 : {31, 32, 33} - F_index : Size=1, Index=None, Ordered=False - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {1, 2, 3} -4 Declarations: E_index E F_index F +2 Declarations: E F 6 Set Declarations A : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members diff --git a/examples/pyomobook/pyomo-components-ch/set_initialization.txt b/examples/pyomobook/pyomo-components-ch/set_initialization.txt index af2ba54a8d2..29900ccb7b2 100644 --- a/examples/pyomobook/pyomo-components-ch/set_initialization.txt +++ b/examples/pyomobook/pyomo-components-ch/set_initialization.txt @@ -1,19 +1,16 @@ -10 Set Declarations +7 Set Declarations B : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {2, 3, 4} C : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 2 : Any : 2 : {(1, 4), (9, 16)} - F : Size=3, Index=F_index, Ordered=Insertion + F : Size=3, Index={2, 3, 4}, Ordered=Insertion Key : Dimen : Domain : Size : Members 2 : 1 : Any : 3 : {1, 3, 5} 3 : 1 : Any : 3 : {2, 4, 6} 4 : 1 : Any : 3 : {3, 5, 7} - F_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {2, 3, 4} - J : Size=9, Index=J_index, Ordered=Insertion + J : Size=9, Index=B*B, Ordered=Insertion Key : Dimen : Domain : Size : Members (2, 2) : 1 : Any : 4 : {0, 1, 2, 3} (2, 3) : 1 : Any : 6 : {0, 1, 2, 3, 4, 5} @@ -24,21 +21,15 @@ (4, 2) : 1 : Any : 8 : {0, 1, 2, 3, 4, 5, 6, 7} (4, 3) : 1 : Any : 12 : {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11} (4, 4) : 1 : Any : 16 : {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} - J_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : B*B : 9 : {(2, 2), (2, 3), (2, 4), (3, 2), (3, 3), (3, 4), (4, 2), (4, 3), (4, 4)} P : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 5 : {1, 2, 3, 5, 7} Q : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 4 : {4, 6, 8, 9} - R : Size=2, Index=R_index, Ordered=Insertion + R : Size=2, Index={1, 2, 3}, Ordered=Insertion Key : Dimen : Domain : Size : Members 1 : 1 : Any : 1 : {1,} 2 : 1 : Any : 2 : {1, 2} - R_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {1, 2, 3} -10 Declarations: B C F_index F J_index J P Q R_index R +7 Declarations: B C F J P Q R diff --git a/examples/pyomobook/scripts-ch/warehouse_cuts.txt b/examples/pyomobook/scripts-ch/warehouse_cuts.txt index 9afe6c4e944..1f097e06cea 100644 --- a/examples/pyomobook/scripts-ch/warehouse_cuts.txt +++ b/examples/pyomobook/scripts-ch/warehouse_cuts.txt @@ -1,7 +1,7 @@ --- Solver Status: optimal --- Optimal Obj. Value = 2745.0 -y : Size=3, Index=y_index +y : Size=3, Index={Harlingen, Memphis, Ashland} Key : Lower : Value : Upper : Fixed : Stale : Domain Ashland : 0 : 1.0 : 1 : False : False : Binary Harlingen : 0 : 1.0 : 1 : False : False : Binary @@ -9,7 +9,7 @@ y : Size=3, Index=y_index --- Solver Status: optimal --- Optimal Obj. Value = 3168.0 -y : Size=3, Index=y_index +y : Size=3, Index={Harlingen, Memphis, Ashland} Key : Lower : Value : Upper : Fixed : Stale : Domain Ashland : 0 : 1.0 : 1 : False : False : Binary Harlingen : 0 : 0.0 : 1 : False : False : Binary @@ -17,7 +17,7 @@ y : Size=3, Index=y_index --- Solver Status: optimal --- Optimal Obj. Value = 3563.0 -y : Size=3, Index=y_index +y : Size=3, Index={Harlingen, Memphis, Ashland} Key : Lower : Value : Upper : Fixed : Stale : Domain Ashland : 0 : 0.0 : 1 : False : False : Binary Harlingen : 0 : 1.0 : 1 : False : False : Binary @@ -25,7 +25,7 @@ y : Size=3, Index=y_index --- Solver Status: optimal --- Optimal Obj. Value = 3986.0 -y : Size=3, Index=y_index +y : Size=3, Index={Harlingen, Memphis, Ashland} Key : Lower : Value : Upper : Fixed : Stale : Domain Ashland : 0 : 0.0 : 1 : False : False : Binary Harlingen : 0 : 0.0 : 1 : False : False : Binary @@ -33,7 +33,7 @@ y : Size=3, Index=y_index --- Solver Status: optimal --- Optimal Obj. Value = 4367.0 -y : Size=3, Index=y_index +y : Size=3, Index={Harlingen, Memphis, Ashland} Key : Lower : Value : Upper : Fixed : Stale : Domain Ashland : 0 : 1.0 : 1 : False : False : Binary Harlingen : 0 : 0.0 : 1 : False : False : Binary @@ -41,7 +41,7 @@ y : Size=3, Index=y_index --- Solver Status: optimal --- Optimal Obj. Value = 5302.0 -y : Size=3, Index=y_index +y : Size=3, Index={Harlingen, Memphis, Ashland} Key : Lower : Value : Upper : Fixed : Stale : Domain Ashland : 0 : 0.0 : 1 : False : False : Binary Harlingen : 0 : 1.0 : 1 : False : False : Binary diff --git a/examples/pyomobook/scripts-ch/warehouse_script.txt b/examples/pyomobook/scripts-ch/warehouse_script.txt index b922643dd2b..fac3aef0880 100644 --- a/examples/pyomobook/scripts-ch/warehouse_script.txt +++ b/examples/pyomobook/scripts-ch/warehouse_script.txt @@ -1,36 +1,10 @@ -y : Size=3, Index=y_index +y : Size=3, Index={Harlingen, Memphis, Ashland} Key : Lower : Value : Upper : Fixed : Stale : Domain Ashland : 0 : 1.0 : 1 : False : False : Binary Harlingen : 0 : 1.0 : 1 : False : False : Binary Memphis : 0 : 0.0 : 1 : False : False : Binary -8 Set Declarations - one_per_cust_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 4 : {'NYC', 'LA', 'Chicago', 'Houston'} - warehouse_active_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : warehouse_active_index_0*warehouse_active_index_1 : 12 : {('Harlingen', 'NYC'), ('Harlingen', 'LA'), ('Harlingen', 'Chicago'), ('Harlingen', 'Houston'), ('Memphis', 'NYC'), ('Memphis', 'LA'), ('Memphis', 'Chicago'), ('Memphis', 'Houston'), ('Ashland', 'NYC'), ('Ashland', 'LA'), ('Ashland', 'Chicago'), ('Ashland', 'Houston')} - warehouse_active_index_0 : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {'Harlingen', 'Memphis', 'Ashland'} - warehouse_active_index_1 : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 4 : {'NYC', 'LA', 'Chicago', 'Houston'} - x_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : x_index_0*x_index_1 : 12 : {('Harlingen', 'NYC'), ('Harlingen', 'LA'), ('Harlingen', 'Chicago'), ('Harlingen', 'Houston'), ('Memphis', 'NYC'), ('Memphis', 'LA'), ('Memphis', 'Chicago'), ('Memphis', 'Houston'), ('Ashland', 'NYC'), ('Ashland', 'LA'), ('Ashland', 'Chicago'), ('Ashland', 'Houston')} - x_index_0 : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {'Harlingen', 'Memphis', 'Ashland'} - x_index_1 : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 4 : {'NYC', 'LA', 'Chicago', 'Houston'} - y_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {'Harlingen', 'Memphis', 'Ashland'} - 2 Var Declarations - x : Size=12, Index=x_index + x : Size=12, Index={Harlingen, Memphis, Ashland}*{NYC, LA, Chicago, Houston} Key : Lower : Value : Upper : Fixed : Stale : Domain ('Ashland', 'Chicago') : 0 : 1.0 : 1 : False : False : Reals ('Ashland', 'Houston') : 0 : 0.0 : 1 : False : False : Reals @@ -40,11 +14,11 @@ y : Size=3, Index=y_index ('Harlingen', 'Houston') : 0 : 1.0 : 1 : False : False : Reals ('Harlingen', 'LA') : 0 : 1.0 : 1 : False : False : Reals ('Harlingen', 'NYC') : 0 : 0.0 : 1 : False : False : Reals - ('Memphis', 'Chicago') : 0 : -0.0 : 1 : False : False : Reals + ('Memphis', 'Chicago') : 0 : 0.0 : 1 : False : False : Reals ('Memphis', 'Houston') : 0 : 0.0 : 1 : False : False : Reals ('Memphis', 'LA') : 0 : 0.0 : 1 : False : False : Reals ('Memphis', 'NYC') : 0 : 0.0 : 1 : False : False : Reals - y : Size=3, Index=y_index + y : Size=3, Index={Harlingen, Memphis, Ashland} Key : Lower : Value : Upper : Fixed : Stale : Domain Ashland : 0 : 1.0 : 1 : False : False : Binary Harlingen : 0 : 1.0 : 1 : False : False : Binary @@ -59,13 +33,13 @@ y : Size=3, Index=y_index num_warehouses : Size=1, Index=None, Active=True Key : Lower : Body : Upper : Active None : -Inf : y[Harlingen] + y[Memphis] + y[Ashland] : 2.0 : True - one_per_cust : Size=4, Index=one_per_cust_index, Active=True + one_per_cust : Size=4, Index={NYC, LA, Chicago, Houston}, Active=True Key : Lower : Body : Upper : Active Chicago : 1.0 : x[Harlingen,Chicago] + x[Memphis,Chicago] + x[Ashland,Chicago] : 1.0 : True Houston : 1.0 : x[Harlingen,Houston] + x[Memphis,Houston] + x[Ashland,Houston] : 1.0 : True LA : 1.0 : x[Harlingen,LA] + x[Memphis,LA] + x[Ashland,LA] : 1.0 : True NYC : 1.0 : x[Harlingen,NYC] + x[Memphis,NYC] + x[Ashland,NYC] : 1.0 : True - warehouse_active : Size=12, Index=warehouse_active_index, Active=True + warehouse_active : Size=12, Index={Harlingen, Memphis, Ashland}*{NYC, LA, Chicago, Houston}, Active=True Key : Lower : Body : Upper : Active ('Ashland', 'Chicago') : -Inf : x[Ashland,Chicago] - y[Ashland] : 0.0 : True ('Ashland', 'Houston') : -Inf : x[Ashland,Houston] - y[Ashland] : 0.0 : True @@ -80,4 +54,4 @@ y : Size=3, Index=y_index ('Memphis', 'LA') : -Inf : x[Memphis,LA] - y[Memphis] : 0.0 : True ('Memphis', 'NYC') : -Inf : x[Memphis,NYC] - y[Memphis] : 0.0 : True -14 Declarations: x_index_0 x_index_1 x_index x y_index y obj one_per_cust_index one_per_cust warehouse_active_index_0 warehouse_active_index_1 warehouse_active_index warehouse_active num_warehouses +6 Declarations: x y obj one_per_cust warehouse_active num_warehouses diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 46af1277ba5..8c592dc6580 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -296,8 +296,6 @@ def test_add_decision_rule_vars_positive_case(self): m.working_model.del_component(m.working_model.decision_rule_var_0) m.working_model.del_component(m.working_model.decision_rule_var_1) - m.working_model.del_component(m.working_model.decision_rule_var_0_index) - m.working_model.del_component(m.working_model.decision_rule_var_1_index) config.decision_rule_order = 2 @@ -395,8 +393,6 @@ def test_correct_number_of_decision_rule_constraints(self): # === Decision rule vars have been added m.working_model.del_component(m.working_model.decision_rule_var_0) m.working_model.del_component(m.working_model.decision_rule_var_1) - m.working_model.del_component(m.working_model.decision_rule_var_0_index) - m.working_model.del_component(m.working_model.decision_rule_var_1_index) m.working_model.decision_rule_var_0 = Var([0, 1, 2, 3, 4, 5], initialize=0) m.working_model.decision_rule_var_1 = Var([0, 1, 2, 3, 4, 5], initialize=0) diff --git a/pyomo/core/base/reference.py b/pyomo/core/base/reference.py index 79ae83b97be..62f7a813b5b 100644 --- a/pyomo/core/base/reference.py +++ b/pyomo/core/base/reference.py @@ -612,7 +612,7 @@ def Reference(reference, ctype=NOTSET): ... >>> m.r1 = Reference(m.b[:,:].x) >>> m.r1.pprint() - r1 : Size=4, Index=r1_index, ReferenceTo=b[:, :].x + r1 : Size=4, Index={1, 2}*{3, 4}, ReferenceTo=b[:, :].x Key : Lower : Value : Upper : Fixed : Stale : Domain (1, 3) : 1 : None : 3 : False : True : Reals (1, 4) : 1 : None : 4 : False : True : Reals @@ -625,7 +625,7 @@ def Reference(reference, ctype=NOTSET): >>> m.r2 = Reference(m.b[:,3].x) >>> m.r2.pprint() - r2 : Size=2, Index=b_index_0, ReferenceTo=b[:, 3].x + r2 : Size=2, Index={1, 2}, ReferenceTo=b[:, 3].x Key : Lower : Value : Upper : Fixed : Stale : Domain 1 : 1 : None : 3 : False : True : Reals 2 : 2 : None : 3 : False : True : Reals @@ -642,7 +642,7 @@ def Reference(reference, ctype=NOTSET): ... >>> m.r3 = Reference(m.b[:].x[:]) >>> m.r3.pprint() - r3 : Size=4, Index=r3_index, ReferenceTo=b[:].x[:] + r3 : Size=4, Index=ReferenceSet(b[:].x[:]), ReferenceTo=b[:].x[:] Key : Lower : Value : Upper : Fixed : Stale : Domain (1, 3) : 1 : None : None : False : True : Reals (1, 4) : 1 : None : None : False : True : Reals @@ -657,7 +657,7 @@ def Reference(reference, ctype=NOTSET): >>> m.r3[1,4] = 10 >>> m.b[1].x.pprint() - x : Size=2, Index=b[1].x_index + x : Size=2, Index={3, 4} Key : Lower : Value : Upper : Fixed : Stale : Domain 3 : 1 : None : None : False : True : Reals 4 : 1 : 10 : None : False : False : Reals diff --git a/pyomo/core/tests/unit/test_block.py b/pyomo/core/tests/unit/test_block.py index f68850d9421..50e08d4c616 100644 --- a/pyomo/core/tests/unit/test_block.py +++ b/pyomo/core/tests/unit/test_block.py @@ -2626,19 +2626,16 @@ def test_pprint(self): m = HierarchicalModel().model buf = StringIO() m.pprint(ostream=buf) - ref = """3 Set Declarations + ref = """2 Set Declarations a1_IDX : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 2 : {5, 4} a3_IDX : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 2 : {6, 7} - a_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {1, 2, 3} 3 Block Declarations - a : Size=3, Index=a_index, Active=True + a : Size=3, Index={1, 2, 3}, Active=True a[1] : Active=True 2 Block Declarations c : Size=2, Index=a1_IDX, Active=True @@ -2668,9 +2665,9 @@ def test_pprint(self): c : Size=1, Index=None, Active=True 0 Declarations: -6 Declarations: a1_IDX a3_IDX c a_index a b +5 Declarations: a1_IDX a3_IDX c a b """ - print(buf.getvalue()) + self.maxDiff = None self.assertEqual(ref, buf.getvalue()) @unittest.skipIf(not 'glpk' in solvers, "glpk solver is not available") diff --git a/pyomo/core/tests/unit/test_componentuid.py b/pyomo/core/tests/unit/test_componentuid.py index 1c9b3c444bf..2273869104f 100644 --- a/pyomo/core/tests/unit/test_componentuid.py +++ b/pyomo/core/tests/unit/test_componentuid.py @@ -601,31 +601,26 @@ def test_generate_cuid_string_map(self): ComponentUID.generate_cuid_string_map(model, repr_version=1), ComponentUID.generate_cuid_string_map(model), ) - self.assertEqual(len(cuids[0]), 29) - self.assertEqual(len(cuids[1]), 29) + self.assertEqual(len(cuids[0]), 24) + self.assertEqual(len(cuids[1]), 24) for obj in [ model, model.x, model.y, - model.y_index, model.y[1], model.y[2], model.V, - model.V_index, model.V['a', 'b'], model.V[1, '2'], model.V[3, 4], model.b, model.b.z, - model.b.z_index, model.b.z[1], model.b.z['2'], getattr(model.b, '.H'), - getattr(model.b, '.H_index'), getattr(model.b, '.H')['a'], getattr(model.b, '.H')[2], model.B, - model.B_index, model.B['a'], getattr(model.B['a'], '.k'), model.B[2], @@ -642,23 +637,20 @@ def test_generate_cuid_string_map(self): ), ComponentUID.generate_cuid_string_map(model, descend_into=False), ) - self.assertEqual(len(cuids[0]), 18) - self.assertEqual(len(cuids[1]), 18) + self.assertEqual(len(cuids[0]), 15) + self.assertEqual(len(cuids[1]), 15) for obj in [ model, model.x, model.y, - model.y_index, model.y[1], model.y[2], model.V, - model.V_index, model.V['a', 'b'], model.V[1, '2'], model.V[3, 4], model.b, model.B, - model.B_index, model.B['a'], model.B[2], model.component('c tuple')[(1,)], diff --git a/pyomo/core/tests/unit/test_connector.py b/pyomo/core/tests/unit/test_connector.py index 1dde9f3af24..0af07de50e3 100644 --- a/pyomo/core/tests/unit/test_connector.py +++ b/pyomo/core/tests/unit/test_connector.py @@ -301,7 +301,7 @@ def test_expand_single_scalar(self): m.component('c.expanded').pprint(ostream=os) self.assertEqual( os.getvalue(), - """c.expanded : Size=1, Index='c.expanded_index', Active=True + """c.expanded : Size=1, Index={1}, Active=True Key : Lower : Body : Upper : Active 1 : 1.0 : x : 1.0 : True """, @@ -336,7 +336,7 @@ def test_expand_scalar(self): m.component('c.expanded').pprint(ostream=os) self.assertEqual( os.getvalue(), - """c.expanded : Size=2, Index='c.expanded_index', Active=True + """c.expanded : Size=2, Index={1, 2}, Active=True Key : Lower : Body : Upper : Active 1 : 1.0 : x : 1.0 : True 2 : 1.0 : y : 1.0 : True @@ -372,7 +372,7 @@ def test_expand_expression(self): m.component('c.expanded').pprint(ostream=os) self.assertEqual( os.getvalue(), - """c.expanded : Size=2, Index='c.expanded_index', Active=True + """c.expanded : Size=2, Index={1, 2}, Active=True Key : Lower : Body : Upper : Active 1 : 1.0 : - x : 1.0 : True 2 : 1.0 : 1 + y : 1.0 : True @@ -408,7 +408,7 @@ def test_expand_indexed(self): m.component('c.expanded').pprint(ostream=os) self.assertEqual( os.getvalue(), - """c.expanded : Size=3, Index='c.expanded_index', Active=True + """c.expanded : Size=3, Index={1, 2, 3}, Active=True Key : Lower : Body : Upper : Active 1 : 1.0 : x[1] : 1.0 : True 2 : 1.0 : x[2] : 1.0 : True @@ -451,7 +451,7 @@ def test_expand_empty_scalar(self): m.component('c.expanded').pprint(ostream=os) self.assertEqual( os.getvalue(), - """c.expanded : Size=2, Index='c.expanded_index', Active=True + """c.expanded : Size=2, Index={1, 2}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : x - 'ECON.auto.x' : 0.0 : True 2 : 0.0 : y - 'ECON.auto.y' : 0.0 : True @@ -488,7 +488,7 @@ def test_expand_empty_expression(self): m.component('c.expanded').pprint(ostream=os) self.assertEqual( os.getvalue(), - """c.expanded : Size=2, Index='c.expanded_index', Active=True + """c.expanded : Size=2, Index={1, 2}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : - x - 'ECON.auto.x' : 0.0 : True 2 : 0.0 : 1 + y - 'ECON.auto.y' : 0.0 : True @@ -533,7 +533,7 @@ def test_expand_empty_indexed(self): m.component('c.expanded').pprint(ostream=os) self.assertEqual( os.getvalue(), - """c.expanded : Size=3, Index='c.expanded_index', Active=True + """c.expanded : Size=3, Index={1, 2, 3}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : x[1] - 'ECON.auto.x'[1] : 0.0 : True 2 : 0.0 : x[2] - 'ECON.auto.x'[2] : 0.0 : True @@ -590,7 +590,7 @@ def test_expand_multiple_empty_indexed(self): m.component('c.expanded').pprint(ostream=os) self.assertEqual( os.getvalue(), - """c.expanded : Size=3, Index='c.expanded_index', Active=True + """c.expanded : Size=3, Index={1, 2, 3}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : x[1] - 'ECON1.auto.x'[1] : 0.0 : True 2 : 0.0 : x[2] - 'ECON1.auto.x'[2] : 0.0 : True @@ -602,7 +602,7 @@ def test_expand_multiple_empty_indexed(self): m.component('d.expanded').pprint(ostream=os) self.assertEqual( os.getvalue(), - """d.expanded : Size=3, Index='d.expanded_index', Active=True + """d.expanded : Size=3, Index={1, 2, 3}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : 'ECON2.auto.x'[1] - 'ECON1.auto.x'[1] : 0.0 : True 2 : 0.0 : 'ECON2.auto.x'[2] - 'ECON1.auto.x'[2] : 0.0 : True @@ -653,7 +653,7 @@ def test_expand_multiple_indexed(self): m.component('c.expanded').pprint(ostream=os) self.assertEqual( os.getvalue(), - """c.expanded : Size=3, Index='c.expanded_index', Active=True + """c.expanded : Size=3, Index={1, 2, 3}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : x[1] - a2[1] : 0.0 : True 2 : 0.0 : x[2] - a2[2] : 0.0 : True @@ -665,7 +665,7 @@ def test_expand_multiple_indexed(self): m.component('d.expanded').pprint(ostream=os) self.assertEqual( os.getvalue(), - """d.expanded : Size=3, Index='d.expanded_index', Active=True + """d.expanded : Size=3, Index={1, 2, 3}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : a1[1] - a2[1] : 0.0 : True 2 : 0.0 : a1[2] - a2[2] : 0.0 : True @@ -734,7 +734,7 @@ def test_expand_implicit_indexed(self): m.component('c.expanded').pprint(ostream=os) self.assertEqual( os.getvalue(), - """c.expanded : Size=3, Index='c.expanded_index', Active=True + """c.expanded : Size=3, Index={1, 2, 3}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : x[1] - a2[1] : 0.0 : True 2 : 0.0 : x[2] - a2[2] : 0.0 : True @@ -746,7 +746,7 @@ def test_expand_implicit_indexed(self): m.component('d.expanded').pprint(ostream=os) self.assertEqual( os.getvalue(), - """d.expanded : Size=3, Index='d.expanded_index', Active=True + """d.expanded : Size=3, Index={1, 2, 3}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : 'ECON2.auto.x'[1] - x[1] : 0.0 : True 2 : 0.0 : 'ECON2.auto.x'[2] - x[2] : 0.0 : True @@ -789,7 +789,7 @@ def test_varlist_aggregator(self): m.component('c.expanded').pprint(ostream=os) self.assertEqual( os.getvalue(), - """c.expanded : Size=2, Index='c.expanded_index', Active=True + """c.expanded : Size=2, Index={1, 2}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : flow[1] - 'ECON1.auto.flow' : 0.0 : True 2 : 0.0 : phase - 'ECON1.auto.phase' : 0.0 : True @@ -800,7 +800,7 @@ def test_varlist_aggregator(self): m.component('d.expanded').pprint(ostream=os) self.assertEqual( os.getvalue(), - """d.expanded : Size=2, Index='d.expanded_index', Active=True + """d.expanded : Size=2, Index={1, 2}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : 'ECON2.auto.flow' - flow[2] : 0.0 : True 2 : 0.0 : 'ECON2.auto.phase' - phase : 0.0 : True @@ -844,7 +844,7 @@ def test_indexed_connector(self): m.component('eq.expanded').pprint(ostream=os) self.assertEqual( os.getvalue(), - """eq.expanded : Size=1, Index='eq.expanded_index', Active=True + """eq.expanded : Size=1, Index={1}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : x - y : 0.0 : True """, diff --git a/pyomo/core/tests/unit/test_expr5.txt b/pyomo/core/tests/unit/test_expr5.txt index a5fc934bd77..2bf78cb4985 100644 --- a/pyomo/core/tests/unit/test_expr5.txt +++ b/pyomo/core/tests/unit/test_expr5.txt @@ -1,11 +1,8 @@ -2 Set Declarations +1 Set Declarations A : set A Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {1, 2, 3} - c3_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 1 : {1,} 2 Param Declarations B : param B @@ -49,8 +46,8 @@ 2 : -Inf : B[2]*x[2] : 1.0 : True 3 : -Inf : B[3]*x[3] : 1.0 : True c3 : con c3 - Size=1, Index=c3_index, Active=True + Size=1, Index={1}, Active=True Key : Lower : Body : Upper : Active 1 : -Inf : y : 0.0 : True -10 Declarations: A B C x y o c1 c2 c3_index c3 +9 Declarations: A B C x y o c1 c2 c3 diff --git a/pyomo/core/tests/unit/test_expression.py b/pyomo/core/tests/unit/test_expression.py index 8dca0062dd0..bd5a0aad6c9 100644 --- a/pyomo/core/tests/unit/test_expression.py +++ b/pyomo/core/tests/unit/test_expression.py @@ -742,7 +742,7 @@ def test_pprint_oldStyle(self): e : Size=1, Index=None Key : Expression None : sum(mon(1, x), 2) -E : Size=2, Index=E_index +E : Size=2, Index={1, 2} Key : Expression 1 : sum(pow(x, 2), 1) 2 : sum(pow(x, 2), 1) @@ -761,7 +761,7 @@ def test_pprint_oldStyle(self): e : Size=1, Index=None Key : Expression None : 1.0 -E : Size=2, Index=E_index +E : Size=2, Index={1, 2} Key : Expression 1 : 2.0 2 : sum(pow(x, 2), 1) @@ -780,7 +780,7 @@ def test_pprint_oldStyle(self): e : Size=1, Index=None Key : Expression None : Undefined -E : Size=2, Index=E_index +E : Size=2, Index={1, 2} Key : Expression 1 : Undefined 2 : sum(pow(x, 2), 1) @@ -806,7 +806,7 @@ def test_pprint_newStyle(self): e : Size=1, Index=None Key : Expression None : x + 2 -E : Size=2, Index=E_index +E : Size=2, Index={1, 2} Key : Expression 1 : x**2 + 1 2 : x**2 + 1 @@ -830,7 +830,7 @@ def test_pprint_newStyle(self): e : Size=1, Index=None Key : Expression None : 1.0 -E : Size=2, Index=E_index +E : Size=2, Index={1, 2} Key : Expression 1 : 2.0 2 : x**2 + 1 @@ -849,7 +849,7 @@ def test_pprint_newStyle(self): e : Size=1, Index=None Key : Expression None : Undefined -E : Size=2, Index=E_index +E : Size=2, Index={1, 2} Key : Expression 1 : Undefined 2 : x**2 + 1 diff --git a/pyomo/core/tests/unit/test_reference.py b/pyomo/core/tests/unit/test_reference.py index a7a470b1a3b..6c2e1d28053 100644 --- a/pyomo/core/tests/unit/test_reference.py +++ b/pyomo/core/tests/unit/test_reference.py @@ -729,7 +729,6 @@ def test_component_data_reference(self): self.assertIs(m.r.ctype, Var) self.assertIsNot(m.r.index_set(), m.y.index_set()) - self.assertIs(m.y.index_set(), m.y_index) self.assertIs(m.r.index_set(), UnindexedComponent_ReferenceSet) self.assertEqual(len(m.r), 1) self.assertTrue(m.r.is_reference()) @@ -773,7 +772,7 @@ def test_reference_var_pprint(self): m.r.pprint(ostream=buf) self.assertEqual( buf.getvalue(), - """r : Size=2, Index=x_index, ReferenceTo=x + """r : Size=2, Index={1, 2}, ReferenceTo=x Key : Lower : Value : Upper : Fixed : Stale : Domain 1 : None : 4 : None : False : False : Reals 2 : None : 8 : None : False : False : Reals @@ -784,7 +783,7 @@ def test_reference_var_pprint(self): m.s.pprint(ostream=buf) self.assertEqual( buf.getvalue(), - """s : Size=2, Index=x_index, ReferenceTo=x[:, ...] + """s : Size=2, Index={1, 2}, ReferenceTo=x[:, ...] Key : Lower : Value : Upper : Fixed : Stale : Domain 1 : None : 4 : None : False : False : Reals 2 : None : 8 : None : False : False : Reals @@ -799,7 +798,7 @@ def test_reference_indexedcomponent_pprint(self): m.r.pprint(ostream=buf) self.assertEqual( buf.getvalue(), - """r : Size=2, Index=x_index, ReferenceTo=x + """r : Size=2, Index={1, 2}, ReferenceTo=x Key : Object 1 : 2 : @@ -810,7 +809,7 @@ def test_reference_indexedcomponent_pprint(self): m.s.pprint(ostream=buf) self.assertEqual( buf.getvalue(), - """s : Size=2, Index=x_index, ReferenceTo=x[:, ...] + """s : Size=2, Index={1, 2}, ReferenceTo=x[:, ...] Key : Object 1 : 2 : @@ -1380,7 +1379,7 @@ def b(b, i): self.assertEqual( buf.getvalue().strip(), """ -r : Size=4, Index=r_index, ReferenceTo=b[:].x[:] +r : Size=4, Index=ReferenceSet(b[:].x[:]), ReferenceTo=b[:].x[:] Key : Lower : Value : Upper : Fixed : Stale : Domain (1, 3) : 1 : None : None : False : True : Reals (1, 4) : 1 : None : None : False : True : Reals diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index a1072e7156c..6c7511359a1 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -993,9 +993,7 @@ def __ge__(self, other): output = StringIO() with LoggingIntercept(output, 'pyomo.core', logging.DEBUG): i = SetOf([1, 2, 3]) - self.assertEqual(output.getvalue(), "") - i.construct() - ref = 'Constructing SetOf, name=OrderedSetOf, from data=None\n' + ref = 'Constructing SetOf, name=[1, 2, 3], from data=None\n' self.assertEqual(output.getvalue(), ref) # Calling construct() twice bypasses construction the second # time around @@ -1811,7 +1809,7 @@ def test_check_values(self): class Test_SetOperator(unittest.TestCase): def test_construct(self): p = Param(initialize=3) - a = RangeSet(p) + a = RangeSet(p, name='a') output = StringIO() with LoggingIntercept(output, 'pyomo.core', logging.DEBUG): i = a * a @@ -1820,12 +1818,9 @@ def test_construct(self): with LoggingIntercept(output, 'pyomo.core', logging.DEBUG): i.construct() ref = ( - 'Constructing SetOperator, name=SetProduct_OrderedSet, ' - 'from data=None\n' - 'Constructing RangeSet, name=FiniteScalarRangeSet, ' - 'from data=None\n' - 'Constructing Set, name=SetProduct_OrderedSet, ' - 'from data=None\n' + 'Constructing SetOperator, name=a*a, from data=None\n' + 'Constructing Set, name=a*a, from data=None\n' + 'Constructing RangeSet, name=a, from data=None\n' ) self.assertEqual(output.getvalue(), ref) # Calling construct() twice bypasses construction the second @@ -1937,8 +1932,8 @@ def test_domain_and_pprint(self): m.A.pprint(ostream=output) ref = """ A : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 1 : I | A_index_0 : 4 : {1, 2, 3, 4} + Key : Dimen : Domain : Size : Members + None : 1 : I | {3, 4} : 4 : {1, 2, 3, 4} """.strip() self.assertEqual(output.getvalue().strip(), ref) @@ -2213,8 +2208,8 @@ def test_domain_and_pprint(self): m.A.pprint(ostream=output) ref = """ A : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 1 : I & A_index_0 : 0 : {} + Key : Dimen : Domain : Size : Members + None : 1 : I & {3, 4} : 0 : {} """.strip() self.assertEqual(output.getvalue().strip(), ref) @@ -2491,8 +2486,8 @@ def test_domain_and_pprint(self): m.A.pprint(ostream=output) ref = """ A : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 1 : I - A_index_0 : 2 : {1, 2} + Key : Dimen : Domain : Size : Members + None : 1 : I - {3, 4} : 2 : {1, 2} """.strip() self.assertEqual(output.getvalue().strip(), ref) @@ -2720,8 +2715,8 @@ def test_domain_and_pprint(self): m.A.pprint(ostream=output) ref = """ A : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 1 : I ^ A_index_0 : 4 : {1, 2, 3, 4} + Key : Dimen : Domain : Size : Members + None : 1 : I ^ {3, 4} : 4 : {1, 2, 3, 4} """.strip() self.assertEqual(output.getvalue().strip(), ref) @@ -2982,8 +2977,8 @@ def test_domain_and_pprint(self): m.A.pprint(ostream=output) ref = """ A : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : I*A_index_0 : 4 : {(1, 3), (1, 4), (2, 3), (2, 4)} + Key : Dimen : Domain : Size : Members + None : 2 : I*{3, 4} : 4 : {(1, 3), (1, 4), (2, 3), (2, 4)} """.strip() self.assertEqual(output.getvalue().strip(), ref) @@ -4406,17 +4401,17 @@ def test_domain(self): self.assertEqual(list(m.I), [0, 2.0, 4]) with self.assertRaisesRegex( ValueError, - 'The value is not in the domain ' r'\(Integers & I_domain_index_0_index_1', + r'The value is not in the domain \(Integers & \[0:inf:2\]\) & \[0..9\]', ): m.I.add(1.5) with self.assertRaisesRegex( ValueError, - 'The value is not in the domain ' r'\(Integers & I_domain_index_0_index_1', + r'The value is not in the domain \(Integers & \[0:inf:2\]\) & \[0..9\]', ): m.I.add(1) with self.assertRaisesRegex( ValueError, - 'The value is not in the domain ' r'\(Integers & I_domain_index_0_index_1', + r'The value is not in the domain \(Integers & \[0:inf:2\]\) & \[0..9\]', ): m.I.add(10) @@ -4454,8 +4449,8 @@ def myFcn(x): Key : Dimen : Domain : Size : Members None : 2 : Any : 2 : {(3, 4), (1, 2)} M : Size=1, Index=None, Ordered=False - Key : Dimen : Domain : Size : Members - None : 1 : Reals - M_index_1 : Inf : ([-inf..0) | (0..inf]) + Key : Dimen : Domain : Size : Members + None : 1 : Reals - [0] : Inf : ([-inf..0) | (0..inf]) N : Size=1, Index=None, Ordered=False Key : Dimen : Domain : Size : Members None : 1 : Integers - Reals : Inf : [] @@ -4465,12 +4460,7 @@ def myFcn(x): Key : Finite : Members None : True : [1:3] -1 SetOf Declarations - M_index_1 : Dimen=1, Size=1, Bounds=(0, 0) - Key : Ordered : Members - None : True : [0] - -8 Declarations: I_index I J K L M_index_1 M N""".strip(), +7 Declarations: I_index I J K L M N""".strip(), ) def test_pickle(self): @@ -4556,11 +4546,11 @@ def test_construction(self): ref = """ I : Size=0, Index=None, Ordered=Insertion Not constructed -II : Size=0, Index=II_index, Ordered=Insertion +II : Size=0, Index={1, 2, 3}, Ordered=Insertion Not constructed J : Size=0, Index=None, Ordered=Insertion Not constructed -JJ : Size=0, Index=JJ_index, Ordered=Insertion +JJ : Size=0, Index={1, 2, 3}, Ordered=Insertion Not constructed""".strip() self.assertEqual(output.getvalue().strip(), ref) @@ -4827,7 +4817,7 @@ def _i_init(m, i): output = StringIO() m.I.pprint(ostream=output) ref = """ -I : Size=2, Index=I_index, Ordered=Insertion +I : Size=2, Index={1, 2, 3, 4, 5}, Ordered=Insertion Key : Dimen : Domain : Size : Members 2 : 1 : Any : 2 : {0, 1} 4 : 1 : Any : 4 : {0, 1, 2, 3} @@ -6301,14 +6291,11 @@ def objective_rule(model_arg): output = StringIO() m.pprint(ostream=output) ref = """ -3 Set Declarations +2 Set Declarations arc_keys : Set of arcs Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 2 : arc_keys_domain : 2 : {(0, 0), (0, 1)} - arc_keys_domain : Size=1, Index=None, Ordered=True Key : Dimen : Domain : Size : Members - None : 2 : node_keys*node_keys : 4 : {(0, 0), (0, 1), (1, 0), (1, 1)} + None : 2 : node_keys*node_keys : 2 : {(0, 0), (0, 1)} node_keys : Set of nodes Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members @@ -6325,7 +6312,7 @@ def objective_rule(model_arg): Key : Active : Sense : Expression None : True : minimize : arc_variables[0,0] + arc_variables[0,1] -5 Declarations: node_keys arc_keys_domain arc_keys arc_variables obj +4 Declarations: node_keys arc_keys arc_variables obj """.strip() self.assertEqual(output.getvalue().strip(), ref) @@ -6334,18 +6321,15 @@ def objective_rule(model_arg): output = StringIO() m.pprint(ostream=output) ref = """ -3 Set Declarations +2 Set Declarations arc_keys : Set of arcs Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : None : arc_keys_domain : 2 : {ArcKey(node_from=NodeKey(id=0), node_to=NodeKey(id=0)), ArcKey(node_from=NodeKey(id=0), node_to=NodeKey(id=1))} - arc_keys_domain : Size=1, Index=None, Ordered=True Key : Dimen : Domain : Size : Members - None : None : node_keys*node_keys : 4 : {(NodeKey(id=0), NodeKey(id=0)), (NodeKey(id=0), NodeKey(id=1)), (NodeKey(id=1), NodeKey(id=0)), (NodeKey(id=1), NodeKey(id=1))} + None : 2 : node_keys*node_keys : 2 : {ArcKey(node_from=NodeKey(id=0), node_to=NodeKey(id=0)), ArcKey(node_from=NodeKey(id=0), node_to=NodeKey(id=1))} node_keys : Set of nodes Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members - None : None : Any : 2 : {NodeKey(id=0), NodeKey(id=1)} + None : 1 : Any : 2 : {NodeKey(id=0), NodeKey(id=1)} 1 Var Declarations arc_variables : Size=2, Index=arc_keys @@ -6358,7 +6342,7 @@ def objective_rule(model_arg): Key : Active : Sense : Expression None : True : minimize : arc_variables[ArcKey(node_from=NodeKey(id=0), node_to=NodeKey(id=0))] + arc_variables[ArcKey(node_from=NodeKey(id=0), node_to=NodeKey(id=1))] -5 Declarations: node_keys arc_keys_domain arc_keys arc_variables obj +4 Declarations: node_keys arc_keys arc_variables obj """.strip() self.assertEqual(output.getvalue().strip(), ref) diff --git a/pyomo/core/tests/unit/test_visitor.py b/pyomo/core/tests/unit/test_visitor.py index 086c57aa560..5625b63f272 100644 --- a/pyomo/core/tests/unit/test_visitor.py +++ b/pyomo/core/tests/unit/test_visitor.py @@ -405,7 +405,6 @@ def test_replacement_walker0(self): ) del M.w - del M.w_index M.w = VarList() e = 2 * sum_product(M.z, M.x) walker = ReplacementWalkerTest1(M) diff --git a/pyomo/core/tests/unit/varpprint.txt b/pyomo/core/tests/unit/varpprint.txt index bd49b881417..a8c33c6b007 100644 --- a/pyomo/core/tests/unit/varpprint.txt +++ b/pyomo/core/tests/unit/varpprint.txt @@ -1,13 +1,7 @@ -3 Set Declarations +1 Set Declarations a : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {1, 2, 3} - cl_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 10 : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} - o3_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : a*a : 9 : {(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3), (3, 1), (3, 2), (3, 3)} 2 Param Declarations A : Size=1, Index=None, Domain=Any, Default=-1, Mutable=True @@ -37,7 +31,7 @@ 1 : True : minimize : b[1] 2 : True : minimize : b[2] 3 : True : minimize : b[3] - o3 : Size=0, Index=o3_index, Active=True + o3 : Size=0, Index=a*a, Active=True Key : Active : Sense : Expression 19 Constraint Declarations @@ -97,7 +91,7 @@ c9b : Size=1, Index=None, Active=True Key : Lower : Body : Upper : Active None : -Inf : c : A + A : True - cl : Size=10, Index=cl_index, Active=True + cl : Size=10, Index={1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, Active=True Key : Lower : Body : Upper : Active 1 : -Inf : d - c : 0.0 : True 2 : -Inf : d - 2*c : 0.0 : True @@ -110,4 +104,4 @@ 9 : -Inf : d - 9*c : 0.0 : True 10 : -Inf : d - 10*c : 0.0 : True -30 Declarations: a b c d e A B o2 o3_index o3 c1 c2 c3 c4 c5 c6a c7a c7b c8 c9a c9b c10a c11 c15a c16a c12 c13a c14a cl_index cl +28 Declarations: a b c d e A B o2 o3 c1 c2 c3 c4 c5 c6a c7a c7b c8 c9a c9b c10a c11 c15a c16a c12 c13a c14a cl diff --git a/pyomo/dae/tests/test_diffvar.py b/pyomo/dae/tests/test_diffvar.py index 718781d5916..7ac54445f5c 100644 --- a/pyomo/dae/tests/test_diffvar.py +++ b/pyomo/dae/tests/test_diffvar.py @@ -69,7 +69,6 @@ def test_valid(self): del m.dv del m.dv2 del m.v - del m.v_index m.v = Var(m.x, m.t) m.dv = DerivativeVar(m.v, wrt=m.x) diff --git a/pyomo/mpec/tests/cov2_None.txt b/pyomo/mpec/tests/cov2_None.txt index 2f7d59572a8..c3c0baeeb9e 100644 --- a/pyomo/mpec/tests/cov2_None.txt +++ b/pyomo/mpec/tests/cov2_None.txt @@ -1,2 +1,2 @@ -cc : Size=0, Index=cc_index, Active=True +cc : Size=0, Index={0, 1, 2}, Active=True Key : Arg0 : Arg1 : Active diff --git a/pyomo/mpec/tests/cov2_mpec.nl.txt b/pyomo/mpec/tests/cov2_mpec.nl.txt index a526784344b..9b7b9ed53f4 100644 --- a/pyomo/mpec/tests/cov2_mpec.nl.txt +++ b/pyomo/mpec/tests/cov2_mpec.nl.txt @@ -1,8 +1,3 @@ -1 Set Declarations - cc_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {0, 1, 2} - 4 Var Declarations x1 : Size=1, Index=None Key : Lower : Value : Upper : Fixed : Stale : Domain @@ -23,7 +18,7 @@ None : 0.5 : x1 : 0.5 : True 1 Block Declarations - cc : Size=0, Index=cc_index, Active=True + cc : Size=0, Index={0, 1, 2}, Active=True Key : Arg0 : Arg1 : Active -7 Declarations: y x1 x2 x3 cc_index cc keep_var_con +6 Declarations: y x1 x2 x3 cc keep_var_con diff --git a/pyomo/mpec/tests/cov2_mpec.simple_disjunction.txt b/pyomo/mpec/tests/cov2_mpec.simple_disjunction.txt index 2f7d59572a8..c3c0baeeb9e 100644 --- a/pyomo/mpec/tests/cov2_mpec.simple_disjunction.txt +++ b/pyomo/mpec/tests/cov2_mpec.simple_disjunction.txt @@ -1,2 +1,2 @@ -cc : Size=0, Index=cc_index, Active=True +cc : Size=0, Index={0, 1, 2}, Active=True Key : Arg0 : Arg1 : Active diff --git a/pyomo/mpec/tests/cov2_mpec.simple_nonlinear.txt b/pyomo/mpec/tests/cov2_mpec.simple_nonlinear.txt index 2f7d59572a8..c3c0baeeb9e 100644 --- a/pyomo/mpec/tests/cov2_mpec.simple_nonlinear.txt +++ b/pyomo/mpec/tests/cov2_mpec.simple_nonlinear.txt @@ -1,2 +1,2 @@ -cc : Size=0, Index=cc_index, Active=True +cc : Size=0, Index={0, 1, 2}, Active=True Key : Arg0 : Arg1 : Active diff --git a/pyomo/mpec/tests/cov2_mpec.standard_form.txt b/pyomo/mpec/tests/cov2_mpec.standard_form.txt index 2f7d59572a8..c3c0baeeb9e 100644 --- a/pyomo/mpec/tests/cov2_mpec.standard_form.txt +++ b/pyomo/mpec/tests/cov2_mpec.standard_form.txt @@ -1,2 +1,2 @@ -cc : Size=0, Index=cc_index, Active=True +cc : Size=0, Index={0, 1, 2}, Active=True Key : Arg0 : Arg1 : Active diff --git a/pyomo/mpec/tests/list1_None.txt b/pyomo/mpec/tests/list1_None.txt index 8e849242bcd..34c358a1521 100644 --- a/pyomo/mpec/tests/list1_None.txt +++ b/pyomo/mpec/tests/list1_None.txt @@ -1,4 +1,4 @@ -cc : Size=2, Index=cc_index, Active=True +cc : Size=2, Index={1, 2}, Active=True Key : Arg0 : Arg1 : Active 1 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 2 : True diff --git a/pyomo/mpec/tests/list1_mpec.nl.txt b/pyomo/mpec/tests/list1_mpec.nl.txt index 16310c59317..62edc488b47 100644 --- a/pyomo/mpec/tests/list1_mpec.nl.txt +++ b/pyomo/mpec/tests/list1_mpec.nl.txt @@ -1,8 +1,3 @@ -1 Set Declarations - cc_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 2 : {1, 2} - 4 Var Declarations x1 : Size=1, Index=None Key : Lower : Value : Upper : Fixed : Stale : Domain @@ -18,7 +13,7 @@ None : None : None : None : False : True : Reals 1 Block Declarations - cc : Size=2, Index=cc_index, Active=True + cc : Size=2, Index={1, 2}, Active=True Key : Arg0 : Arg1 : Active 1 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 2 : True @@ -37,4 +32,4 @@ 1 Declarations: c -6 Declarations: y x1 x2 x3 cc_index cc +5 Declarations: y x1 x2 x3 cc diff --git a/pyomo/mpec/tests/list1_mpec.simple_disjunction.txt b/pyomo/mpec/tests/list1_mpec.simple_disjunction.txt index 816e56af56c..c2bfe5e0399 100644 --- a/pyomo/mpec/tests/list1_mpec.simple_disjunction.txt +++ b/pyomo/mpec/tests/list1_mpec.simple_disjunction.txt @@ -1,4 +1,4 @@ -cc : Size=2, Index=cc_index, Active=True +cc : Size=2, Index={1, 2}, Active=True Key : Arg0 : Arg1 : Active 1 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 2 : True diff --git a/pyomo/mpec/tests/list1_mpec.simple_nonlinear.txt b/pyomo/mpec/tests/list1_mpec.simple_nonlinear.txt index 816e56af56c..c2bfe5e0399 100644 --- a/pyomo/mpec/tests/list1_mpec.simple_nonlinear.txt +++ b/pyomo/mpec/tests/list1_mpec.simple_nonlinear.txt @@ -1,4 +1,4 @@ -cc : Size=2, Index=cc_index, Active=True +cc : Size=2, Index={1, 2}, Active=True Key : Arg0 : Arg1 : Active 1 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 2 : True diff --git a/pyomo/mpec/tests/list1_mpec.standard_form.txt b/pyomo/mpec/tests/list1_mpec.standard_form.txt index 816e56af56c..c2bfe5e0399 100644 --- a/pyomo/mpec/tests/list1_mpec.standard_form.txt +++ b/pyomo/mpec/tests/list1_mpec.standard_form.txt @@ -1,4 +1,4 @@ -cc : Size=2, Index=cc_index, Active=True +cc : Size=2, Index={1, 2}, Active=True Key : Arg0 : Arg1 : Active 1 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 2 : True diff --git a/pyomo/mpec/tests/list2_None.txt b/pyomo/mpec/tests/list2_None.txt index cc84321fe3e..465bc347766 100644 --- a/pyomo/mpec/tests/list2_None.txt +++ b/pyomo/mpec/tests/list2_None.txt @@ -1,4 +1,4 @@ -cc : Size=3, Index=cc_index, Active=True +cc : Size=3, Index={1, 2, 3}, Active=True Key : Arg0 : Arg1 : Active 1 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 1 : False diff --git a/pyomo/mpec/tests/list2_mpec.nl.txt b/pyomo/mpec/tests/list2_mpec.nl.txt index c8c461e08e8..6dc49cef8dd 100644 --- a/pyomo/mpec/tests/list2_mpec.nl.txt +++ b/pyomo/mpec/tests/list2_mpec.nl.txt @@ -1,8 +1,3 @@ -1 Set Declarations - cc_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {1, 2, 3} - 4 Var Declarations x1 : Size=1, Index=None Key : Lower : Value : Upper : Fixed : Stale : Domain @@ -18,7 +13,7 @@ None : None : None : None : False : True : Reals 1 Block Declarations - cc : Size=3, Index=cc_index, Active=True + cc : Size=3, Index={1, 2, 3}, Active=True Key : Arg0 : Arg1 : Active 1 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 1 : False @@ -40,4 +35,4 @@ 1 Declarations: c -6 Declarations: y x1 x2 x3 cc_index cc +5 Declarations: y x1 x2 x3 cc diff --git a/pyomo/mpec/tests/list2_mpec.simple_disjunction.txt b/pyomo/mpec/tests/list2_mpec.simple_disjunction.txt index 82688e8f017..c71d6461d22 100644 --- a/pyomo/mpec/tests/list2_mpec.simple_disjunction.txt +++ b/pyomo/mpec/tests/list2_mpec.simple_disjunction.txt @@ -1,4 +1,4 @@ -cc : Size=3, Index=cc_index, Active=True +cc : Size=3, Index={1, 2, 3}, Active=True Key : Arg0 : Arg1 : Active 1 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 1 : False diff --git a/pyomo/mpec/tests/list2_mpec.simple_nonlinear.txt b/pyomo/mpec/tests/list2_mpec.simple_nonlinear.txt index 82688e8f017..c71d6461d22 100644 --- a/pyomo/mpec/tests/list2_mpec.simple_nonlinear.txt +++ b/pyomo/mpec/tests/list2_mpec.simple_nonlinear.txt @@ -1,4 +1,4 @@ -cc : Size=3, Index=cc_index, Active=True +cc : Size=3, Index={1, 2, 3}, Active=True Key : Arg0 : Arg1 : Active 1 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 1 : False diff --git a/pyomo/mpec/tests/list2_mpec.standard_form.txt b/pyomo/mpec/tests/list2_mpec.standard_form.txt index 82688e8f017..c71d6461d22 100644 --- a/pyomo/mpec/tests/list2_mpec.standard_form.txt +++ b/pyomo/mpec/tests/list2_mpec.standard_form.txt @@ -1,4 +1,4 @@ -cc : Size=3, Index=cc_index, Active=True +cc : Size=3, Index={1, 2, 3}, Active=True Key : Arg0 : Arg1 : Active 1 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 1 : False diff --git a/pyomo/mpec/tests/list5_None.txt b/pyomo/mpec/tests/list5_None.txt index 8e6ed9a8164..962ee6cbc3a 100644 --- a/pyomo/mpec/tests/list5_None.txt +++ b/pyomo/mpec/tests/list5_None.txt @@ -1,4 +1,4 @@ -cc : Size=3, Index=cc_index, Active=True +cc : Size=3, Index={1, 2, 3}, Active=True Key : Arg0 : Arg1 : Active 1 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 1 : True diff --git a/pyomo/mpec/tests/list5_mpec.nl.txt b/pyomo/mpec/tests/list5_mpec.nl.txt index adb64af0457..93ee89f3389 100644 --- a/pyomo/mpec/tests/list5_mpec.nl.txt +++ b/pyomo/mpec/tests/list5_mpec.nl.txt @@ -1,8 +1,3 @@ -1 Set Declarations - cc_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {1, 2, 3} - 4 Var Declarations x1 : Size=1, Index=None Key : Lower : Value : Upper : Fixed : Stale : Domain @@ -18,7 +13,7 @@ None : None : None : None : False : True : Reals 1 Block Declarations - cc : Size=3, Index=cc_index, Active=True + cc : Size=3, Index={1, 2, 3}, Active=True Key : Arg0 : Arg1 : Active 1 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 1 : True @@ -45,4 +40,4 @@ 1 Declarations: c -6 Declarations: y x1 x2 x3 cc_index cc +5 Declarations: y x1 x2 x3 cc diff --git a/pyomo/mpec/tests/list5_mpec.simple_disjunction.txt b/pyomo/mpec/tests/list5_mpec.simple_disjunction.txt index 69178523d96..15622fa84e1 100644 --- a/pyomo/mpec/tests/list5_mpec.simple_disjunction.txt +++ b/pyomo/mpec/tests/list5_mpec.simple_disjunction.txt @@ -1,4 +1,4 @@ -cc : Size=3, Index=cc_index, Active=True +cc : Size=3, Index={1, 2, 3}, Active=True Key : Arg0 : Arg1 : Active 1 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 1 : True diff --git a/pyomo/mpec/tests/list5_mpec.simple_nonlinear.txt b/pyomo/mpec/tests/list5_mpec.simple_nonlinear.txt index 69178523d96..15622fa84e1 100644 --- a/pyomo/mpec/tests/list5_mpec.simple_nonlinear.txt +++ b/pyomo/mpec/tests/list5_mpec.simple_nonlinear.txt @@ -1,4 +1,4 @@ -cc : Size=3, Index=cc_index, Active=True +cc : Size=3, Index={1, 2, 3}, Active=True Key : Arg0 : Arg1 : Active 1 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 1 : True diff --git a/pyomo/mpec/tests/list5_mpec.standard_form.txt b/pyomo/mpec/tests/list5_mpec.standard_form.txt index 69178523d96..15622fa84e1 100644 --- a/pyomo/mpec/tests/list5_mpec.standard_form.txt +++ b/pyomo/mpec/tests/list5_mpec.standard_form.txt @@ -1,4 +1,4 @@ -cc : Size=3, Index=cc_index, Active=True +cc : Size=3, Index={1, 2, 3}, Active=True Key : Arg0 : Arg1 : Active 1 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 1 : True diff --git a/pyomo/mpec/tests/t10_None.txt b/pyomo/mpec/tests/t10_None.txt index afc38166ab3..7d6b4c429cc 100644 --- a/pyomo/mpec/tests/t10_None.txt +++ b/pyomo/mpec/tests/t10_None.txt @@ -1,4 +1,4 @@ -cc : Size=3, Index=cc_index, Active=True +cc : Size=3, Index={0, 1, 2}, Active=True Key : Arg0 : Arg1 : Active 0 : y + x3 : x1 + 2*x2 == 0 : True 1 : y + x3 : x1 + 2*x2 == 1 : False diff --git a/pyomo/mpec/tests/t10_mpec.nl.txt b/pyomo/mpec/tests/t10_mpec.nl.txt index a4a16713eaa..12db893ddba 100644 --- a/pyomo/mpec/tests/t10_mpec.nl.txt +++ b/pyomo/mpec/tests/t10_mpec.nl.txt @@ -1,8 +1,3 @@ -1 Set Declarations - cc_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {0, 1, 2} - 4 Var Declarations x1 : Size=1, Index=None Key : Lower : Value : Upper : Fixed : Stale : Domain @@ -18,7 +13,7 @@ None : None : None : None : False : True : Reals 1 Block Declarations - cc : Size=3, Index=cc_index, Active=True + cc : Size=3, Index={0, 1, 2}, Active=True Key : Arg0 : Arg1 : Active 0 : y + x3 : x1 + 2*x2 == 0 : True 1 : y + x3 : x1 + 2*x2 == 1 : False @@ -40,4 +35,4 @@ 1 Declarations: c -6 Declarations: y x1 x2 x3 cc_index cc +5 Declarations: y x1 x2 x3 cc diff --git a/pyomo/mpec/tests/t10_mpec.simple_disjunction.txt b/pyomo/mpec/tests/t10_mpec.simple_disjunction.txt index c53c1b8e62b..37aaaafcf68 100644 --- a/pyomo/mpec/tests/t10_mpec.simple_disjunction.txt +++ b/pyomo/mpec/tests/t10_mpec.simple_disjunction.txt @@ -1,4 +1,4 @@ -cc : Size=3, Index=cc_index, Active=True +cc : Size=3, Index={0, 1, 2}, Active=True Key : Arg0 : Arg1 : Active 0 : y + x3 : x1 + 2*x2 == 0 : True 1 : y + x3 : x1 + 2*x2 == 1 : False diff --git a/pyomo/mpec/tests/t10_mpec.simple_nonlinear.txt b/pyomo/mpec/tests/t10_mpec.simple_nonlinear.txt index c53c1b8e62b..37aaaafcf68 100644 --- a/pyomo/mpec/tests/t10_mpec.simple_nonlinear.txt +++ b/pyomo/mpec/tests/t10_mpec.simple_nonlinear.txt @@ -1,4 +1,4 @@ -cc : Size=3, Index=cc_index, Active=True +cc : Size=3, Index={0, 1, 2}, Active=True Key : Arg0 : Arg1 : Active 0 : y + x3 : x1 + 2*x2 == 0 : True 1 : y + x3 : x1 + 2*x2 == 1 : False diff --git a/pyomo/mpec/tests/t10_mpec.standard_form.txt b/pyomo/mpec/tests/t10_mpec.standard_form.txt index c53c1b8e62b..37aaaafcf68 100644 --- a/pyomo/mpec/tests/t10_mpec.standard_form.txt +++ b/pyomo/mpec/tests/t10_mpec.standard_form.txt @@ -1,4 +1,4 @@ -cc : Size=3, Index=cc_index, Active=True +cc : Size=3, Index={0, 1, 2}, Active=True Key : Arg0 : Arg1 : Active 0 : y + x3 : x1 + 2*x2 == 0 : True 1 : y + x3 : x1 + 2*x2 == 1 : False diff --git a/pyomo/mpec/tests/t13_None.txt b/pyomo/mpec/tests/t13_None.txt index b2e24eb1166..fde3cc15a18 100644 --- a/pyomo/mpec/tests/t13_None.txt +++ b/pyomo/mpec/tests/t13_None.txt @@ -1,4 +1,4 @@ -cc : Size=2, Index=cc_index, Active=True +cc : Size=2, Index={0, 1, 2}, Active=True Key : Arg0 : Arg1 : Active 0 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 2 : True diff --git a/pyomo/mpec/tests/t13_mpec.nl.txt b/pyomo/mpec/tests/t13_mpec.nl.txt index dc47767efb7..9e709e35b6f 100644 --- a/pyomo/mpec/tests/t13_mpec.nl.txt +++ b/pyomo/mpec/tests/t13_mpec.nl.txt @@ -1,8 +1,3 @@ -1 Set Declarations - cc_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {0, 1, 2} - 4 Var Declarations x1 : Size=1, Index=None Key : Lower : Value : Upper : Fixed : Stale : Domain @@ -18,7 +13,7 @@ None : None : None : None : False : True : Reals 1 Block Declarations - cc : Size=2, Index=cc_index, Active=True + cc : Size=2, Index={0, 1, 2}, Active=True Key : Arg0 : Arg1 : Active 0 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 2 : True @@ -37,4 +32,4 @@ 1 Declarations: c -6 Declarations: y x1 x2 x3 cc_index cc +5 Declarations: y x1 x2 x3 cc diff --git a/pyomo/mpec/tests/t13_mpec.simple_disjunction.txt b/pyomo/mpec/tests/t13_mpec.simple_disjunction.txt index 1ff09babad8..9b361c7e503 100644 --- a/pyomo/mpec/tests/t13_mpec.simple_disjunction.txt +++ b/pyomo/mpec/tests/t13_mpec.simple_disjunction.txt @@ -1,4 +1,4 @@ -cc : Size=2, Index=cc_index, Active=True +cc : Size=2, Index={0, 1, 2}, Active=True Key : Arg0 : Arg1 : Active 0 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 2 : True diff --git a/pyomo/mpec/tests/t13_mpec.simple_nonlinear.txt b/pyomo/mpec/tests/t13_mpec.simple_nonlinear.txt index 1ff09babad8..9b361c7e503 100644 --- a/pyomo/mpec/tests/t13_mpec.simple_nonlinear.txt +++ b/pyomo/mpec/tests/t13_mpec.simple_nonlinear.txt @@ -1,4 +1,4 @@ -cc : Size=2, Index=cc_index, Active=True +cc : Size=2, Index={0, 1, 2}, Active=True Key : Arg0 : Arg1 : Active 0 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 2 : True diff --git a/pyomo/mpec/tests/t13_mpec.standard_form.txt b/pyomo/mpec/tests/t13_mpec.standard_form.txt index 1ff09babad8..9b361c7e503 100644 --- a/pyomo/mpec/tests/t13_mpec.standard_form.txt +++ b/pyomo/mpec/tests/t13_mpec.standard_form.txt @@ -1,4 +1,4 @@ -cc : Size=2, Index=cc_index, Active=True +cc : Size=2, Index={0, 1, 2}, Active=True Key : Arg0 : Arg1 : Active 0 : y + x3 : x1 + 2*x2 == 0 : True 2 : y + x3 : x1 + 2*x2 == 2 : True diff --git a/pyomo/network/tests/test_arc.py b/pyomo/network/tests/test_arc.py index cd340cace7a..3ea1aeeb380 100644 --- a/pyomo/network/tests/test_arc.py +++ b/pyomo/network/tests/test_arc.py @@ -504,11 +504,11 @@ def test_expand_indexed(self): os.getvalue(), """c_expanded : Size=1, Index=None, Active=True 3 Constraint Declarations - a_equality : Size=2, Index=x_index, Active=True + a_equality : Size=2, Index={1, 2}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : x[1] - t[1] : 0.0 : True 2 : 0.0 : x[2] - t[2] : 0.0 : True - b_equality : Size=4, Index=y_index, Active=True + b_equality : Size=4, Index={1, 2}*{1, 2}, Active=True Key : Lower : Body : Upper : Active (1, 1) : 0.0 : y[1,1] - u[1,1] : 0.0 : True (1, 2) : 0.0 : y[1,2] - u[1,2] : 0.0 : True @@ -677,7 +677,7 @@ def test_expand_empty_indexed(self): os.getvalue(), """c_expanded : Size=1, Index=None, Active=True 2 Constraint Declarations - x_equality : Size=2, Index=x_index, Active=True + x_equality : Size=2, Index={1, 2}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : x[1] - EPRT_auto_x[1] : 0.0 : True 2 : 0.0 : x[2] - EPRT_auto_x[2] : 0.0 : True @@ -739,7 +739,7 @@ def test_expand_multiple_empty_indexed(self): os.getvalue(), """c_expanded : Size=1, Index=None, Active=True 2 Constraint Declarations - x_equality : Size=2, Index=x_index, Active=True + x_equality : Size=2, Index={1, 2}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : x[1] - EPRT1_auto_x[1] : 0.0 : True 2 : 0.0 : x[2] - EPRT1_auto_x[2] : 0.0 : True @@ -757,7 +757,7 @@ def test_expand_multiple_empty_indexed(self): os.getvalue(), """d_expanded : Size=1, Index=None, Active=True 2 Constraint Declarations - x_equality : Size=2, Index=x_index, Active=True + x_equality : Size=2, Index={1, 2}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : EPRT2_auto_x[1] - EPRT1_auto_x[1] : 0.0 : True 2 : 0.0 : EPRT2_auto_x[2] - EPRT1_auto_x[2] : 0.0 : True @@ -812,7 +812,7 @@ def test_expand_multiple_indexed(self): os.getvalue(), """c_expanded : Size=1, Index=None, Active=True 2 Constraint Declarations - x_equality : Size=2, Index=x_index, Active=True + x_equality : Size=2, Index={1, 2}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : x[1] - a1[1] : 0.0 : True 2 : 0.0 : x[2] - a1[2] : 0.0 : True @@ -830,7 +830,7 @@ def test_expand_multiple_indexed(self): os.getvalue(), """d_expanded : Size=1, Index=None, Active=True 2 Constraint Declarations - x_equality : Size=2, Index=x_index, Active=True + x_equality : Size=2, Index={1, 2}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : a2[1] - a1[1] : 0.0 : True 2 : 0.0 : a2[2] - a1[2] : 0.0 : True @@ -903,7 +903,7 @@ def test_expand_implicit_indexed(self): os.getvalue(), """c_expanded : Size=1, Index=None, Active=True 2 Constraint Declarations - x_equality : Size=2, Index=a2_index, Active=True + x_equality : Size=2, Index={1, 2}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : a2[1] - x[1] : 0.0 : True 2 : 0.0 : a2[2] - x[2] : 0.0 : True @@ -921,7 +921,7 @@ def test_expand_implicit_indexed(self): os.getvalue(), """d_expanded : Size=1, Index=None, Active=True 2 Constraint Declarations - x_equality : Size=2, Index=a2_index, Active=True + x_equality : Size=2, Index={1, 2}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : EPRT2_auto_x[1] - x[1] : 0.0 : True 2 : 0.0 : EPRT2_auto_x[2] - x[2] : 0.0 : True @@ -964,7 +964,7 @@ def rule(m, i): m.component('eq_expanded').pprint(ostream=os) self.assertEqual( os.getvalue(), - """eq_expanded : Size=2, Index=eq_index, Active=True + """eq_expanded : Size=2, Index={1, 2}, Active=True eq_expanded[1] : Active=True 1 Constraint Declarations v_equality : Size=1, Index=None, Active=True From 34ec87ccef8ae9bbddfbfd6105a8821ccd728baf Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 11:49:06 -0700 Subject: [PATCH 0644/1204] Additional baseline update --- pyomo/core/tests/unit/test_set.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index 6c7511359a1..f04a8229cf0 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -1819,7 +1819,6 @@ def test_construct(self): i.construct() ref = ( 'Constructing SetOperator, name=a*a, from data=None\n' - 'Constructing Set, name=a*a, from data=None\n' 'Constructing RangeSet, name=a, from data=None\n' ) self.assertEqual(output.getvalue(), ref) From 5ea9c15af2090077fd64b2a143bddc0b97328a0f Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 11:56:15 -0700 Subject: [PATCH 0645/1204] Replace _implicit_subsets with _anonymous_sets --- pyomo/core/base/block.py | 69 ++++----------- pyomo/core/base/boolean_var.py | 4 + pyomo/core/base/constraint.py | 10 ++- pyomo/core/base/expression.py | 4 + pyomo/core/base/indexed_component.py | 33 ++++--- pyomo/core/base/logical_constraint.py | 9 +- pyomo/core/base/objective.py | 10 ++- pyomo/core/base/param.py | 6 +- pyomo/core/base/set.py | 121 +++++++++++++++++--------- pyomo/core/base/var.py | 18 ++-- pyomo/gdp/disjunct.py | 4 + pyomo/mpec/complementarity.py | 11 ++- pyomo/network/arc.py | 12 ++- pyomo/network/port.py | 12 ++- 14 files changed, 185 insertions(+), 138 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index fd5322ba686..43418089826 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -51,7 +51,7 @@ from pyomo.core.base.enums import SortComponents, TraversalStrategy from pyomo.core.base.global_set import UnindexedComponent_index from pyomo.core.base.componentuid import ComponentUID -from pyomo.core.base.set import Any, GlobalSetBase, _SetDataBase +from pyomo.core.base.set import Any from pyomo.core.base.var import Var from pyomo.core.base.initializer import Initializer from pyomo.core.base.indexed_component import ( @@ -846,47 +846,6 @@ def transfer_attributes_from(self, src): ): setattr(self, k, v) - def _add_implicit_sets(self, val): - """TODO: This method has known issues (see tickets) and needs to be - reviewed. [JDS 9/2014]""" - - _component_sets = getattr(val, '_implicit_subsets', None) - # - # FIXME: The name attribute should begin with "_", and None - # should replace "_unknown_" - # - if _component_sets is not None: - for ctr, tset in enumerate(_component_sets): - if tset.parent_component().parent_block() is None and not isinstance( - tset.parent_component(), GlobalSetBase - ): - self.add_component("%s_index_%d" % (val.local_name, ctr), tset) - if ( - getattr(val, '_index_set', None) is not None - and isinstance(val._index_set, _SetDataBase) - and val._index_set.parent_component().parent_block() is None - and not isinstance(val._index_set.parent_component(), GlobalSetBase) - ): - self.add_component( - "%s_index" % (val.local_name,), val._index_set.parent_component() - ) - if ( - getattr(val, 'initialize', None) is not None - and isinstance(val.initialize, _SetDataBase) - and val.initialize.parent_component().parent_block() is None - and not isinstance(val.initialize.parent_component(), GlobalSetBase) - ): - self.add_component( - "%s_index_init" % (val.local_name,), val.initialize.parent_component() - ) - if ( - getattr(val, 'domain', None) is not None - and isinstance(val.domain, _SetDataBase) - and val.domain.parent_block() is None - and not isinstance(val.domain, GlobalSetBase) - ): - self.add_component("%s_domain" % (val.local_name,), val.domain) - def collect_ctypes(self, active=None, descend_into=True): """ Count all component types stored on or under this @@ -1066,16 +1025,11 @@ def add_component(self, name, val): val._parent = weakref.ref(self) val._name = name # - # We want to add the temporary / implicit sets first so that - # they get constructed before this component - # - # FIXME: This is sloppy and wasteful (most components trigger - # this, even when there is no need for it). We should - # reconsider the whole _implicit_subsets logic to defer this - # kind of thing to an "update_parent()" method on the - # components. + # Update the context of any anonymous sets # - self._add_implicit_sets(val) + if getattr(val, '_anonymous_sets', None) is not None: + for _set in val._anonymous_sets: + _set._parent = val._parent # # Add the component to the underlying Component store # @@ -1148,9 +1102,8 @@ def add_component(self, name, val): # added to the class by Block.__init__() # if getattr(_component, '_constructed', False): - # NB: we don't have to construct the temporary / implicit - # sets here: if necessary, that happens when - # _add_implicit_sets() calls add_component(). + # NB: we don't have to construct the anonymous sets here: if + # necessary, that happens in component.construct() if _BlockConstruction.data: data = _BlockConstruction.data.get(id(self), None) if data is not None: @@ -1236,6 +1189,10 @@ def del_component(self, name_or_object): # Clear the _parent attribute obj._parent = None + # Update the context of any anonymous sets + if getattr(obj, '_anonymous_sets', None) is not None: + for _set in obj._anonymous_sets: + _set._parent = None # Now that this component is not in the _decl map, we can call # delattr as usual. @@ -2150,6 +2107,10 @@ def construct(self, data=None): timer = ConstructionTimer(self) self._constructed = True + if self._anonymous_sets is not None: + for _set in self._anonymous_sets: + _set.construct() + # Constructing blocks is tricky. Scalar blocks are already # partially constructed (they have _data[None] == self) in order # to support Abstract blocks. The block may therefore already diff --git a/pyomo/core/base/boolean_var.py b/pyomo/core/base/boolean_var.py index e2aebb4e466..aae132a5abf 100644 --- a/pyomo/core/base/boolean_var.py +++ b/pyomo/core/base/boolean_var.py @@ -383,6 +383,10 @@ def construct(self, data=None): timer = ConstructionTimer(self) self._constructed = True + if self._anonymous_sets is not None: + for _set in self._anonymous_sets: + _set.construct() + # # Construct _BooleanVarData objects for all index values # diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index 53afa35c70c..aafacaebdaf 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -717,8 +717,6 @@ class Constraint(ActiveIndexedComponent): A dictionary from the index set to component data objects _index The set of valid indices - _implicit_subsets - A tuple of set objects that represents the index set _model A weakref to the model that owns this component _parent @@ -772,6 +770,10 @@ def construct(self, data=None): if is_debug_set(logger): logger.debug("Constructing constraint %s" % (self.name)) + if self._anonymous_sets is not None: + for _set in self._anonymous_sets: + _set.construct() + rule = self.rule try: # We do not (currently) accept data for constructing Constraints @@ -1068,7 +1070,9 @@ def construct(self, data=None): if is_debug_set(logger): logger.debug("Constructing constraint list %s" % (self.name)) - self.index_set().construct() + if self._anonymous_sets is not None: + for _set in self._anonymous_sets: + _set.construct() if self.rule is not None: _rule = self.rule(self.parent_block(), ()) diff --git a/pyomo/core/base/expression.py b/pyomo/core/base/expression.py index df9abf0a5a5..83ee2864180 100644 --- a/pyomo/core/base/expression.py +++ b/pyomo/core/base/expression.py @@ -394,6 +394,10 @@ def construct(self, data=None): % (self.name, str(data)) ) + if self._anonymous_sets is not None: + for _set in self._anonymous_sets: + _set.construct() + try: # We do not (currently) accept data for constructing Constraints assert data is None diff --git a/pyomo/core/base/indexed_component.py b/pyomo/core/base/indexed_component.py index b474281f5b9..34df06845be 100644 --- a/pyomo/core/base/indexed_component.py +++ b/pyomo/core/base/indexed_component.py @@ -32,6 +32,7 @@ from pyomo.core.pyomoobject import PyomoObject from pyomo.common import DeveloperError from pyomo.common.autoslots import fast_deepcopy +from pyomo.common.collections import ComponentSet from pyomo.common.dependencies import numpy as np, numpy_available from pyomo.common.deprecation import deprecated, deprecation_warning from pyomo.common.errors import DeveloperError, TemplateExpressionError @@ -304,37 +305,33 @@ def __init__(self, *args, **kwds): # self._data = {} # - if len(args) == 0 or (len(args) == 1 and args[0] is UnindexedComponent_set): + if len(args) == 0 or (args[0] is UnindexedComponent_set and len(args) == 1): # # If no indexing sets are provided, generate a dummy index # - self._implicit_subsets = None self._index_set = UnindexedComponent_set + self._anonymous_sets = None elif len(args) == 1: # # If a single indexing set is provided, just process it. # - self._implicit_subsets = None - self._index_set = BASE.set.process_setarg(args[0]) + self._index_set, self._anonymous_sets = BASE.set.process_setarg(args[0]) else: # # If multiple indexing sets are provided, process them all, - # and store the cross-product of these sets. The individual - # sets need to stored in the Pyomo model, so the - # _implicit_subsets class data is used for this temporary - # storage. + # and store the cross-product of these sets. # - # Example: Pyomo allows things like - # "Param([1,2,3], range(100), initialize=0)". This - # needs to create *3* sets: two SetOf components and then - # the SetProduct. That means that the component needs to - # hold on to the implicit SetOf objects until the component - # is assigned to a model (where the implicit subsets can be - # "transferred" to the model). + # Example: Pyomo allows things like "Param([1,2,3], + # range(100), initialize=0)". This needs to create *3* + # sets: two SetOf components and then the SetProduct. As + # the user declined to name any of these sets, we will not + # make up names and instead store them on the model as + # "anonymous components" # - tmp = [BASE.set.process_setarg(x) for x in args] - self._implicit_subsets = tmp - self._index_set = tmp[0].cross(*tmp[1:]) + self._index_set = BASE.set.SetProduct(*args) + self._anonymous_sets = ComponentSet((self._index_set,)) + if self._index_set._anonymous_sets is not None: + self._anonymous_sets.update(self._index_set._anonymous_sets) def _create_objects_for_deepcopy(self, memo, component_list): _new = self.__class__.__new__(self.__class__) diff --git a/pyomo/core/base/logical_constraint.py b/pyomo/core/base/logical_constraint.py index 6d553c66fed..dd2f9f95cb9 100644 --- a/pyomo/core/base/logical_constraint.py +++ b/pyomo/core/base/logical_constraint.py @@ -210,8 +210,6 @@ class LogicalConstraint(ActiveIndexedComponent): A dictionary from the index set to component data objects _index_set The set of valid indices - _implicit_subsets - A tuple of set objects that represents the index set _model A weakref to the model that owns this component _parent @@ -280,6 +278,10 @@ def construct(self, data=None): timer = ConstructionTimer(self) self._constructed = True + if self._anonymous_sets is not None: + for _set in self._anonymous_sets: + _set.construct() + _init_expr = self._init_expr _init_rule = self.rule # @@ -532,6 +534,9 @@ def construct(self, data=None): if self._constructed: return self._constructed = True + if self._anonymous_sets is not None: + for _set in self._anonymous_sets: + _set.construct() assert self._init_expr is None _init_rule = self.rule diff --git a/pyomo/core/base/objective.py b/pyomo/core/base/objective.py index 3c625d81c2d..c4491504a31 100644 --- a/pyomo/core/base/objective.py +++ b/pyomo/core/base/objective.py @@ -242,8 +242,6 @@ class Objective(ActiveIndexedComponent): A dictionary from the index set to component data objects _index The set of valid indices - _implicit_subsets - A tuple of set objects that represents the index set _model A weakref to the model that owns this component _parent @@ -291,6 +289,10 @@ def construct(self, data=None): if is_debug_set(logger): logger.debug("Constructing objective %s" % (self.name)) + if self._anonymous_sets is not None: + for _set in self._anonymous_sets: + _set.construct() + rule = self.rule try: # We do not (currently) accept data for constructing Objectives @@ -586,7 +588,9 @@ def construct(self, data=None): if is_debug_set(logger): logger.debug("Constructing objective list %s" % (self.name)) - self.index_set().construct() + if self._anonymous_sets is not None: + for _set in self._anonymous_sets: + _set.construct() if self.rule is not None: _rule = self.rule(self.parent_block(), ()) diff --git a/pyomo/core/base/param.py b/pyomo/core/base/param.py index a6b893ec2c9..495117ce8dd 100644 --- a/pyomo/core/base/param.py +++ b/pyomo/core/base/param.py @@ -331,7 +331,7 @@ def __init__(self, *args, **kwd): if _domain_rule is None: self.domain = _ImplicitAny(owner=self, name='Any') else: - self.domain = SetInitializer(_domain_rule)(self.parent_block(), None) + self.domain = SetInitializer(_domain_rule)(self.parent_block(), None, self) # After IndexedComponent.__init__ so we can call is_indexed(). self._rule = Initializer( _init, @@ -784,6 +784,10 @@ def construct(self, data=None): ) self._mutable = True + if self._anonymous_sets is not None: + for _set in self._anonymous_sets: + _set.construct() + try: # # If the default value is a simple type, we check it versus diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index 6dfc3f07427..57903975183 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -17,6 +17,7 @@ import weakref from pyomo.common.pyomo_typing import overload +from pyomo.common.collections import ComponentSet from pyomo.common.deprecation import deprecated, deprecation_warning, RenamedClass from pyomo.common.errors import DeveloperError, PyomoException from pyomo.common.log import is_debug_set @@ -125,7 +126,17 @@ def process_setarg(arg): if isinstance(arg, _SetDataBase): - return arg + if ( + getattr(arg, '_parent', None) is not None + or getattr(arg, '_anonymous_sets', None) is GlobalSetBase + or arg.parent_component()._parent is not None + ): + return arg, None + _anonymous = ComponentSet((arg,)) + if getattr(arg, '_anonymous_sets', None) is not None: + _anonymous.update(arg._anonymous_sets) + return arg, _anonymous + elif isinstance(arg, _ComponentBase): if isinstance(arg, IndexedComponent) and arg.is_indexed(): raise TypeError( @@ -168,7 +179,7 @@ def process_setarg(arg): ) ): ans.construct() - return ans + return process_setarg(ans) # TBD: should lists/tuples be copied into Sets, or # should we preserve the reference using SetOf? @@ -188,19 +199,20 @@ def process_setarg(arg): # create the Set: # _defer_construct = False - if inspect.isgenerator(arg): - _ordered = True - _defer_construct = True - elif inspect.isfunction(arg): - _ordered = True - _defer_construct = True - elif not hasattr(arg, '__contains__'): - raise TypeError( - "Cannot create a Set from data that does not support " - "__contains__. Expected set-like object supporting " - "collections.abc.Collection interface, but received '%s'." - % (type(arg).__name__,) - ) + if not hasattr(arg, '__contains__'): + if inspect.isgenerator(arg): + _ordered = True + _defer_construct = True + elif inspect.isfunction(arg): + _ordered = True + _defer_construct = True + else: + raise TypeError( + "Cannot create a Set from data that does not support " + "__contains__. Expected set-like object supporting " + "collections.abc.Collection interface, but received '%s'." + % (type(arg).__name__,) + ) elif arg.__class__ is type: # This catches the (deprecated) RealSet API. return process_setarg(arg()) @@ -221,7 +233,10 @@ def process_setarg(arg): # Or we can do the simple thing and just use SetOf: # # ans = SetOf(arg) - return ans + _anonymous = ComponentSet((ans,)) + if getattr(ans, '_anonymous_sets', None) is not None: + _anonymous.update(_anonymous_sets) + return ans, _anonymous @deprecated( @@ -308,11 +323,22 @@ def intersect(self, other): else: self._set = SetIntersectInitializer(self._set, other) - def __call__(self, parent, idx): + def __call__(self, parent, idx, obj): if self._set is None: return Any - else: - return process_setarg(self._set(parent, idx)) + _ans, _anonymous = process_setarg(self._set(parent, idx)) + if _anonymous: + pc = obj.parent_component() + if getattr(pc, '_anonymous_sets', None) is None: + pc._anonymous_sets = _anonymous + else: + pc._anonymous_sets.update(_anonymous) + for _set in _anonymous: + _set._parent = pc._parent + if pc._constructed: + for _set in _anonymous: + _set.construct() + return _ans def constant(self): return self._set is None or self._set.constant() @@ -2089,7 +2115,7 @@ def __init__(self, *args, **kwds): # order to correctly parse the data stream. if not self.is_indexed(): if self._init_domain.constant(): - self._domain = self._init_domain(self.parent_block(), None) + self._domain = self._init_domain(self.parent_block(), None, self) if self._init_dimen.constant(): self._dimen = self._init_dimen(self.parent_block(), None) @@ -2109,6 +2135,11 @@ def construct(self, data=None): if is_debug_set(logger): logger.debug("Constructing Set, name=%s, from data=%r" % (self.name, data)) self._constructed = True + + if self._anonymous_sets is not None: + for _set in self._anonymous_sets: + _set.construct() + if data is not None: # Data supplied to construct() should override data provided # to the constructor @@ -2163,7 +2194,9 @@ def _getitem_when_not_present(self, index): ) _d = None - domain = self._init_domain(_block, index) + domain = self._init_domain(_block, index, self) + if domain is not None: + domain.construct() if _d is UnknownSetDimen and domain is not None and domain.dimen is not None: _d = domain.dimen @@ -2187,11 +2220,9 @@ def _getitem_when_not_present(self, index): else: obj = self._data[index] = self._ComponentDataClass(component=self) obj._index = index + obj._domain = domain if _d is not UnknownSetDimen: obj._dimen = _d - if domain is not None: - obj._domain = domain - domain.parent_component().construct() if self._init_validate is not None: try: obj._validate = Initializer(self._init_validate(_block, index)) @@ -3232,31 +3263,37 @@ class SetOperator(_SetData, Set): def __init__(self, *args, **kwds): _SetData.__init__(self, component=self) Set.__init__(self, **kwds) - implicit = [] - sets = [] - for _set in args: - _new_set = process_setarg(_set) - sets.append(_new_set) - if _new_set is not _set or _new_set.parent_block() is None: - implicit.append(_new_set) - self._sets = tuple(sets) - self._implicit_subsets = tuple(implicit) - # We will implicitly construct all set operators if the operands - # are all constructed. + self._sets, _anonymous = zip(*(process_setarg(_set) for _set in args)) + _anonymous = tuple(filter(None, _anonymous)) + if _anonymous: + self._anonymous_sets = ComponentSet() + for _set in _anonymous: + self._anonymous_sets.update(_set) + # We will immediately construct all set operators if the operands + # are all themselves constructed. if all(_.parent_component()._constructed for _ in self._sets): self.construct() def construct(self, data=None): if self._constructed: return + self._constructed = True + timer = ConstructionTimer(self) if is_debug_set(logger): logger.debug( - "Constructing SetOperator, name=%s, from data=%r" % (self.name, data) + "Constructing SetOperator, name=%s, from data=%r" % (self, data) ) - for s in self._sets: - s.parent_component().construct() - super(SetOperator, self).construct() + + if self._anonymous_sets is not None: + for _set in self._anonymous_sets: + _set.construct() + + # This ensures backwards compatibility by causing all scalar + # sets (including set operators) to be initialized (and + # potentially empty) after construct(). + self._getitem_when_not_present(None) + if data: deprecation_warning( "Providing construction data to SetOperator objects is " @@ -4350,7 +4387,11 @@ def __new__(cls, *args, **kwds): name = base_set.name else: name = cls_name - ans = RangeSet(ranges=list(range_init(None, None).ranges()), name=name) + tmp = Set() + ans = RangeSet( + ranges=list(range_init(None, None, tmp).ranges()), name=name + ) + ans._anonymous_sets = tmp._anonymous_sets if name_kwd is None and (cls_name is not None or bounds is not None): ans._name += str(ans.bounds()) else: diff --git a/pyomo/core/base/var.py b/pyomo/core/base/var.py index e7e9e4f8f2f..8d5b93f3ace 100644 --- a/pyomo/core/base/var.py +++ b/pyomo/core/base/var.py @@ -436,7 +436,9 @@ def domain(self): @domain.setter def domain(self, domain): try: - self._domain = SetInitializer(domain)(self.parent_block(), self.index()) + self._domain = SetInitializer(domain)( + self.parent_block(), self.index(), self + ) except: logger.error( "%s is not a valid domain. Variable domains must be an " @@ -774,6 +776,10 @@ def construct(self, data=None): if is_debug_set(logger): logger.debug("Constructing Variable %s" % (self.name,)) + if self._anonymous_sets is not None: + for _set in self._anonymous_sets: + _set.construct() + # Note: define 'index' to avoid 'variable referenced before # assignment' in the error message generated in the 'except:' # block below. @@ -854,7 +860,7 @@ def construct(self, data=None): # We can directly set the attribute (not the # property) because the SetInitializer ensures # that the value is a proper Set. - obj._domain = self._rule_domain(block, index) + obj._domain = self._rule_domain(block, index, self) if call_bounds_rule: for index, obj in self._data.items(): obj.lower, obj.upper = self._rule_bounds(block, index) @@ -891,7 +897,7 @@ def _getitem_when_not_present(self, index): obj._index = index # We can directly set the attribute (not the property) because # the SetInitializer ensures that the value is a proper Set. - obj._domain = self._rule_domain(parent, index) + obj._domain = self._rule_domain(parent, index, self) if self._rule_bounds is not None: obj.lower, obj.upper = self._rule_bounds(parent, index) if self._rule_init is not None: @@ -1013,17 +1019,17 @@ def domain(self, domain): try: domain_rule = SetInitializer(domain) if domain_rule.constant(): - domain = domain_rule(self.parent_block(), None) + domain = domain_rule(self.parent_block(), None, self) for vardata in self.values(): vardata._domain = domain elif domain_rule.contains_indices(): parent = self.parent_block() for index in domain_rule.indices(): - self[index]._domain = domain_rule(parent, index) + self[index]._domain = domain_rule(parent, index, self) else: parent = self.parent_block() for index, vardata in self.items(): - vardata._domain = domain_rule(parent, index) + vardata._domain = domain_rule(parent, index, self) except: logger.error( "%s is not a valid domain. Variable domains must be an " diff --git a/pyomo/gdp/disjunct.py b/pyomo/gdp/disjunct.py index eca6d93d732..842388e7502 100644 --- a/pyomo/gdp/disjunct.py +++ b/pyomo/gdp/disjunct.py @@ -700,6 +700,10 @@ def construct(self, data=None): timer = ConstructionTimer(self) self._constructed = True + if self._anonymous_sets is not None: + for _set in self._anonymous_sets: + _set.construct() + _self_parent = self.parent_block() if not self.is_indexed(): if self._init_rule is not None: diff --git a/pyomo/mpec/complementarity.py b/pyomo/mpec/complementarity.py index df991ce9686..4eccd453cbc 100644 --- a/pyomo/mpec/complementarity.py +++ b/pyomo/mpec/complementarity.py @@ -357,13 +357,18 @@ def construct(self, data=None): """ Construct the expression(s) for this complementarity condition. """ - if is_debug_set(logger): - logger.debug("Constructing complementarity list %s", self.name) if self._constructed: return - timer = ConstructionTimer(self) self._constructed = True + timer = ConstructionTimer(self) + if is_debug_set(logger): + logger.debug("Constructing complementarity list %s", self.name) + + if self._anonymous_sets is not None: + for _set in self._anonymous_sets: + _set.construct() + if self._init_rule is not None: _init = self._init_rule(self.parent_block(), ()) for cc in iter(_init): diff --git a/pyomo/network/arc.py b/pyomo/network/arc.py index ff1874b0274..04d96a2c531 100644 --- a/pyomo/network/arc.py +++ b/pyomo/network/arc.py @@ -296,14 +296,18 @@ def __init__(self, *args, **kwds): def construct(self, data=None): """Initialize the Arc""" - if is_debug_set(logger): - logger.debug("Constructing Arc %s" % self.name) - if self._constructed: return + self._constructed = True + + if is_debug_set(logger): + logger.debug("Constructing Arc %s" % self.name) timer = ConstructionTimer(self) - self._constructed = True + + if self._anonymous_sets is not None: + for _set in self._anonymous_sets: + _set.construct() if self._rule is None and self._init_vals is None: # No construction rule or values specified diff --git a/pyomo/network/port.py b/pyomo/network/port.py index 4afb0e23ed0..c0e40e090f5 100644 --- a/pyomo/network/port.py +++ b/pyomo/network/port.py @@ -346,14 +346,18 @@ def _getitem_when_not_present(self, idx): return tmp def construct(self, data=None): - if is_debug_set(logger): # pragma:nocover - logger.debug("Constructing Port, name=%s, from data=%s" % (self.name, data)) - if self._constructed: return + self._constructed = True timer = ConstructionTimer(self) - self._constructed = True + + if is_debug_set(logger): # pragma:nocover + logger.debug("Constructing Port, name=%s, from data=%s" % (self.name, data)) + + if self._anonymous_sets is not None: + for _set in self._anonymous_sets: + _set.construct() # Construct _PortData objects for all index values if self.is_indexed(): From fa64b1e20b49f05d43a56c66998d4a6750455618 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 12:31:43 -0700 Subject: [PATCH 0646/1204] Track changes to SetInitializer API --- pyomo/core/tests/unit/test_set.py | 42 ++++++++++++++++++------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index f04a8229cf0..ed01dff568b 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -112,17 +112,19 @@ class Test_SetInitializer(unittest.TestCase): def test_single_set(self): + tmp = Set() # a placeholder to accumulate _anonymous_sets references + a = SetInitializer(None) self.assertIs(type(a), SetInitializer) self.assertIsNone(a._set) - self.assertIs(a(None, None), Any) + self.assertIs(a(None, None, tmp), Any) self.assertTrue(a.constant()) self.assertFalse(a.verified) a = SetInitializer(Reals) self.assertIs(type(a), SetInitializer) self.assertIs(type(a._set), ConstantInitializer) - self.assertIs(a(None, None), Reals) + self.assertIs(a(None, None, tmp), Reals) self.assertIs(a._set.val, Reals) self.assertTrue(a.constant()) self.assertFalse(a.verified) @@ -130,18 +132,20 @@ def test_single_set(self): a = SetInitializer({1: Reals}) self.assertIs(type(a), SetInitializer) self.assertIs(type(a._set), ItemInitializer) - self.assertIs(a(None, 1), Reals) + self.assertIs(a(None, 1, tmp), Reals) self.assertFalse(a.constant()) self.assertFalse(a.verified) def test_intersect(self): + tmp = Set() # a placeholder to accumulate _anonymous_sets references + a = SetInitializer(None) a.intersect(SetInitializer(None)) self.assertIs(type(a), SetInitializer) self.assertIsNone(a._set) self.assertTrue(a.constant()) self.assertFalse(a.verified) - self.assertIs(a(None, None), Any) + self.assertIs(a(None, None, tmp), Any) a = SetInitializer(None) a.intersect(SetInitializer(Reals)) @@ -150,7 +154,7 @@ def test_intersect(self): self.assertIs(a._set.val, Reals) self.assertTrue(a.constant()) self.assertFalse(a.verified) - self.assertIs(a(None, None), Reals) + self.assertIs(a(None, None, tmp), Reals) a = SetInitializer(None) a.intersect(BoundsInitializer(5, default_step=1)) @@ -158,7 +162,7 @@ def test_intersect(self): self.assertIs(type(a._set), BoundsInitializer) self.assertTrue(a.constant()) self.assertFalse(a.verified) - self.assertEqual(a(None, None), RangeSet(5)) + self.assertEqual(a(None, None, tmp), RangeSet(5)) a = SetInitializer(Reals) a.intersect(SetInitializer(None)) @@ -167,7 +171,7 @@ def test_intersect(self): self.assertIs(a._set.val, Reals) self.assertTrue(a.constant()) self.assertFalse(a.verified) - self.assertIs(a(None, None), Reals) + self.assertIs(a(None, None, tmp), Reals) a = SetInitializer(Reals) a.intersect(SetInitializer(Integers)) @@ -179,7 +183,7 @@ def test_intersect(self): self.assertIs(a._set._B.val, Integers) self.assertTrue(a.constant()) self.assertFalse(a.verified) - s = a(None, None) + s = a(None, None, tmp) self.assertIs(type(s), SetIntersection_InfiniteSet) self.assertIs(s._sets[0], Reals) self.assertIs(s._sets[1], Integers) @@ -195,7 +199,7 @@ def test_intersect(self): self.assertIs(a._set._A._B.val, Integers) self.assertTrue(a.constant()) self.assertFalse(a.verified) - s = a(None, None) + s = a(None, None, tmp) self.assertIs(type(s), SetIntersection_OrderedSet) self.assertIs(type(s._sets[0]), SetIntersection_InfiniteSet) self.assertIsInstance(s._sets[1], RangeSet) @@ -212,7 +216,7 @@ def test_intersect(self): self.assertIs(a._set._A._B.val, Integers) self.assertTrue(a.constant()) self.assertFalse(a.verified) - s = a(None, None) + s = a(None, None, tmp) self.assertIs(type(s), SetIntersection_InfiniteSet) p.construct() s.construct() @@ -236,8 +240,8 @@ def test_intersect(self): self.assertFalse(a.constant()) self.assertFalse(a.verified) with self.assertRaises(KeyError): - a(None, None) - s = a(None, 1) + a(None, None, tmp) + s = a(None, 1, tmp) self.assertIs(type(s), SetIntersection_InfiniteSet) p.construct() s.construct() @@ -304,15 +308,17 @@ def test_boundsinit(self): self.assertEqual(s, RangeSet(0, 5)) def test_setdefault(self): + tmp = Set() # a placeholder to accumulate _anonymous_sets references + a = SetInitializer(None) - self.assertIs(a(None, None), Any) + self.assertIs(a(None, None, tmp), Any) a.setdefault(Reals) - self.assertIs(a(None, None), Reals) + self.assertIs(a(None, None, tmp), Reals) a = SetInitializer(Integers) - self.assertIs(a(None, None), Integers) + self.assertIs(a(None, None, tmp), Integers) a.setdefault(Reals) - self.assertIs(a(None, None), Integers) + self.assertIs(a(None, None, tmp), Integers) a = BoundsInitializer(5, default_step=1) self.assertEqual(a(None, None), RangeSet(5)) @@ -321,9 +327,9 @@ def test_setdefault(self): a = SetInitializer(Reals) a.intersect(SetInitializer(Integers)) - self.assertIs(type(a(None, None)), SetIntersection_InfiniteSet) + self.assertIs(type(a(None, None, tmp)), SetIntersection_InfiniteSet) a.setdefault(RangeSet(5)) - self.assertIs(type(a(None, None)), SetIntersection_InfiniteSet) + self.assertIs(type(a(None, None, tmp)), SetIntersection_InfiniteSet) def test_indices(self): a = SetInitializer(None) From 85496b85d41509be91f823b48530e6f9b8a63513 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 12:32:16 -0700 Subject: [PATCH 0647/1204] Track changes to SetProduct dimen when not flatting --- pyomo/core/tests/unit/test_set.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index ed01dff568b..a344cbd585c 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -3098,7 +3098,7 @@ def test_no_normalize_index(self): x = I * J normalize_index.flatten = False - self.assertIs(x.dimen, None) + self.assertIs(x.dimen, 2) self.assertIn(((1, 2), 3), x) self.assertIn((1, (2, 3)), x) # if we are not flattening, then lookup must match the @@ -3273,7 +3273,7 @@ def test_ordered_multidim_setproduct(self): ((3, 4), (7, 8)), ] self.assertEqual(list(x), ref) - self.assertEqual(x.dimen, None) + self.assertEqual(x.dimen, 2) finally: SetModule.FLATTEN_CROSS_PRODUCT = origFlattenCross @@ -3317,7 +3317,7 @@ def test_ordered_nondim_setproduct(self): (1, (2, 3), 5), ] self.assertEqual(list(x), ref) - self.assertEqual(x.dimen, None) + self.assertEqual(x.dimen, 3) finally: SetModule.FLATTEN_CROSS_PRODUCT = origFlattenCross @@ -3369,7 +3369,7 @@ def test_ordered_nondim_setproduct(self): self.assertEqual(list(x), ref) for i, v in enumerate(ref): self.assertEqual(x[i + 1], v) - self.assertEqual(x.dimen, None) + self.assertEqual(x.dimen, 4) finally: SetModule.FLATTEN_CROSS_PRODUCT = origFlattenCross @@ -5252,7 +5252,7 @@ def test_no_normalize_index(self): m.I = Set() self.assertIs(m.I._dimen, UnknownSetDimen) self.assertTrue(m.I.add((1, (2, 3)))) - self.assertIs(m.I._dimen, None) + self.assertIs(m.I._dimen, 2) self.assertNotIn(((1, 2), 3), m.I) self.assertIn((1, (2, 3)), m.I) self.assertNotIn((1, 2, 3), m.I) From 1120943f7e3d002470ca6c2f9c960910e47ed569 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 12:33:10 -0700 Subject: [PATCH 0648/1204] Make flatten tests more robust to test failures (guarantee normalize_index.flatten state is restored) --- pyomo/dae/tests/test_flatten.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pyomo/dae/tests/test_flatten.py b/pyomo/dae/tests/test_flatten.py index a6ea824c3ef..d228b2bdd62 100644 --- a/pyomo/dae/tests/test_flatten.py +++ b/pyomo/dae/tests/test_flatten.py @@ -49,6 +49,12 @@ class TestAssumedBehavior(unittest.TestCase): immediately obvious would be the case. """ + def setUp(self): + self._orig_flatten = normalize_index.flatten + + def tearDown(self): + normalize_index.flatten = self._orig_flatten + def test_cross(self): m = ConcreteModel() m.s1 = Set(initialize=[1, 2]) @@ -313,6 +319,12 @@ def c_rule(m, t): class TestFlatten(_TestFlattenBase, unittest.TestCase): + def setUp(self): + self._orig_flatten = normalize_index.flatten + + def tearDown(self): + normalize_index.flatten = self._orig_flatten + def _model1_1d_sets(self): # One-dimensional sets, no skipping. m = ConcreteModel() From 4c6ca65cbbe228363f958485846a2b8d821dcc88 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 12:39:23 -0700 Subject: [PATCH 0649/1204] Standardize structure of component.construct() methods --- pyomo/core/base/block.py | 9 +++++---- pyomo/core/base/constraint.py | 3 +-- pyomo/core/base/logical_constraint.py | 10 +++++----- pyomo/core/base/objective.py | 3 +-- pyomo/core/base/set.py | 25 ++++++++++++++++--------- 5 files changed, 28 insertions(+), 22 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 43418089826..ca1edba2a6f 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -2095,6 +2095,11 @@ def construct(self, data=None): """ Initialize the block """ + if self._constructed: + return + self._constructed = True + + timer = ConstructionTimer(self) if is_debug_set(logger): logger.debug( "Constructing %s '%s', from data=%s", @@ -2102,10 +2107,6 @@ def construct(self, data=None): self.name, str(data), ) - if self._constructed: - return - timer = ConstructionTimer(self) - self._constructed = True if self._anonymous_sets is not None: for _set in self._anonymous_sets: diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index aafacaebdaf..9f39ac873a1 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -1047,8 +1047,7 @@ def __init__(self, **kwargs): _rule = kwargs.pop('rule', None) self._starting_index = kwargs.pop('starting_index', 1) - args = (Set(dimen=1),) - super(ConstraintList, self).__init__(*args, **kwargs) + super(ConstraintList, self).__init__(Set(dimen=1), **kwargs) self.rule = Initializer( _rule, treat_sequences_as_mappings=False, allow_generators=True diff --git a/pyomo/core/base/logical_constraint.py b/pyomo/core/base/logical_constraint.py index dd2f9f95cb9..2cfb68f4a5d 100644 --- a/pyomo/core/base/logical_constraint.py +++ b/pyomo/core/base/logical_constraint.py @@ -518,22 +518,22 @@ class LogicalConstraintList(IndexedLogicalConstraint): def __init__(self, **kwargs): """Constructor""" - args = (Set(),) if 'expr' in kwargs: raise ValueError("LogicalConstraintList does not accept the 'expr' keyword") - LogicalConstraint.__init__(self, *args, **kwargs) + LogicalConstraint.__init__(self, Set(dimen=1), **kwargs) def construct(self, data=None): """ Construct the expression(s) for this logical constraint. """ + if self._constructed: + return + self._constructed = True + generate_debug_messages = is_debug_set(logger) if generate_debug_messages: logger.debug("Constructing logical constraint list %s" % self.name) - if self._constructed: - return - self._constructed = True if self._anonymous_sets is not None: for _set in self._anonymous_sets: _set.construct() diff --git a/pyomo/core/base/objective.py b/pyomo/core/base/objective.py index c4491504a31..b72d0bd5d1b 100644 --- a/pyomo/core/base/objective.py +++ b/pyomo/core/base/objective.py @@ -567,8 +567,7 @@ def __init__(self, **kwargs): _rule = kwargs.pop('rule', None) self._starting_index = kwargs.pop('starting_index', 1) - args = (Set(dimen=1),) - super().__init__(*args, **kwargs) + super().__init__(Set(dimen=1), **kwargs) self.rule = Initializer(_rule, allow_generators=True) # HACK to make the "counted call" syntax work. We wait until diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index 57903975183..6b15905dd14 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -2131,10 +2131,11 @@ def check_values(self): def construct(self, data=None): if self._constructed: return + self._constructed = True + timer = ConstructionTimer(self) if is_debug_set(logger): - logger.debug("Constructing Set, name=%s, from data=%r" % (self.name, data)) - self._constructed = True + logger.debug("Constructing Set, name=%s, from data=%r" % (self, data)) if self._anonymous_sets is not None: for _set in self._anonymous_sets: @@ -2479,6 +2480,7 @@ def __init__(self, reference, **kwds): kwds.setdefault('ctype', SetOf) Component.__init__(self, **kwds) self._ref = reference + self.construct() def __str__(self): if self.parent_block() is not None: @@ -2488,12 +2490,11 @@ def __str__(self): def construct(self, data=None): if self._constructed: return + self._constructed = True + timer = ConstructionTimer(self) if is_debug_set(logger): - logger.debug( - "Constructing SetOf, name=%s, from data=%r" % (self.name, data) - ) - self._constructed = True + logger.debug("Constructing SetOf, name=%s, from data=%r" % (self, data)) timer.report() @property @@ -2985,11 +2986,16 @@ def __str__(self): def construct(self, data=None): if self._constructed: return + timer = ConstructionTimer(self) if is_debug_set(logger): - logger.debug( - "Constructing RangeSet, name=%s, from data=%r" % (self.name, data) - ) + logger.debug("Constructing RangeSet, name=%s, from data=%r" % (self, data)) + # Note: we cannot set the constructed flag until after we have + # generated the debug message: the debug message needs the name, + # which in turn may need ranges(), which has not been + # constructed. + self._constructed = True + if data is not None: raise ValueError( "RangeSet.construct() does not support the data= argument.\n" @@ -4260,6 +4266,7 @@ class _EmptySet(_FiniteSetMixin, _SetData, Set): def __init__(self, **kwds): _SetData.__init__(self, component=self) Set.__init__(self, **kwds) + self.construct() def get(self, val, default=None): return default From 2d7757a8c9b7b6daae96e84de4afca80967b0a48 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 12:59:03 -0700 Subject: [PATCH 0650/1204] Do not explicitly assign floating component names to class type --- pyomo/core/base/component.py | 4 +++- pyomo/core/base/set.py | 46 ++++++++++++++++++++---------------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index a8550f8f469..1c59da15cec 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -501,7 +501,7 @@ def __init__(self, **kwds): # self._ctype = kwds.pop('ctype', None) self.doc = kwds.pop('doc', None) - self._name = kwds.pop('name', str(type(self).__name__)) + self._name = kwds.pop('name', None) if kwds: raise ValueError( "Unexpected keyword options found while constructing '%s':\n\t%s" @@ -625,6 +625,8 @@ def getname(self, fully_qualified=False, name_buffer=None, relative_to=None): Generate fully_qualified names relative to the specified block. """ local_name = self._name + if local_name is None: + local_name = type(self).__name__ if fully_qualified: pb = self.parent_block() if relative_to is None: diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index 6b15905dd14..3be207288d0 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -1344,7 +1344,7 @@ def __len__(self): return len(self._values) def __str__(self): - if self.parent_block() is not None: + if self.parent_component()._name is not None: return self.name if not self.parent_component()._constructed: return type(self).__name__ @@ -2483,7 +2483,7 @@ def __init__(self, reference, **kwds): self.construct() def __str__(self): - if self.parent_block() is not None: + if self._name is not None: return self.name return str(self._ref) @@ -2966,14 +2966,12 @@ def __init__(self, *args, **kwds): pass def __str__(self): - if self.parent_block() is not None: + # Named, components should return their name e.g., Reals + if self._name is not None: return self.name # Unconstructed floating components return their type if not self._constructed: return type(self).__name__ - # Named, constructed components should return their name e.g., Reals - if type(self).__name__ != self._name: - return self.name # Floating, unnamed constructed components return their ranges() ans = ' | '.join(str(_) for _ in self.ranges()) if ' | ' in ans: @@ -3003,19 +3001,9 @@ def construct(self, data=None): "as numbers, constants, or Params to the RangeSet() " "declaration" ) - self._constructed = True args, ranges = self._init_data - if any(not is_constant(arg) for arg in args): - logger.warning( - "Constructing RangeSet '%s' from non-constant data (e.g., " - "Var or mutable Param). The linkage between this RangeSet " - "and the original source data will be broken, so updating " - "the data value in the future will not be reflected in this " - "RangeSet. To suppress this warning, explicitly convert " - "the source data to a constant type (e.g., float, int, or " - "immutable Param)" % (self.name,) - ) + nonconstant_data_warning = any(not is_constant(arg) for arg in args) args = tuple(value(arg) for arg in args) if type(ranges) is not tuple: ranges = tuple(ranges) @@ -3176,6 +3164,22 @@ def construct(self, data=None): "Set %s" % (val, self.name) ) + # Defer the warning about non-constant args until after the + # component has been constructed, so that the conversion of the + # component to a rational string will work (anonymous RangeSets + # will report their ranges, which aren't present until + # construction is over) + if nonconstant_data_warning: + logger.warning( + "Constructing RangeSet '%s' from non-constant data (e.g., " + "Var or mutable Param). The linkage between this RangeSet " + "and the original source data will be broken, so updating " + "the data value in the future will not be reflected in this " + "RangeSet. To suppress this warning, explicitly convert " + "the source data to a constant type (e.g., float, int, or " + "immutable Param)" % (self,) + ) + timer.report() # @@ -3320,7 +3324,7 @@ def construct(self, data=None): if fail: raise ValueError( "Constructing SetOperator %s with incompatible data " - "(data=%s}" % (self.name, data) + "(data=%s}" % (self, data) ) timer.report() @@ -3346,7 +3350,7 @@ def __len__(self): ) def __str__(self): - if self.parent_block() is not None: + if self._name is not None: return self.name return self._expression_str() @@ -4245,7 +4249,7 @@ def domain(self): return Any def __str__(self): - if self.parent_block() is not None: + if self._name is not None: return self.name return type(self).__name__ @@ -4291,7 +4295,7 @@ def domain(self): return EmptySet def __str__(self): - if self.parent_block() is not None: + if self._name is not None: return self.name return type(self).__name__ From 5c916d4896a225af7a3d7fbb733be46a39cd9d89 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 12:59:30 -0700 Subject: [PATCH 0651/1204] Mock up additional Set API for UnindexedComponent_set --- pyomo/core/base/global_set.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pyomo/core/base/global_set.py b/pyomo/core/base/global_set.py index f4d97403308..f9a6dddc33b 100644 --- a/pyomo/core/base/global_set.py +++ b/pyomo/core/base/global_set.py @@ -72,8 +72,11 @@ def _parent(self, val): class _UnindexedComponent_set(GlobalSetBase): local_name = 'UnindexedComponent_set' + _anonymous_sets = GlobalSetBase + def __init__(self, name): self.name = name + self._constructed = True def __contains__(self, val): return val is None @@ -180,6 +183,12 @@ def prev(self, item, step=1): def prevw(self, item, step=1): return self.nextw(item, -step) + def parent_block(self): + return None + + def parent_component(self): + return self + UnindexedComponent_set = _UnindexedComponent_set('UnindexedComponent_set') GlobalSets[UnindexedComponent_set.local_name] = UnindexedComponent_set From f1116c07b75ce3d15c708429a3d864658eeae157 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 13:01:19 -0700 Subject: [PATCH 0652/1204] SetProduct should report a meaningful dimen even when not flattening indices --- pyomo/core/base/indexed_component.py | 6 ++++-- pyomo/core/base/indexed_component_slice.py | 3 +-- pyomo/core/base/set.py | 6 ++++-- pyomo/dae/flatten.py | 24 ++++++++++++++++++++-- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/pyomo/core/base/indexed_component.py b/pyomo/core/base/indexed_component.py index 34df06845be..d29ae3cd43f 100644 --- a/pyomo/core/base/indexed_component.py +++ b/pyomo/core/base/indexed_component.py @@ -1001,11 +1001,13 @@ def _processUnhashableIndex(self, idx): slice_dim -= 1 if normalize_index.flatten: set_dim = self.dim() - elif self._implicit_subsets is None: + elif not self.is_indexed(): # Scalar component. set_dim = 0 else: - set_dim = len(self._implicit_subsets) + set_dim = self.index_set().dimen + if set_dim is None: + set_dim = 1 structurally_valid = False if slice_dim == set_dim or set_dim is None: diff --git a/pyomo/core/base/indexed_component_slice.py b/pyomo/core/base/indexed_component_slice.py index 9779711a19b..8fd625bfeaa 100644 --- a/pyomo/core/base/indexed_component_slice.py +++ b/pyomo/core/base/indexed_component_slice.py @@ -402,8 +402,7 @@ def __init__(self, component, fixed, sliced, ellipsis, iter_over_index, sort): self.last_index = () self.tuplize_unflattened_index = ( - self.component._implicit_subsets is None - or len(self.component._implicit_subsets) == 1 + len(list(self.component.index_set().subsets())) <= 1 ) if fixed is None and sliced is None and ellipsis is None: diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index 3be207288d0..32ae08fca23 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -1382,8 +1382,10 @@ def add(self, *values): else: # If we are not normalizing indices, then we cannot reliably # infer the set dimen + _d = 1 + if isinstance(value, Sequence) and self.dimen != 1: + _d = len(value) _value = value - _d = None if _value not in self._domain: raise ValueError( "Cannot add value %s to Set %s.\n" @@ -3944,7 +3946,7 @@ def bounds(self): @property def dimen(self): if not (FLATTEN_CROSS_PRODUCT and normalize_index.flatten): - return None + return len(self._sets) # By convention, "None" trumps UnknownSetDimen. That is, a set # product is "non-dimentioned" if any term is non-dimentioned, # even if we do not yet know the dimentionality of another term. diff --git a/pyomo/dae/flatten.py b/pyomo/dae/flatten.py index 595f90b3dc7..d6da8bb84d5 100644 --- a/pyomo/dae/flatten.py +++ b/pyomo/dae/flatten.py @@ -200,8 +200,28 @@ def slice_component_along_sets(component, sets, context_slice=None, normalize=No # # Note that c_slice is not necessarily a slice. # We enter this loop even if no sets need slicing. - temp_slice = c_slice.duplicate() - next(iter(temp_slice)) + try: + next(iter(c_slice.duplicate())) + except IndexError: + if normalize_index.flatten: + raise + # There is an edge case where when we are not + # flattening indices the dimensionality of an + # index can change between a SetProduct and the + # member Sets: the member set can have dimen>1 + # (or even None!), but the dimen of that portion + # of the SetProduct is always 1. Since we are + # just checking that the c_slice isn't + # completely empty, we will allow matching with + # an Ellipsis + _empty = True + try: + next(iter(base_component[...])) + _empty = False + except: + pass + if _empty: + raise if (normalize is None and normalize_index.flatten) or normalize: # Most users probably want this index to be normalized, # so they can more conveniently use it as a key in a From 1d3c761dc4d1cf76cfe3373a2e54f344aec7ffa3 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 13:03:53 -0700 Subject: [PATCH 0653/1204] Simplify definition of reverse Set operators --- pyomo/core/base/set.py | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index 32ae08fca23..5af30bfb3a2 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -1152,33 +1152,23 @@ def cross(self, *args): def __ror__(self, other): # See the discussion of Set vs SetOf in process_setarg above - # - # return SetOf(other) | self - return process_setarg(other) | self + return SetUnion(other, self) def __rand__(self, other): # See the discussion of Set vs SetOf in process_setarg above - # - # return SetOf(other) & self - return process_setarg(other) & self + return SetIntersection(other, self) def __rsub__(self, other): # See the discussion of Set vs SetOf in process_setarg above - # - # return SetOf(other) - self - return process_setarg(other) - self + return SetDifference(other, self) def __rxor__(self, other): # See the discussion of Set vs SetOf in process_setarg above - # - # return SetOf(other) ^ self - return process_setarg(other) ^ self + return SetSymmetricDifference(other, self) def __rmul__(self, other): # See the discussion of Set vs SetOf in process_setarg above - # - # return SetOf(other) * self - return process_setarg(other) * self + return SetProduct(other, self) def __lt__(self, other): """ From 61aab8507e2328544c2c77056babcf287c059e99 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 13:05:40 -0700 Subject: [PATCH 0654/1204] Add a flag to _anonymous_sets to more easily detect GlobalSets --- pyomo/core/base/set.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index 5af30bfb3a2..5ba42bbb554 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -4424,6 +4424,9 @@ def get_interval(self): # Cache the set bounds / interval _set._bounds = obj.bounds() _set._interval = obj.get_interval() + # Now that the set is constructed, override the _anonymous_sets to + # mark the set as a global set (used by process_setarg) + _set._anonymous_sets = GlobalSetBase return _set From ffdd9c8bf4aa73d4b5af330fc9c3b5e3adec777a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 13:05:57 -0700 Subject: [PATCH 0655/1204] Track move to anonymous sets --- pyomo/contrib/benders/benders_cuts.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/contrib/benders/benders_cuts.py b/pyomo/contrib/benders/benders_cuts.py index 5eb2e91cc82..3f63e1d5cbe 100644 --- a/pyomo/contrib/benders/benders_cuts.py +++ b/pyomo/contrib/benders/benders_cuts.py @@ -335,7 +335,6 @@ def generate_cut(self): subproblem_solver.remove_constraint(c) subproblem_solver.remove_constraint(subproblem.fix_eta) del subproblem.fix_complicating_vars - del subproblem.fix_complicating_vars_index del subproblem.fix_eta total_num_subproblems = self.global_num_subproblems() From c5fec87b7520a3366034690e7e4f8a7d318765e8 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 13:06:19 -0700 Subject: [PATCH 0656/1204] NFC: update documentation --- pyomo/core/base/indexed_component.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pyomo/core/base/indexed_component.py b/pyomo/core/base/indexed_component.py index d29ae3cd43f..11cfc923b28 100644 --- a/pyomo/core/base/indexed_component.py +++ b/pyomo/core/base/indexed_component.py @@ -256,8 +256,7 @@ def rule_wrapper(rule, wrapping_fcn, positional_arg_map=None): class IndexedComponent(Component): - """ - This is the base class for all indexed modeling components. + """This is the base class for all indexed modeling components. This class stores a dictionary, self._data, that maps indices to component data objects. The object self._index_set defines valid keys for this dictionary, and the dictionary keys may be a @@ -279,11 +278,16 @@ class IndexedComponent(Component): doc A text string describing this component Private class attributes: - _data A dictionary from the index set to - component data objects - _index_set The set of valid indices - _implicit_subsets A temporary data element that stores - sets that are transferred to the model + + _data: A dictionary from the index set to component data objects + + _index_set: The set of valid indices + + _anonymous_sets: A ComponentSet of "anonymous" sets used by this + component. Anonymous sets are Set / SetOperator / RangeSet + that compose attributes like _index_set, but are not + themselves explicitly assigned (and named) on any Block + """ class Skip(object): From 453b955f4791353ae0ba5eb5e05fdc52a9e10bda Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 13:08:00 -0700 Subject: [PATCH 0657/1204] Guard use of constructed Set API for unconstructed Sets --- pyomo/core/base/set.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index 5ba42bbb554..3361cc05fe0 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -589,6 +589,8 @@ def __eq__(self, other): # ranges (or no ranges). We will re-generate non-finite sets to # make sure we get an accurate "finiteness" flag. if hasattr(other, 'isfinite'): + if not other.parent_component().is_constructed(): + return False other_isfinite = other.isfinite() if not other_isfinite: try: From 147202bcc2eaa321c9d3389e9401a862ef5f5776 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 13:08:25 -0700 Subject: [PATCH 0658/1204] Ensure Any sets are fully constructed --- pyomo/core/base/set.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index 3361cc05fe0..3106d183b8e 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -4216,6 +4216,7 @@ def __init__(self, **kwds): # accept (and ignore) this value. kwds.setdefault('domain', self) Set.__init__(self, **kwds) + self.construct() def get(self, val, default=None): return val if val is not Ellipsis else default From d9d88ef3c17db3a48794bd800a0861e828f60244 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 13:09:17 -0700 Subject: [PATCH 0659/1204] Now that _implicit_subsets has been removed, we can disable domain on AbstractScalarVar --- pyomo/core/base/var.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyomo/core/base/var.py b/pyomo/core/base/var.py index 8d5b93f3ace..40613f78554 100644 --- a/pyomo/core/base/var.py +++ b/pyomo/core/base/var.py @@ -66,8 +66,7 @@ + [(_, False) for _ in integer_global_set_ids] ) _VARDATA_API = ( - # including 'domain' runs afoul of logic in Block._add_implicit_sets() - # 'domain', + 'domain', 'bounds', 'lower', 'upper', From 032534660755a175422b3d687348648cb74d3f25 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 13:09:35 -0700 Subject: [PATCH 0660/1204] Update benders tests to use common.dependencies, relax dependency on CPLEX --- pyomo/contrib/benders/tests/test_benders.py | 37 ++++++++------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/pyomo/contrib/benders/tests/test_benders.py b/pyomo/contrib/benders/tests/test_benders.py index 26a2a0b7910..f1d4be32494 100644 --- a/pyomo/contrib/benders/tests/test_benders.py +++ b/pyomo/contrib/benders/tests/test_benders.py @@ -10,35 +10,24 @@ # ___________________________________________________________________________ import pyomo.common.unittest as unittest -from pyomo.contrib.benders.benders_cuts import BendersCutGenerator import pyomo.environ as pyo -try: - import mpi4py - - mpi4py_available = True -except: - mpi4py_available = False -try: - import numpy as np - - numpy_available = True -except: - numpy_available = False - +from pyomo.common.dependencies import mpi4py_available, numpy_available +from pyomo.contrib.benders.benders_cuts import BendersCutGenerator -ipopt_opt = pyo.SolverFactory('ipopt') -ipopt_available = ipopt_opt.available(exception_flag=False) +ipopt_available = pyo.SolverFactory('ipopt').available(exception_flag=False) -cplex_opt = pyo.SolverFactory('cplex_direct') -cplex_available = cplex_opt.available(exception_flag=False) +for mip_name in ('cplex_direct', 'gurobi_direct', 'gurobi', 'cplex', 'glpk', 'cbc'): + mip_available = pyo.SolverFactory(mip_name).available(exception_flag=False) + if mip_available: + break @unittest.pytest.mark.mpi class MPITestBenders(unittest.TestCase): @unittest.skipIf(not mpi4py_available, 'mpi4py is not available.') @unittest.skipIf(not numpy_available, 'numpy is not available.') - @unittest.skipIf(not cplex_available, 'cplex is not available.') + @unittest.skipIf(not mip_available, 'MIP solver is not available.') def test_farmer(self): class Farmer(object): def __init__(self): @@ -200,9 +189,9 @@ def EnforceQuotas_rule(m, i): subproblem_fn=create_subproblem, subproblem_fn_kwargs=subproblem_fn_kwargs, root_eta=m.eta[s], - subproblem_solver='cplex_direct', + subproblem_solver=mip_name, ) - opt = pyo.SolverFactory('cplex_direct') + opt = pyo.SolverFactory(mip_name) for i in range(30): res = opt.solve(m, tee=False) @@ -261,7 +250,7 @@ def create_subproblem(root): @unittest.skipIf(not mpi4py_available, 'mpi4py is not available.') @unittest.skipIf(not numpy_available, 'numpy is not available.') - @unittest.skipIf(not cplex_available, 'cplex is not available.') + @unittest.skipIf(not mip_available, 'MIP solver is not available.') def test_four_scen_farmer(self): class FourScenFarmer(object): def __init__(self): @@ -430,9 +419,9 @@ def EnforceQuotas_rule(m, i): subproblem_fn=create_subproblem, subproblem_fn_kwargs=subproblem_fn_kwargs, root_eta=m.eta[s], - subproblem_solver='cplex_direct', + subproblem_solver=mip_name, ) - opt = pyo.SolverFactory('cplex_direct') + opt = pyo.SolverFactory(mip_name) for i in range(30): res = opt.solve(m, tee=False) From b24fe364f43b0855ce115e3dbb262988da3ef3ad Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 14:59:43 -0700 Subject: [PATCH 0661/1204] Prevent importing test_solver_cases at module scope --- pyomo/core/tests/examples/test_kernel_examples.py | 6 +++--- pyomo/solvers/tests/checks/test_BARON.py | 4 ++-- pyomo/solvers/tests/mip/test_asl.py | 4 ++-- pyomo/solvers/tests/mip/test_ipopt.py | 4 ++-- pyomo/solvers/tests/mip/test_scip.py | 4 ++-- .../solvers/tests/piecewise_linear/test_piecewise_linear.py | 6 +++--- .../tests/piecewise_linear/test_piecewise_linear_kernel.py | 6 +++--- pyomo/solvers/tests/testcases.py | 6 +++--- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/pyomo/core/tests/examples/test_kernel_examples.py b/pyomo/core/tests/examples/test_kernel_examples.py index 7039f457f84..0434d9127a3 100644 --- a/pyomo/core/tests/examples/test_kernel_examples.py +++ b/pyomo/core/tests/examples/test_kernel_examples.py @@ -44,10 +44,10 @@ def setUpModule(): global testing_solvers import pyomo.environ - from pyomo.solvers.tests.solvers import test_solver_cases + from pyomo.solvers.tests.solvers import test_solver_cases as _test_solver_cases - for _solver, _io in test_solver_cases(): - if (_solver, _io) in testing_solvers and test_solver_cases( + for _solver, _io in _test_solver_cases(): + if (_solver, _io) in testing_solvers and _test_solver_cases( _solver, _io ).available: testing_solvers[_solver, _io] = True diff --git a/pyomo/solvers/tests/checks/test_BARON.py b/pyomo/solvers/tests/checks/test_BARON.py index eb58076b09c..897f1e88a42 100644 --- a/pyomo/solvers/tests/checks/test_BARON.py +++ b/pyomo/solvers/tests/checks/test_BARON.py @@ -20,9 +20,9 @@ from pyomo.opt import SolverFactory, TerminationCondition # check if BARON is available -from pyomo.solvers.tests.solvers import test_solver_cases +from pyomo.solvers.tests.solvers import test_solver_cases as _test_solver_cases -baron_available = test_solver_cases('baron', 'bar').available +baron_available = _test_solver_cases('baron', 'bar').available @unittest.skipIf(not baron_available, "The 'BARON' solver is not available") diff --git a/pyomo/solvers/tests/mip/test_asl.py b/pyomo/solvers/tests/mip/test_asl.py index 1e6a9e53030..42b77df7d87 100644 --- a/pyomo/solvers/tests/mip/test_asl.py +++ b/pyomo/solvers/tests/mip/test_asl.py @@ -47,9 +47,9 @@ class mock_all(unittest.TestCase): def setUpClass(cls): global cplexamp_available import pyomo.environ - from pyomo.solvers.tests.solvers import test_solver_cases + from pyomo.solvers.tests.solvers import test_solver_cases as _test_solver_cases - cplexamp_available = test_solver_cases('cplex', 'nl').available + cplexamp_available = _test_solver_cases('cplex', 'nl').available def setUp(self): self.do_setup(False) diff --git a/pyomo/solvers/tests/mip/test_ipopt.py b/pyomo/solvers/tests/mip/test_ipopt.py index ca553c12447..bccb4f2a27c 100644 --- a/pyomo/solvers/tests/mip/test_ipopt.py +++ b/pyomo/solvers/tests/mip/test_ipopt.py @@ -42,9 +42,9 @@ class Test(unittest.TestCase): def setUpClass(cls): global ipopt_available import pyomo.environ - from pyomo.solvers.tests.solvers import test_solver_cases + from pyomo.solvers.tests.solvers import test_solver_cases as _test_solver_cases - ipopt_available = test_solver_cases('ipopt', 'nl').available + ipopt_available = _test_solver_cases('ipopt', 'nl').available def setUp(self): if not ipopt_available: diff --git a/pyomo/solvers/tests/mip/test_scip.py b/pyomo/solvers/tests/mip/test_scip.py index 8a43b120a34..7fffdc53c13 100644 --- a/pyomo/solvers/tests/mip/test_scip.py +++ b/pyomo/solvers/tests/mip/test_scip.py @@ -33,9 +33,9 @@ class Test(unittest.TestCase): def setUpClass(cls): global scip_available import pyomo.environ - from pyomo.solvers.tests.solvers import test_solver_cases + from pyomo.solvers.tests.solvers import test_solver_cases as _test_solver_cases - scip_available = test_solver_cases('scip', 'nl').available + scip_available = _test_solver_cases('scip', 'nl').available def setUp(self): if not scip_available: diff --git a/pyomo/solvers/tests/piecewise_linear/test_piecewise_linear.py b/pyomo/solvers/tests/piecewise_linear/test_piecewise_linear.py index adf1a000fb4..bfa206a987b 100644 --- a/pyomo/solvers/tests/piecewise_linear/test_piecewise_linear.py +++ b/pyomo/solvers/tests/piecewise_linear/test_piecewise_linear.py @@ -22,7 +22,7 @@ from pyomo.core.base import Var from pyomo.core.base.objective import minimize, maximize from pyomo.core.base.piecewise import Bound, PWRepn -from pyomo.solvers.tests.solvers import test_solver_cases +from pyomo.solvers.tests.solvers import test_solver_cases as _test_solver_cases smoke_problems = ['convex_var', 'step_var', 'step_vararray'] @@ -50,8 +50,8 @@ # testing_solvers['ipopt','nl'] = False # testing_solvers['cplex','python'] = False # testing_solvers['_cplex_persistent','python'] = False -for _solver, _io in test_solver_cases(): - if (_solver, _io) in testing_solvers and test_solver_cases(_solver, _io).available: +for _solver, _io in _test_solver_cases(): + if (_solver, _io) in testing_solvers and _test_solver_cases(_solver, _io).available: testing_solvers[_solver, _io] = True diff --git a/pyomo/solvers/tests/piecewise_linear/test_piecewise_linear_kernel.py b/pyomo/solvers/tests/piecewise_linear/test_piecewise_linear_kernel.py index 516ee25ffa3..4137d9d3eed 100644 --- a/pyomo/solvers/tests/piecewise_linear/test_piecewise_linear_kernel.py +++ b/pyomo/solvers/tests/piecewise_linear/test_piecewise_linear_kernel.py @@ -19,7 +19,7 @@ from pyomo.common.fileutils import import_file from pyomo.kernel import SolverFactory, variable, maximize, minimize -from pyomo.solvers.tests.solvers import test_solver_cases +from pyomo.solvers.tests.solvers import test_solver_cases as _test_solver_cases problems = ['convex_var', 'concave_var', 'piecewise_var', 'step_var'] @@ -30,8 +30,8 @@ # testing_solvers['ipopt','nl'] = False # testing_solvers['cplex','python'] = False # testing_solvers['_cplex_persistent','python'] = False -for _solver, _io in test_solver_cases(): - if (_solver, _io) in testing_solvers and test_solver_cases(_solver, _io).available: +for _solver, _io in _test_solver_cases(): + if (_solver, _io) in testing_solvers and _test_solver_cases(_solver, _io).available: testing_solvers[_solver, _io] = True diff --git a/pyomo/solvers/tests/testcases.py b/pyomo/solvers/tests/testcases.py index eaebbcd9003..f5920ed6814 100644 --- a/pyomo/solvers/tests/testcases.py +++ b/pyomo/solvers/tests/testcases.py @@ -15,7 +15,7 @@ from pyomo.common.collections import Bunch from pyomo.opt import TerminationCondition from pyomo.solvers.tests.models.base import all_models -from pyomo.solvers.tests.solvers import test_solver_cases +from pyomo.solvers.tests.solvers import test_solver_cases as _test_solver_cases from pyomo.core.kernel.block import IBlock # For expected failures that appear in all known version @@ -297,8 +297,8 @@ def generate_scenarios(arg=None): _model = all_models(model) if not arg is None and not arg(_model): continue - for solver, io in sorted(test_solver_cases()): - _solver_case = test_solver_cases(solver, io) + for solver, io in sorted(_test_solver_cases()): + _solver_case = _test_solver_cases(solver, io) _ver = _solver_case.version # Skip this test case if the solver doesn't support the From c679fa138ce1df9420180197afd92fdf2be26b62 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 15:00:29 -0700 Subject: [PATCH 0662/1204] Track move of gaussian_kde into stats namespace --- pyomo/contrib/parmest/graphics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/parmest/graphics.py b/pyomo/contrib/parmest/graphics.py index b8dfa243b9a..65efb5cfd64 100644 --- a/pyomo/contrib/parmest/graphics.py +++ b/pyomo/contrib/parmest/graphics.py @@ -152,7 +152,7 @@ def _add_scipy_dist_CI( data_slice.append(np.array([[theta_star[var]] * ncells] * ncells)) data_slice = np.dstack(tuple(data_slice)) - elif isinstance(dist, stats.kde.gaussian_kde): + elif isinstance(dist, stats.gaussian_kde): for var in theta_star.index: if var == xvar: data_slice.append(X.ravel()) From 47eac442efe06dd88a0e3ee695af95ca77620002 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 15:00:54 -0700 Subject: [PATCH 0663/1204] Use factorial from math and not numpy.math --- pyomo/dae/plugins/colloc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/dae/plugins/colloc.py b/pyomo/dae/plugins/colloc.py index c95d4b1a672..7f86e8bc2e2 100644 --- a/pyomo/dae/plugins/colloc.py +++ b/pyomo/dae/plugins/colloc.py @@ -10,6 +10,7 @@ # ___________________________________________________________________________ import logging +import math # If the user has numpy then the collocation points and the a matrix for # the Runge-Kutta basis formulation will be calculated as needed. @@ -156,7 +157,7 @@ def conv(a, b): def calc_cp(alpha, beta, k): gamma = [] - factorial = numpy.math.factorial + factorial = math.factorial for i in range(k + 1): num = factorial(alpha + k) * factorial(alpha + beta + k + i) From 95255cbecab494ae2ad8e28a35a588725a79f999 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 15:01:16 -0700 Subject: [PATCH 0664/1204] Remove refereces to clone_counter in online docs --- .../developer_reference/expressions/managing.rst | 14 -------------- .../expressions/context_managers.rst | 2 +- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/doc/OnlineDocs/developer_reference/expressions/managing.rst b/doc/OnlineDocs/developer_reference/expressions/managing.rst index db045e55b6c..344d101074c 100644 --- a/doc/OnlineDocs/developer_reference/expressions/managing.rst +++ b/doc/OnlineDocs/developer_reference/expressions/managing.rst @@ -86,20 +86,6 @@ a consistent ordering of terms that should make it easier to interpret expressions. -Cloning Expressions -------------------- - -Expressions are automatically cloned only during certain expression -transformations. Since this can be an expensive operation, the -:data:`clone_counter ` context -manager object is provided to track the number of times the -:func:`clone_expression ` -function is executed. - -For example: - -.. literalinclude:: ../../tests/expr/managing_ex4.spy - Evaluating Expressions ---------------------- diff --git a/doc/OnlineDocs/library_reference/expressions/context_managers.rst b/doc/OnlineDocs/library_reference/expressions/context_managers.rst index 521334aef16..0e92f583c73 100644 --- a/doc/OnlineDocs/library_reference/expressions/context_managers.rst +++ b/doc/OnlineDocs/library_reference/expressions/context_managers.rst @@ -8,6 +8,6 @@ Context Managers .. autoclass:: pyomo.core.expr.linear_expression :members: -.. autoclass:: pyomo.core.expr.clone_counter +.. autoclass:: pyomo.core.expr.current.clone_counter :members: From 648faf04b802825ca963d010d073f8172baea9f4 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 15:01:32 -0700 Subject: [PATCH 0665/1204] Update expected output from LBB --- doc/OnlineDocs/contributed_packages/gdpopt.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/OnlineDocs/contributed_packages/gdpopt.rst b/doc/OnlineDocs/contributed_packages/gdpopt.rst index 5e5b8ccce5d..d550b0ced76 100644 --- a/doc/OnlineDocs/contributed_packages/gdpopt.rst +++ b/doc/OnlineDocs/contributed_packages/gdpopt.rst @@ -175,7 +175,10 @@ To use the GDPopt-LBB solver, define your Pyomo GDP model as usual: >>> m.djn = Disjunction(expr=[m.y1, m.y2]) Invoke the GDPopt-LBB solver + >>> results = SolverFactory('gdpopt.lbb').solve(m) + WARNING: 09/06/22: The GDPopt LBB algorithm currently has known issues. Please + use the results with caution and report any bugs! >>> print(results) # doctest: +SKIP >>> print(results.solver.status) From dac1a8096fa2eae416edcdaf42182f5b0c399a16 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 16:10:38 -0700 Subject: [PATCH 0666/1204] Defer imports of ctypes, random --- pyomo/common/dependencies.py | 14 ++++++++++++++ pyomo/common/env.py | 2 +- pyomo/common/fileutils.py | 2 +- pyomo/common/modeling.py | 4 ++-- pyomo/environ/tests/test_environ.py | 8 -------- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 350762bc8ad..1f1e8cdbfd2 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -743,6 +743,12 @@ def _finalize_yaml(module, available): yaml_load_args['Loader'] = module.SafeLoader +def _finalize_ctypes(module, available): + # ctypes.util must be explicitly imported (and fileutils assumes + # this has already happened) + import ctypes.util + + def _finalize_scipy(module, available): if available: # Import key subpackages that we will want to assume are present @@ -835,6 +841,14 @@ def _pyutilib_importer(): return importlib.import_module('pyutilib') +# Standard libraries that are slower to import and not strictly required +# on all platforms / situations. +ctypes, _ = attempt_import( + 'ctypes', deferred_submodules=['util'], callback=_finalize_ctypes +) +random, _ = attempt_import('random') + +# Commonly-used optional dependencies dill, dill_available = attempt_import('dill') mpi4py, mpi4py_available = attempt_import('mpi4py') networkx, networkx_available = attempt_import('networkx') diff --git a/pyomo/common/env.py b/pyomo/common/env.py index a90efcc2787..38890846896 100644 --- a/pyomo/common/env.py +++ b/pyomo/common/env.py @@ -9,9 +9,9 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -import ctypes import os +from .dependencies import ctypes def _as_bytes(val): """Helper function to coerce a string to a bytes() object""" diff --git a/pyomo/common/fileutils.py b/pyomo/common/fileutils.py index 16933df64af..557901c401e 100644 --- a/pyomo/common/fileutils.py +++ b/pyomo/common/fileutils.py @@ -32,7 +32,6 @@ PathData """ -import ctypes.util import glob import inspect import logging @@ -42,6 +41,7 @@ import sys from . import envvar +from .dependencies import ctypes from .deprecation import deprecated, relocated_module_attribute relocated_module_attribute('StreamIndenter', 'pyomo.common.formatting', version='6.2') diff --git a/pyomo/common/modeling.py b/pyomo/common/modeling.py index b3a6d59fcf0..5ecc56cce9b 100644 --- a/pyomo/common/modeling.py +++ b/pyomo/common/modeling.py @@ -9,8 +9,8 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from random import random import sys +from .dependencies import random def randint(a, b): @@ -21,7 +21,7 @@ def randint(a, b): can support deterministic testing (i.e., setting the random.seed and expecting the same sequence), we will implement a simple, but stable version of randint().""" - return int((b - a + 1) * random()) + return int((b - a + 1) * random.random()) def unique_component_name(instance, name): diff --git a/pyomo/environ/tests/test_environ.py b/pyomo/environ/tests/test_environ.py index 02e4d723145..532f411d9e4 100644 --- a/pyomo/environ/tests/test_environ.py +++ b/pyomo/environ/tests/test_environ.py @@ -16,14 +16,8 @@ import sys import subprocess -from collections import namedtuple - import pyomo.common.unittest as unittest -from pyomo.common.dependencies import numpy_available, attempt_import - -pyro4, pyro4_available = attempt_import('Pyro4') - class ImportData(object): def __init__(self): @@ -145,7 +139,6 @@ def test_tpl_import_time(self): 'base64', # Imported on Windows 'cPickle', 'csv', - 'ctypes', 'decimal', 'gc', # Imported on MacOS, Windows; Linux in 3.10 'glob', @@ -157,7 +150,6 @@ def test_tpl_import_time(self): 'logging', 'pickle', 'platform', - 'random', # Imported on MacOS, Windows 'shlex', 'socket', # Imported on MacOS, Windows; Linux in 3.10 'tempfile', # Imported on MacOS, Windows From cfa6ff49f5d20c07af63faaba0b0877e1e327cb5 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 16:14:05 -0700 Subject: [PATCH 0667/1204] Defer resolution of numpy import --- pyomo/common/dependencies.py | 3 +- pyomo/common/env.py | 1 + pyomo/common/numeric_types.py | 8 +++++ pyomo/core/base/indexed_component.py | 12 ++++--- pyomo/core/expr/numeric_expr.py | 38 ++++----------------- pyomo/core/tests/unit/test_numpy_expr.py | 2 +- pyomo/core/tests/unit/test_numvalue.py | 42 ++++++++++++++++-------- pyomo/environ/tests/test_environ.py | 2 -- 8 files changed, 55 insertions(+), 53 deletions(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 1f1e8cdbfd2..0a179b5c2de 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -18,7 +18,6 @@ from .deprecation import deprecated, deprecation_warning, in_testing_environment from .errors import DeferredImportError -from . import numeric_types SUPPRESS_DEPENDENCY_WARNINGS = False @@ -784,6 +783,8 @@ def _finalize_matplotlib(module, available): def _finalize_numpy(np, available): if not available: return + from . import numeric_types + # Register ndarray as a native type to prevent 1-element ndarrays # from accidentally registering ndarray as a native_numeric_type. numeric_types.native_types.add(np.ndarray) diff --git a/pyomo/common/env.py b/pyomo/common/env.py index 38890846896..2ce0f368b9e 100644 --- a/pyomo/common/env.py +++ b/pyomo/common/env.py @@ -13,6 +13,7 @@ from .dependencies import ctypes + def _as_bytes(val): """Helper function to coerce a string to a bytes() object""" if isinstance(val, bytes): diff --git a/pyomo/common/numeric_types.py b/pyomo/common/numeric_types.py index af7eeded3cf..bd71b29f005 100644 --- a/pyomo/common/numeric_types.py +++ b/pyomo/common/numeric_types.py @@ -12,6 +12,7 @@ import logging import sys +from pyomo.common.dependencies import numpy_available from pyomo.common.deprecation import deprecated, relocated_module_attribute from pyomo.common.errors import TemplateExpressionError @@ -207,6 +208,13 @@ def check_if_numeric_type(obj): if obj_class in native_types: return obj_class in native_numeric_types + if 'numpy' in obj_class.__module__: + # trigger the resolution of numpy_available and check if this + # type was automatically registered + bool(numpy_available) + if obj_class in native_numeric_types: + return True + try: obj_plus_0 = obj + 0 obj_p0_class = obj_plus_0.__class__ diff --git a/pyomo/core/base/indexed_component.py b/pyomo/core/base/indexed_component.py index b474281f5b9..54fa0bf6a91 100644 --- a/pyomo/core/base/indexed_component.py +++ b/pyomo/core/base/indexed_component.py @@ -21,14 +21,13 @@ import pyomo.core.expr as EXPR import pyomo.core.base as BASE -from pyomo.core.expr.numeric_expr import NumericNDArray -from pyomo.core.expr.numvalue import native_types from pyomo.core.base.indexed_component_slice import IndexedComponent_slice from pyomo.core.base.initializer import Initializer from pyomo.core.base.component import Component, ActiveComponent from pyomo.core.base.config import PyomoOptions from pyomo.core.base.enums import SortComponents from pyomo.core.base.global_set import UnindexedComponent_set +from pyomo.core.expr.numeric_expr import _ndarray from pyomo.core.pyomoobject import PyomoObject from pyomo.common import DeveloperError from pyomo.common.autoslots import fast_deepcopy @@ -36,6 +35,7 @@ from pyomo.common.deprecation import deprecated, deprecation_warning from pyomo.common.errors import DeveloperError, TemplateExpressionError from pyomo.common.modeling import NOTSET +from pyomo.common.numeric_types import native_types from pyomo.common.sorting import sorted_robust from collections.abc import Sequence @@ -1216,7 +1216,7 @@ class IndexedComponent_NDArrayMixin(object): def __array__(self, dtype=None): if not self.is_indexed(): - ans = NumericNDArray(shape=(1,), dtype=object) + ans = _ndarray.NumericNDArray(shape=(1,), dtype=object) ans[0] = self return ans @@ -1236,10 +1236,12 @@ def __array__(self, dtype=None): % (self, bounds[0], bounds[1]) ) shape = tuple(b + 1 for b in bounds[1]) - ans = NumericNDArray(shape=shape, dtype=object) + ans = _ndarray.NumericNDArray(shape=shape, dtype=object) for k, v in self.items(): ans[k] = v return ans def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): - return NumericNDArray.__array_ufunc__(None, ufunc, method, *inputs, **kwargs) + return _ndarray.NumericNDArray.__array_ufunc__( + None, ufunc, method, *inputs, **kwargs + ) diff --git a/pyomo/core/expr/numeric_expr.py b/pyomo/core/expr/numeric_expr.py index 1e3039b5727..3eb7861e341 100644 --- a/pyomo/core/expr/numeric_expr.py +++ b/pyomo/core/expr/numeric_expr.py @@ -19,7 +19,7 @@ from math import isclose -from pyomo.common.dependencies import numpy as np, numpy_available +from pyomo.common.dependencies import attempt_import from pyomo.common.deprecation import ( deprecated, deprecation_warning, @@ -47,6 +47,9 @@ # Note: pyggyback on expr.base's use of attempt_import(visitor) from pyomo.core.expr.base import ExpressionBase, NPV_Mixin, visitor + +_ndarray, _ = attempt_import('pyomo.core.expr.ndarray') + relocated_module_attribute( 'is_potentially_variable', 'pyomo.core.expr.numvalue.is_potentially_variable', @@ -634,7 +637,9 @@ def __abs__(self): return _abs_dispatcher[self.__class__](self) def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): - return NumericNDArray.__array_ufunc__(None, ufunc, method, *inputs, **kwargs) + return _ndarray.NumericNDArray.__array_ufunc__( + None, ufunc, method, *inputs, **kwargs + ) def to_string(self, verbose=None, labeler=None, smap=None, compute_values=False): """Return a string representation of the expression tree. @@ -671,35 +676,6 @@ def to_string(self, verbose=None, labeler=None, smap=None, compute_values=False) return str(self) -# -# Note: the "if numpy_available" in the class definition also ensures -# that the numpy types are registered if numpy is in fact available -# -# TODO: Move this to a separate module to support avoiding the numpy -# import if numpy is not actually used. -class NumericNDArray(np.ndarray if numpy_available else object): - """An ndarray subclass that stores Pyomo numeric expressions""" - - def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): - if method == '__call__': - # Convert all incoming types to ndarray (to prevent recursion) - args = [np.asarray(i) for i in inputs] - # Set the return type to be an 'object'. This prevents the - # logical operators from casting the result to a bool. This - # requires numpy >= 1.6 - kwargs['dtype'] = object - - # Delegate to the base ufunc, but return an instance of this - # class so that additional operators hit this method. - ans = getattr(ufunc, method)(*args, **kwargs) - if isinstance(ans, np.ndarray): - if ans.size == 1: - return ans[0] - return ans.view(NumericNDArray) - else: - return ans - - # ------------------------------------------------------- # # Expression classes diff --git a/pyomo/core/tests/unit/test_numpy_expr.py b/pyomo/core/tests/unit/test_numpy_expr.py index df20f30f9b4..8f58eb29e56 100644 --- a/pyomo/core/tests/unit/test_numpy_expr.py +++ b/pyomo/core/tests/unit/test_numpy_expr.py @@ -29,7 +29,7 @@ Reals, ) from pyomo.core.expr import MonomialTermExpression -from pyomo.core.expr.numeric_expr import NumericNDArray +from pyomo.core.expr.ndarray import NumericNDArray from pyomo.core.expr.numvalue import as_numeric from pyomo.core.expr.compare import compare_expressions from pyomo.core.expr.relational_expr import InequalityExpression diff --git a/pyomo/core/tests/unit/test_numvalue.py b/pyomo/core/tests/unit/test_numvalue.py index 0f9e42f552a..8c6b0e02ed4 100644 --- a/pyomo/core/tests/unit/test_numvalue.py +++ b/pyomo/core/tests/unit/test_numvalue.py @@ -12,8 +12,12 @@ # Unit Tests for Python numeric values # +import subprocess +import sys from math import nan, inf + import pyomo.common.unittest as unittest +from pyomo.common.dependencies import numpy, numpy_available from pyomo.environ import ( value, @@ -38,13 +42,6 @@ ) from pyomo.common.numeric_types import _native_boolean_types -try: - import numpy - - numpy_available = True -except: - numpy_available = False - class MyBogusType(object): def __init__(self, val=0): @@ -541,30 +538,49 @@ def test_unknownNumericType(self): native_numeric_types.remove(MyBogusNumericType) native_types.remove(MyBogusNumericType) + @unittest.skipUnless(numpy_available, "This test requires NumPy") def test_numpy_basic_float_registration(self): - if not numpy_available: - self.skipTest("This test requires NumPy") self.assertIn(numpy.float_, native_numeric_types) self.assertNotIn(numpy.float_, native_integer_types) self.assertIn(numpy.float_, _native_boolean_types) self.assertIn(numpy.float_, native_types) + @unittest.skipUnless(numpy_available, "This test requires NumPy") def test_numpy_basic_int_registration(self): - if not numpy_available: - self.skipTest("This test requires NumPy") self.assertIn(numpy.int_, native_numeric_types) self.assertIn(numpy.int_, native_integer_types) self.assertIn(numpy.int_, _native_boolean_types) self.assertIn(numpy.int_, native_types) + @unittest.skipUnless(numpy_available, "This test requires NumPy") def test_numpy_basic_bool_registration(self): - if not numpy_available: - self.skipTest("This test requires NumPy") self.assertNotIn(numpy.bool_, native_numeric_types) self.assertNotIn(numpy.bool_, native_integer_types) self.assertIn(numpy.bool_, _native_boolean_types) self.assertIn(numpy.bool_, native_types) + @unittest.skipUnless(numpy_available, "This test requires NumPy") + def test_automatic_numpy_registration(self): + cmd = ( + 'import pyomo; from pyomo.core.base import Var; import numpy as np; ' + 'print(np.float64 in pyomo.common.numeric_types.native_numeric_types); ' + '%s; print(np.float64 in pyomo.common.numeric_types.native_numeric_types)' + ) + + def _tester(expr): + rc = subprocess.run( + [sys.executable, '-c', cmd % expr], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + self.assertEqual((rc.returncode, rc.stdout), (0, "False\nTrue\n")) + + _tester('Var() <= np.float64(5)') + _tester('np.float64(5) <= Var()') + _tester('np.float64(5) + Var()') + _tester('Var() + np.float64(5)') + if __name__ == "__main__": unittest.main() diff --git a/pyomo/environ/tests/test_environ.py b/pyomo/environ/tests/test_environ.py index 532f411d9e4..1a1a3ffbfc9 100644 --- a/pyomo/environ/tests/test_environ.py +++ b/pyomo/environ/tests/test_environ.py @@ -160,8 +160,6 @@ def test_tpl_import_time(self): } # Non-standard-library TPLs that Pyomo will load unconditionally ref.add('ply') - if numpy_available: - ref.add('numpy') diff = set(_[0] for _ in tpl_by_time[-5:]).difference(ref) self.assertEqual( diff, set(), "Unexpected module found in 5 slowest-loading TPL modules" From 7c9829f653f94b955e7c2622933b16302d4780ef Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 16:14:29 -0700 Subject: [PATCH 0668/1204] Add subprocess as an expected dependency --- pyomo/environ/tests/test_environ.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/environ/tests/test_environ.py b/pyomo/environ/tests/test_environ.py index 1a1a3ffbfc9..4c945988aee 100644 --- a/pyomo/environ/tests/test_environ.py +++ b/pyomo/environ/tests/test_environ.py @@ -143,7 +143,7 @@ def test_tpl_import_time(self): 'gc', # Imported on MacOS, Windows; Linux in 3.10 'glob', 'heapq', # Added in Python 3.10 - 'importlib', # Imported on Windows + 'importlib', 'inspect', 'json', # Imported on Windows 'locale', # Added in Python 3.9 @@ -152,6 +152,7 @@ def test_tpl_import_time(self): 'platform', 'shlex', 'socket', # Imported on MacOS, Windows; Linux in 3.10 + 'subprocess', 'tempfile', # Imported on MacOS, Windows 'textwrap', 'typing', From 7187fc291dfb59373c4e73f4bf7bff880d9373ed Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 4 Jan 2024 15:50:21 -0700 Subject: [PATCH 0669/1204] Backwards compability: Process legacy options. --- pyomo/solver/base.py | 8 ++++---- pyomo/solver/ipopt.py | 23 +++++++++++++++-------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 48adc44c4d7..48b48db14b9 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -532,8 +532,8 @@ def options(self): class. """ for solver_name in ['gurobi', 'ipopt', 'cplex', 'cbc', 'highs']: - if hasattr(self, solver_name + '_options'): - return getattr(self, solver_name + '_options') + if hasattr(self, 'solver_options'): + return getattr(self, 'solver_options') raise NotImplementedError('Could not find the correct options') @options.setter @@ -543,8 +543,8 @@ def options(self, val): """ found = False for solver_name in ['gurobi', 'ipopt', 'cplex', 'cbc', 'highs']: - if hasattr(self, solver_name + '_options'): - setattr(self, solver_name + '_options', val) + if hasattr(self, 'solver_options'): + setattr(self, 'solver_options', val) found = True if not found: raise NotImplementedError('Could not find the correct options') diff --git a/pyomo/solver/ipopt.py b/pyomo/solver/ipopt.py index 1b4c0eb36cb..406f4291c44 100644 --- a/pyomo/solver/ipopt.py +++ b/pyomo/solver/ipopt.py @@ -14,7 +14,7 @@ import datetime import io import sys -from typing import Mapping, Optional +from typing import Mapping, Optional, Dict from pyomo.common import Executable from pyomo.common.config import ConfigValue, NonNegativeInt, NonNegativeFloat @@ -188,7 +188,7 @@ def __init__(self, **kwds): self._config = self.CONFIG(kwds) self._writer = NLWriter() self._writer.config.skip_trivial_constraints = True - self.ipopt_options = self._config.solver_options + self._solver_options = self._config.solver_options def available(self): if self.config.executable.path() is None: @@ -216,6 +216,14 @@ def config(self): def config(self, val): self._config = val + @property + def solver_options(self): + return self._solver_options + + @solver_options.setter + def solver_options(self, val: Dict): + self._solver_options = val + @property def symbol_map(self): return self._symbol_map @@ -240,15 +248,14 @@ def _create_command_line(self, basename: str, config: ipoptConfig, opt_file: boo cmd = [str(config.executable), basename + '.nl', '-AMPL'] if opt_file: cmd.append('option_file_name=' + basename + '.opt') - if 'option_file_name' in config.solver_options: + if 'option_file_name' in self.solver_options: raise ValueError( 'Pyomo generates the ipopt options file as part of the solve method. ' 'Add all options to ipopt.config.solver_options instead.' ) - self.ipopt_options = dict(config.solver_options) - if config.time_limit is not None and 'max_cpu_time' not in self.ipopt_options: - self.ipopt_options['max_cpu_time'] = config.time_limit - for k, val in self.ipopt_options.items(): + if config.time_limit is not None and 'max_cpu_time' not in self.solver_options: + self.solver_options['max_cpu_time'] = config.time_limit + for k, val in self.solver_options.items(): if k in ipopt_command_line_options: cmd.append(str(k) + '=' + str(val)) return cmd @@ -309,7 +316,7 @@ def solve(self, model, **kwds): # Write the opt_file, if there should be one; return a bool to say # whether or not we have one (so we can correctly build the command line) opt_file = self._write_options_file( - filename=basename, options=config.solver_options + filename=basename, options=self.solver_options ) # Call ipopt - passing the files via the subprocess cmd = self._create_command_line( From 6fb37ce225905bcd5d59924c6a404142f114b30b Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 4 Jan 2024 16:04:27 -0700 Subject: [PATCH 0670/1204] Add new option to legacy interface for forwards compability --- pyomo/solver/base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 48b48db14b9..9b52c61f642 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -387,6 +387,7 @@ def solve( options: Optional[Dict] = None, keepfiles: bool = False, symbolic_solver_labels: bool = False, + raise_exception_on_nonoptimal_result: bool = False ): """ Solve method: maps new solve method style to backwards compatible version. @@ -404,6 +405,9 @@ def solve( self.config.symbolic_solver_labels = symbolic_solver_labels self.config.time_limit = timelimit self.config.report_timing = report_timing + # This is a new flag in the interface. To preserve backwards compability, + # its default is set to "False" + self.config.raise_exception_on_nonoptimal_result = raise_exception_on_nonoptimal_result if solver_io is not None: raise NotImplementedError('Still working on this') if suffixes is not None: From a79f34856be419de027f1f59abed357850bfb758 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 4 Jan 2024 16:09:03 -0700 Subject: [PATCH 0671/1204] Apply black --- pyomo/solver/base.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 9b52c61f642..202b0422cee 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -387,7 +387,7 @@ def solve( options: Optional[Dict] = None, keepfiles: bool = False, symbolic_solver_labels: bool = False, - raise_exception_on_nonoptimal_result: bool = False + raise_exception_on_nonoptimal_result: bool = False, ): """ Solve method: maps new solve method style to backwards compatible version. @@ -407,7 +407,9 @@ def solve( self.config.report_timing = report_timing # This is a new flag in the interface. To preserve backwards compability, # its default is set to "False" - self.config.raise_exception_on_nonoptimal_result = raise_exception_on_nonoptimal_result + self.config.raise_exception_on_nonoptimal_result = ( + raise_exception_on_nonoptimal_result + ) if solver_io is not None: raise NotImplementedError('Still working on this') if suffixes is not None: From d66dc2516991ab0c7db62b1d1296af34dc14fd88 Mon Sep 17 00:00:00 2001 From: Sam Brockie Date: Fri, 5 Jan 2024 08:49:57 +0000 Subject: [PATCH 0672/1204] Replace deprecated alias with standard library module --- pyomo/dae/plugins/colloc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/dae/plugins/colloc.py b/pyomo/dae/plugins/colloc.py index c95d4b1a672..7f86e8bc2e2 100644 --- a/pyomo/dae/plugins/colloc.py +++ b/pyomo/dae/plugins/colloc.py @@ -10,6 +10,7 @@ # ___________________________________________________________________________ import logging +import math # If the user has numpy then the collocation points and the a matrix for # the Runge-Kutta basis formulation will be calculated as needed. @@ -156,7 +157,7 @@ def conv(a, b): def calc_cp(alpha, beta, k): gamma = [] - factorial = numpy.math.factorial + factorial = math.factorial for i in range(k + 1): num = factorial(alpha + k) * factorial(alpha + beta + k + i) From 182fd4cb11d338cd4bea910649b3454ece597d25 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 5 Jan 2024 14:57:16 -0700 Subject: [PATCH 0673/1204] Moving NumericNDArray to separate module --- pyomo/core/expr/ndarray.py | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 pyomo/core/expr/ndarray.py diff --git a/pyomo/core/expr/ndarray.py b/pyomo/core/expr/ndarray.py new file mode 100644 index 00000000000..fcbe5477a08 --- /dev/null +++ b/pyomo/core/expr/ndarray.py @@ -0,0 +1,39 @@ +# ___________________________________________________________________________ +# +# 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.common.dependencies import numpy as np, numpy_available + + +# +# Note: the "if numpy_available" in the class definition also ensures +# that the numpy types are registered if numpy is in fact available +# +class NumericNDArray(np.ndarray if numpy_available else object): + """An ndarray subclass that stores Pyomo numeric expressions""" + + def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): + if method == '__call__': + # Convert all incoming types to ndarray (to prevent recursion) + args = [np.asarray(i) for i in inputs] + # Set the return type to be an 'object'. This prevents the + # logical operators from casting the result to a bool. This + # requires numpy >= 1.6 + kwargs['dtype'] = object + + # Delegate to the base ufunc, but return an instance of this + # class so that additional operators hit this method. + ans = getattr(ufunc, method)(*args, **kwargs) + if isinstance(ans, np.ndarray): + if ans.size == 1: + return ans[0] + return ans.view(NumericNDArray) + else: + return ans From 405eb6e2be78cfcb279b7efc65f44aa0c0b46658 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 5 Jan 2024 15:35:51 -0700 Subject: [PATCH 0674/1204] Add ctypes to the expected list of libraries loaded by environ --- pyomo/environ/tests/test_environ.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/environ/tests/test_environ.py b/pyomo/environ/tests/test_environ.py index 4c945988aee..b223ba0e916 100644 --- a/pyomo/environ/tests/test_environ.py +++ b/pyomo/environ/tests/test_environ.py @@ -139,6 +139,7 @@ def test_tpl_import_time(self): 'base64', # Imported on Windows 'cPickle', 'csv', + 'ctypes', # mandatory import in core/base/external.py; TODO: fix this 'decimal', 'gc', # Imported on MacOS, Windows; Linux in 3.10 'glob', From 76fee13fae1bd0888dbadee083aa13d3bb437786 Mon Sep 17 00:00:00 2001 From: robbybp Date: Sat, 6 Jan 2024 15:52:52 -0700 Subject: [PATCH 0675/1204] move AMPLRepnVisitor construction into ConfigValue validation --- pyomo/contrib/incidence_analysis/config.py | 95 ++++++++++++++++++- pyomo/contrib/incidence_analysis/incidence.py | 42 ++------ pyomo/contrib/incidence_analysis/interface.py | 85 +++-------------- 3 files changed, 116 insertions(+), 106 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/config.py b/pyomo/contrib/incidence_analysis/config.py index db9accbddc4..31b2bd3fc22 100644 --- a/pyomo/contrib/incidence_analysis/config.py +++ b/pyomo/contrib/incidence_analysis/config.py @@ -13,6 +13,9 @@ import enum from pyomo.common.config import ConfigDict, ConfigValue, InEnum +from pyomo.common.modeling import NOTSET +from pyomo.repn.plugins.nl_writer import AMPLRepnVisitor, AMPLRepn, text_nl_template +from pyomo.repn.util import FileDeterminism, FileDeterminism_to_SortComponents class IncidenceMethod(enum.Enum): @@ -62,7 +65,92 @@ class IncidenceMethod(enum.Enum): ) -IncidenceConfig = ConfigDict() +class _ReconstructVisitor: + pass + + +def _amplrepnvisitor_validator(visitor=_ReconstructVisitor): + # This checks for and returns a valid AMPLRepnVisitor, but I don't want + # to construct this if we're not using IncidenceMethod.ampl_repn. + # It is not necessarily the end of the world if we construct this, however, + # as the code should still work. + if visitor is _ReconstructVisitor: + subexpression_cache = {} + subexpression_order = [] + external_functions = {} + var_map = {} + used_named_expressions = set() + symbolic_solver_labels = False + # TODO: Explore potential performance benefit of exporting defined variables. + # This likely only shows up if we can preserve the subexpression cache across + # multiple constraint expressions. + export_defined_variables = False + sorter = FileDeterminism_to_SortComponents(FileDeterminism.ORDERED) + amplvisitor = AMPLRepnVisitor( + text_nl_template, + subexpression_cache, + subexpression_order, + external_functions, + var_map, + used_named_expressions, + symbolic_solver_labels, + export_defined_variables, + sorter, + ) + elif not isinstance(visitor, AMPLRepnVisitor): + raise TypeError( + "'visitor' config argument should be an instance of AMPLRepnVisitor" + ) + else: + amplvisitor = visitor + return amplvisitor + + +_ampl_repn_visitor = ConfigValue( + default=_ReconstructVisitor, + domain=_amplrepnvisitor_validator, + description="Visitor used to generate AMPLRepn of each constraint", +) + + +class _IncidenceConfigDict(ConfigDict): + + def __call__( + self, + value=NOTSET, + default=NOTSET, + domain=NOTSET, + description=NOTSET, + doc=NOTSET, + visibility=NOTSET, + implicit=NOTSET, + implicit_domain=NOTSET, + preserve_implicit=False, + ): + init_value = value + new = super().__call__( + value=value, + default=default, + domain=domain, + description=description, + doc=doc, + visibility=visibility, + implicit=implicit, + implicit_domain=implicit_domain, + preserve_implicit=preserve_implicit, + ) + + if ( + new.method == IncidenceMethod.ampl_repn + and "ampl_repn_visitor" not in init_value + ): + new.ampl_repn_visitor = _ReconstructVisitor + + return new + + + +IncidenceConfig = _IncidenceConfigDict() """Options for incidence graph generation - ``include_fixed`` -- Flag indicating whether fixed variables should be included @@ -71,6 +159,8 @@ class IncidenceMethod(enum.Enum): should be included. - ``method`` -- Method used to identify incident variables. Must be a value of the ``IncidenceMethod`` enum. +- ``ampl_repn_visitor`` -- Expression visitor used to generate ``AMPLRepn`` of each + constraint. Must be an instance of ``AMPLRepnVisitor``. """ @@ -82,3 +172,6 @@ class IncidenceMethod(enum.Enum): IncidenceConfig.declare("method", _method) + + +IncidenceConfig.declare("ampl_repn_visitor", _ampl_repn_visitor) diff --git a/pyomo/contrib/incidence_analysis/incidence.py b/pyomo/contrib/incidence_analysis/incidence.py index 62ba7a0aec7..17307e89600 100644 --- a/pyomo/contrib/incidence_analysis/incidence.py +++ b/pyomo/contrib/incidence_analysis/incidence.py @@ -80,38 +80,14 @@ def _get_incident_via_standard_repn( return unique_variables -def _get_incident_via_ampl_repn(expr, linear_only, visitor=None): - if visitor is None: - subexpression_cache = {} - subexpression_order = [] - external_functions = {} - var_map = {} - used_named_expressions = set() - symbolic_solver_labels = False - # TODO: Explore potential performance benefit of exporting defined variables. - # This likely only shows up if we can preserve the subexpression cache across - # multiple constraint expressions. - export_defined_variables = False - sorter = FileDeterminism_to_SortComponents(FileDeterminism.ORDERED) - visitor = AMPLRepnVisitor( - text_nl_template, - subexpression_cache, - subexpression_order, - external_functions, - var_map, - used_named_expressions, - symbolic_solver_labels, - export_defined_variables, - sorter, - ) - AMPLRepn.ActiveVisitor = visitor - try: - repn = visitor.walk_expression((expr, None, 0, 1.0)) - finally: - AMPLRepn.ActiveVisitor = None - else: - var_map = visitor.var_map +def _get_incident_via_ampl_repn(expr, linear_only, visitor): + var_map = visitor.var_map + orig_activevisitor = AMPLRepn.ActiveVisitor + AMPLRepn.ActiveVisitor = visitor + try: repn = visitor.walk_expression((expr, None, 0, 1.0)) + finally: + AMPLRepn.ActiveVisitor = orig_activevisitor nonlinear_var_ids = [] if repn.nonlinear is None else repn.nonlinear[1] nonlinear_var_id_set = set() @@ -172,11 +148,11 @@ def get_incident_variables(expr, **kwds): ['x[1]', 'x[2]'] """ - visitor = kwds.pop("visitor", None) config = IncidenceConfig(kwds) method = config.method include_fixed = config.include_fixed linear_only = config.linear_only + amplrepnvisitor = config.ampl_repn_visitor if linear_only and method is IncidenceMethod.identify_variables: raise RuntimeError( "linear_only=True is not supported when using identify_variables" @@ -194,7 +170,7 @@ def get_incident_variables(expr, **kwds): expr, include_fixed, linear_only, compute_values=True ) elif method is IncidenceMethod.ampl_repn: - return _get_incident_via_ampl_repn(expr, linear_only, visitor=visitor) + return _get_incident_via_ampl_repn(expr, linear_only, amplrepnvisitor) else: raise ValueError( f"Unrecognized value {method} for the method used to identify incident" diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index ce5f4780210..b8a6c1275f9 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -93,6 +93,8 @@ def get_bipartite_incidence_graph(variables, constraints, **kwds): ``networkx.Graph`` """ + # Note that this ConfigDict contains the visitor that we will re-use + # when constructing constraints. config = IncidenceConfig(kwds) _check_unindexed(variables + constraints) N = len(variables) @@ -101,38 +103,10 @@ def get_bipartite_incidence_graph(variables, constraints, **kwds): graph.add_nodes_from(range(M), bipartite=0) graph.add_nodes_from(range(M, M + N), bipartite=1) var_node_map = ComponentMap((v, M + i) for i, v in enumerate(variables)) - - if config.method == IncidenceMethod.ampl_repn: - subexpression_cache = {} - subexpression_order = [] - external_functions = {} - var_map = {} - used_named_expressions = set() - symbolic_solver_labels = False - export_defined_variables = False - sorter = FileDeterminism_to_SortComponents(FileDeterminism.ORDERED) - visitor = AMPLRepnVisitor( - text_nl_template, - subexpression_cache, - subexpression_order, - external_functions, - var_map, - used_named_expressions, - symbolic_solver_labels, - export_defined_variables, - sorter, - ) - else: - visitor = None - - AMPLRepn.ActiveVisitor = visitor - try: - for i, con in enumerate(constraints): - for var in get_incident_variables(con.body, visitor=visitor, **config): - if var in var_node_map: - graph.add_edge(i, var_node_map[var]) - finally: - AMPLRepn.ActiveVisitor = None + for i, con in enumerate(constraints): + for var in get_incident_variables(con.body, **config): + if var in var_node_map: + graph.add_edge(i, var_node_map[var]) return graph @@ -193,46 +167,14 @@ def extract_bipartite_subgraph(graph, nodes0, nodes1): def _generate_variables_in_constraints(constraints, **kwds): + # Note: We construct a visitor here config = IncidenceConfig(kwds) - - if config.method == IncidenceMethod.ampl_repn: - subexpression_cache = {} - subexpression_order = [] - external_functions = {} - var_map = {} - used_named_expressions = set() - symbolic_solver_labels = False - export_defined_variables = False - sorter = FileDeterminism_to_SortComponents(FileDeterminism.ORDERED) - visitor = AMPLRepnVisitor( - text_nl_template, - subexpression_cache, - subexpression_order, - external_functions, - var_map, - used_named_expressions, - symbolic_solver_labels, - export_defined_variables, - sorter, - ) - else: - visitor = None - - AMPLRepn.ActiveVisitor = visitor - try: - known_vars = ComponentSet() - for con in constraints: - for var in get_incident_variables(con.body, visitor=visitor, **config): - if var not in known_vars: - known_vars.add(var) - yield var - finally: - # NOTE: I believe this is only guaranteed to be called when the - # generator is garbage collected. This could lead to some nasty - # bug where ActiveVisitor is set for longer than we intend. - # TODO: Convert this into a function. (or yield from variables - # after this try/finally. - AMPLRepn.ActiveVisitor = None + known_vars = ComponentSet() + for con in constraints: + for var in get_incident_variables(con.body, **config): + if var not in known_vars: + known_vars.add(var) + yield var def get_structural_incidence_matrix(variables, constraints, **kwds): @@ -329,7 +271,6 @@ class IncidenceGraphInterface(object): ``evaluate_jacobian_eq`` method instead of ``evaluate_jacobian`` rather than checking constraint expression types. - """ def __init__(self, model=None, active=True, include_inequality=True, **kwds): From e3ddb015059458899c4e1cc492dbe88d08e8a7ad Mon Sep 17 00:00:00 2001 From: robbybp Date: Sat, 6 Jan 2024 15:54:57 -0700 Subject: [PATCH 0676/1204] remove whitespace --- pyomo/contrib/incidence_analysis/config.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/config.py b/pyomo/contrib/incidence_analysis/config.py index 31b2bd3fc22..036c563ae75 100644 --- a/pyomo/contrib/incidence_analysis/config.py +++ b/pyomo/contrib/incidence_analysis/config.py @@ -114,7 +114,6 @@ def _amplrepnvisitor_validator(visitor=_ReconstructVisitor): class _IncidenceConfigDict(ConfigDict): - def __call__( self, value=NOTSET, @@ -149,7 +148,6 @@ def __call__( return new - IncidenceConfig = _IncidenceConfigDict() """Options for incidence graph generation From 175ea1e808d7ee116902684fe279d7cc461eb667 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 7 Jan 2024 20:27:51 -0700 Subject: [PATCH 0677/1204] Updating baseline to reflect anonymous set changes --- examples/pyomobook/performance-ch/wl.txt | 112 ++++++++++++----------- 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/examples/pyomobook/performance-ch/wl.txt b/examples/pyomobook/performance-ch/wl.txt index fbbd11fa32a..b4f16ac5294 100644 --- a/examples/pyomobook/performance-ch/wl.txt +++ b/examples/pyomobook/performance-ch/wl.txt @@ -3,96 +3,102 @@ Building model 0 seconds to construct Block ConcreteModel; 1 index total 0 seconds to construct Set Any; 1 index total 0 seconds to construct Param P; 1 index total + 0 seconds to construct SetOf OrderedSetOf 0 seconds to construct Set OrderedScalarSet; 1 index total + 0 seconds to construct SetOf OrderedSetOf 0 seconds to construct Set OrderedScalarSet; 1 index total 0 seconds to construct Set SetProduct_OrderedSet; 1 index total - 0 seconds to construct Set SetProduct_OrderedSet; 1 index total - 0.15 seconds to construct Var x; 40000 indices total + 0.10 seconds to construct Var x; 40000 indices total + 0 seconds to construct SetOf OrderedSetOf 0 seconds to construct Set OrderedScalarSet; 1 index total 0 seconds to construct Var y; 200 indices total - 0.26 seconds to construct Objective obj; 1 index total + 0.15 seconds to construct Objective obj; 1 index total + 0 seconds to construct SetOf OrderedSetOf 0 seconds to construct Set OrderedScalarSet; 1 index total - 0.13 seconds to construct Constraint demand; 200 indices total + 0.14 seconds to construct Constraint demand; 200 indices total + 0 seconds to construct SetOf OrderedSetOf 0 seconds to construct Set OrderedScalarSet; 1 index total + 0 seconds to construct SetOf OrderedSetOf 0 seconds to construct Set OrderedScalarSet; 1 index total 0 seconds to construct Set SetProduct_OrderedSet; 1 index total - 0 seconds to construct Set SetProduct_OrderedSet; 1 index total - 0.82 seconds to construct Constraint warehouse_active; 40000 indices total + 0.40 seconds to construct Constraint warehouse_active; 40000 indices total 0 seconds to construct Constraint num_warehouses; 1 index total Building model with LinearExpression ------------------------------------ 0 seconds to construct Block ConcreteModel; 1 index total 0 seconds to construct Set Any; 1 index total 0 seconds to construct Param P; 1 index total + 0 seconds to construct SetOf OrderedSetOf 0 seconds to construct Set OrderedScalarSet; 1 index total + 0 seconds to construct SetOf OrderedSetOf 0 seconds to construct Set OrderedScalarSet; 1 index total 0 seconds to construct Set SetProduct_OrderedSet; 1 index total - 0 seconds to construct Set SetProduct_OrderedSet; 1 index total - 0.08 seconds to construct Var x; 40000 indices total + 0.16 seconds to construct Var x; 40000 indices total + 0 seconds to construct SetOf OrderedSetOf 0 seconds to construct Set OrderedScalarSet; 1 index total 0 seconds to construct Var y; 200 indices total - 0.33 seconds to construct Objective obj; 1 index total + 0.06 seconds to construct Objective obj; 1 index total + 0 seconds to construct SetOf OrderedSetOf 0 seconds to construct Set OrderedScalarSet; 1 index total - 0.13 seconds to construct Constraint demand; 200 indices total + 0.05 seconds to construct Constraint demand; 200 indices total + 0 seconds to construct SetOf OrderedSetOf 0 seconds to construct Set OrderedScalarSet; 1 index total + 0 seconds to construct SetOf OrderedSetOf 0 seconds to construct Set OrderedScalarSet; 1 index total 0 seconds to construct Set SetProduct_OrderedSet; 1 index total - 0 seconds to construct Set SetProduct_OrderedSet; 1 index total - 0.59 seconds to construct Constraint warehouse_active; 40000 indices total + 0.52 seconds to construct Constraint warehouse_active; 40000 indices total 0 seconds to construct Constraint num_warehouses; 1 index total [ 0.00] start -[+ 1.74] Built model -[+ 7.39] Wrote LP file and solved -[+ 11.36] finished parameter sweep - 14919301 function calls (14916699 primitive calls) in 15.948 seconds +[+ 0.84] Built model +[+ 2.56] Wrote LP file and solved +[+ 14.55] Finished parameter sweep + 7371718 function calls (7368022 primitive calls) in 17.474 seconds Ordered by: cumulative time - List reduced from 590 to 15 due to restriction <15> + List reduced from 671 to 15 due to restriction <15> ncalls tottime percall cumtime percall filename:lineno(function) - 1 0.002 0.002 15.948 15.948 /export/home/dlwoodruff/Documents/BookIII/trunk/pyomo/examples/doc/pyomobook/performance-ch/wl.py:112(solve_parametric) - 30 0.007 0.000 15.721 0.524 /export/home/dlwoodruff/software/pyomo/pyomo/opt/base/solvers.py:511(solve) - 30 0.001 0.000 9.150 0.305 /export/home/dlwoodruff/software/pyomo/pyomo/solvers/plugins/solvers/GUROBI.py:191(_presolve) - 30 0.001 0.000 9.149 0.305 /export/home/dlwoodruff/software/pyomo/pyomo/opt/solver/shellcmd.py:188(_presolve) - 30 0.001 0.000 9.134 0.304 /export/home/dlwoodruff/software/pyomo/pyomo/opt/base/solvers.py:651(_presolve) - 30 0.000 0.000 9.133 0.304 /export/home/dlwoodruff/software/pyomo/pyomo/opt/base/solvers.py:719(_convert_problem) - 30 0.002 0.000 9.133 0.304 /export/home/dlwoodruff/software/pyomo/pyomo/opt/base/convert.py:31(convert_problem) - 30 0.001 0.000 9.093 0.303 /export/home/dlwoodruff/software/pyomo/pyomo/solvers/plugins/converter/model.py:43(apply) - 30 0.001 0.000 9.080 0.303 /export/home/dlwoodruff/software/pyomo/pyomo/core/base/block.py:1756(write) - 30 0.008 0.000 9.077 0.303 /export/home/dlwoodruff/software/pyomo/pyomo/repn/plugins/cpxlp.py:81(__call__) - 30 1.308 0.044 9.065 0.302 /export/home/dlwoodruff/software/pyomo/pyomo/repn/plugins/cpxlp.py:377(_print_model_LP) - 30 0.002 0.000 5.016 0.167 /export/home/dlwoodruff/software/pyomo/pyomo/opt/solver/shellcmd.py:223(_apply_solver) - 30 0.002 0.000 5.013 0.167 /export/home/dlwoodruff/software/pyomo/pyomo/opt/solver/shellcmd.py:289(_execute_command) - 30 0.006 0.000 5.011 0.167 /export/home/dlwoodruff/software/pyutilib/pyutilib/subprocess/processmngr.py:433(run_command) - 30 0.001 0.000 4.388 0.146 /export/home/dlwoodruff/software/pyutilib/pyutilib/subprocess/processmngr.py:829(wait) + 1 0.001 0.001 17.474 17.474 /home/jdsiiro/Research/pyomo/examples/pyomobook/performance-ch/wl.py:132(solve_parametric) + 30 0.002 0.000 17.397 0.580 /home/jdsiiro/Research/pyomo/pyomo/opt/base/solvers.py:530(solve) + 30 0.001 0.000 14.176 0.473 /home/jdsiiro/Research/pyomo/pyomo/opt/solver/shellcmd.py:247(_apply_solver) + 30 0.002 0.000 14.173 0.472 /home/jdsiiro/Research/pyomo/pyomo/opt/solver/shellcmd.py:310(_execute_command) + 30 0.001 0.000 14.152 0.472 /projects/sems/install/rhel7-x86_64/pyomo/compiler/python/3.11.6/lib/python3.11/subprocess.py:506(run) + 30 0.000 0.000 14.050 0.468 /projects/sems/install/rhel7-x86_64/pyomo/compiler/python/3.11.6/lib/python3.11/subprocess.py:1165(communicate) + 60 0.000 0.000 14.050 0.234 /projects/sems/install/rhel7-x86_64/pyomo/compiler/python/3.11.6/lib/python3.11/subprocess.py:1259(wait) + 60 0.001 0.000 14.049 0.234 /projects/sems/install/rhel7-x86_64/pyomo/compiler/python/3.11.6/lib/python3.11/subprocess.py:2014(_wait) + 30 0.000 0.000 14.049 0.468 /projects/sems/install/rhel7-x86_64/pyomo/compiler/python/3.11.6/lib/python3.11/subprocess.py:2001(_try_wait) + 30 14.048 0.468 14.048 0.468 {built-in method posix.waitpid} + 30 0.000 0.000 2.147 0.072 /home/jdsiiro/Research/pyomo/pyomo/solvers/plugins/solvers/GUROBI.py:214(_presolve) + 30 0.000 0.000 2.147 0.072 /home/jdsiiro/Research/pyomo/pyomo/opt/solver/shellcmd.py:215(_presolve) + 30 0.000 0.000 2.139 0.071 /home/jdsiiro/Research/pyomo/pyomo/opt/base/solvers.py:687(_presolve) + 30 0.000 0.000 2.138 0.071 /home/jdsiiro/Research/pyomo/pyomo/opt/base/solvers.py:756(_convert_problem) + 30 0.001 0.000 2.138 0.071 /home/jdsiiro/Research/pyomo/pyomo/opt/base/convert.py:27(convert_problem) - 14919301 function calls (14916699 primitive calls) in 15.948 seconds + 7371718 function calls (7368022 primitive calls) in 17.474 seconds Ordered by: internal time - List reduced from 590 to 15 due to restriction <15> + List reduced from 671 to 15 due to restriction <15> ncalls tottime percall cumtime percall filename:lineno(function) - 30 4.381 0.146 4.381 0.146 {built-in method posix.waitpid} - 30 1.308 0.044 9.065 0.302 /export/home/dlwoodruff/software/pyomo/pyomo/repn/plugins/cpxlp.py:377(_print_model_LP) - 76560 0.703 0.000 1.165 0.000 /export/home/dlwoodruff/software/pyomo/pyomo/repn/plugins/cpxlp.py:178(_print_expr_canonical) - 76560 0.682 0.000 0.858 0.000 /export/home/dlwoodruff/software/pyomo/pyomo/repn/standard_repn.py:424(_collect_sum) - 30 0.544 0.018 0.791 0.026 /export/home/dlwoodruff/software/pyomo/pyomo/solvers/plugins/solvers/GUROBI.py:365(process_soln_file) - 76560 0.539 0.000 1.691 0.000 /export/home/dlwoodruff/software/pyomo/pyomo/repn/standard_repn.py:973(_generate_standard_repn) - 306000 0.507 0.000 0.893 0.000 /export/home/dlwoodruff/software/pyomo/pyomo/core/base/set.py:581(bounds) - 30 0.367 0.012 0.367 0.012 {built-in method posix.read} - 76560 0.323 0.000 2.291 0.000 /export/home/dlwoodruff/software/pyomo/pyomo/repn/standard_repn.py:245(generate_standard_repn) - 76560 0.263 0.000 2.923 0.000 /export/home/dlwoodruff/software/pyomo/pyomo/repn/plugins/cpxlp.py:569(constraint_generator) - 225090 0.262 0.000 0.336 0.000 /export/home/dlwoodruff/software/pyomo/pyomo/core/base/constraint.py:228(has_ub) - 153060 0.249 0.000 0.422 0.000 /export/home/dlwoodruff/software/pyomo/pyomo/core/expr/symbol_map.py:82(createSymbol) - 77220 0.220 0.000 0.457 0.000 {built-in method builtins.sorted} - 30 0.201 0.007 0.202 0.007 {built-in method _posixsubprocess.fork_exec} - 153000 0.185 0.000 0.690 0.000 /export/home/dlwoodruff/software/pyomo/pyomo/core/base/var.py:407(ub) + 30 14.048 0.468 14.048 0.468 {built-in method posix.waitpid} + 30 0.324 0.011 2.101 0.070 /home/jdsiiro/Research/pyomo/pyomo/repn/plugins/lp_writer.py:250(write) + 76560 0.278 0.000 0.666 0.000 /home/jdsiiro/Research/pyomo/pyomo/repn/plugins/lp_writer.py:576(write_expression) + 30 0.258 0.009 0.524 0.017 /home/jdsiiro/Research/pyomo/pyomo/solvers/plugins/solvers/GUROBI.py:394(process_soln_file) + 76560 0.230 0.000 0.412 0.000 /home/jdsiiro/Research/pyomo/pyomo/repn/linear.py:664(_before_linear) + 301530 0.128 0.000 0.176 0.000 /home/jdsiiro/Research/pyomo/pyomo/core/expr/symbol_map.py:133(getSymbol) + 30 0.121 0.004 0.196 0.007 /home/jdsiiro/Research/pyomo/pyomo/core/base/PyomoModel.py:461(select) + 77190 0.119 0.000 0.165 0.000 /home/jdsiiro/Research/pyomo/pyomo/solvers/plugins/solvers/GUROBI.py:451() + 30 0.118 0.004 0.290 0.010 /home/jdsiiro/Research/pyomo/pyomo/core/base/PyomoModel.py:337(add_solution) + 30 0.094 0.003 0.094 0.003 {built-in method _posixsubprocess.fork_exec} + 239550 0.083 0.000 0.083 0.000 /home/jdsiiro/Research/pyomo/pyomo/core/base/indexed_component.py:612(__getitem__) + 76530 0.082 0.000 0.109 0.000 /home/jdsiiro/Research/pyomo/pyomo/core/expr/symbol_map.py:63(addSymbol) + 1062470 0.082 0.000 0.082 0.000 {built-in method builtins.id} + 76560 0.075 0.000 0.081 0.000 /home/jdsiiro/Research/pyomo/pyomo/repn/linear.py:834(finalizeResult) + 163050 0.074 0.000 0.131 0.000 /home/jdsiiro/Research/pyomo/pyomo/core/base/var.py:1050(__getitem__) -[ 36.46] Resetting the tic/toc delta timer -Using license file /export/home/dlwoodruff/software/gurobi900/linux64/../lic/gurobi.lic -Academic license - for non-commercial use only -[+ 1.21] finished parameter sweep with persistent interface +[ 0.00] Resetting the tic/toc delta timer +[+ 0.49] Finished parameter sweep with persistent interface From 193e80b70c4ae9cb3f2b5504a29d4341d9f0bcb5 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Mon, 8 Jan 2024 13:51:05 -0700 Subject: [PATCH 0678/1204] Update link to workshop slides --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bd399252efb..2f8a25403c2 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ version, we will remove testing for that Python version. ### Tutorials and Examples -* [Pyomo Workshop Slides](https://software.sandia.gov/downloads/pub/pyomo/Pyomo-Workshop-Summer-2018.pdf) +* [Pyomo Workshop Slides](https://github.com/Pyomo/pyomo-tutorials/blob/main/Pyomo-Workshop-December-2023.pdf) * [Prof. Jeffrey Kantor's Pyomo Cookbook](https://jckantor.github.io/ND-Pyomo-Cookbook/) * [Pyomo Gallery](https://github.com/Pyomo/PyomoGallery) From 125a656daaeeeb5360594e1f20a26dc2db2748c0 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Mon, 8 Jan 2024 14:10:09 -0700 Subject: [PATCH 0679/1204] Add link to workshop material on readthedocs --- doc/OnlineDocs/tutorial_examples.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/OnlineDocs/tutorial_examples.rst b/doc/OnlineDocs/tutorial_examples.rst index decd9827f6b..dc58b6a6f59 100644 --- a/doc/OnlineDocs/tutorial_examples.rst +++ b/doc/OnlineDocs/tutorial_examples.rst @@ -3,6 +3,9 @@ Pyomo Tutorial Examples Additional Pyomo tutorials and examples can be found at the following links: +`Pyomo Workshop Slides and Exercises +`_ + `Prof. Jeffrey Kantor's Pyomo Cookbook `_ From af976085cb977a86f2b06a6b38a1f1d19556018c Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 9 Jan 2024 09:07:16 -0700 Subject: [PATCH 0680/1204] See if QT tests still fail --- .github/workflows/test_branches.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index ff24c731d94..bdfa2c537ad 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -360,16 +360,16 @@ jobs: done # TODO: This is a hack to stop test_qt.py from running until we # can better troubleshoot why it fails on GHA - for QTPACKAGE in qt pyqt; do - # Because conda is insane, removing packages can cause - # unrelated packages to be updated (breaking version - # specifications specified previously, e.g., in - # setup.py). There doesn't appear to be a good - # workaround, so we will just force-remove (recognizing - # that it may break other conda cruft). - conda remove --force-remove $QTPACKAGE \ - || echo "$QTPACKAGE not in this environment" - done + # for QTPACKAGE in qt pyqt; do + # # Because conda is insane, removing packages can cause + # # unrelated packages to be updated (breaking version + # # specifications specified previously, e.g., in + # # setup.py). There doesn't appear to be a good + # # workaround, so we will just force-remove (recognizing + # # that it may break other conda cruft). + # conda remove --force-remove $QTPACKAGE \ + # || echo "$QTPACKAGE not in this environment" + # done fi # Re-try Pyomo (optional) dependencies with pip if test -n "$PYPI_DEPENDENCIES"; then From 655c61c0aa1a08c7673b71e75541aff9bad67a5d Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 9 Jan 2024 09:18:10 -0700 Subject: [PATCH 0681/1204] Abstract the Book baseline test driver so we can re-use in online docs --- examples/pyomobook/test_book_examples.py | 631 +++++------------------ pyomo/common/unittest.py | 409 +++++++++++++++ 2 files changed, 528 insertions(+), 512 deletions(-) diff --git a/examples/pyomobook/test_book_examples.py b/examples/pyomobook/test_book_examples.py index 1aa2a3ed6b3..af7e9e33d20 100644 --- a/examples/pyomobook/test_book_examples.py +++ b/examples/pyomobook/test_book_examples.py @@ -10,34 +10,13 @@ # ___________________________________________________________________________ import pyomo.common.unittest as unittest -import filecmp import glob import os -import os.path -import re -import subprocess -import sys -from itertools import zip_longest -from pyomo.opt import check_available_solvers -from pyomo.common.dependencies import attempt_import, check_min_version -from pyomo.common.fileutils import this_file_dir, import_file -from pyomo.common.log import LoggingIntercept, pyomo_formatter -from pyomo.common.tee import capture_output +from pyomo.common.dependencies import attempt_import +from pyomo.common.fileutils import this_file_dir import pyomo.environ as pyo -def gurobi_fully_licensed(): - m = pyo.ConcreteModel() - m.x = pyo.Var(list(range(2001)), within=pyo.NonNegativeReals) - m.o = pyo.Objective(expr=sum(m.x.values())) - try: - results = pyo.SolverFactory('gurobi').solve(m, tee=True) - pyo.assert_optimal_termination(results) - return True - except: - return False - - parameterized, param_available = attempt_import('parameterized') if not param_available: raise unittest.SkipTest('Parameterized is not available.') @@ -47,504 +26,132 @@ def gurobi_fully_licensed(): bool(matplotlib_available) -# Find all *.txt files, and use them to define baseline tests currdir = this_file_dir() -datadir = currdir - -solver_dependencies = { - # abstract_ch - 'test_abstract_ch_wl_abstract_script': ['glpk'], - 'test_abstract_ch_pyomo_wl_abstract': ['glpk'], - 'test_abstract_ch_pyomo_solve1': ['glpk'], - 'test_abstract_ch_pyomo_solve2': ['glpk'], - 'test_abstract_ch_pyomo_solve3': ['glpk'], - 'test_abstract_ch_pyomo_solve4': ['glpk'], - 'test_abstract_ch_pyomo_solve5': ['glpk'], - 'test_abstract_ch_pyomo_diet1': ['glpk'], - 'test_abstract_ch_pyomo_buildactions_works': ['glpk'], - 'test_abstract_ch_pyomo_abstract5_ns1': ['glpk'], - 'test_abstract_ch_pyomo_abstract5_ns2': ['glpk'], - 'test_abstract_ch_pyomo_abstract5_ns3': ['glpk'], - 'test_abstract_ch_pyomo_abstract6': ['glpk'], - 'test_abstract_ch_pyomo_abstract7': ['glpk'], - 'test_abstract_ch_pyomo_AbstractH': ['ipopt'], - 'test_abstract_ch_AbstHLinScript': ['glpk'], - 'test_abstract_ch_pyomo_AbstractHLinear': ['glpk'], - # blocks_ch - 'test_blocks_ch_lotsizing': ['glpk'], - 'test_blocks_ch_blocks_lotsizing': ['glpk'], - # dae_ch - 'test_dae_ch_run_path_constraint_tester': ['ipopt'], - # gdp_ch - 'test_gdp_ch_pyomo_gdp_uc': ['glpk'], - 'test_gdp_ch_pyomo_scont': ['glpk'], - 'test_gdp_ch_pyomo_scont2': ['glpk'], - 'test_gdp_ch_scont_script': ['glpk'], - # intro_ch' - 'test_intro_ch_pyomo_concrete1_generic': ['glpk'], - 'test_intro_ch_pyomo_concrete1': ['glpk'], - 'test_intro_ch_pyomo_coloring_concrete': ['glpk'], - 'test_intro_ch_pyomo_abstract5': ['glpk'], - # mpec_ch - 'test_mpec_ch_path1': ['path'], - 'test_mpec_ch_nlp_ex1b': ['ipopt'], - 'test_mpec_ch_nlp_ex1c': ['ipopt'], - 'test_mpec_ch_nlp_ex1d': ['ipopt'], - 'test_mpec_ch_nlp_ex1e': ['ipopt'], - 'test_mpec_ch_nlp_ex2': ['ipopt'], - 'test_mpec_ch_nlp1': ['ipopt'], - 'test_mpec_ch_nlp2': ['ipopt'], - 'test_mpec_ch_nlp3': ['ipopt'], - 'test_mpec_ch_mip1': ['glpk'], - # nonlinear_ch - 'test_rosen_rosenbrock': ['ipopt'], - 'test_react_design_ReactorDesign': ['ipopt'], - 'test_react_design_ReactorDesignTable': ['ipopt'], - 'test_multimodal_multimodal_init1': ['ipopt'], - 'test_multimodal_multimodal_init2': ['ipopt'], - 'test_disease_est_disease_estimation': ['ipopt'], - 'test_deer_DeerProblem': ['ipopt'], - # scripts_ch - 'test_sudoku_sudoku_run': ['glpk'], - 'test_scripts_ch_warehouse_script': ['glpk'], - 'test_scripts_ch_warehouse_print': ['glpk'], - 'test_scripts_ch_warehouse_cuts': ['glpk'], - 'test_scripts_ch_prob_mod_ex': ['glpk'], - 'test_scripts_ch_attributes': ['glpk'], - # optimization_ch - 'test_optimization_ch_ConcHLinScript': ['glpk'], - # overview_ch - 'test_overview_ch_wl_mutable_excel': ['glpk'], - 'test_overview_ch_wl_excel': ['glpk'], - 'test_overview_ch_wl_concrete_script': ['glpk'], - 'test_overview_ch_wl_abstract_script': ['glpk'], - 'test_overview_ch_pyomo_wl_abstract': ['glpk'], - # performance_ch - 'test_performance_ch_wl': ['gurobi', 'gurobi_persistent', 'gurobi_license'], - 'test_performance_ch_persistent': ['gurobi_persistent'], -} -package_dependencies = { - # abstract_ch' - 'test_abstract_ch_pyomo_solve4': ['yaml'], - 'test_abstract_ch_pyomo_solve5': ['yaml'], - # gdp_ch - 'test_gdp_ch_pyomo_scont': ['yaml'], - 'test_gdp_ch_pyomo_scont2': ['yaml'], - 'test_gdp_ch_pyomo_gdp_uc': ['sympy'], - # overview_ch' - 'test_overview_ch_wl_excel': ['pandas', 'xlrd'], - 'test_overview_ch_wl_mutable_excel': ['pandas', 'xlrd'], - # scripts_ch' - 'test_scripts_ch_warehouse_cuts': ['matplotlib'], - # performance_ch' - 'test_performance_ch_wl': ['numpy', 'matplotlib'], -} - -# -# Initialize the availability data -# -solvers_used = set(sum(list(solver_dependencies.values()), [])) -available_solvers = check_available_solvers(*solvers_used) -if gurobi_fully_licensed(): - available_solvers.append('gurobi_license') -solver_available = {solver_: (solver_ in available_solvers) for solver_ in solvers_used} - -package_available = {} -package_modules = {} -packages_used = set(sum(list(package_dependencies.values()), [])) -for package_ in packages_used: - pack, pack_avail = attempt_import(package_) - package_available[package_] = pack_avail - package_modules[package_] = pack - - -def check_skip(name): - """ - Return a boolean if the test should be skipped - """ - - if name in solver_dependencies: - solvers_ = solver_dependencies[name] - if not all([solver_available[i] for i in solvers_]): - # Skip the test because a solver is not available - _missing = [] - for i in solvers_: - if not solver_available[i]: - _missing.append(i) - return "Solver%s %s %s not available" % ( - 's' if len(_missing) > 1 else '', - ", ".join(_missing), - 'are' if len(_missing) > 1 else 'is', - ) - - if name in package_dependencies: - packages_ = package_dependencies[name] - if not all([package_available[i] for i in packages_]): - # Skip the test because a package is not available - _missing = [] - for i in packages_: - if not package_available[i]: - _missing.append(i) - return "Package%s %s %s not available" % ( - 's' if len(_missing) > 1 else '', - ", ".join(_missing), - 'are' if len(_missing) > 1 else 'is', - ) - - # This is a hack, xlrd dropped support for .xlsx files in 2.0.1 which - # causes problems with older versions of Pandas<=1.1.5 so skipping - # tests requiring both these packages when incompatible versions are found - if ( - 'pandas' in package_dependencies[name] - and 'xlrd' in package_dependencies[name] - ): - if check_min_version( - package_modules['xlrd'], '2.0.1' - ) and not check_min_version(package_modules['pandas'], '1.1.6'): - return "Incompatible versions of xlrd and pandas" - - return False - -def filter(line): - """ - Ignore certain text when comparing output with baseline - """ - for field in ( - '[', - 'password:', - 'http:', - 'Job ', - 'Importing module', - 'Function', - 'File', - 'Matplotlib', - '-------', - '=======', - ' ^', - ): - if line.startswith(field): - return True - for field in ( - 'Total CPU', - 'Ipopt', - 'license', - 'Status: optimal', - 'Status: feasible', - 'time:', - 'Time:', - 'with format cpxlp', - 'usermodel = 1 else '', + ", ".join(_missing), + 'are' if len(_missing) > 1 else 'is', + ) + + if name in self.package_dependencies: + packages_ = self.package_dependencies[name] + if not all([self.package_available[i] for i in packages_]): + # Skip the test because a package is not available + _missing = [] + for i in packages_: + if not self.package_available[i]: + _missing.append(i) + return "Package%s %s %s not available" % ( + 's' if len(_missing) > 1 else '', + ", ".join(_missing), + 'are' if len(_missing) > 1 else 'is', + ) + + # This is a hack, xlrd dropped support for .xlsx files in 2.0.1 which + # causes problems with older versions of Pandas<=1.1.5 so skipping + # tests requiring both these packages when incompatible versions are found + if ( + 'pandas' in self.package_dependencies[name] + and 'xlrd' in self.package_dependencies[name] + ): + if check_min_version( + self.package_modules['xlrd'], '2.0.1' + ) and not check_min_version(self.package_modules['pandas'], '1.1.6'): + return "Incompatible versions of xlrd and pandas" + + return False + + def filter_fcn(self, line): + """ + Ignore certain text when comparing output with baseline + """ + for field in ( + '[', + 'password:', + 'http:', + 'Job ', + 'Importing module', + 'Function', + 'File', + 'Matplotlib', + '-------', + '=======', + ' ^', + ): + if line.startswith(field): + return True + for field in ( + 'Total CPU', + 'Ipopt', + 'license', + 'Status: optimal', + 'Status: feasible', + 'time:', + 'Time:', + 'with format cpxlp', + 'usermodel = Date: Tue, 9 Jan 2024 09:22:18 -0700 Subject: [PATCH 0682/1204] Update OnlineDocs test driver to use the standard baseline driver --- doc/OnlineDocs/tests/test_examples.py | 299 +++++--------------------- 1 file changed, 48 insertions(+), 251 deletions(-) diff --git a/doc/OnlineDocs/tests/test_examples.py b/doc/OnlineDocs/tests/test_examples.py index 0ee6a249c38..89b95fff843 100644 --- a/doc/OnlineDocs/tests/test_examples.py +++ b/doc/OnlineDocs/tests/test_examples.py @@ -1,269 +1,66 @@ -# Imports +# ___________________________________________________________________________ +# +# 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.common.unittest as unittest import glob import os -import os.path -import sys -import pyomo.environ +from pyomo.common.dependencies import attempt_import +from pyomo.common.fileutils import this_file_dir +import pyomo.environ as pyo + + +parameterized, param_available = attempt_import('parameterized') +if not param_available: + raise unittest.SkipTest('Parameterized is not available.') + +# Needed for testing (switches the matplotlib backend): +from pyomo.common.dependencies import matplotlib_available + +bool(matplotlib_available) -try: - import yaml +currdir = this_file_dir() - yaml_available = True -except: - yaml_available = False -# Find all *.txt files, and use them to define baseline tests -currdir = os.path.dirname(os.path.abspath(__file__)) -datadir = currdir -testdirs = [currdir] +class TestOnlineDocExamples(unittest.BaseLineTestDriver, unittest.TestCase): + # Only test files in directories ending in -ch. These directories + # contain the updated python and scripting files corresponding to + # each chapter in the book. + py_tests, sh_tests = unittest.BaseLineTestDriver.gather_tests( + list(filter(os.path.isdir, glob.glob(os.path.join(currdir, '*')))) + ) -solver_dependencies = { - 'Test_nonlinear_ch': { - 'test_rosen_pyomo_rosen': 'ipopt', - 'test_react_design_run_pyomo_reactor_table': 'ipopt', - 'test_react_design_run_pyomo_reactor': 'ipopt', - 'test_multimodal_pyomo_multimodal_init1': 'ipopt', - 'test_multimodal_pyomo_multimodal_init2': 'ipopt', - 'test_disease_est_run_disease_summary': 'ipopt', - 'test_disease_est_run_disease_callback': 'ipopt', - 'test_deer_run_deer': 'ipopt', - }, - 'Test_mpec_ch': {'test_mpec_ch_path1': 'path'}, - 'Test_dae_ch': {'test_run_path_constraint_tester': 'ipopt'}, -} -package_dependencies = { - 'Test_data': { + solver_dependencies = {} + package_dependencies = { + # data 'test_data_ABCD9': ['pyodbc'], 'test_data_ABCD8': ['pyodbc'], 'test_data_ABCD7': ['win32com'], - }, - 'Test_dataportal': { - 'test_dataportal_dataportal_tab': ['xlrd'], + # dataportal + 'test_dataportal_dataportal_tab': ['xlrd', 'pyutilib'], 'test_dataportal_set_initialization': ['numpy'], 'test_dataportal_param_initialization': ['numpy'], - }, -} -solver_available = {} -package_available = {} - -only_book_tests = set(['Test_nonlinear_ch', 'Test_scripts_ch']) - - -def _check_available(name): - from pyomo.opt.base.solvers import check_available_solvers - - return bool(check_available_solvers(name)) - - -def check_skip(tfname_, name): - # - # Skip if YAML isn't installed - # - if not yaml_available: - return "YAML is not available" - # - # Initialize the availability data - # - if len(solver_available) == 0: - for tf_ in solver_dependencies: - for n_ in solver_dependencies[tf_]: - solver_ = solver_dependencies[tf_][n_] - if not solver_ in solver_available: - solver_available[solver_] = _check_available(solver_) - for tf_ in package_dependencies: - for n_ in package_dependencies[tf_]: - packages_ = package_dependencies[tf_][n_] - for package_ in packages_: - if not package_ in package_available: - try: - __import__(package_) - package_available[package_] = True - except: - package_available[package_] = False - # - # Return a boolean if the test should be skipped - # - if tfname_ in solver_dependencies: - if ( - name in solver_dependencies[tfname_] - and not solver_available[solver_dependencies[tfname_][name]] - ): - # Skip the test because a solver is not available - # print('Skipping %s because of missing solver' %(name)) - return 'Solver "%s" is not available' % ( - solver_dependencies[tfname_][name], - ) - if tfname_ in package_dependencies: - if name in package_dependencies[tfname_]: - packages_ = package_dependencies[tfname_][name] - if not all([package_available[i] for i in packages_]): - # Skip the test because a package is not available - # print('Skipping %s because of missing package' %(name)) - _missing = [] - for i in packages_: - if not package_available[i]: - _missing.append(i) - return "Package%s %s %s not available" % ( - 's' if len(_missing) > 1 else '', - ", ".join(_missing), - 'are' if len(_missing) > 1 else 'is', - ) - return False - - -def filter(line): - # Ignore certain text when comparing output with baseline - - # Ipopt 3.12.4 puts BACKSPACE (chr(8) / ^H) into the output. - line = line.strip(" \n\t" + chr(8)) - - if not line: - return True - for field in ( - '[', - 'password:', - 'http:', - 'Job ', - 'Importing module', - 'Function', - 'File', - ): - if line.startswith(field): - return True - for field in ( - 'Total CPU', - 'Ipopt', - 'Status: optimal', - 'Status: feasible', - 'time:', - 'Time:', - 'with format cpxlp', - 'usermodel = Date: Tue, 9 Jan 2024 09:28:24 -0700 Subject: [PATCH 0683/1204] Update baselines to track changes to LP writer --- doc/OnlineDocs/tests/data/pyomo.diet1.txt | 16 ++++++++-------- doc/OnlineDocs/tests/data/pyomo.diet2.txt | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/doc/OnlineDocs/tests/data/pyomo.diet1.txt b/doc/OnlineDocs/tests/data/pyomo.diet1.txt index 4b67e92c80c..fd8c87d51d9 100644 --- a/doc/OnlineDocs/tests/data/pyomo.diet1.txt +++ b/doc/OnlineDocs/tests/data/pyomo.diet1.txt @@ -1,16 +1,16 @@ [ 0.00] Setting up Pyomo environment [ 0.00] Applying Pyomo preprocessing actions [ 0.00] Creating model -[ 0.02] Applying solver -[ 0.03] Processing results +[ 0.01] Applying solver +[ 0.02] Processing results Number of solutions: 1 Solution Information Gap: 0.0 Status: optimal Function Value: 2.81 Solver results file: results.yml -[ 0.04] Applying Pyomo postprocessing actions -[ 0.04] Pyomo Finished +[ 0.02] Applying Pyomo postprocessing actions +[ 0.02] Pyomo Finished # ========================================================== # = Solver Results = # ========================================================== @@ -22,9 +22,9 @@ Problem: Lower bound: 2.81 Upper bound: 2.81 Number of objectives: 1 - Number of constraints: 4 - Number of variables: 10 - Number of nonzeros: 10 + Number of constraints: 3 + Number of variables: 9 + Number of nonzeros: 9 Sense: minimize # ---------------------------------------------------------- # Solver Information @@ -37,7 +37,7 @@ Solver: Number of bounded subproblems: 1 Number of created subproblems: 1 Error rc: 0 - Time: 0.00816035270690918 + Time: 0.002644062042236328 # ---------------------------------------------------------- # Solution Information # ---------------------------------------------------------- diff --git a/doc/OnlineDocs/tests/data/pyomo.diet2.txt b/doc/OnlineDocs/tests/data/pyomo.diet2.txt index 00405216fe1..7ed879d500f 100644 --- a/doc/OnlineDocs/tests/data/pyomo.diet2.txt +++ b/doc/OnlineDocs/tests/data/pyomo.diet2.txt @@ -1,16 +1,16 @@ [ 0.00] Setting up Pyomo environment [ 0.00] Applying Pyomo preprocessing actions [ 0.00] Creating model -[ 0.03] Applying solver -[ 0.05] Processing results +[ 0.01] Applying solver +[ 0.01] Processing results Number of solutions: 1 Solution Information Gap: 0.0 Status: optimal Function Value: 2.81 Solver results file: results.yml -[ 0.05] Applying Pyomo postprocessing actions -[ 0.05] Pyomo Finished +[ 0.01] Applying Pyomo postprocessing actions +[ 0.01] Pyomo Finished # ========================================================== # = Solver Results = # ========================================================== @@ -22,9 +22,9 @@ Problem: Lower bound: 2.81 Upper bound: 2.81 Number of objectives: 1 - Number of constraints: 4 - Number of variables: 10 - Number of nonzeros: 10 + Number of constraints: 3 + Number of variables: 9 + Number of nonzeros: 9 Sense: minimize # ---------------------------------------------------------- # Solver Information @@ -37,7 +37,7 @@ Solver: Number of bounded subproblems: 1 Number of created subproblems: 1 Error rc: 0 - Time: 0.006503582000732422 + Time: 0.0018515586853027344 # ---------------------------------------------------------- # Solution Information # ---------------------------------------------------------- From 92a5bb60f02c053c678b6c0d15b695a7e608f472 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 9 Jan 2024 09:29:22 -0700 Subject: [PATCH 0684/1204] Update baselines to track changes in Set pprint --- doc/OnlineDocs/tests/data/table0.txt | 5 +- doc/OnlineDocs/tests/data/table0.ul.txt | 5 +- doc/OnlineDocs/tests/data/table1.txt | 5 +- doc/OnlineDocs/tests/data/table2.txt | 15 +- doc/OnlineDocs/tests/data/table3.txt | 20 ++- doc/OnlineDocs/tests/data/table3.ul.txt | 20 ++- doc/OnlineDocs/tests/data/table4.txt | 10 +- doc/OnlineDocs/tests/data/table4.ul.txt | 10 +- doc/OnlineDocs/tests/data/table5.txt | 10 +- doc/OnlineDocs/tests/data/table7.txt | 10 +- .../tests/dataportal/dataportal_tab.txt | 161 ++++++++++------- .../tests/dataportal/param_initialization.txt | 10 +- .../tests/dataportal/set_initialization.txt | 52 +++--- doc/OnlineDocs/tests/kernel/examples.txt | 162 ++++++++++-------- 14 files changed, 284 insertions(+), 211 deletions(-) diff --git a/doc/OnlineDocs/tests/data/table0.txt b/doc/OnlineDocs/tests/data/table0.txt index ecd2417333d..c2e75dd97a6 100644 --- a/doc/OnlineDocs/tests/data/table0.txt +++ b/doc/OnlineDocs/tests/data/table0.txt @@ -1,6 +1,7 @@ 1 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} 1 Param Declarations M : Size=3, Index=A, Domain=Any, Default=None, Mutable=False diff --git a/doc/OnlineDocs/tests/data/table0.ul.txt b/doc/OnlineDocs/tests/data/table0.ul.txt index ecd2417333d..c2e75dd97a6 100644 --- a/doc/OnlineDocs/tests/data/table0.ul.txt +++ b/doc/OnlineDocs/tests/data/table0.ul.txt @@ -1,6 +1,7 @@ 1 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} 1 Param Declarations M : Size=3, Index=A, Domain=Any, Default=None, Mutable=False diff --git a/doc/OnlineDocs/tests/data/table1.txt b/doc/OnlineDocs/tests/data/table1.txt index ecd2417333d..c2e75dd97a6 100644 --- a/doc/OnlineDocs/tests/data/table1.txt +++ b/doc/OnlineDocs/tests/data/table1.txt @@ -1,6 +1,7 @@ 1 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} 1 Param Declarations M : Size=3, Index=A, Domain=Any, Default=None, Mutable=False diff --git a/doc/OnlineDocs/tests/data/table2.txt b/doc/OnlineDocs/tests/data/table2.txt index 6621e27f70c..60eb55aab4a 100644 --- a/doc/OnlineDocs/tests/data/table2.txt +++ b/doc/OnlineDocs/tests/data/table2.txt @@ -1,10 +1,13 @@ 3 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] - B : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['B1', 'B2', 'B3'] - N_index : Dim=0, Dimen=2, Size=9, Domain=None, Ordered=False, Bounds=None - Virtual + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} + B : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'B1', 'B2', 'B3'} + N_index : Size=1, Index=None, Ordered=True + Key : Dimen : Domain : Size : Members + None : 2 : A*B : 9 : {('A1', 'B1'), ('A1', 'B2'), ('A1', 'B3'), ('A2', 'B1'), ('A2', 'B2'), ('A2', 'B3'), ('A3', 'B1'), ('A3', 'B2'), ('A3', 'B3')} 2 Param Declarations M : Size=3, Index=A, Domain=Any, Default=None, Mutable=False diff --git a/doc/OnlineDocs/tests/data/table3.txt b/doc/OnlineDocs/tests/data/table3.txt index e4194f6790d..cb5e63b30d4 100644 --- a/doc/OnlineDocs/tests/data/table3.txt +++ b/doc/OnlineDocs/tests/data/table3.txt @@ -1,12 +1,16 @@ 4 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] - B : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['B1', 'B2', 'B3'] - N_index : Dim=0, Dimen=2, Size=9, Domain=None, Ordered=False, Bounds=None - Virtual - Z : Dim=0, Dimen=2, Size=3, Domain=None, Ordered=False, Bounds=None - [('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} + B : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'B1', 'B2', 'B3'} + N_index : Size=1, Index=None, Ordered=True + Key : Dimen : Domain : Size : Members + None : 2 : A*B : 9 : {('A1', 'B1'), ('A1', 'B2'), ('A1', 'B3'), ('A2', 'B1'), ('A2', 'B2'), ('A2', 'B3'), ('A3', 'B1'), ('A3', 'B2'), ('A3', 'B3')} + Z : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 2 : Any : 3 : {('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')} 2 Param Declarations M : Size=3, Index=A, Domain=Any, Default=None, Mutable=False diff --git a/doc/OnlineDocs/tests/data/table3.ul.txt b/doc/OnlineDocs/tests/data/table3.ul.txt index e4194f6790d..cb5e63b30d4 100644 --- a/doc/OnlineDocs/tests/data/table3.ul.txt +++ b/doc/OnlineDocs/tests/data/table3.ul.txt @@ -1,12 +1,16 @@ 4 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] - B : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['B1', 'B2', 'B3'] - N_index : Dim=0, Dimen=2, Size=9, Domain=None, Ordered=False, Bounds=None - Virtual - Z : Dim=0, Dimen=2, Size=3, Domain=None, Ordered=False, Bounds=None - [('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} + B : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'B1', 'B2', 'B3'} + N_index : Size=1, Index=None, Ordered=True + Key : Dimen : Domain : Size : Members + None : 2 : A*B : 9 : {('A1', 'B1'), ('A1', 'B2'), ('A1', 'B3'), ('A2', 'B1'), ('A2', 'B2'), ('A2', 'B3'), ('A3', 'B1'), ('A3', 'B2'), ('A3', 'B3')} + Z : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 2 : Any : 3 : {('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')} 2 Param Declarations M : Size=3, Index=A, Domain=Any, Default=None, Mutable=False diff --git a/doc/OnlineDocs/tests/data/table4.txt b/doc/OnlineDocs/tests/data/table4.txt index eb49be14de2..f86004c342a 100644 --- a/doc/OnlineDocs/tests/data/table4.txt +++ b/doc/OnlineDocs/tests/data/table4.txt @@ -1,8 +1,10 @@ 2 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] - Z : Dim=0, Dimen=2, Size=3, Domain=None, Ordered=False, Bounds=None - [('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} + Z : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 2 : Any : 3 : {('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')} 2 Param Declarations M : Size=3, Index=A, Domain=Any, Default=None, Mutable=False diff --git a/doc/OnlineDocs/tests/data/table4.ul.txt b/doc/OnlineDocs/tests/data/table4.ul.txt index eb49be14de2..f86004c342a 100644 --- a/doc/OnlineDocs/tests/data/table4.ul.txt +++ b/doc/OnlineDocs/tests/data/table4.ul.txt @@ -1,8 +1,10 @@ 2 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] - Z : Dim=0, Dimen=2, Size=3, Domain=None, Ordered=False, Bounds=None - [('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} + Z : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 2 : Any : 3 : {('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')} 2 Param Declarations M : Size=3, Index=A, Domain=Any, Default=None, Mutable=False diff --git a/doc/OnlineDocs/tests/data/table5.txt b/doc/OnlineDocs/tests/data/table5.txt index 76d8c59010f..084757b781b 100644 --- a/doc/OnlineDocs/tests/data/table5.txt +++ b/doc/OnlineDocs/tests/data/table5.txt @@ -1,7 +1,9 @@ 2 Set Declarations - Y : Dim=0, Dimen=2, Size=3, Domain=None, Ordered=False, Bounds=None - [(4.3, 5.3), (4.4, 5.4), (4.5, 5.5)] - Z : Dim=0, Dimen=2, Size=3, Domain=None, Ordered=False, Bounds=None - [('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')] + Y : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 2 : Any : 3 : {(4.3, 5.3), (4.4, 5.4), (4.5, 5.5)} + Z : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 2 : Any : 3 : {('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')} 2 Declarations: Z Y diff --git a/doc/OnlineDocs/tests/data/table7.txt b/doc/OnlineDocs/tests/data/table7.txt index 275e8543528..8ddbfde38be 100644 --- a/doc/OnlineDocs/tests/data/table7.txt +++ b/doc/OnlineDocs/tests/data/table7.txt @@ -1,8 +1,10 @@ 2 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] - Z : Dim=0, Dimen=2, Size=3, Domain=None, Ordered=False, Bounds=None - [('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} + Z : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 2 : Any : 3 : {('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')} 1 Param Declarations M : Size=3, Index=A, Domain=Any, Default=None, Mutable=False diff --git a/doc/OnlineDocs/tests/dataportal/dataportal_tab.txt b/doc/OnlineDocs/tests/dataportal/dataportal_tab.txt index a3fa2bd8067..2e507971157 100644 --- a/doc/OnlineDocs/tests/dataportal/dataportal_tab.txt +++ b/doc/OnlineDocs/tests/dataportal/dataportal_tab.txt @@ -1,21 +1,25 @@ 1 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} 1 Declarations: A 1 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} 1 Declarations: A 1 Set Declarations - C : Dim=0, Dimen=2, Size=9, Domain=None, Ordered=False, Bounds=None - [('A1', 1), ('A1', 2), ('A1', 3), ('A2', 1), ('A2', 2), ('A2', 3), ('A3', 1), ('A3', 2), ('A3', 3)] + C : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 2 : Any : 9 : {('A1', 1), ('A1', 2), ('A1', 3), ('A2', 1), ('A2', 2), ('A2', 3), ('A3', 1), ('A3', 2), ('A3', 3)} 1 Declarations: C 1 Set Declarations - D : Dim=0, Dimen=2, Size=3, Domain=None, Ordered=False, Bounds=None - [('A1', 1), ('A2', 2), ('A3', 3)] + D : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 2 : Any : 3 : {('A1', 1), ('A2', 2), ('A3', 3)} 1 Declarations: D 1 Param Declarations @@ -25,8 +29,9 @@ 1 Declarations: z 1 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} 1 Param Declarations y : Size=3, Index=A, Domain=Any, Default=None, Mutable=False @@ -37,8 +42,9 @@ 2 Declarations: A y 1 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} 2 Param Declarations w : Size=3, Index=A, Domain=Any, Default=None, Mutable=False @@ -54,8 +60,9 @@ 3 Declarations: A x w 1 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} 1 Param Declarations y : Size=3, Index=A, Domain=Any, Default=None, Mutable=False @@ -66,8 +73,9 @@ 2 Declarations: A y 1 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} 1 Param Declarations w : Size=3, Index=A, Domain=Any, Default=None, Mutable=False @@ -78,12 +86,15 @@ 2 Declarations: A w 3 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] - I : Dim=0, Dimen=1, Size=4, Domain=None, Ordered=False, Bounds=None - ['I1', 'I2', 'I3', 'I4'] - u_index : Dim=0, Dimen=2, Size=12, Domain=None, Ordered=False, Bounds=None - Virtual + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} + I : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 4 : {'I1', 'I2', 'I3', 'I4'} + u_index : Size=1, Index=None, Ordered=True + Key : Dimen : Domain : Size : Members + None : 2 : I*A : 12 : {('I1', 'A1'), ('I1', 'A2'), ('I1', 'A3'), ('I2', 'A1'), ('I2', 'A2'), ('I2', 'A3'), ('I3', 'A1'), ('I3', 'A2'), ('I3', 'A3'), ('I4', 'A1'), ('I4', 'A2'), ('I4', 'A3')} 1 Param Declarations u : Size=12, Index=u_index, Domain=Any, Default=None, Mutable=False @@ -103,12 +114,15 @@ 4 Declarations: A I u_index u 3 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] - I : Dim=0, Dimen=1, Size=4, Domain=None, Ordered=False, Bounds=None - ['I1', 'I2', 'I3', 'I4'] - t_index : Dim=0, Dimen=2, Size=12, Domain=None, Ordered=False, Bounds=None - Virtual + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} + I : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 4 : {'I1', 'I2', 'I3', 'I4'} + t_index : Size=1, Index=None, Ordered=True + Key : Dimen : Domain : Size : Members + None : 2 : A*I : 12 : {('A1', 'I1'), ('A1', 'I2'), ('A1', 'I3'), ('A1', 'I4'), ('A2', 'I1'), ('A2', 'I2'), ('A2', 'I3'), ('A2', 'I4'), ('A3', 'I1'), ('A3', 'I2'), ('A3', 'I3'), ('A3', 'I4')} 1 Param Declarations t : Size=12, Index=t_index, Domain=Any, Default=None, Mutable=False @@ -128,8 +142,9 @@ 4 Declarations: A I t_index t 1 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} 1 Param Declarations s : Size=2, Index=A, Domain=Any, Default=None, Mutable=False @@ -139,8 +154,9 @@ 2 Declarations: A s 1 Set Declarations - A : Dim=0, Dimen=1, Size=4, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3', 'A4'] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 4 : {'A1', 'A2', 'A3', 'A4'} 1 Param Declarations y : Size=3, Index=A, Domain=Any, Default=None, Mutable=False @@ -151,8 +167,9 @@ 2 Declarations: A y 1 Set Declarations - A : Dim=0, Dimen=2, Size=3, Domain=None, Ordered=False, Bounds=None - [('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 2 : Any : 3 : {('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')} 1 Param Declarations p : Size=3, Index=A, Domain=Any, Default=None, Mutable=False @@ -163,13 +180,15 @@ 2 Declarations: A p 1 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} 1 Declarations: A 1 Set Declarations - y_index : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] + y_index : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} 2 Param Declarations y : Size=3, Index=y_index, Domain=Any, Default=None, Mutable=False @@ -188,8 +207,9 @@ A1 3.3 A2 3.4 A3 3.5 1 Set Declarations - A : Dim=0, Dimen=2, Size=3, Domain=None, Ordered=False, Bounds=None - [('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 2 : Any : 3 : {('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')} 1 Param Declarations p : Size=3, Index=A, Domain=Any, Default=None, Mutable=False @@ -200,8 +220,9 @@ A3 3.5 2 Declarations: A p 1 Set Declarations - A : Dim=0, Dimen=2, Size=0, Domain=None, Ordered=False, Bounds=None - [] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 2 : Any : 0 : {} 1 Param Declarations p : Size=0, Index=A, Domain=Any, Default=None, Mutable=False @@ -209,8 +230,9 @@ A3 3.5 2 Declarations: A p 1 Set Declarations - A : Dim=0, Dimen=2, Size=3, Domain=None, Ordered=False, Bounds=None - [('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 2 : Any : 3 : {('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')} 1 Param Declarations p : Size=3, Index=A, Domain=Any, Default=None, Mutable=False @@ -221,8 +243,9 @@ A3 3.5 2 Declarations: A p 1 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} 1 Param Declarations p : Size=3, Index=A, Domain=Any, Default=None, Mutable=False @@ -233,14 +256,16 @@ A3 3.5 2 Declarations: A p 3 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] - B : Dim=0, Dimen=2, Size=3, Domain=None, Ordered=False, Bounds=None - [(1, 'B1'), (2, 'B2'), (3, 'B3')] - C : Dim=1, Dimen=1, Size=6, Domain=None, ArraySize=2, Ordered=False, Bounds=None - Key : Members - A1 : [1, 2, 3] - A3 : [10, 20, 30] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} + B : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 2 : Any : 3 : {(1, 'B1'), (2, 'B2'), (3, 'B3')} + C : Size=2, Index=A, Ordered=Insertion + Key : Dimen : Domain : Size : Members + A1 : 1 : Any : 3 : {1, 2, 3} + A3 : 1 : Any : 3 : {10, 20, 30} 3 Param Declarations p : Size=1, Index=None, Domain=Any, Default=None, Mutable=False @@ -259,14 +284,16 @@ A3 3.5 6 Declarations: A B C p q r 3 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - ['A1', 'A2', 'A3'] - B : Dim=0, Dimen=2, Size=3, Domain=None, Ordered=False, Bounds=None - [(1, 'B1'), (2, 'B2'), (3, 'B3')] - C : Dim=1, Dimen=1, Size=6, Domain=None, ArraySize=2, Ordered=False, Bounds=None - Key : Members - A1 : [1, 2, 3] - A3 : [10, 20, 30] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {'A1', 'A2', 'A3'} + B : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 2 : Any : 3 : {(1, 'B1'), (2, 'B2'), (3, 'B3')} + C : Size=2, Index=A, Ordered=Insertion + Key : Dimen : Domain : Size : Members + A1 : 1 : Any : 3 : {1, 2, 3} + A3 : 1 : Any : 3 : {10, 20, 30} 3 Param Declarations p : Size=1, Index=None, Domain=Any, Default=None, Mutable=False @@ -285,12 +312,14 @@ A3 3.5 6 Declarations: A B C p q r 1 Set Declarations - C : Dim=0, Dimen=2, Size=9, Domain=None, Ordered=False, Bounds=None - [('A1', 1), ('A1', 2), ('A1', 3), ('A2', 1), ('A2', 2), ('A2', 3), ('A3', 1), ('A3', 2), ('A3', 3)] + C : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 2 : Any : 9 : {('A1', 1), ('A1', 2), ('A1', 3), ('A2', 1), ('A2', 2), ('A2', 3), ('A3', 1), ('A3', 2), ('A3', 3)} 1 Declarations: C 1 Set Declarations - C : Dim=0, Dimen=2, Size=3, Domain=None, Ordered=False, Bounds=None - [('A1', 1), ('A2', 2), ('A3', 3)] + C : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 2 : Any : 3 : {('A1', 1), ('A2', 2), ('A3', 3)} 1 Declarations: C diff --git a/doc/OnlineDocs/tests/dataportal/param_initialization.txt b/doc/OnlineDocs/tests/dataportal/param_initialization.txt index baf24eac293..fec8a06a84a 100644 --- a/doc/OnlineDocs/tests/dataportal/param_initialization.txt +++ b/doc/OnlineDocs/tests/dataportal/param_initialization.txt @@ -1,8 +1,10 @@ 2 Set Declarations - b_index : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=(1, 3) - [1, 2, 3] - c_index : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=(1, 3) - [1, 2, 3] + b_index : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {1, 2, 3} + c_index : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {1, 2, 3} 3 Param Declarations a : Size=1, Index=None, Domain=Any, Default=None, Mutable=False diff --git a/doc/OnlineDocs/tests/dataportal/set_initialization.txt b/doc/OnlineDocs/tests/dataportal/set_initialization.txt index 3bfbdad1cdc..c6be448eba9 100644 --- a/doc/OnlineDocs/tests/dataportal/set_initialization.txt +++ b/doc/OnlineDocs/tests/dataportal/set_initialization.txt @@ -1,24 +1,34 @@ +WARNING: Initializing ordered Set B with a fundamentally unordered data source +(type: set). This WILL potentially lead to nondeterministic behavior in Pyomo 9 Set Declarations - A : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=(2, 5) - [2, 3, 5] - B : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=(2, 5) - [2, 3, 5] - C : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=(2, 5) - [2, 3, 5] - D : Dim=0, Dimen=1, Size=9, Domain=None, Ordered=False, Bounds=(0, 8) - [0, 1, 2, 3, 4, 5, 6, 7, 8] - E : Dim=0, Dimen=1, Size=1, Domain=None, Ordered=False, Bounds=(2, 2) - [2] - F : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=(2, 5) - [2, 3, 5] - G : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=(2, 5) - [2, 3, 5] - H : Dim=1, Dimen=1, Size=9, Domain=None, ArraySize=3, Ordered=False, Bounds=None - Key : Members - 2 : [1, 3, 5] - 3 : [2, 4, 6] - 4 : [3, 5, 7] - H_index : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=(2, 4) - [2, 3, 4] + A : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {2, 3, 5} + B : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {2, 3, 5} + C : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {2, 3, 5} + D : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 9 : {0, 1, 2, 3, 4, 5, 6, 7, 8} + E : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 1 : {2,} + F : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {2, 3, 5} + G : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {2, 3, 5} + H : Size=3, Index=H_index, Ordered=Insertion + Key : Dimen : Domain : Size : Members + 2 : 1 : Any : 3 : {1, 3, 5} + 3 : 1 : Any : 3 : {2, 4, 6} + 4 : 1 : Any : 3 : {3, 5, 7} + H_index : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {2, 3, 4} 9 Declarations: A B C D E F G H_index H diff --git a/doc/OnlineDocs/tests/kernel/examples.txt b/doc/OnlineDocs/tests/kernel/examples.txt index c8a0cde2e36..4b13e25c157 100644 --- a/doc/OnlineDocs/tests/kernel/examples.txt +++ b/doc/OnlineDocs/tests/kernel/examples.txt @@ -1,20 +1,27 @@ 6 Set Declarations - cd_index : Dim=0, Dimen=2, Size=6, Domain=None, Ordered=True, Bounds=None - Virtual - cl_index : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - [1, 2, 3] - ol_index : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - [1, 2, 3] - s : Dim=0, Dimen=1, Size=2, Domain=None, Ordered=Insertion, Bounds=(1, 2) - [1, 2] - sd_index : Dim=0, Dimen=1, Size=2, Domain=None, Ordered=False, Bounds=(1, 2) - [1, 2] - vl_index : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - [1, 2, 3] + cd_index : Size=1, Index=None, Ordered=True + Key : Dimen : Domain : Size : Members + None : 2 : s*q : 6 : {(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3)} + cl_index : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {1, 2, 3} + ol_index : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {1, 2, 3} + s : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 2 : {1, 2} + sd_index : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 2 : {1, 2} + vl_index : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {1, 2, 3} 1 RangeSet Declarations - q : Dim=0, Dimen=1, Size=3, Domain=Integers, Ordered=True, Bounds=(1, 3) - Virtual + q : Dimen=1, Size=3, Bounds=(1, 3) + Key : Finite : Members + None : True : [1:3] 2 Param Declarations p : Size=1, Index=None, Domain=Any, Default=None, Mutable=True @@ -84,64 +91,67 @@ 3 : -5.0 : vl[3] - v : 5.0 : True 3 SOSConstraint Declarations - sd : Size=2 Index= sd_index - 1 - Type=1 - Weight : Variable - 1 : vd[1] - 2 : vd[2] - 2 - Type=1 - Weight : Variable - 1 : vl[1] - 2 : vl[2] - 3 : vl[3] - sos1 : Size=1 - Type=1 - Weight : Variable - 1 : vl[1] - 2 : vl[2] - 3 : vl[3] - sos2 : Size=1 - Type=2 - Weight : Variable - 1 : vd[1] - 2 : vd[2] + sd : Size=2 Index= sd_index + 1 + Type=1 + Weight : Variable + 1 : vd[1] + 2 : vd[2] + 2 + Type=1 + Weight : Variable + 1 : vl[1] + 2 : vl[2] + 3 : vl[3] + sos1 : Size=1 + Type=1 + Weight : Variable + 1 : vl[1] + 2 : vl[2] + 3 : vl[3] + sos2 : Size=1 + Type=2 + Weight : Variable + 1 : vd[1] + 2 : vd[2] 2 Block Declarations b : Size=1, Index=None, Active=True 0 Declarations: - 2 Set Declarations - SOS2_constraint_index : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=None - [1, 2, 3] - SOS2_y_index : Dim=0, Dimen=1, Size=4, Domain=None, Ordered=False, Bounds=(0, 3) - [0, 1, 2, 3] + pw : Size=1, Index=None, Active=True + 2 Set Declarations + SOS2_constraint_index : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 3 : {1, 2, 3} + SOS2_y_index : Size=1, Index=None, Ordered=Insertion + Key : Dimen : Domain : Size : Members + None : 1 : Any : 4 : {0, 1, 2, 3} - 1 Var Declarations - SOS2_y : Size=4, Index=pw.SOS2_y_index - Key : Lower : Value : Upper : Fixed : Stale : Domain - 0 : 0 : None : None : False : True : NonNegativeReals - 1 : 0 : None : None : False : True : NonNegativeReals - 2 : 0 : None : None : False : True : NonNegativeReals - 3 : 0 : None : None : False : True : NonNegativeReals + 1 Var Declarations + SOS2_y : Size=4, Index=pw.SOS2_y_index + Key : Lower : Value : Upper : Fixed : Stale : Domain + 0 : 0 : None : None : False : True : NonNegativeReals + 1 : 0 : None : None : False : True : NonNegativeReals + 2 : 0 : None : None : False : True : NonNegativeReals + 3 : 0 : None : None : False : True : NonNegativeReals - 1 Constraint Declarations - SOS2_constraint : Size=3, Index=pw.SOS2_constraint_index, Active=True - Key : Lower : Body : Upper : Active - 1 : 0.0 : v - (pw.SOS2_y[0] + 2*pw.SOS2_y[1] + 3*pw.SOS2_y[2] + 4*pw.SOS2_y[3]) : 0.0 : True - 2 : 0.0 : f - (pw.SOS2_y[0] + 2*pw.SOS2_y[1] + pw.SOS2_y[2] + 2*pw.SOS2_y[3]) : 0.0 : True - 3 : 1.0 : pw.SOS2_y[0] + pw.SOS2_y[1] + pw.SOS2_y[2] + pw.SOS2_y[3] : 1.0 : True + 1 Constraint Declarations + SOS2_constraint : Size=3, Index=pw.SOS2_constraint_index, Active=True + Key : Lower : Body : Upper : Active + 1 : 0.0 : v - (pw.SOS2_y[0] + 2*pw.SOS2_y[1] + 3*pw.SOS2_y[2] + 4*pw.SOS2_y[3]) : 0.0 : True + 2 : 0.0 : f - (pw.SOS2_y[0] + 2*pw.SOS2_y[1] + pw.SOS2_y[2] + 2*pw.SOS2_y[3]) : 0.0 : True + 3 : 1.0 : pw.SOS2_y[0] + pw.SOS2_y[1] + pw.SOS2_y[2] + pw.SOS2_y[3] : 1.0 : True - 1 SOSConstraint Declarations - SOS2_sosconstraint : Size=1 - Type=2 - Weight : Variable - 1 : pw.SOS2_y[0] - 2 : pw.SOS2_y[1] - 3 : pw.SOS2_y[2] - 4 : pw.SOS2_y[3] + 1 SOSConstraint Declarations + SOS2_sosconstraint : Size=1 + Type=2 + Weight : Variable + 1 : pw.SOS2_y[0] + 2 : pw.SOS2_y[1] + 3 : pw.SOS2_y[2] + 4 : pw.SOS2_y[3] - 5 Declarations: SOS2_y_index SOS2_y SOS2_constraint_index SOS2_constraint SOS2_sosconstraint + 5 Declarations: SOS2_y_index SOS2_y SOS2_constraint_index SOS2_constraint SOS2_sosconstraint 1 Suffix Declarations dual : Direction=Suffix.IMPORT, Datatype=Suffix.FLOAT @@ -166,14 +176,14 @@ - vl[0]: variable(active=True, value=None, bounds=(2,None), domain_type=RealSet, fixed=False, stale=True) - vl[1]: variable(active=True, value=None, bounds=(2,None), domain_type=RealSet, fixed=False, stale=True) - vl[2]: variable(active=True, value=None, bounds=(2,None), domain_type=RealSet, fixed=False, stale=True) - - c: constraint(active=True, expr=vd[1] + vd[2] <= 9.0) + - c: constraint(active=True, expr=vd[1] + vd[2] <= 9) - cd: constraint_dict(active=True, ctype=IConstraint) - - cd[(1, 0)]: constraint(active=True, expr=vd[1] == 0.0) - - cd[(1, 1)]: constraint(active=True, expr=vd[1] == 1.0) - - cd[(1, 2)]: constraint(active=True, expr=vd[1] == 2.0) - - cd[(2, 0)]: constraint(active=True, expr=vd[2] == 0.0) - - cd[(2, 1)]: constraint(active=True, expr=vd[2] == 1.0) - - cd[(2, 2)]: constraint(active=True, expr=vd[2] == 2.0) + - cd[(1, 0)]: constraint(active=True, expr=vd[1] == 0) + - cd[(1, 1)]: constraint(active=True, expr=vd[1] == 1) + - cd[(1, 2)]: constraint(active=True, expr=vd[1] == 2) + - cd[(2, 0)]: constraint(active=True, expr=vd[2] == 0) + - cd[(2, 1)]: constraint(active=True, expr=vd[2] == 1) + - cd[(2, 2)]: constraint(active=True, expr=vd[2] == 2) - cl: constraint_list(active=True, ctype=IConstraint) - cl[0]: constraint(active=True, expr=-5 <= vl[0] - v <= 5) - cl[1]: constraint(active=True, expr=-5 <= vl[1] - v <= 5) @@ -216,9 +226,9 @@ - pw.v[2]: variable(active=True, value=None, bounds=(0,None), domain_type=RealSet, fixed=False, stale=True) - pw.v[3]: variable(active=True, value=None, bounds=(0,None), domain_type=RealSet, fixed=False, stale=True) - pw.c: constraint_list(active=True, ctype=IConstraint) - - pw.c[0]: linear_constraint(active=True, expr=pw.v[0] + 2*pw.v[1] + 3*pw.v[2] + 4*pw.v[3] - v == 0.0) - - pw.c[1]: linear_constraint(active=True, expr=pw.v[0] + 2*pw.v[1] + pw.v[2] + 2*pw.v[3] - f == 0.0) - - pw.c[2]: linear_constraint(active=True, expr=pw.v[0] + pw.v[1] + pw.v[2] + pw.v[3] == 1.0) + - pw.c[0]: linear_constraint(active=True, expr=pw.v[0] + 2*pw.v[1] + 3*pw.v[2] + 4*pw.v[3] - v == 0) + - pw.c[1]: linear_constraint(active=True, expr=pw.v[0] + 2*pw.v[1] + pw.v[2] + 2*pw.v[3] - f == 0) + - pw.c[2]: linear_constraint(active=True, expr=pw.v[0] + pw.v[1] + pw.v[2] + pw.v[3] == 1) - pw.s: sos(active=True, level=2, entries=['(pw.v[0],1)', '(pw.v[1],2)', '(pw.v[2],3)', '(pw.v[3],4)']) -2.0 KB -8.4 KB +1.9 KB +9.5 KB From 235c46608d826ab36ae76cb9920d44b4d2b27670 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 9 Jan 2024 09:32:53 -0700 Subject: [PATCH 0685/1204] Update baseline to track changes in pyomo 6 expression system --- doc/OnlineDocs/tests/expr/performance.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/OnlineDocs/tests/expr/performance.txt b/doc/OnlineDocs/tests/expr/performance.txt index c1387a51ce4..6bfd0bd1d5a 100644 --- a/doc/OnlineDocs/tests/expr/performance.txt +++ b/doc/OnlineDocs/tests/expr/performance.txt @@ -10,5 +10,5 @@ x[0] + x[1]**2 + x[2]**2 + x[3]**2 + x[4]**2 x[0] + x[1] + x[2] + x[3] + x[4] + x[5] + x[6] + x[7] + x[8] + x[9] x[0]*y[0] + x[1]*y[1] + x[2]*y[2] + x[3]*y[3] + x[4]*y[4] + x[5]*y[5] + x[6]*y[6] + x[7]*y[7] + x[8]*y[8] + x[9]*y[9] x[1]*y[1] + x[2]*y[2] + x[3]*y[3] + x[4]*y[4] + x[5]*y[5] -x[0]*(1/y[0]) + x[1]*(1/y[1]) + x[2]*(1/y[2]) + x[3]*(1/y[3]) + x[4]*(1/y[4]) + x[5]*(1/y[5]) + x[6]*(1/y[6]) + x[7]*(1/y[7]) + x[8]*(1/y[8]) + x[9]*(1/y[9]) -(1/(x[0]*y[0])) + (1/(x[1]*y[1])) + (1/(x[2]*y[2])) + (1/(x[3]*y[3])) + (1/(x[4]*y[4])) + (1/(x[5]*y[5])) + (1/(x[6]*y[6])) + (1/(x[7]*y[7])) + (1/(x[8]*y[8])) + (1/(x[9]*y[9])) +x[0]/y[0] + x[1]/y[1] + x[2]/y[2] + x[3]/y[3] + x[4]/y[4] + x[5]/y[5] + x[6]/y[6] + x[7]/y[7] + x[8]/y[8] + x[9]/y[9] +1/(x[0]*y[0]) + 1/(x[1]*y[1]) + 1/(x[2]*y[2]) + 1/(x[3]*y[3]) + 1/(x[4]*y[4]) + 1/(x[5]*y[5]) + 1/(x[6]*y[6]) + 1/(x[7]*y[7]) + 1/(x[8]*y[8]) + 1/(x[9]*y[9]) From efb836e345a9310eeb64d275174195d59b96fc8d Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 9 Jan 2024 09:34:38 -0700 Subject: [PATCH 0686/1204] Update visitor documentation; remove errors and bugs --- .../expressions/managing.rst | 128 +++++++++--------- .../expressions/visitors.rst | 9 ++ doc/OnlineDocs/tests/expr/managing.py | 124 +++++++---------- doc/OnlineDocs/tests/expr/managing.txt | 8 +- pyomo/core/expr/visitor.py | 49 ++++--- 5 files changed, 158 insertions(+), 160 deletions(-) diff --git a/doc/OnlineDocs/developer_reference/expressions/managing.rst b/doc/OnlineDocs/developer_reference/expressions/managing.rst index 344d101074c..43c5ec34816 100644 --- a/doc/OnlineDocs/developer_reference/expressions/managing.rst +++ b/doc/OnlineDocs/developer_reference/expressions/managing.rst @@ -28,12 +28,14 @@ string representation that is a nested functional form. For example: Labeler and Symbol Map ~~~~~~~~~~~~~~~~~~~~~~ -The string representation used for variables in expression can be customized to -define different label formats. If the :data:`labeler` option is specified, then this -function (or class functor) is used to generate a string label used to represent the variable. Pyomo -defines a variety of labelers in the `pyomo.core.base.label` module. For example, the -:class:`NumericLabeler` defines a functor that can be used to sequentially generate -simple labels with a prefix followed by the variable count: +The string representation used for variables in expression can be +customized to define different label formats. If the :data:`labeler` +option is specified, then this function (or class functor) is used to +generate a string label used to represent the variable. Pyomo defines a +variety of labelers in the `pyomo.core.base.label` module. For example, +the :class:`NumericLabeler` defines a functor that can be used to +sequentially generate simple labels with a prefix followed by the +variable count: .. literalinclude:: ../../tests/expr/managing_ex2.spy @@ -46,44 +48,20 @@ variables in different expressions have a consistent label in their associated string representations. -Standardized String Representations -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The :data:`standardize` option can be used to re-order the string -representation to print polynomial terms before nonlinear terms. By -default, :data:`standardize` is :const:`False`, and the string -representation reflects the order in which terms were combined to -form the expression. Pyomo does not guarantee that the string -representation exactly matches the Python expression order, since -some simplification and re-ordering of terms is done automatically to -improve the efficiency of expression generation. But in most cases -the string representation will closely correspond to the -Python expression order. - -If :data:`standardize` is :const:`True`, then the pyomo expression -is processed to identify polynomial terms, and the string representation -consists of the constant and linear terms followed by -an expression that contains other nonlinear terms. For example: - -.. literalinclude:: ../../tests/expr/managing_ex3.spy - Other Ways to Generate String Representations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There are two other standard ways to generate string representations: -* Call the :func:`__str__` magic method (e.g. using the Python :func:`str()` function. This - calls :func:`expression_to_string ` with - the option :data:`standardize` equal to :const:`True` (see below). +* Call the :func:`__str__` magic method (e.g. using the Python + :func:`str()` function. This calls :func:`expression_to_string + `, using the default values for + all arguments. -* Call the :func:`to_string` method on the :class:`ExpressionBase ` class. - This defaults to calling :func:`expression_to_string ` with - the option :data:`standardize` equal to :const:`False` (see below). - -In practice, we expect at the :func:`__str__` magic method will be -used by most users, and the standardization of the output provides -a consistent ordering of terms that should make it easier to interpret -expressions. +* Call the :func:`to_string` method on the + :class:`ExpressionBase` class. This + calls :func:`expression_to_string + ` and accepts the same arguments. Evaluating Expressions @@ -108,7 +86,7 @@ raise an exception. This exception can be suppressed using the .. literalinclude:: ../../tests/expr/managing_ex7.spy -This option is useful in contexts where adding a try block is inconvenient +This option is useful in contexts where adding a try block is inconvenient in your modeling script. .. note:: @@ -127,7 +105,7 @@ Expression transformations sometimes need to find all nodes in an expression tree that are of a given type. Pyomo contains two utility functions that support this functionality. First, the :func:`identify_components ` -function is a generator function that walks the expression tree and yields all +function is a generator function that walks the expression tree and yields all nodes whose type is in a specified set of node types. For example: .. literalinclude:: ../../tests/expr/managing_ex8.spy @@ -152,8 +130,15 @@ is computed using the values of its children. Walking an expression tree can be tricky, and the code requires intimate knowledge of the design of the expression system. Pyomo includes -several classes that define so-called visitor patterns for walking -expression tree: +several classes that define visitor patterns for walking expression +tree: + +:class:`StreamBasedExpressionVisitor ` + The most general and extensible visitor class. This visitor + implements an event-based approach for walking the tree inspired by + the ``expat`` library for processing XML files. The visitor has + seven event callbacks that users can hook into, providing very + fine-grained control over the expression walker. :class:`SimpleExpressionVisitor ` A :func:`visitor` method is called for each node in the tree, @@ -173,33 +158,39 @@ expression tree: These classes define a variety of suitable tree search methods: -* :class:`SimpleExpressionVisitor ` +* :class:`StreamBasedExpressionVisitor ` - * **xbfs**: breadth-first search where leaf nodes are immediately visited - * **xbfs_yield_leaves**: breadth-first search where leaf nodes are immediately visited, and the visit method yields a value + * ``walk_expression``: depth-first traversal of the expression tree. -* :class:`ExpressionValueVisitor ` +* :class:`ExpressionReplacementVisitor ` - * **dfs_postorder_stack**: postorder depth-first search using a stack + * ``walk_expression``: depth-first traversal of the expression tree. -* :class:`ExpressionReplacementVisitor ` +* :class:`SimpleExpressionVisitor ` - * **dfs_postorder_stack**: postorder depth-first search using a stack + * ``xbfs``: breadth-first search where leaf nodes are immediately visited + * ``xbfs_yield_leaves``: breadth-first search where leaf nodes are + immediately visited, and the visit method yields a value -.. note:: +* :class:`ExpressionValueVisitor ` + + * ``dfs_postorder_stack``: postorder depth-first search using a + nonrecursive stack - The PyUtilib visitor classes define several other search methods - that could be used with Pyomo expressions. But these are the - only search methods currently used within Pyomo. -To implement a visitor object, a user creates a subclass of one of these -classes. Only one of a few methods will need to be defined to -implement the visitor: +To implement a visitor object, a user needs to provide specializations +for specific events. For legacy visitors based on the PyUtilib +visitor pattern (e.g., :class:`SimpleExpressionVisitor` and +:class:`ExpressionValueVisitor`), one must create a subclass of one of these +classes and override at least one of the following: :func:`visitor` Defines the operation that is performed when a node is visited. In - the :class:`ExpressionValueVisitor ` and :class:`ExpressionReplacementVisitor ` visitor classes, this - method returns a value that is used by its parent node. + the :class:`ExpressionValueVisitor + ` and + :class:`ExpressionReplacementVisitor + ` visitor classes, + this method returns a value that is used by its parent node. :func:`visiting_potential_leaf` Checks if the search should terminate with this node. If no, @@ -211,9 +202,17 @@ implement the visitor: class. :func:`finalize` - This method defines the final value that is returned from the + This method defines the final value that is returned from the visitor. This is not normally redefined. +For modern visitors based on the :class:`StreamBasedExpressionVisitor +`, one can either define a +subclass, pass the callbacks to an instance of the base class, or assign +the callbacks as attributes on an instance of the base class. The +:class:`StreamBasedExpressionVisitor +` provides seven +callbacks, which are documented in the class documentation. + Detailed documentation of the APIs for these methods is provided with the class documentation for these visitors. @@ -226,7 +225,7 @@ class: .. literalinclude:: ../../tests/expr/managing_visitor1.spy -The class constructor creates a counter, and the :func:`visit` method +The class constructor creates a counter, and the :func:`visit` method increments this counter for every node that is visited. The :func:`finalize` method returns the value of this counter after the tree has been walked. The following function illustrates this use of this visitor class: @@ -261,16 +260,13 @@ class: .. literalinclude:: ../../tests/expr/managing_visitor5.spy -No :func:`visit` method needs to be defined. The -:func:`visiting_potential_leaf` function identifies variable nodes +No other method need to be defined. The +:func:`beforeChild` method identifies variable nodes and returns a product expression that contains a mutable parameter. -The :class:`_LinearExpression` class has a different representation -that embeds variables. Hence, this class must be handled -in a separate condition that explicitly transforms this sub-expression. .. literalinclude:: ../../tests/expr/managing_visitor6.spy -The :func:`scale_expression` function is called with an expression and +The :func:`scale_expression` function is called with an expression and a dictionary, :attr:`scale`, that maps variable ID to model parameter. For example: .. literalinclude:: ../../tests/expr/managing_visitor7.spy diff --git a/doc/OnlineDocs/library_reference/expressions/visitors.rst b/doc/OnlineDocs/library_reference/expressions/visitors.rst index f91107a6e8d..77cffe7905f 100644 --- a/doc/OnlineDocs/library_reference/expressions/visitors.rst +++ b/doc/OnlineDocs/library_reference/expressions/visitors.rst @@ -2,10 +2,19 @@ Visitor Classes =============== +.. autoclass:: pyomo.core.expr.StreamBasedExpressionVisitor + :members: + :inherited-members: + .. autoclass:: pyomo.core.expr.SimpleExpressionVisitor :members: + :inherited-members: + .. autoclass:: pyomo.core.expr.ExpressionValueVisitor :members: + :inherited-members: + .. autoclass:: pyomo.core.expr.ExpressionReplacementVisitor :members: + :inherited-members: diff --git a/doc/OnlineDocs/tests/expr/managing.py b/doc/OnlineDocs/tests/expr/managing.py index 0a2709fe96f..0a59c13bc1b 100644 --- a/doc/OnlineDocs/tests/expr/managing.py +++ b/doc/OnlineDocs/tests/expr/managing.py @@ -5,7 +5,7 @@ # --------------------------------------------- # @ex1 -from pyomo.core.expr import current as EXPR +import pyomo.core.expr as EXPR M = ConcreteModel() M.x = Var() @@ -21,7 +21,7 @@ # --------------------------------------------- # @ex2 -from pyomo.core.expr import current as EXPR +import pyomo.core.expr as EXPR M = ConcreteModel() M.x = Var() @@ -33,35 +33,6 @@ print(EXPR.expression_to_string(e, labeler=NumericLabeler('x'))) # @ex2 -# --------------------------------------------- -# @ex3 -from pyomo.core.expr import current as EXPR - -M = ConcreteModel() -M.x = Var() -M.y = Var() - -e = sin(M.x) + 2 * M.y + M.x * M.y - 3 - -# -3 + 2*y + sin(x) + x*y -print(EXPR.expression_to_string(e, standardize=True)) -# @ex3 - -# --------------------------------------------- -# @ex4 -from pyomo.core.expr import current as EXPR - -M = ConcreteModel() -M.x = Var() - -with EXPR.clone_counter() as counter: - start = counter.count - e1 = sin(M.x) - e2 = e1.clone() - total = counter.count - start - assert total == 1 -# @ex4 - # --------------------------------------------- # @ex5 M = ConcreteModel() @@ -85,7 +56,7 @@ # --------------------------------------------- # @ex8 -from pyomo.core.expr import current as EXPR +import pyomo.core.expr as EXPR M = ConcreteModel() M.x = Var() @@ -98,7 +69,7 @@ # --------------------------------------------- # @ex9 -from pyomo.core.expr import current as EXPR +import pyomo.core.expr as EXPR M = ConcreteModel() M.x = Var() @@ -116,7 +87,7 @@ # --------------------------------------------- # @visitor1 -from pyomo.core.expr import current as EXPR +import pyomo.core.expr as EXPR class SizeofVisitor(EXPR.SimpleExpressionVisitor): @@ -129,8 +100,7 @@ def visit(self, node): def finalize(self): return self.counter - -# @visitor1 + # @visitor1 # --------------------------------------------- @@ -144,13 +114,12 @@ def sizeof_expression(expr): # Compute the value using the :func:`xbfs` search method. # return visitor.xbfs(expr) + # @visitor2 -# @visitor2 - # --------------------------------------------- # @visitor3 -from pyomo.core.expr import current as EXPR +import pyomo.core.expr as EXPR class CloneVisitor(EXPR.ExpressionValueVisitor): @@ -161,22 +130,17 @@ def visit(self, node, values): # # Clone the interior node # - return node.construct_clone(tuple(values), self.memo) + return node.create_node_with_local_data(values) def visiting_potential_leaf(self, node): # # Clone leaf nodes in the expression tree # - if ( - node.__class__ in native_numeric_types - or node.__class__ not in pyomo5_expression_types - ): + if node.__class__ in native_numeric_types or not node.is_expression_type(): return True, copy.deepcopy(node, self.memo) return False, None - - -# @visitor3 + # @visitor3 # --------------------------------------------- @@ -191,13 +155,29 @@ def clone_expression(expr): # search method. # return visitor.dfs_postorder_stack(expr) - - -# @visitor4 + # @visitor4 + + +# Test: +m = ConcreteModel() +m.x = Var(range(2)) +m.p = Param(range(5), mutable=True) +e = m.x[0] + 5 * m.x[1] +ce = clone_expression(e) +print(e is not ce) +# True +print(str(e)) +# x[0] + 5*x[1] +print(str(ce)) +# x[0] + 5*x[1] +print(e.arg(0) is not ce.arg(0)) +# True +print(e.arg(1) is not ce.arg(1)) +# True # --------------------------------------------- # @visitor5 -from pyomo.core.expr import current as EXPR +import pyomo.core.expr as EXPR class ScalingVisitor(EXPR.ExpressionReplacementVisitor): @@ -205,29 +185,24 @@ def __init__(self, scale): super(ScalingVisitor, self).__init__() self.scale = scale - def visiting_potential_leaf(self, node): + def beforeChild(self, node, child, child_idx): # - # Clone leaf nodes in the expression tree + # Native numeric types are terminal nodes; this also catches all + # nodes that do not conform to the ExpressionBase API (i.e., + # define is_variable_type) # - if node.__class__ in native_numeric_types: - return True, node - - if node.is_variable_type(): - return True, self.scale[id(node)] * node - - if isinstance(node, EXPR.LinearExpression): - node_ = copy.deepcopy(node) - node_.constant = node.constant - node_.linear_vars = copy.copy(node.linear_vars) - node_.linear_coefs = [] - for i, v in enumerate(node.linear_vars): - node_.linear_coefs.append(node.linear_coefs[i] * self.scale[id(v)]) - return True, node_ - - return False, None - - -# @visitor5 + if child.__class__ in native_numeric_types: + return False, child + # + # Replace leaf variables with scaled variables + # + if child.is_variable_type(): + return False, self.scale[id(child)] * child + # + # Everything else can be processed normally + # + return True, None + # @visitor5 # --------------------------------------------- @@ -241,11 +216,10 @@ def scale_expression(expr, scale): # Scale the expression using the :func:`dfs_postorder_stack` # search method. # - return visitor.dfs_postorder_stack(expr) + return visitor.walk_expression(expr) + # @visitor6 -# @visitor6 - # --------------------------------------------- # @visitor7 M = ConcreteModel() diff --git a/doc/OnlineDocs/tests/expr/managing.txt b/doc/OnlineDocs/tests/expr/managing.txt index 5a22c846a8b..d236c942d25 100644 --- a/doc/OnlineDocs/tests/expr/managing.txt +++ b/doc/OnlineDocs/tests/expr/managing.txt @@ -1,5 +1,9 @@ sin(x) + 2*x -sum(sin(x), prod(2, x)) +sum(sin(x), mon(2, x)) sin(x1) + 2*x2 --3 + 2*y + x*y + sin(x) +True +x[0] + 5*x[1] +x[0] + 5*x[1] +True +True p[0]*x[0] + p[1]*x[1] + p[2]*x[2] + p[3]*x[3] + p[4]*x[4] diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index c8f22ba1d3a..ca3a1c9e745 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -82,11 +82,13 @@ class RevertToNonrecursive(Exception): class StreamBasedExpressionVisitor(object): """This class implements a generic stream-based expression walker. - This visitor walks an expression tree using a depth-first strategy - and generates a full event stream similar to other tree visitors - (e.g., the expat XML parser). The following events are triggered - through callback functions as the traversal enters and leaves nodes - in the tree: + This visitor walks an expression tree using a depth-first strategy + and generates a full event stream similar to other tree visitors + (e.g., the expat XML parser). The following events are triggered + through callback functions as the traversal enters and leaves nodes + in the tree: + + :: initializeWalker(expr) -> walk, result enterNode(N1) -> args, data @@ -100,7 +102,7 @@ class StreamBasedExpressionVisitor(object): exitNode(N1, data) -> N1_result finalizeWalker(result) -> result - Individual event callbacks match the following signatures: + Individual event callbacks match the following signatures: walk, result = initializeWalker(self, expr): @@ -123,7 +125,7 @@ class StreamBasedExpressionVisitor(object): not defined, the default behavior is equivalent to returning (None, []). - node_result = exitNode(self, node, data): + node_result = exitNode(self, node, data): exitNode() is called after the node is completely processed (as the walker returns up the tree to the parent node). It is @@ -133,7 +135,7 @@ class StreamBasedExpressionVisitor(object): this node. If not specified, the default action is to return the data object from enterNode(). - descend, child_result = beforeChild(self, node, child, child_idx): + descend, child_result = beforeChild(self, node, child, child_idx): beforeChild() is called by a node for every child before entering the child node. The node, child node, and child index @@ -145,7 +147,7 @@ class StreamBasedExpressionVisitor(object): equivalent to (True, None). The default behavior if not specified is equivalent to (True, None). - data = acceptChildResult(self, node, data, child_result, child_idx): + data = acceptChildResult(self, node, data, child_result, child_idx): acceptChildResult() is called for each child result being returned to a node. This callback is responsible for recording @@ -156,7 +158,7 @@ class StreamBasedExpressionVisitor(object): returned. If acceptChildResult is not specified, it does nothing if data is None, otherwise it calls data.append(result). - afterChild(self, node, child, child_idx): + afterChild(self, node, child, child_idx): afterChild() is called by a node for every child node immediately after processing the node is complete before control @@ -165,7 +167,7 @@ class StreamBasedExpressionVisitor(object): are passed, and nothing is returned. If afterChild is not specified, no action takes place. - finalizeResult(self, result): + finalizeResult(self, result): finalizeResult() is called once after the entire expression tree has been walked. It is passed the result returned by the root @@ -173,10 +175,10 @@ class StreamBasedExpressionVisitor(object): the walker returns the result obtained from the exitNode callback on the root node. - Clients interact with this class by either deriving from it and - implementing the necessary callbacks (see above), assigning callable - functions to an instance of this class, or passing the callback - functions as arguments to this class' constructor. + Clients interact with this class by either deriving from it and + implementing the necessary callbacks (see above), assigning callable + functions to an instance of this class, or passing the callback + functions as arguments to this class' constructor. """ @@ -254,7 +256,14 @@ def wrapper(*args): ) def walk_expression(self, expr): - """Walk an expression, calling registered callbacks.""" + """Walk an expression, calling registered callbacks. + + This is the standard interface for running the visitor. It + defaults to using an efficient recursive implementation of the + visitor, falling back on :py:meth:`walk_expression_nonrecursive` + if the recursion stack gets too deep. + + """ if self.initializeWalker is not None: walk, root = self.initializeWalker(expr) if not walk: @@ -496,7 +505,13 @@ def _recursive_frame_to_nonrecursive_stack(self, local): ) def walk_expression_nonrecursive(self, expr): - """Walk an expression, calling registered callbacks.""" + """Nonrecursively walk an expression, calling registered callbacks. + + This routine is safer than the recursive walkers for deep (or + unbalanced) trees. It is, however, slightly slower than the + recursive implementations. + + """ # # This walker uses a linked list to store the stack (instead of # an array). The nodes of the linked list are 6-member tuples: From 85952ff702c836914f3099f1fc544a07bc69fbba Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 9 Jan 2024 09:39:22 -0700 Subject: [PATCH 0687/1204] Add testing of OnlineDocs to Jenkins, GHA drivers --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- .jenkins.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index ff24c731d94..98954debde5 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -623,7 +623,7 @@ jobs: $PYTHON_EXE -m pytest -v \ -W ignore::Warning ${{matrix.category}} \ pyomo `pwd`/pyomo-model-libraries \ - `pwd`/examples/pyomobook --junitxml="TEST-pyomo.xml" + `pwd`/examples `pwd`/doc --junitxml="TEST-pyomo.xml" - name: Run Pyomo MPI tests if: matrix.mpi != 0 diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 6e5604bea47..3d7c2f58e2d 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -653,7 +653,7 @@ jobs: $PYTHON_EXE -m pytest -v \ -W ignore::Warning ${{matrix.category}} \ pyomo `pwd`/pyomo-model-libraries \ - `pwd`/examples/pyomobook --junitxml="TEST-pyomo.xml" + `pwd`/examples `pwd`/doc --junitxml="TEST-pyomo.xml" - name: Run Pyomo MPI tests if: matrix.mpi != 0 diff --git a/.jenkins.sh b/.jenkins.sh index 544cb549175..37be6113ed9 100644 --- a/.jenkins.sh +++ b/.jenkins.sh @@ -38,7 +38,7 @@ if test -z "$WORKSPACE"; then export WORKSPACE=`pwd` fi if test -z "$TEST_SUITES"; then - export TEST_SUITES="${WORKSPACE}/pyomo/pyomo ${WORKSPACE}/pyomo-model-libraries ${WORKSPACE}/pyomo/examples/pyomobook" + export TEST_SUITES="${WORKSPACE}/pyomo/pyomo ${WORKSPACE}/pyomo-model-libraries ${WORKSPACE}/pyomo/examples ${WORKSPACE}/pyomo/doc" fi if test -z "$SLIM"; then export VENV_SYSTEM_PACKAGES='--system-site-packages' From f933a3f020f86e2b7f3ab6ccbbd507f6435eb904 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 9 Jan 2024 09:42:17 -0700 Subject: [PATCH 0688/1204] NFC: Apply black --- pyomo/common/unittest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/common/unittest.py b/pyomo/common/unittest.py index 785afcf6316..dcd1b87f398 100644 --- a/pyomo/common/unittest.py +++ b/pyomo/common/unittest.py @@ -908,7 +908,6 @@ def python_test_driver(self, tname, test_file, base_file): finally: os.chdir(cwd) - try: self.compare_baselines(OUT.getvalue(), baseline) except: From 749583a7e3f62e893ca96102d136f5aeb435f240 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 9 Jan 2024 09:44:22 -0700 Subject: [PATCH 0689/1204] NFC: fix typo --- pyomo/common/unittest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/common/unittest.py b/pyomo/common/unittest.py index dcd1b87f398..b7d8973a186 100644 --- a/pyomo/common/unittest.py +++ b/pyomo/common/unittest.py @@ -597,7 +597,7 @@ def __init__(self, test): super().__init__(test) def initialize_dependencies(self): - # Note: aas a rule, pyomo.common is not allowed to import from + # Note: as a rule, pyomo.common is not allowed to import from # the rest of Pyomo. we permit it here because a) this is not # at module scope, and b) there is really no better / more # logical place in pyomo to put this code. From b5f8ebce4f1fbe6b97614fbfd50e4cf3a69da424 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 9 Jan 2024 10:11:19 -0700 Subject: [PATCH 0690/1204] Test pytest-qt fixture in conda --- .github/workflows/test_branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index bdfa2c537ad..fd3e27a21d5 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -331,7 +331,7 @@ jobs: if test -z "${{matrix.slim}}"; then PYVER=$(echo "py${{matrix.python}}" | sed 's/\.//g') echo "Installing for $PYVER" - for PKG in 'cplex>=12.10' docplex 'gurobi=10.0.3' xpress cyipopt pymumps scip; do + for PKG in 'cplex>=12.10' docplex 'gurobi=10.0.3' xpress cyipopt pymumps scip pytest-qt; do echo "" echo "*** Install $PKG ***" # conda can literally take an hour to determine that a From 240e3e86e37c9d219fa53b8927bbb1319fb5e727 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 9 Jan 2024 10:38:00 -0700 Subject: [PATCH 0691/1204] Updating package dependencies for OnlineDOcs kernel test --- doc/OnlineDocs/tests/test_examples.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/OnlineDocs/tests/test_examples.py b/doc/OnlineDocs/tests/test_examples.py index 89b95fff843..3f9b05b4a3f 100644 --- a/doc/OnlineDocs/tests/test_examples.py +++ b/doc/OnlineDocs/tests/test_examples.py @@ -47,6 +47,8 @@ class TestOnlineDocExamples(unittest.BaseLineTestDriver, unittest.TestCase): 'test_dataportal_dataportal_tab': ['xlrd', 'pyutilib'], 'test_dataportal_set_initialization': ['numpy'], 'test_dataportal_param_initialization': ['numpy'], + # kernel + 'test_kernel_examples': ['pympler'], } @parameterized.parameterized.expand( From 698ac6146159a68f1f11af273907879387b9499a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 9 Jan 2024 11:30:17 -0700 Subject: [PATCH 0692/1204] Update package dependency for OnlineDocs data test --- doc/OnlineDocs/tests/test_examples.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/OnlineDocs/tests/test_examples.py b/doc/OnlineDocs/tests/test_examples.py index 3f9b05b4a3f..caac8e36256 100644 --- a/doc/OnlineDocs/tests/test_examples.py +++ b/doc/OnlineDocs/tests/test_examples.py @@ -42,7 +42,7 @@ class TestOnlineDocExamples(unittest.BaseLineTestDriver, unittest.TestCase): # data 'test_data_ABCD9': ['pyodbc'], 'test_data_ABCD8': ['pyodbc'], - 'test_data_ABCD7': ['win32com'], + 'test_data_ABCD7': ['win32com', 'pyutilib'], # dataportal 'test_dataportal_dataportal_tab': ['xlrd', 'pyutilib'], 'test_dataportal_set_initialization': ['numpy'], From ec301e3b480b2f1ccb2837275fe85f95fd562138 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 9 Jan 2024 14:49:44 -0700 Subject: [PATCH 0693/1204] Different way of install pytest-qt --- .github/workflows/test_branches.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index fd3e27a21d5..5f0a8987240 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -327,11 +327,11 @@ jobs: echo "*** Install Pyomo dependencies ***" # Note: this will fail the build if any installation fails (or # possibly if it outputs messages to stderr) - conda install --update-deps -q -y $CONDA_DEPENDENCIES + conda install --update-deps -q -y $CONDA_DEPENDENCIES pytest-qt if test -z "${{matrix.slim}}"; then PYVER=$(echo "py${{matrix.python}}" | sed 's/\.//g') echo "Installing for $PYVER" - for PKG in 'cplex>=12.10' docplex 'gurobi=10.0.3' xpress cyipopt pymumps scip pytest-qt; do + for PKG in 'cplex>=12.10' docplex 'gurobi=10.0.3' xpress cyipopt pymumps scip; do echo "" echo "*** Install $PKG ***" # conda can literally take an hour to determine that a From 5dce7fa3187ce036a0263c86c4728c692247ef4e Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 9 Jan 2024 15:10:22 -0700 Subject: [PATCH 0694/1204] Implement suggestion from StackOverflow for a resolution --- .github/workflows/test_branches.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 5f0a8987240..ada588127c7 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -194,6 +194,9 @@ jobs: sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ install libopenblas-dev gfortran liblapack-dev glpk-utils sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os + sudo apt-get -qq install libxcb-xinerama0 pyqt5-dev-tools + # start xvfb in the background + sudo /usr/bin/Xvfb $DISPLAY -screen 0 1280x1024x24 & - name: Update Windows if: matrix.TARGET == 'win' From d1bfee32d18ece84ea1efe4dacf912f9f4aaf787 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 9 Jan 2024 15:26:17 -0700 Subject: [PATCH 0695/1204] Different stackoverflow suggestion --- .github/workflows/test_branches.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index ada588127c7..0376e19a5ce 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -194,7 +194,8 @@ jobs: sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ install libopenblas-dev gfortran liblapack-dev glpk-utils sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os - sudo apt-get -qq install libxcb-xinerama0 pyqt5-dev-tools + sudo apt-get install -y xvfb libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xinput0 libxcb-xfixes0 libxcb-shape0 libglib2.0-0 libgl1-mesa-dev + sudo apt-get install '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev # start xvfb in the background sudo /usr/bin/Xvfb $DISPLAY -screen 0 1280x1024x24 & From 3b81976053e891a379008b21af92bc244c0fa583 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 9 Jan 2024 15:33:39 -0700 Subject: [PATCH 0696/1204] Missed env - trying env vars --- .github/workflows/test_branches.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 0376e19a5ce..281847b101a 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -26,6 +26,9 @@ env: CACHE_VER: v221013.1 NEOS_EMAIL: tests@pyomo.org SRC_REF: ${{ github.head_ref || github.ref }} + # Display must be available globally for linux to know where xvfb is + DISPLAY: ":99.0" + QT_SELECT: "qt6" jobs: lint: From 3e3c8fd5cd76b0da9ef4e89bb03039ae30dc1a6b Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 9 Jan 2024 15:48:43 -0700 Subject: [PATCH 0697/1204] Ignore memory usage reported in examples tests --- .../library_reference/kernel/examples/transformer.py | 4 ++-- doc/OnlineDocs/tests/kernel/examples.txt | 4 ++-- pyomo/common/unittest.py | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/OnlineDocs/library_reference/kernel/examples/transformer.py b/doc/OnlineDocs/library_reference/kernel/examples/transformer.py index 3d8449a191d..66893008cf9 100644 --- a/doc/OnlineDocs/library_reference/kernel/examples/transformer.py +++ b/doc/OnlineDocs/library_reference/kernel/examples/transformer.py @@ -37,7 +37,7 @@ def connect_v_out(self, v_out): # @kernel -print(_fmt(pympler.asizeof.asizeof(Transformer()))) +print("Memory:", _fmt(pympler.asizeof.asizeof(Transformer()))) # @aml @@ -52,4 +52,4 @@ def Transformer(): # @aml -print(_fmt(pympler.asizeof.asizeof(Transformer()))) +print("Memory:", _fmt(pympler.asizeof.asizeof(Transformer()))) diff --git a/doc/OnlineDocs/tests/kernel/examples.txt b/doc/OnlineDocs/tests/kernel/examples.txt index 4b13e25c157..306eff0e929 100644 --- a/doc/OnlineDocs/tests/kernel/examples.txt +++ b/doc/OnlineDocs/tests/kernel/examples.txt @@ -230,5 +230,5 @@ - pw.c[1]: linear_constraint(active=True, expr=pw.v[0] + 2*pw.v[1] + pw.v[2] + 2*pw.v[3] - f == 0) - pw.c[2]: linear_constraint(active=True, expr=pw.v[0] + pw.v[1] + pw.v[2] + pw.v[3] == 1) - pw.s: sos(active=True, level=2, entries=['(pw.v[0],1)', '(pw.v[1],2)', '(pw.v[2],3)', '(pw.v[3],4)']) -1.9 KB -9.5 KB +Memory: 1.9 KB +Memory: 9.5 KB diff --git a/pyomo/common/unittest.py b/pyomo/common/unittest.py index b7d8973a186..2c93a49e1ee 100644 --- a/pyomo/common/unittest.py +++ b/pyomo/common/unittest.py @@ -732,6 +732,7 @@ def filter_fcn(self, line): 'Function', 'File', 'Matplotlib', + 'Memory:', '-------', '=======', ' ^', From 88a62581232974b65df26fc8a190fc39f8736a61 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 9 Jan 2024 15:49:16 -0700 Subject: [PATCH 0698/1204] Baseline update --- examples/pyomobook/performance-ch/wl.txt | 94 ++++++++++++------------ 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/examples/pyomobook/performance-ch/wl.txt b/examples/pyomobook/performance-ch/wl.txt index fbbd11fa32a..f7d2e0ada19 100644 --- a/examples/pyomobook/performance-ch/wl.txt +++ b/examples/pyomobook/performance-ch/wl.txt @@ -7,17 +7,17 @@ Building model 0 seconds to construct Set OrderedScalarSet; 1 index total 0 seconds to construct Set SetProduct_OrderedSet; 1 index total 0 seconds to construct Set SetProduct_OrderedSet; 1 index total - 0.15 seconds to construct Var x; 40000 indices total + 0.02 seconds to construct Var x; 40000 indices total 0 seconds to construct Set OrderedScalarSet; 1 index total 0 seconds to construct Var y; 200 indices total - 0.26 seconds to construct Objective obj; 1 index total + 0.13 seconds to construct Objective obj; 1 index total 0 seconds to construct Set OrderedScalarSet; 1 index total 0.13 seconds to construct Constraint demand; 200 indices total 0 seconds to construct Set OrderedScalarSet; 1 index total 0 seconds to construct Set OrderedScalarSet; 1 index total 0 seconds to construct Set SetProduct_OrderedSet; 1 index total 0 seconds to construct Set SetProduct_OrderedSet; 1 index total - 0.82 seconds to construct Constraint warehouse_active; 40000 indices total + 0.48 seconds to construct Constraint warehouse_active; 40000 indices total 0 seconds to construct Constraint num_warehouses; 1 index total Building model with LinearExpression ------------------------------------ @@ -28,71 +28,69 @@ Building model with LinearExpression 0 seconds to construct Set OrderedScalarSet; 1 index total 0 seconds to construct Set SetProduct_OrderedSet; 1 index total 0 seconds to construct Set SetProduct_OrderedSet; 1 index total - 0.08 seconds to construct Var x; 40000 indices total + 0.02 seconds to construct Var x; 40000 indices total 0 seconds to construct Set OrderedScalarSet; 1 index total 0 seconds to construct Var y; 200 indices total - 0.33 seconds to construct Objective obj; 1 index total + 0.06 seconds to construct Objective obj; 1 index total 0 seconds to construct Set OrderedScalarSet; 1 index total - 0.13 seconds to construct Constraint demand; 200 indices total + 0.18 seconds to construct Constraint demand; 200 indices total 0 seconds to construct Set OrderedScalarSet; 1 index total 0 seconds to construct Set OrderedScalarSet; 1 index total 0 seconds to construct Set SetProduct_OrderedSet; 1 index total 0 seconds to construct Set SetProduct_OrderedSet; 1 index total - 0.59 seconds to construct Constraint warehouse_active; 40000 indices total + 0.33 seconds to construct Constraint warehouse_active; 40000 indices total 0 seconds to construct Constraint num_warehouses; 1 index total [ 0.00] start -[+ 1.74] Built model -[+ 7.39] Wrote LP file and solved -[+ 11.36] finished parameter sweep - 14919301 function calls (14916699 primitive calls) in 15.948 seconds +[+ 0.79] Built model +[+ 2.56] Wrote LP file and solved +[+ 10.96] Finished parameter sweep + 7372057 function calls (7368345 primitive calls) in 13.627 seconds Ordered by: cumulative time - List reduced from 590 to 15 due to restriction <15> + List reduced from 673 to 15 due to restriction <15> ncalls tottime percall cumtime percall filename:lineno(function) - 1 0.002 0.002 15.948 15.948 /export/home/dlwoodruff/Documents/BookIII/trunk/pyomo/examples/doc/pyomobook/performance-ch/wl.py:112(solve_parametric) - 30 0.007 0.000 15.721 0.524 /export/home/dlwoodruff/software/pyomo/pyomo/opt/base/solvers.py:511(solve) - 30 0.001 0.000 9.150 0.305 /export/home/dlwoodruff/software/pyomo/pyomo/solvers/plugins/solvers/GUROBI.py:191(_presolve) - 30 0.001 0.000 9.149 0.305 /export/home/dlwoodruff/software/pyomo/pyomo/opt/solver/shellcmd.py:188(_presolve) - 30 0.001 0.000 9.134 0.304 /export/home/dlwoodruff/software/pyomo/pyomo/opt/base/solvers.py:651(_presolve) - 30 0.000 0.000 9.133 0.304 /export/home/dlwoodruff/software/pyomo/pyomo/opt/base/solvers.py:719(_convert_problem) - 30 0.002 0.000 9.133 0.304 /export/home/dlwoodruff/software/pyomo/pyomo/opt/base/convert.py:31(convert_problem) - 30 0.001 0.000 9.093 0.303 /export/home/dlwoodruff/software/pyomo/pyomo/solvers/plugins/converter/model.py:43(apply) - 30 0.001 0.000 9.080 0.303 /export/home/dlwoodruff/software/pyomo/pyomo/core/base/block.py:1756(write) - 30 0.008 0.000 9.077 0.303 /export/home/dlwoodruff/software/pyomo/pyomo/repn/plugins/cpxlp.py:81(__call__) - 30 1.308 0.044 9.065 0.302 /export/home/dlwoodruff/software/pyomo/pyomo/repn/plugins/cpxlp.py:377(_print_model_LP) - 30 0.002 0.000 5.016 0.167 /export/home/dlwoodruff/software/pyomo/pyomo/opt/solver/shellcmd.py:223(_apply_solver) - 30 0.002 0.000 5.013 0.167 /export/home/dlwoodruff/software/pyomo/pyomo/opt/solver/shellcmd.py:289(_execute_command) - 30 0.006 0.000 5.011 0.167 /export/home/dlwoodruff/software/pyutilib/pyutilib/subprocess/processmngr.py:433(run_command) - 30 0.001 0.000 4.388 0.146 /export/home/dlwoodruff/software/pyutilib/pyutilib/subprocess/processmngr.py:829(wait) + 1 0.001 0.001 13.627 13.627 /home/jdsiiro/Research/pyomo/examples/pyomobook/performance-ch/wl.py:132(solve_parametric) + 30 0.002 0.000 13.551 0.452 /home/jdsiiro/Research/pyomo/pyomo/opt/base/solvers.py:530(solve) + 30 0.001 0.000 10.383 0.346 /home/jdsiiro/Research/pyomo/pyomo/opt/solver/shellcmd.py:247(_apply_solver) + 30 0.002 0.000 10.381 0.346 /home/jdsiiro/Research/pyomo/pyomo/opt/solver/shellcmd.py:310(_execute_command) + 30 0.001 0.000 10.360 0.345 /projects/sems/install/rhel7-x86_64/pyomo/compiler/python/3.11.6/lib/python3.11/subprocess.py:506(run) + 30 0.000 0.000 10.288 0.343 /projects/sems/install/rhel7-x86_64/pyomo/compiler/python/3.11.6/lib/python3.11/subprocess.py:1165(communicate) + 60 0.000 0.000 10.287 0.171 /projects/sems/install/rhel7-x86_64/pyomo/compiler/python/3.11.6/lib/python3.11/subprocess.py:1259(wait) + 60 0.001 0.000 10.287 0.171 /projects/sems/install/rhel7-x86_64/pyomo/compiler/python/3.11.6/lib/python3.11/subprocess.py:2014(_wait) + 30 0.000 0.000 10.286 0.343 /projects/sems/install/rhel7-x86_64/pyomo/compiler/python/3.11.6/lib/python3.11/subprocess.py:2001(_try_wait) + 30 10.286 0.343 10.286 0.343 {built-in method posix.waitpid} + 30 0.000 0.000 2.123 0.071 /home/jdsiiro/Research/pyomo/pyomo/solvers/plugins/solvers/GUROBI.py:214(_presolve) + 30 0.000 0.000 2.122 0.071 /home/jdsiiro/Research/pyomo/pyomo/opt/solver/shellcmd.py:215(_presolve) + 30 0.000 0.000 2.114 0.070 /home/jdsiiro/Research/pyomo/pyomo/opt/base/solvers.py:687(_presolve) + 30 0.000 0.000 2.114 0.070 /home/jdsiiro/Research/pyomo/pyomo/opt/base/solvers.py:756(_convert_problem) + 30 0.001 0.000 2.114 0.070 /home/jdsiiro/Research/pyomo/pyomo/opt/base/convert.py:27(convert_problem) - 14919301 function calls (14916699 primitive calls) in 15.948 seconds + 7372057 function calls (7368345 primitive calls) in 13.627 seconds Ordered by: internal time - List reduced from 590 to 15 due to restriction <15> + List reduced from 673 to 15 due to restriction <15> ncalls tottime percall cumtime percall filename:lineno(function) - 30 4.381 0.146 4.381 0.146 {built-in method posix.waitpid} - 30 1.308 0.044 9.065 0.302 /export/home/dlwoodruff/software/pyomo/pyomo/repn/plugins/cpxlp.py:377(_print_model_LP) - 76560 0.703 0.000 1.165 0.000 /export/home/dlwoodruff/software/pyomo/pyomo/repn/plugins/cpxlp.py:178(_print_expr_canonical) - 76560 0.682 0.000 0.858 0.000 /export/home/dlwoodruff/software/pyomo/pyomo/repn/standard_repn.py:424(_collect_sum) - 30 0.544 0.018 0.791 0.026 /export/home/dlwoodruff/software/pyomo/pyomo/solvers/plugins/solvers/GUROBI.py:365(process_soln_file) - 76560 0.539 0.000 1.691 0.000 /export/home/dlwoodruff/software/pyomo/pyomo/repn/standard_repn.py:973(_generate_standard_repn) - 306000 0.507 0.000 0.893 0.000 /export/home/dlwoodruff/software/pyomo/pyomo/core/base/set.py:581(bounds) - 30 0.367 0.012 0.367 0.012 {built-in method posix.read} - 76560 0.323 0.000 2.291 0.000 /export/home/dlwoodruff/software/pyomo/pyomo/repn/standard_repn.py:245(generate_standard_repn) - 76560 0.263 0.000 2.923 0.000 /export/home/dlwoodruff/software/pyomo/pyomo/repn/plugins/cpxlp.py:569(constraint_generator) - 225090 0.262 0.000 0.336 0.000 /export/home/dlwoodruff/software/pyomo/pyomo/core/base/constraint.py:228(has_ub) - 153060 0.249 0.000 0.422 0.000 /export/home/dlwoodruff/software/pyomo/pyomo/core/expr/symbol_map.py:82(createSymbol) - 77220 0.220 0.000 0.457 0.000 {built-in method builtins.sorted} - 30 0.201 0.007 0.202 0.007 {built-in method _posixsubprocess.fork_exec} - 153000 0.185 0.000 0.690 0.000 /export/home/dlwoodruff/software/pyomo/pyomo/core/base/var.py:407(ub) + 30 10.286 0.343 10.286 0.343 {built-in method posix.waitpid} + 30 0.325 0.011 2.078 0.069 /home/jdsiiro/Research/pyomo/pyomo/repn/plugins/lp_writer.py:250(write) + 76560 0.278 0.000 0.668 0.000 /home/jdsiiro/Research/pyomo/pyomo/repn/plugins/lp_writer.py:576(write_expression) + 30 0.248 0.008 0.508 0.017 /home/jdsiiro/Research/pyomo/pyomo/solvers/plugins/solvers/GUROBI.py:394(process_soln_file) + 76560 0.221 0.000 0.395 0.000 /home/jdsiiro/Research/pyomo/pyomo/repn/linear.py:664(_before_linear) + 301530 0.131 0.000 0.178 0.000 /home/jdsiiro/Research/pyomo/pyomo/core/expr/symbol_map.py:133(getSymbol) + 30 0.119 0.004 0.192 0.006 /home/jdsiiro/Research/pyomo/pyomo/core/base/PyomoModel.py:461(select) + 77190 0.117 0.000 0.161 0.000 /home/jdsiiro/Research/pyomo/pyomo/solvers/plugins/solvers/GUROBI.py:451() + 30 0.116 0.004 0.285 0.010 /home/jdsiiro/Research/pyomo/pyomo/core/base/PyomoModel.py:337(add_solution) + 76530 0.080 0.000 0.106 0.000 /home/jdsiiro/Research/pyomo/pyomo/core/expr/symbol_map.py:63(addSymbol) + 239550 0.079 0.000 0.079 0.000 /home/jdsiiro/Research/pyomo/pyomo/core/base/indexed_component.py:611(__getitem__) + 1062450 0.078 0.000 0.078 0.000 {built-in method builtins.id} + 163050 0.074 0.000 0.128 0.000 /home/jdsiiro/Research/pyomo/pyomo/core/base/var.py:1045(__getitem__) + 76560 0.074 0.000 0.080 0.000 /home/jdsiiro/Research/pyomo/pyomo/repn/linear.py:834(finalizeResult) + 153150 0.073 0.000 0.191 0.000 /home/jdsiiro/Research/pyomo/pyomo/core/base/block.py:1505(_component_data_itervalues) -[ 36.46] Resetting the tic/toc delta timer -Using license file /export/home/dlwoodruff/software/gurobi900/linux64/../lic/gurobi.lic -Academic license - for non-commercial use only -[+ 1.21] finished parameter sweep with persistent interface +[ 0.00] Resetting the tic/toc delta timer +[+ 0.66] Finished parameter sweep with persistent interface From d34b50cb6a522df7f931f47986e4a74b54a0910e Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 9 Jan 2024 15:49:35 -0700 Subject: [PATCH 0699/1204] Refine baseline test filter patterns --- pyomo/common/unittest.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pyomo/common/unittest.py b/pyomo/common/unittest.py index 2c93a49e1ee..a77c2d6bd56 100644 --- a/pyomo/common/unittest.py +++ b/pyomo/common/unittest.py @@ -743,8 +743,8 @@ def filter_fcn(self, line): 'Total CPU', 'Ipopt', 'license', - 'Status: optimal', - 'Status: feasible', + #'Status: optimal', + #'Status: feasible', 'time:', 'Time:', 'with format cpxlp', @@ -752,12 +752,13 @@ def filter_fcn(self, line): 'execution time=', 'Solver results file:', 'TokenServer', + # ignore entries in pstats reports: 'function calls', 'List reduced', '.py:', - '{built-in method', - '{method', - '{pyomo.core.expr.numvalue.as_numeric}', + ' {built-in method', + ' {method', + ' {pyomo.core.expr.numvalue.as_numeric}', ): if field in line: return True From 641f5347ebbb9ee9adfcf2d37b6c5dc80cd1d6c0 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 9 Jan 2024 16:06:39 -0700 Subject: [PATCH 0700/1204] Add another qt dep --- .github/workflows/test_branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 281847b101a..976fa643254 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -334,7 +334,7 @@ jobs: echo "*** Install Pyomo dependencies ***" # Note: this will fail the build if any installation fails (or # possibly if it outputs messages to stderr) - conda install --update-deps -q -y $CONDA_DEPENDENCIES pytest-qt + conda install --update-deps -q -y $CONDA_DEPENDENCIES qtconsole pint pytest-qt if test -z "${{matrix.slim}}"; then PYVER=$(echo "py${{matrix.python}}" | sed 's/\.//g') echo "Installing for $PYVER" From 8503401b1232563edd04143ab6e5a3feda574e77 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 9 Jan 2024 16:52:01 -0700 Subject: [PATCH 0701/1204] Attempt with GitHub action for headless testing --- .github/workflows/test_branches.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 976fa643254..b92b112c40a 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -27,8 +27,8 @@ env: NEOS_EMAIL: tests@pyomo.org SRC_REF: ${{ github.head_ref || github.ref }} # Display must be available globally for linux to know where xvfb is - DISPLAY: ":99.0" - QT_SELECT: "qt6" + # DISPLAY: ":99.0" + # QT_SELECT: "qt6" jobs: lint: @@ -197,10 +197,10 @@ jobs: sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ install libopenblas-dev gfortran liblapack-dev glpk-utils sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os - sudo apt-get install -y xvfb libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xinput0 libxcb-xfixes0 libxcb-shape0 libglib2.0-0 libgl1-mesa-dev - sudo apt-get install '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev - # start xvfb in the background - sudo /usr/bin/Xvfb $DISPLAY -screen 0 1280x1024x24 & + # sudo apt-get install -y xvfb libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xinput0 libxcb-xfixes0 libxcb-shape0 libglib2.0-0 libgl1-mesa-dev + # sudo apt-get install '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev + # # start xvfb in the background + # sudo /usr/bin/Xvfb $DISPLAY -screen 0 1280x1024x24 & - name: Update Windows if: matrix.TARGET == 'win' @@ -220,6 +220,12 @@ jobs: auto-update-conda: false python-version: ${{ matrix.python }} + - name: Set up headless display + if: matrix.PYENV == 'conda' + uses: pyvista/setup-headless-display-action@v2 + with: + qt: true + # GitHub actions is very fragile when it comes to setting up various # Python interpreters, expecially the setup-miniconda interface. # Per the setup-miniconda documentation, it is important to always From 9c3d7610ccd47ff2cca14d0eb03c9aec5ac6e394 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 9 Jan 2024 17:23:28 -0700 Subject: [PATCH 0702/1204] Use a deep import when checking pyutilib availability (catches pyutilib's incompatibility with python 3.12) --- doc/OnlineDocs/tests/test_examples.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/OnlineDocs/tests/test_examples.py b/doc/OnlineDocs/tests/test_examples.py index caac8e36256..40d4127fd74 100644 --- a/doc/OnlineDocs/tests/test_examples.py +++ b/doc/OnlineDocs/tests/test_examples.py @@ -42,9 +42,9 @@ class TestOnlineDocExamples(unittest.BaseLineTestDriver, unittest.TestCase): # data 'test_data_ABCD9': ['pyodbc'], 'test_data_ABCD8': ['pyodbc'], - 'test_data_ABCD7': ['win32com', 'pyutilib'], + 'test_data_ABCD7': ['win32com', 'pyutilib.excel.spreadsheet'], # dataportal - 'test_dataportal_dataportal_tab': ['xlrd', 'pyutilib'], + 'test_dataportal_dataportal_tab': ['xlrd', 'pyutilib.excel.spreadsheet'], 'test_dataportal_set_initialization': ['numpy'], 'test_dataportal_param_initialization': ['numpy'], # kernel From f4ce9b9e775132a2c8b19823d43f5737e3fca376 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 9 Jan 2024 17:49:41 -0700 Subject: [PATCH 0703/1204] GDP transformation to MINLP --- pyomo/gdp/plugins/__init__.py | 1 + pyomo/gdp/plugins/gdp_to_minlp.py | 208 ++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 pyomo/gdp/plugins/gdp_to_minlp.py diff --git a/pyomo/gdp/plugins/__init__.py b/pyomo/gdp/plugins/__init__.py index 1222ce500f1..39761697a0f 100644 --- a/pyomo/gdp/plugins/__init__.py +++ b/pyomo/gdp/plugins/__init__.py @@ -22,3 +22,4 @@ def load(): import pyomo.gdp.plugins.multiple_bigm import pyomo.gdp.plugins.transform_current_disjunctive_state import pyomo.gdp.plugins.bound_pretransformation + import pyomo.gdp.plugins.gdp_to_minlp diff --git a/pyomo/gdp/plugins/gdp_to_minlp.py b/pyomo/gdp/plugins/gdp_to_minlp.py new file mode 100644 index 00000000000..e2be6789aae --- /dev/null +++ b/pyomo/gdp/plugins/gdp_to_minlp.py @@ -0,0 +1,208 @@ +from .gdp_to_mip_transformation import GDP_to_MIP_Transformation +from pyomo.common.config import ConfigDict, ConfigValue +from pyomo.core.base import TransformationFactory +from pyomo.core.util import target_list +from pyomo.core import ( + Block, + BooleanVar, + Connector, + Constraint, + Param, + Set, + SetOf, + Var, + Expression, + SortComponents, + TraversalStrategy, + value, + RangeSet, + NonNegativeIntegers, + Binary, + Any, +) +from pyomo.core.base import TransformationFactory, Reference +import pyomo.core.expr as EXPR +from pyomo.gdp import Disjunct, Disjunction, GDP_Error +from pyomo.gdp.plugins.bigm_mixin import ( + _BigM_MixIn, + _get_bigM_suffix_list, + _warn_for_unused_bigM_args, +) +from pyomo.gdp.plugins.gdp_to_mip_transformation import GDP_to_MIP_Transformation +from pyomo.gdp.transformed_disjunct import _TransformedDisjunct +from pyomo.gdp.util import is_child_of, _get_constraint_transBlock, _to_dict +from pyomo.core.util import target_list +from pyomo.network import Port +from pyomo.repn import generate_standard_repn +from weakref import ref as weakref_ref, ReferenceType +import logging +from pyomo.gdp import GDP_Error +from pyomo.common.collections import ComponentSet +from pyomo.contrib.fbbt.expression_bounds_walker import ExpressionBoundsVisitor +import pyomo.contrib.fbbt.interval as interval +from pyomo.core import Suffix + + +logger = logging.getLogger('pyomo.gdp.gdp_to_minlp') + + +@TransformationFactory.register( + 'gdp.gdp_to_minlp', doc="Reformulate the GDP as an MINLP." +) +class GDPToMINLPTransformation(GDP_to_MIP_Transformation): + CONFIG = ConfigDict("gdp.gdp_to_minlp") + CONFIG.declare( + 'targets', + ConfigValue( + default=None, + domain=target_list, + description="target or list of targets that will be relaxed", + doc=""" + + This specifies the list of components to relax. 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.""", + ), + ) + + transformation_name = 'gdp_to_minlp' + + def __init__(self): + super().__init__(logger) + + def _apply_to(self, instance, **kwds): + try: + self._apply_to_impl(instance, **kwds) + finally: + self._restore_state() + + def _apply_to_impl(self, instance, **kwds): + self._process_arguments(instance, **kwds) + + # filter out inactive targets and handle case where targets aren't + # specified. + targets = self._filter_targets(instance) + # transform logical constraints based on targets + self._transform_logical_constraints(instance, targets) + # we need to preprocess targets to make sure that if there are any + # disjunctions in targets that their disjuncts appear before them in + # the list. + gdp_tree = self._get_gdp_tree_from_targets(instance, targets) + preprocessed_targets = gdp_tree.reverse_topological_sort() + + for t in preprocessed_targets: + if t.ctype is Disjunction: + self._transform_disjunctionData( + t, + t.index(), + parent_disjunct=gdp_tree.parent(t), + root_disjunct=gdp_tree.root_disjunct(t), + ) + + def _transform_disjunctionData( + self, obj, index, parent_disjunct=None, root_disjunct=None + ): + (transBlock, xorConstraint) = self._setup_transform_disjunctionData( + obj, root_disjunct + ) + + # add or (or xor) constraint + or_expr = 0 + for disjunct in obj.disjuncts: + or_expr += disjunct.binary_indicator_var + self._transform_disjunct(disjunct, transBlock) + + rhs = 1 if parent_disjunct is None else parent_disjunct.binary_indicator_var + if obj.xor: + xorConstraint[index] = or_expr == rhs + else: + xorConstraint[index] = or_expr >= rhs + # Mark the DisjunctionData as transformed by mapping it to its XOR + # constraint. + obj._algebraic_constraint = weakref_ref(xorConstraint[index]) + + # and deactivate for the writers + obj.deactivate() + + def _transform_disjunct(self, obj, transBlock): + # We're not using the preprocessed list here, so this could be + # inactive. We've already done the error checking in preprocessing, so + # we just skip it here. + if not obj.active: + return + + relaxationBlock = self._get_disjunct_transformation_block(obj, transBlock) + + # Transform each component within this disjunct + self._transform_block_components(obj, obj) + + # deactivate disjunct to keep the writers happy + obj._deactivate_without_fixing_indicator() + + def _transform_constraint( + self, obj, disjunct + ): + # add constraint to the transformation block, we'll transform it there. + transBlock = disjunct._transformation_block() + constraintMap = transBlock._constraintMap + + disjunctionRelaxationBlock = transBlock.parent_block() + + # We will make indexes from ({obj.local_name} x obj.index_set() x ['lb', + # 'ub']), but don't bother construct that set here, as taking Cartesian + # products is kind of expensive (and redundant since we have the + # original model) + newConstraint = transBlock.transformedConstraints + + for i in sorted(obj.keys()): + c = obj[i] + if not c.active: + continue + + self._add_constraint_expressions( + c, i, disjunct.binary_indicator_var, newConstraint, constraintMap + ) + + # deactivate because we relaxed + c.deactivate() + + def _add_constraint_expressions( + self, c, i, indicator_var, newConstraint, constraintMap + ): + # Since we are both combining components from multiple blocks and using + # local names, we need to make sure that the first index for + # transformedConstraints is guaranteed to be unique. We just grab the + # current length of the list here since that will be monotonically + # increasing and hence unique. We'll append it to the + # slightly-more-human-readable constraint name for something familiar + # but unique. (Note that we really could do this outside of the loop + # over the constraint indices, but I don't think it matters a lot.) + unique = len(newConstraint) + name = c.local_name + "_%s" % unique + + lb, ub = c.lower, c.upper + if (c.equality or lb is ub) and lb is not None: + # equality + newConstraint.add((name, i, 'eq'), lb * indicator_var == c.body * indicator_var) + constraintMap['transformedConstraints'][c] = [newConstraint[name, i, 'eq']] + constraintMap['srcConstraints'][newConstraint[name, i, 'eq']] = c + else: + # inequality + if lb is not None: + newConstraint.add((name, i, 'lb'), lb * indicator_var <= c.body * indicator_var) + constraintMap['transformedConstraints'][c] = [newConstraint[name, i, 'lb']] + constraintMap['srcConstraints'][newConstraint[name, i, 'lb']] = c + if ub is not None: + newConstraint.add((name, i, 'ub'), c.body * indicator_var <= ub * indicator_var) + transformed = constraintMap['transformedConstraints'].get(c) + if transformed is not None: + constraintMap['transformedConstraints'][c].append( + newConstraint[name, i, 'ub'] + ) + else: + constraintMap['transformedConstraints'][c] = [ + newConstraint[name, i, 'ub'] + ] + constraintMap['srcConstraints'][newConstraint[name, i, 'ub']] = c From d8cc9246a55a18332c35b8bb0d8d8b52dd68ffc6 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 07:23:49 -0700 Subject: [PATCH 0704/1204] Clean up action / installs --- .github/workflows/test_branches.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index b92b112c40a..416b559c90f 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -26,9 +26,6 @@ env: CACHE_VER: v221013.1 NEOS_EMAIL: tests@pyomo.org SRC_REF: ${{ github.head_ref || github.ref }} - # Display must be available globally for linux to know where xvfb is - # DISPLAY: ":99.0" - # QT_SELECT: "qt6" jobs: lint: @@ -197,10 +194,6 @@ jobs: sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ install libopenblas-dev gfortran liblapack-dev glpk-utils sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os - # sudo apt-get install -y xvfb libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xinput0 libxcb-xfixes0 libxcb-shape0 libglib2.0-0 libgl1-mesa-dev - # sudo apt-get install '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev - # # start xvfb in the background - # sudo /usr/bin/Xvfb $DISPLAY -screen 0 1280x1024x24 & - name: Update Windows if: matrix.TARGET == 'win' @@ -221,10 +214,11 @@ jobs: python-version: ${{ matrix.python }} - name: Set up headless display - if: matrix.PYENV == 'conda' + if: matrix.PYENV == 'conda' && ${{ matrix.TARGET != 'osx' }} uses: pyvista/setup-headless-display-action@v2 with: qt: true + pyvista: false # GitHub actions is very fragile when it comes to setting up various # Python interpreters, expecially the setup-miniconda interface. From 759bfe4701b9a5b5f07841e7be1101d29eba6571 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 07:26:53 -0700 Subject: [PATCH 0705/1204] Correct logic for headless --- .github/workflows/test_branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 416b559c90f..40fe293a52a 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -214,7 +214,7 @@ jobs: python-version: ${{ matrix.python }} - name: Set up headless display - if: matrix.PYENV == 'conda' && ${{ matrix.TARGET != 'osx' }} + if: ${{ matrix.PYENV == 'conda' && matrix.TARGET != 'osx' }} uses: pyvista/setup-headless-display-action@v2 with: qt: true From f9f52d9b28e5358aa7eca52360891f48d9d22421 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 07:57:11 -0700 Subject: [PATCH 0706/1204] Adjust logic for PYOK --- .github/workflows/test_branches.yml | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 40fe293a52a..d87dc24133c 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -353,11 +353,11 @@ jobs: | sed -r 's/\s+/ /g' | cut -d\ -f3) || echo "" if test -n "$_BUILDS"; then _ISPY=$(echo "$_BUILDS" | grep "^py") \ - || echo "No python build detected" - _PYOK=$(echo "$_BUILDS" | grep "^$PYVER") \ - || echo "No python build matching $PYVER detected" + || echo "\nINFO: No python build detected." + _PYOK=$(echo "$_BUILDS" | grep "^($PYVER|pyh)") \ + || echo "INFO: No python build matching $PYVER detected." if test -z "$_ISPY" -o -n "$_PYOK"; then - echo "... INSTALLING $PKG" + echo "\n... INSTALLING $PKG" conda install -y "$PKG" || _BUILDS="" fi fi @@ -365,18 +365,6 @@ jobs: echo "WARNING: $PKG is not available" fi done - # TODO: This is a hack to stop test_qt.py from running until we - # can better troubleshoot why it fails on GHA - # for QTPACKAGE in qt pyqt; do - # # Because conda is insane, removing packages can cause - # # unrelated packages to be updated (breaking version - # # specifications specified previously, e.g., in - # # setup.py). There doesn't appear to be a good - # # workaround, so we will just force-remove (recognizing - # # that it may break other conda cruft). - # conda remove --force-remove $QTPACKAGE \ - # || echo "$QTPACKAGE not in this environment" - # done fi # Re-try Pyomo (optional) dependencies with pip if test -n "$PYPI_DEPENDENCIES"; then From 330318f80f436057f63ec921ea8b1e5f0121f0a0 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 08:04:20 -0700 Subject: [PATCH 0707/1204] Add note; grep extended expressions --- .github/workflows/test_branches.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index d87dc24133c..0ecc48c6950 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -213,7 +213,10 @@ jobs: auto-update-conda: false python-version: ${{ matrix.python }} - - name: Set up headless display + # This is necessary for qt (UI) tests; the package utilized here does not + # have support for OSX, but we don't do any OSX/conda testing inherently. + # This is just to protect us in case we do add that into the matrix someday. + - name: Set up UI testing infrastructure if: ${{ matrix.PYENV == 'conda' && matrix.TARGET != 'osx' }} uses: pyvista/setup-headless-display-action@v2 with: @@ -354,7 +357,7 @@ jobs: if test -n "$_BUILDS"; then _ISPY=$(echo "$_BUILDS" | grep "^py") \ || echo "\nINFO: No python build detected." - _PYOK=$(echo "$_BUILDS" | grep "^($PYVER|pyh)") \ + _PYOK=$(echo "$_BUILDS" | grep -E "^($PYVER|pyh)") \ || echo "INFO: No python build matching $PYVER detected." if test -z "$_ISPY" -o -n "$_PYOK"; then echo "\n... INSTALLING $PKG" From 1c8e8044d83b83a0620fe81e43bc1d49b61ffa8a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 10 Jan 2024 08:06:40 -0700 Subject: [PATCH 0708/1204] Fix: catch pyutilib errors on Python 3.12 --- doc/OnlineDocs/tests/test_examples.py | 9 +++++++-- pyomo/common/unittest.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/doc/OnlineDocs/tests/test_examples.py b/doc/OnlineDocs/tests/test_examples.py index 40d4127fd74..46c258e91f4 100644 --- a/doc/OnlineDocs/tests/test_examples.py +++ b/doc/OnlineDocs/tests/test_examples.py @@ -38,13 +38,18 @@ class TestOnlineDocExamples(unittest.BaseLineTestDriver, unittest.TestCase): ) solver_dependencies = {} + # Note on package dependencies: two tests actually need + # pyutilib.excel.spreadsheet; however, the pyutilib importer is + # broken on Python>=3.12, so instead of checking for spreadsheet, we + # will check for pyutilib.component, which triggers the importer + # (and catches the error on 3.12) package_dependencies = { # data 'test_data_ABCD9': ['pyodbc'], 'test_data_ABCD8': ['pyodbc'], - 'test_data_ABCD7': ['win32com', 'pyutilib.excel.spreadsheet'], + 'test_data_ABCD7': ['win32com', 'pyutilib.component'], # dataportal - 'test_dataportal_dataportal_tab': ['xlrd', 'pyutilib.excel.spreadsheet'], + 'test_dataportal_dataportal_tab': ['xlrd', 'pyutilib.component'], 'test_dataportal_set_initialization': ['numpy'], 'test_dataportal_param_initialization': ['numpy'], # kernel diff --git a/pyomo/common/unittest.py b/pyomo/common/unittest.py index a77c2d6bd56..cd5f46d00f5 100644 --- a/pyomo/common/unittest.py +++ b/pyomo/common/unittest.py @@ -752,7 +752,7 @@ def filter_fcn(self, line): 'execution time=', 'Solver results file:', 'TokenServer', - # ignore entries in pstats reports: + # next 6 patterns ignore entries in pstats reports: 'function calls', 'List reduced', '.py:', From 1961c79422f106dce9ca2948b42e8b6a2be943db Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 08:17:12 -0700 Subject: [PATCH 0709/1204] Add testing/optional deps to setup.py; clean up action --- .github/workflows/test_branches.yml | 5 +++-- setup.py | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 0ecc48c6950..a11a0d51149 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -337,7 +337,7 @@ jobs: echo "*** Install Pyomo dependencies ***" # Note: this will fail the build if any installation fails (or # possibly if it outputs messages to stderr) - conda install --update-deps -q -y $CONDA_DEPENDENCIES qtconsole pint pytest-qt + conda install --update-deps -q -y $CONDA_DEPENDENCIES if test -z "${{matrix.slim}}"; then PYVER=$(echo "py${{matrix.python}}" | sed 's/\.//g') echo "Installing for $PYVER" @@ -360,7 +360,8 @@ jobs: _PYOK=$(echo "$_BUILDS" | grep -E "^($PYVER|pyh)") \ || echo "INFO: No python build matching $PYVER detected." if test -z "$_ISPY" -o -n "$_PYOK"; then - echo "\n... INSTALLING $PKG" + echo "" + echo "... INSTALLING $PKG" conda install -y "$PKG" || _BUILDS="" fi fi diff --git a/setup.py b/setup.py index dae62e72ca0..55d8915fb2f 100644 --- a/setup.py +++ b/setup.py @@ -246,10 +246,11 @@ def __ne__(self, other): 'tests': [ #'codecov', # useful for testing infrastructures, but not required 'coverage', - 'pytest', - 'pytest-parallel', 'parameterized', 'pybind11', + 'pytest', + 'pytest-parallel', + 'pytest-qt', # for testing contrib.viewer ], 'docs': [ 'Sphinx>4', @@ -279,6 +280,7 @@ def __ne__(self, other): 'plotly', # incidence_analysis 'python-louvain', # community_detection 'pyyaml', # core + 'qtconsole', # contrib.viewer 'scipy', 'sympy', # differentiation 'xlrd', # dataportals From 5e08111a6e81786eeb015369338d71ce0c301208 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 08:28:26 -0700 Subject: [PATCH 0710/1204] Add qtpy to opt deps --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 55d8915fb2f..cbc8f93823b 100644 --- a/setup.py +++ b/setup.py @@ -281,6 +281,7 @@ def __ne__(self, other): 'python-louvain', # community_detection 'pyyaml', # core 'qtconsole', # contrib.viewer + 'qtpy', # contrib.viewer 'scipy', 'sympy', # differentiation 'xlrd', # dataportals From d093e2e03d8137cf7ffaa765909cdb9ea2e7fc20 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 08:37:42 -0700 Subject: [PATCH 0711/1204] tests for GDP transformation to MINLP --- pyomo/gdp/plugins/gdp_to_minlp.py | 44 +--- pyomo/gdp/tests/common_tests.py | 18 ++ pyomo/gdp/tests/test_gdp_to_minlp.py | 300 +++++++++++++++++++++++++++ 3 files changed, 323 insertions(+), 39 deletions(-) create mode 100644 pyomo/gdp/tests/test_gdp_to_minlp.py diff --git a/pyomo/gdp/plugins/gdp_to_minlp.py b/pyomo/gdp/plugins/gdp_to_minlp.py index e2be6789aae..b599d866ac7 100644 --- a/pyomo/gdp/plugins/gdp_to_minlp.py +++ b/pyomo/gdp/plugins/gdp_to_minlp.py @@ -2,45 +2,11 @@ from pyomo.common.config import ConfigDict, ConfigValue from pyomo.core.base import TransformationFactory from pyomo.core.util import target_list -from pyomo.core import ( - Block, - BooleanVar, - Connector, - Constraint, - Param, - Set, - SetOf, - Var, - Expression, - SortComponents, - TraversalStrategy, - value, - RangeSet, - NonNegativeIntegers, - Binary, - Any, -) -from pyomo.core.base import TransformationFactory, Reference -import pyomo.core.expr as EXPR -from pyomo.gdp import Disjunct, Disjunction, GDP_Error -from pyomo.gdp.plugins.bigm_mixin import ( - _BigM_MixIn, - _get_bigM_suffix_list, - _warn_for_unused_bigM_args, -) +from pyomo.gdp import Disjunction from pyomo.gdp.plugins.gdp_to_mip_transformation import GDP_to_MIP_Transformation -from pyomo.gdp.transformed_disjunct import _TransformedDisjunct -from pyomo.gdp.util import is_child_of, _get_constraint_transBlock, _to_dict from pyomo.core.util import target_list -from pyomo.network import Port -from pyomo.repn import generate_standard_repn -from weakref import ref as weakref_ref, ReferenceType +from weakref import ref as weakref_ref import logging -from pyomo.gdp import GDP_Error -from pyomo.common.collections import ComponentSet -from pyomo.contrib.fbbt.expression_bounds_walker import ExpressionBoundsVisitor -import pyomo.contrib.fbbt.interval as interval -from pyomo.core import Suffix logger = logging.getLogger('pyomo.gdp.gdp_to_minlp') @@ -185,17 +151,17 @@ def _add_constraint_expressions( lb, ub = c.lower, c.upper if (c.equality or lb is ub) and lb is not None: # equality - newConstraint.add((name, i, 'eq'), lb * indicator_var == c.body * indicator_var) + newConstraint.add((name, i, 'eq'), c.body * indicator_var - lb * indicator_var == 0) constraintMap['transformedConstraints'][c] = [newConstraint[name, i, 'eq']] constraintMap['srcConstraints'][newConstraint[name, i, 'eq']] = c else: # inequality if lb is not None: - newConstraint.add((name, i, 'lb'), lb * indicator_var <= c.body * indicator_var) + newConstraint.add((name, i, 'lb'), 0 <= c.body * indicator_var - lb * indicator_var) constraintMap['transformedConstraints'][c] = [newConstraint[name, i, 'lb']] constraintMap['srcConstraints'][newConstraint[name, i, 'lb']] = c if ub is not None: - newConstraint.add((name, i, 'ub'), c.body * indicator_var <= ub * indicator_var) + newConstraint.add((name, i, 'ub'), c.body * indicator_var - ub * indicator_var <= 0) transformed = constraintMap['transformedConstraints'].get(c) if transformed is not None: constraintMap['transformedConstraints'][c].append( diff --git a/pyomo/gdp/tests/common_tests.py b/pyomo/gdp/tests/common_tests.py index b475334981b..354c64a6386 100644 --- a/pyomo/gdp/tests/common_tests.py +++ b/pyomo/gdp/tests/common_tests.py @@ -58,6 +58,24 @@ def check_linear_coef(self, repn, var, coef): self.assertAlmostEqual(repn.linear_coefs[var_id], coef) +def check_quadratic_coef(self, repn, v1, v2, coef): + if isinstance(v1, BooleanVar): + v1 = v1.get_associated_binary() + if isinstance(v2, BooleanVar): + v2 = v2.get_associated_binary() + + v1id = id(v1) + v2id = id(v2) + + qcoef_map = dict() + for (_v1, _v2), _coef in zip(repn.quadratic_vars, repn.quadratic_coefs): + qcoef_map[id(_v1), id(_v2)] = _coef + qcoef_map[id(_v2), id(_v1)] = _coef + + self.assertIn((v1id, v2id), qcoef_map) + self.assertAlmostEqual(qcoef_map[v1id, v2id], coef) + + def check_squared_term_coef(self, repn, var, coef): var_id = None for i, (v1, v2) in enumerate(repn.quadratic_vars): diff --git a/pyomo/gdp/tests/test_gdp_to_minlp.py b/pyomo/gdp/tests/test_gdp_to_minlp.py new file mode 100644 index 00000000000..acf04fd7b53 --- /dev/null +++ b/pyomo/gdp/tests/test_gdp_to_minlp.py @@ -0,0 +1,300 @@ +# ___________________________________________________________________________ +# +# 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.common.unittest as unittest + +from pyomo.environ import ( + TransformationFactory, + Block, + Constraint, + ConcreteModel, + Var, + Any, +) +from pyomo.gdp import Disjunct, Disjunction +from pyomo.core.expr.compare import ( + assertExpressionsEqual, +) +from pyomo.repn import generate_standard_repn + +import pyomo.core.expr as EXPR +import pyomo.gdp.tests.models as models +import pyomo.gdp.tests.common_tests as ct + +import random + + +class CommonTests: + def diff_apply_to_and_create_using(self, model): + ct.diff_apply_to_and_create_using(self, model, 'gdp.gdp_to_minlp') + + +class TwoTermDisj(unittest.TestCase, CommonTests): + def setUp(self): + # set seed so we can test name collisions predictably + random.seed(666) + + def test_new_block_created(self): + m = models.makeTwoTermDisj() + TransformationFactory('gdp.gdp_to_minlp').apply_to(m) + + # we have a transformation block + transBlock = m.component("_pyomo_gdp_gdp_to_minlp_reformulation") + self.assertIsInstance(transBlock, Block) + + disjBlock = transBlock.component("relaxedDisjuncts") + self.assertIsInstance(disjBlock, Block) + self.assertEqual(len(disjBlock), 2) + # it has the disjuncts on it + self.assertIs(m.d[0].transformation_block, disjBlock[0]) + self.assertIs(m.d[1].transformation_block, disjBlock[1]) + + def test_disjunction_deactivated(self): + ct.check_disjunction_deactivated(self, 'gdp_to_minlp') + + def test_disjunctDatas_deactivated(self): + ct.check_disjunctDatas_deactivated(self, 'gdp_to_minlp') + + def test_do_not_transform_twice_if_disjunction_reactivated(self): + ct.check_do_not_transform_twice_if_disjunction_reactivated(self, 'gdp_to_minlp') + + def test_xor_constraint_mapping(self): + ct.check_xor_constraint_mapping(self, 'gdp_to_minlp') + + def test_xor_constraint_mapping_two_disjunctions(self): + ct.check_xor_constraint_mapping_two_disjunctions(self, 'gdp_to_minlp') + + def test_disjunct_mapping(self): + ct.check_disjunct_mapping(self, 'gdp_to_minlp') + + def test_disjunct_and_constraint_maps(self): + """Tests the actual data structures used to store the maps.""" + m = models.makeTwoTermDisj() + gdp_to_minlp = TransformationFactory('gdp.gdp_to_minlp') + gdp_to_minlp.apply_to(m) + disjBlock = m._pyomo_gdp_gdp_to_minlp_reformulation.relaxedDisjuncts + oldblock = m.component("d") + + # we are counting on the fact that the disjuncts get relaxed in the + # same order every time. + for i in [0, 1]: + self.assertIs(oldblock[i].transformation_block, disjBlock[i]) + self.assertIs(gdp_to_minlp.get_src_disjunct(disjBlock[i]), oldblock[i]) + + # check constraint dict has right mapping + c1_list = gdp_to_minlp.get_transformed_constraints(oldblock[1].c1) + # this is an equality + self.assertEqual(len(c1_list), 1) + self.assertIs(c1_list[0].parent_block(), disjBlock[1]) + self.assertIs(gdp_to_minlp.get_src_constraint(c1_list[0]), oldblock[1].c1) + + c2_list = gdp_to_minlp.get_transformed_constraints(oldblock[1].c2) + # just ub + self.assertEqual(len(c2_list), 1) + self.assertIs(c2_list[0].parent_block(), disjBlock[1]) + self.assertIs(gdp_to_minlp.get_src_constraint(c2_list[0]), oldblock[1].c2) + + c_list = gdp_to_minlp.get_transformed_constraints(oldblock[0].c) + # just lb + self.assertEqual(len(c_list), 1) + self.assertIs(c_list[0].parent_block(), disjBlock[0]) + self.assertIs(gdp_to_minlp.get_src_constraint(c_list[0]), oldblock[0].c) + + def test_new_block_nameCollision(self): + ct.check_transformation_block_name_collision(self, 'gdp_to_minlp') + + def test_indicator_vars(self): + ct.check_indicator_vars(self, 'gdp_to_minlp') + + def test_xor_constraints(self): + ct.check_xor_constraint(self, 'gdp_to_minlp') + + def test_or_constraints(self): + m = models.makeTwoTermDisj() + m.disjunction.xor = False + TransformationFactory('gdp.gdp_to_minlp').apply_to(m) + + # check or constraint is an or (upper bound is None) + orcons = m._pyomo_gdp_gdp_to_minlp_reformulation.component("disjunction_xor") + self.assertIsInstance(orcons, Constraint) + assertExpressionsEqual( + self, + orcons.body, + EXPR.LinearExpression( + [ + EXPR.MonomialTermExpression((1, m.d[0].binary_indicator_var)), + EXPR.MonomialTermExpression((1, m.d[1].binary_indicator_var)), + ] + ), + ) + self.assertEqual(orcons.lower, 1) + self.assertIsNone(orcons.upper) + + def test_deactivated_constraints(self): + ct.check_deactivated_constraints(self, 'gdp_to_minlp') + + def test_transformed_constraints(self): + m = models.makeTwoTermDisj() + gdp_to_minlp = TransformationFactory('gdp.gdp_to_minlp') + gdp_to_minlp.apply_to(m) + self.check_transformed_constraints(m, gdp_to_minlp, -3, 2, 7, 2) + + def test_do_not_transform_userDeactivated_disjuncts(self): + ct.check_user_deactivated_disjuncts(self, 'gdp_to_minlp') + + def test_improperly_deactivated_disjuncts(self): + ct.check_improperly_deactivated_disjuncts(self, 'gdp_to_minlp') + + def test_do_not_transform_userDeactivated_IndexedDisjunction(self): + ct.check_do_not_transform_userDeactivated_indexedDisjunction(self, 'gdp_to_minlp') + + # helper method to check the M values in all of the transformed + # constraints (m, M) is the tuple for M. This also relies on the + # disjuncts being transformed in the same order every time. + def check_transformed_constraints(self, model, gdp_to_minlp, cons1lb, cons2lb, cons2ub, cons3ub): + disjBlock = model._pyomo_gdp_gdp_to_minlp_reformulation.relaxedDisjuncts + + # first constraint + c = gdp_to_minlp.get_transformed_constraints(model.d[0].c) + self.assertEqual(len(c), 1) + c_lb = c[0] + self.assertTrue(c[0].active) + repn = generate_standard_repn(c[0].body) + self.assertIsNone(repn.nonlinear_expr) + self.assertEqual(len(repn.quadratic_coefs), 1) + self.assertEqual(len(repn.linear_coefs), 1) + ind_var = model.d[0].indicator_var + ct.check_quadratic_coef(self, repn, model.a, ind_var, 1) + ct.check_linear_coef(self, repn, ind_var, -model.d[0].c.lower) + self.assertEqual(repn.constant, 0) + self.assertEqual(c[0].lower, 0) + self.assertIsNone(c[0].upper) + + # second constraint + c = gdp_to_minlp.get_transformed_constraints(model.d[1].c1) + self.assertEqual(len(c), 1) + c_eq = c[0] + self.assertTrue(c[0].active) + repn = generate_standard_repn(c[0].body) + self.assertTrue(repn.nonlinear_expr is None) + self.assertEqual(len(repn.linear_coefs), 0) + self.assertEqual(len(repn.quadratic_coefs), 1) + ind_var = model.d[1].indicator_var + ct.check_quadratic_coef(self, repn, model.a, ind_var, 1) + self.assertEqual(repn.constant, 0) + self.assertEqual(c[0].lower, 0) + self.assertEqual(c[0].upper, 0) + + # third constraint + c = gdp_to_minlp.get_transformed_constraints(model.d[1].c2) + self.assertEqual(len(c), 1) + c_ub = c[0] + self.assertTrue(c_ub.active) + repn = generate_standard_repn(c_ub.body) + self.assertIsNone(repn.nonlinear_expr) + self.assertEqual(len(repn.linear_coefs), 1) + self.assertEqual(len(repn.quadratic_coefs), 1) + ct.check_quadratic_coef(self, repn, model.x, ind_var, 1) + ct.check_linear_coef(self, repn, ind_var, -model.d[1].c2.upper) + self.assertEqual(repn.constant, 0) + self.assertIsNone(c_ub.lower) + self.assertEqual(c_ub.upper, 0) + + def test_create_using(self): + m = models.makeTwoTermDisj() + self.diff_apply_to_and_create_using(m) + + def test_indexed_constraints_in_disjunct(self): + m = ConcreteModel() + m.I = [1, 2, 3] + m.x = Var(m.I, bounds=(0, 10)) + + def c_rule(b, i): + m = b.model() + return m.x[i] >= i + + def d_rule(d, j): + m = d.model() + d.c = Constraint(m.I[:j], rule=c_rule) + + m.d = Disjunct(m.I, rule=d_rule) + m.disjunction = Disjunction(expr=[m.d[i] for i in m.I]) + + TransformationFactory('gdp.gdp_to_minlp').apply_to(m) + transBlock = m._pyomo_gdp_gdp_to_minlp_reformulation + + # 2 blocks: the original Disjunct and the transformation block + self.assertEqual(len(list(m.component_objects(Block, descend_into=False))), 1) + self.assertEqual(len(list(m.component_objects(Disjunct))), 1) + + # Each relaxed disjunct should have 1 var (the reference to the + # indicator var), and i "d[i].c" Constraints + for i in [1, 2, 3]: + relaxed = transBlock.relaxedDisjuncts[i - 1] + self.assertEqual(len(list(relaxed.component_objects(Var))), 1) + self.assertEqual(len(list(relaxed.component_data_objects(Var))), 1) + self.assertEqual(len(list(relaxed.component_objects(Constraint))), 1) + self.assertEqual(len(list(relaxed.component_data_objects(Constraint))), i) + + def test_virtual_indexed_constraints_in_disjunct(self): + m = ConcreteModel() + m.I = [1, 2, 3] + m.x = Var(m.I, bounds=(0, 10)) + + def d_rule(d, j): + m = d.model() + d.c = Constraint(Any) + for k in range(j): + d.c[k + 1] = m.x[k + 1] >= k + 1 + + m.d = Disjunct(m.I, rule=d_rule) + m.disjunction = Disjunction(expr=[m.d[i] for i in m.I]) + + TransformationFactory('gdp.gdp_to_minlp').apply_to(m) + transBlock = m._pyomo_gdp_gdp_to_minlp_reformulation + + # 2 blocks: the original Disjunct and the transformation block + self.assertEqual(len(list(m.component_objects(Block, descend_into=False))), 1) + self.assertEqual(len(list(m.component_objects(Disjunct))), 1) + + # Each relaxed disjunct should have 1 var (the reference to the + # indicator var), and i "d[i].c" Constraints + for i in [1, 2, 3]: + relaxed = transBlock.relaxedDisjuncts[i - 1] + self.assertEqual(len(list(relaxed.component_objects(Var))), 1) + self.assertEqual(len(list(relaxed.component_data_objects(Var))), 1) + self.assertEqual(len(list(relaxed.component_objects(Constraint))), 1) + self.assertEqual(len(list(relaxed.component_data_objects(Constraint))), i) + + def test_local_var(self): + m = models.localVar() + gdp_to_minlp = TransformationFactory('gdp.gdp_to_minlp') + gdp_to_minlp.apply_to(m) + + # we just need to make sure that constraint was transformed correctly, + # which just means that the M values were correct. + transformedC = gdp_to_minlp.get_transformed_constraints(m.disj2.cons) + self.assertEqual(len(transformedC), 1) + eq = transformedC[0] + repn = generate_standard_repn(eq.body) + self.assertIsNone(repn.nonlinear_expr) + self.assertEqual(len(repn.linear_coefs), 1) + self.assertEqual(len(repn.quadratic_coefs), 2) + ct.check_linear_coef(self, repn, m.disj2.indicator_var, -3) + ct.check_quadratic_coef(self, repn, m.x, m.disj2.indicator_var, 1) + ct.check_quadratic_coef(self, repn, m.disj2.y, m.disj2.indicator_var, 1) + self.assertEqual(repn.constant, 0) + self.assertEqual(eq.lb, 0) + self.assertEqual(eq.ub, 0) + + +if __name__ == '__main__': + unittest.main() From e1d1d60512250db3c11257c3eddf5809019afed2 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 08:42:08 -0700 Subject: [PATCH 0712/1204] run black --- pyomo/gdp/plugins/gdp_to_minlp.py | 20 +++++++++++++------- pyomo/gdp/tests/common_tests.py | 2 +- pyomo/gdp/tests/test_gdp_to_minlp.py | 12 +++++++----- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/pyomo/gdp/plugins/gdp_to_minlp.py b/pyomo/gdp/plugins/gdp_to_minlp.py index b599d866ac7..bec9160ceca 100644 --- a/pyomo/gdp/plugins/gdp_to_minlp.py +++ b/pyomo/gdp/plugins/gdp_to_minlp.py @@ -107,9 +107,7 @@ def _transform_disjunct(self, obj, transBlock): # deactivate disjunct to keep the writers happy obj._deactivate_without_fixing_indicator() - def _transform_constraint( - self, obj, disjunct - ): + def _transform_constraint(self, obj, disjunct): # add constraint to the transformation block, we'll transform it there. transBlock = disjunct._transformation_block() constraintMap = transBlock._constraintMap @@ -151,17 +149,25 @@ def _add_constraint_expressions( lb, ub = c.lower, c.upper if (c.equality or lb is ub) and lb is not None: # equality - newConstraint.add((name, i, 'eq'), c.body * indicator_var - lb * indicator_var == 0) + newConstraint.add( + (name, i, 'eq'), c.body * indicator_var - lb * indicator_var == 0 + ) constraintMap['transformedConstraints'][c] = [newConstraint[name, i, 'eq']] constraintMap['srcConstraints'][newConstraint[name, i, 'eq']] = c else: # inequality if lb is not None: - newConstraint.add((name, i, 'lb'), 0 <= c.body * indicator_var - lb * indicator_var) - constraintMap['transformedConstraints'][c] = [newConstraint[name, i, 'lb']] + newConstraint.add( + (name, i, 'lb'), 0 <= c.body * indicator_var - lb * indicator_var + ) + constraintMap['transformedConstraints'][c] = [ + newConstraint[name, i, 'lb'] + ] constraintMap['srcConstraints'][newConstraint[name, i, 'lb']] = c if ub is not None: - newConstraint.add((name, i, 'ub'), c.body * indicator_var - ub * indicator_var <= 0) + newConstraint.add( + (name, i, 'ub'), c.body * indicator_var - ub * indicator_var <= 0 + ) transformed = constraintMap['transformedConstraints'].get(c) if transformed is not None: constraintMap['transformedConstraints'][c].append( diff --git a/pyomo/gdp/tests/common_tests.py b/pyomo/gdp/tests/common_tests.py index 354c64a6386..4a772a7ae56 100644 --- a/pyomo/gdp/tests/common_tests.py +++ b/pyomo/gdp/tests/common_tests.py @@ -63,7 +63,7 @@ def check_quadratic_coef(self, repn, v1, v2, coef): v1 = v1.get_associated_binary() if isinstance(v2, BooleanVar): v2 = v2.get_associated_binary() - + v1id = id(v1) v2id = id(v2) diff --git a/pyomo/gdp/tests/test_gdp_to_minlp.py b/pyomo/gdp/tests/test_gdp_to_minlp.py index acf04fd7b53..532922ee1cc 100644 --- a/pyomo/gdp/tests/test_gdp_to_minlp.py +++ b/pyomo/gdp/tests/test_gdp_to_minlp.py @@ -20,9 +20,7 @@ Any, ) from pyomo.gdp import Disjunct, Disjunction -from pyomo.core.expr.compare import ( - assertExpressionsEqual, -) +from pyomo.core.expr.compare import assertExpressionsEqual from pyomo.repn import generate_standard_repn import pyomo.core.expr as EXPR @@ -154,12 +152,16 @@ def test_improperly_deactivated_disjuncts(self): ct.check_improperly_deactivated_disjuncts(self, 'gdp_to_minlp') def test_do_not_transform_userDeactivated_IndexedDisjunction(self): - ct.check_do_not_transform_userDeactivated_indexedDisjunction(self, 'gdp_to_minlp') + ct.check_do_not_transform_userDeactivated_indexedDisjunction( + self, 'gdp_to_minlp' + ) # helper method to check the M values in all of the transformed # constraints (m, M) is the tuple for M. This also relies on the # disjuncts being transformed in the same order every time. - def check_transformed_constraints(self, model, gdp_to_minlp, cons1lb, cons2lb, cons2ub, cons3ub): + def check_transformed_constraints( + self, model, gdp_to_minlp, cons1lb, cons2lb, cons2ub, cons3ub + ): disjBlock = model._pyomo_gdp_gdp_to_minlp_reformulation.relaxedDisjuncts # first constraint From 2cc1547280682216690e315cafae1e7af06a4703 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 09:43:15 -0700 Subject: [PATCH 0713/1204] Change to PyQt6 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cbc8f93823b..e68d03de2ac 100644 --- a/setup.py +++ b/setup.py @@ -278,10 +278,10 @@ def __ne__(self, other): #'pathos', # requested for #963, but PR currently closed 'pint', # units 'plotly', # incidence_analysis + 'PyQt6', # contrib.viewer 'python-louvain', # community_detection 'pyyaml', # core 'qtconsole', # contrib.viewer - 'qtpy', # contrib.viewer 'scipy', 'sympy', # differentiation 'xlrd', # dataportals From 08d80c6b5665a515d36229cba66859d967d3f245 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 09:47:38 -0700 Subject: [PATCH 0714/1204] Switching to pyqt5 for conda --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e68d03de2ac..1c9485a8d98 100644 --- a/setup.py +++ b/setup.py @@ -278,7 +278,7 @@ def __ne__(self, other): #'pathos', # requested for #963, but PR currently closed 'pint', # units 'plotly', # incidence_analysis - 'PyQt6', # contrib.viewer + 'PyQt5', # contrib.viewer 'python-louvain', # community_detection 'pyyaml', # core 'qtconsole', # contrib.viewer From 88e95b76c42ff8c4e23235bde3910970233e9932 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 09:59:36 -0700 Subject: [PATCH 0715/1204] Mark pyqt6 as only pypi --- .github/workflows/test_branches.yml | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index a11a0d51149..1c04738a207 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -21,7 +21,7 @@ defaults: env: PYTHONWARNINGS: ignore::UserWarning PYTHON_CORE_PKGS: wheel - PYPI_ONLY: z3-solver + PYPI_ONLY: z3-solver pyqt6 PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels CACHE_VER: v221013.1 NEOS_EMAIL: tests@pyomo.org @@ -192,7 +192,7 @@ jobs: # - install glpk # - ipopt needs: libopenblas-dev gfortran liblapack-dev sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ - install libopenblas-dev gfortran liblapack-dev glpk-utils + install libopenblas-dev gfortran liblapack-dev glpk-utils libgl1 sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os - name: Update Windows diff --git a/setup.py b/setup.py index 1c9485a8d98..e68d03de2ac 100644 --- a/setup.py +++ b/setup.py @@ -278,7 +278,7 @@ def __ne__(self, other): #'pathos', # requested for #963, but PR currently closed 'pint', # units 'plotly', # incidence_analysis - 'PyQt5', # contrib.viewer + 'PyQt6', # contrib.viewer 'python-louvain', # community_detection 'pyyaml', # core 'qtconsole', # contrib.viewer From db71ac123fdb5c92f96c11204af66fa934438eb3 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 10:04:34 -0700 Subject: [PATCH 0716/1204] Attempting to determine what the problem is --- .github/workflows/test_branches.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 1c04738a207..49563561332 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -319,7 +319,7 @@ jobs: fi # HACK: Remove problem packages on conda+Linux if test "${{matrix.TARGET}}" == linux; then - EXCLUDE="casadi numdifftools pint $EXCLUDE" + EXCLUDE="casadi numdifftools $EXCLUDE" fi EXCLUDE=`echo "$EXCLUDE" | xargs` if test -n "$EXCLUDE"; then @@ -329,6 +329,7 @@ jobs: fi for PKG in $PACKAGES; do if [[ " $PYPI_ONLY " == *" $PKG "* ]]; then + echo "Skipping $PKG" PYPI_DEPENDENCIES="$PYPI_DEPENDENCIES $PKG" else CONDA_DEPENDENCIES="$CONDA_DEPENDENCIES $PKG" From 4d29ecf4267ea6e715e3b190127e85cd60475b5c Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 10:08:11 -0700 Subject: [PATCH 0717/1204] pyqt6 is being ignored --- .github/workflows/test_branches.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 49563561332..aad2c9ce420 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -328,8 +328,9 @@ jobs: done fi for PKG in $PACKAGES; do + echo "######### Analyzing $PKG" if [[ " $PYPI_ONLY " == *" $PKG "* ]]; then - echo "Skipping $PKG" + echo "######## Skipping $PKG" PYPI_DEPENDENCIES="$PYPI_DEPENDENCIES $PKG" else CONDA_DEPENDENCIES="$CONDA_DEPENDENCIES $PKG" From 62af8d6a761e443dbc0af027623eddc55f23b6a8 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 10:11:30 -0700 Subject: [PATCH 0718/1204] Capitalization. --- .github/workflows/test_branches.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index aad2c9ce420..843baa7c505 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -21,7 +21,7 @@ defaults: env: PYTHONWARNINGS: ignore::UserWarning PYTHON_CORE_PKGS: wheel - PYPI_ONLY: z3-solver pyqt6 + PYPI_ONLY: z3-solver PyQt6 PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels CACHE_VER: v221013.1 NEOS_EMAIL: tests@pyomo.org @@ -328,9 +328,7 @@ jobs: done fi for PKG in $PACKAGES; do - echo "######### Analyzing $PKG" if [[ " $PYPI_ONLY " == *" $PKG "* ]]; then - echo "######## Skipping $PKG" PYPI_DEPENDENCIES="$PYPI_DEPENDENCIES $PKG" else CONDA_DEPENDENCIES="$CONDA_DEPENDENCIES $PKG" From 7c5f1e1c77992bdf93712f89716698585308dcfb Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 10:37:36 -0700 Subject: [PATCH 0719/1204] UI testing not just on conda --- .github/workflows/test_branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 843baa7c505..4826bd38f59 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -217,7 +217,7 @@ jobs: # have support for OSX, but we don't do any OSX/conda testing inherently. # This is just to protect us in case we do add that into the matrix someday. - name: Set up UI testing infrastructure - if: ${{ matrix.PYENV == 'conda' && matrix.TARGET != 'osx' }} + if: ${{ matrix.TARGET != 'osx' }} uses: pyvista/setup-headless-display-action@v2 with: qt: true From 66f13c2b06e97f4772ecaff76934936424681bf6 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 10:39:15 -0700 Subject: [PATCH 0720/1204] Another lib missing on Linux --- .github/workflows/test_branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 4826bd38f59..d0d59b889e2 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -192,7 +192,7 @@ jobs: # - install glpk # - ipopt needs: libopenblas-dev gfortran liblapack-dev sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ - install libopenblas-dev gfortran liblapack-dev glpk-utils libgl1 + install libopenblas-dev gfortran liblapack-dev glpk-utils libgl1 libegl1 sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os - name: Update Windows From 823887a0b00a6e3591a5911fa141dfd03422f4ea Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 10:54:40 -0700 Subject: [PATCH 0721/1204] Explicitly exclude PyQt6 from conda --- .github/workflows/test_branches.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index d0d59b889e2..7562da1ea27 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -21,7 +21,7 @@ defaults: env: PYTHONWARNINGS: ignore::UserWarning PYTHON_CORE_PKGS: wheel - PYPI_ONLY: z3-solver PyQt6 + PYPI_ONLY: z3-solver PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels CACHE_VER: v221013.1 NEOS_EMAIL: tests@pyomo.org @@ -319,7 +319,7 @@ jobs: fi # HACK: Remove problem packages on conda+Linux if test "${{matrix.TARGET}}" == linux; then - EXCLUDE="casadi numdifftools $EXCLUDE" + EXCLUDE="casadi numdifftools PyQt6 $EXCLUDE" fi EXCLUDE=`echo "$EXCLUDE" | xargs` if test -n "$EXCLUDE"; then @@ -356,7 +356,7 @@ jobs: | sed -r 's/\s+/ /g' | cut -d\ -f3) || echo "" if test -n "$_BUILDS"; then _ISPY=$(echo "$_BUILDS" | grep "^py") \ - || echo "\nINFO: No python build detected." + || echo "INFO: No python build detected." _PYOK=$(echo "$_BUILDS" | grep -E "^($PYVER|pyh)") \ || echo "INFO: No python build matching $PYVER detected." if test -z "$_ISPY" -o -n "$_PYOK"; then From 13125c1cf928f6b2c9961cdbdae9a8ac9cb4b23b Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 11:01:11 -0700 Subject: [PATCH 0722/1204] CapiTaliZation --- .github/workflows/test_branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 7562da1ea27..6a43b136d1c 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -319,7 +319,7 @@ jobs: fi # HACK: Remove problem packages on conda+Linux if test "${{matrix.TARGET}}" == linux; then - EXCLUDE="casadi numdifftools PyQt6 $EXCLUDE" + EXCLUDE="casadi numdifftools pyqt6 $EXCLUDE" fi EXCLUDE=`echo "$EXCLUDE" | xargs` if test -n "$EXCLUDE"; then From 30ae6c68004fbebe0240ff01665f0236014fb6ab Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 11:07:51 -0700 Subject: [PATCH 0723/1204] Conda is annoying --- .github/workflows/test_branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 6a43b136d1c..9153922ffcc 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -321,7 +321,7 @@ jobs: if test "${{matrix.TARGET}}" == linux; then EXCLUDE="casadi numdifftools pyqt6 $EXCLUDE" fi - EXCLUDE=`echo "$EXCLUDE" | xargs` + EXCLUDE=`echo "$EXCLUDE PyQt6 pyqt6" | xargs` if test -n "$EXCLUDE"; then for WORD in $EXCLUDE; do PACKAGES=${PACKAGES//$WORD / } From f0f822b3d4163a06b0540d4bcc01a39cd0316112 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 11:48:19 -0700 Subject: [PATCH 0724/1204] Sync pr workflow with branch --- .github/workflows/test_branches.yml | 5 ++-- .github/workflows/test_pr_and_main.yml | 36 +++++++++++++------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 9153922ffcc..a3804575f83 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -191,6 +191,7 @@ jobs: # Notes: # - install glpk # - ipopt needs: libopenblas-dev gfortran liblapack-dev + # - contrib.viewer needs: libg1l libegl1 sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ install libopenblas-dev gfortran liblapack-dev glpk-utils libgl1 libegl1 sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os @@ -214,7 +215,7 @@ jobs: python-version: ${{ matrix.python }} # This is necessary for qt (UI) tests; the package utilized here does not - # have support for OSX, but we don't do any OSX/conda testing inherently. + # have support for OSX, but we don't do any OSX testing inherently. # This is just to protect us in case we do add that into the matrix someday. - name: Set up UI testing infrastructure if: ${{ matrix.TARGET != 'osx' }} @@ -319,7 +320,7 @@ jobs: fi # HACK: Remove problem packages on conda+Linux if test "${{matrix.TARGET}}" == linux; then - EXCLUDE="casadi numdifftools pyqt6 $EXCLUDE" + EXCLUDE="casadi numdifftools $EXCLUDE" fi EXCLUDE=`echo "$EXCLUDE PyQt6 pyqt6" | xargs` if test -n "$EXCLUDE"; then diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 6e5604bea47..483de737a8a 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -221,8 +221,9 @@ jobs: # Notes: # - install glpk # - ipopt needs: libopenblas-dev gfortran liblapack-dev + # - contrib.viewer needs: libg1l libegl1 sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ - install libopenblas-dev gfortran liblapack-dev glpk-utils + install libopenblas-dev gfortran liblapack-dev glpk-utils libgl1 libegl1 sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os - name: Update Windows @@ -243,6 +244,16 @@ jobs: auto-update-conda: false python-version: ${{ matrix.python }} + # This is necessary for qt (UI) tests; the package utilized here does not + # have support for OSX, but we don't do any OSX testing inherently. + # This is just to protect us in case we do add that into the matrix someday. + - name: Set up UI testing infrastructure + if: ${{ matrix.TARGET != 'osx' }} + uses: pyvista/setup-headless-display-action@v2 + with: + qt: true + pyvista: false + # GitHub actions is very fragile when it comes to setting up various # Python interpreters, expecially the setup-miniconda interface. # Per the setup-miniconda documentation, it is important to always @@ -339,9 +350,9 @@ jobs: fi # HACK: Remove problem packages on conda+Linux if test "${{matrix.TARGET}}" == linux; then - EXCLUDE="casadi numdifftools pint $EXCLUDE" + EXCLUDE="casadi numdifftools $EXCLUDE" fi - EXCLUDE=`echo "$EXCLUDE" | xargs` + EXCLUDE=`echo "$EXCLUDE PyQt6 pyqt6" | xargs` if test -n "$EXCLUDE"; then for WORD in $EXCLUDE; do PACKAGES=${PACKAGES//$WORD / } @@ -376,10 +387,11 @@ jobs: | sed -r 's/\s+/ /g' | cut -d\ -f3) || echo "" if test -n "$_BUILDS"; then _ISPY=$(echo "$_BUILDS" | grep "^py") \ - || echo "No python build detected" - _PYOK=$(echo "$_BUILDS" | grep "^$PYVER") \ - || echo "No python build matching $PYVER detected" + || echo "INFO: No python build detected." + _PYOK=$(echo "$_BUILDS" | grep -E "^($PYVER|pyh)") \ + || echo "INFO: No python build matching $PYVER detected." if test -z "$_ISPY" -o -n "$_PYOK"; then + echo "" echo "... INSTALLING $PKG" conda install -y "$PKG" || _BUILDS="" fi @@ -388,18 +400,6 @@ jobs: echo "WARNING: $PKG is not available" fi done - # TODO: This is a hack to stop test_qt.py from running until we - # can better troubleshoot why it fails on GHA - for QTPACKAGE in qt pyqt; do - # Because conda is insane, removing packages can cause - # unrelated packages to be updated (breaking version - # specifications specified previously, e.g., in - # setup.py). There doesn't appear to be a good - # workaround, so we will just force-remove (recognizing - # that it may break other conda cruft). - conda remove --force-remove $QTPACKAGE \ - || echo "$QTPACKAGE not in this environment" - done fi # Re-try Pyomo (optional) dependencies with pip if test -n "$PYPI_DEPENDENCIES"; then From a80be20f6096c3768f8e61f0509d5939d9cac2e5 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 11:56:47 -0700 Subject: [PATCH 0725/1204] Fix misleading comment --- .github/workflows/test_branches.yml | 3 +-- .github/workflows/test_pr_and_main.yml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index a3804575f83..d6e7b3f39c6 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -215,8 +215,7 @@ jobs: python-version: ${{ matrix.python }} # This is necessary for qt (UI) tests; the package utilized here does not - # have support for OSX, but we don't do any OSX testing inherently. - # This is just to protect us in case we do add that into the matrix someday. + # have support for OSX. - name: Set up UI testing infrastructure if: ${{ matrix.TARGET != 'osx' }} uses: pyvista/setup-headless-display-action@v2 diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 483de737a8a..3d5ef566f2c 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -245,8 +245,7 @@ jobs: python-version: ${{ matrix.python }} # This is necessary for qt (UI) tests; the package utilized here does not - # have support for OSX, but we don't do any OSX testing inherently. - # This is just to protect us in case we do add that into the matrix someday. + # have support for OSX. - name: Set up UI testing infrastructure if: ${{ matrix.TARGET != 'osx' }} uses: pyvista/setup-headless-display-action@v2 From 36b6925c094e1d6275703ea8bd62946f5053e0f7 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 12:05:49 -0700 Subject: [PATCH 0726/1204] Moving deps / exclusions to address slim and pypy --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index d6e7b3f39c6..a14447911da 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -22,7 +22,7 @@ env: PYTHONWARNINGS: ignore::UserWarning PYTHON_CORE_PKGS: wheel PYPI_ONLY: z3-solver - PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels + PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels PyQt6 CACHE_VER: v221013.1 NEOS_EMAIL: tests@pyomo.org SRC_REF: ${{ github.head_ref || github.ref }} diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 3d5ef566f2c..608aecc3552 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -25,7 +25,7 @@ env: PYTHONWARNINGS: ignore::UserWarning PYTHON_CORE_PKGS: wheel PYPI_ONLY: z3-solver - PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels + PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels PyQt6 CACHE_VER: v221013.1 NEOS_EMAIL: tests@pyomo.org SRC_REF: ${{ github.head_ref || github.ref }} diff --git a/setup.py b/setup.py index e68d03de2ac..dbc1a4a6b53 100644 --- a/setup.py +++ b/setup.py @@ -250,7 +250,6 @@ def __ne__(self, other): 'pybind11', 'pytest', 'pytest-parallel', - 'pytest-qt', # for testing contrib.viewer ], 'docs': [ 'Sphinx>4', @@ -279,6 +278,7 @@ def __ne__(self, other): 'pint', # units 'plotly', # incidence_analysis 'PyQt6', # contrib.viewer + 'pytest-qt', # for testing contrib.viewer 'python-louvain', # community_detection 'pyyaml', # core 'qtconsole', # contrib.viewer From f957fe11c9b7e7b32b7dd5116359b025fcf2d285 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 12:15:50 -0700 Subject: [PATCH 0727/1204] Skip anything qt related on pypy --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index a14447911da..53c41d4bbf5 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -22,7 +22,7 @@ env: PYTHONWARNINGS: ignore::UserWarning PYTHON_CORE_PKGS: wheel PYPI_ONLY: z3-solver - PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels PyQt6 + PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels PyQt6 pytest-qt CACHE_VER: v221013.1 NEOS_EMAIL: tests@pyomo.org SRC_REF: ${{ github.head_ref || github.ref }} diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 608aecc3552..39aeed6f123 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -25,7 +25,7 @@ env: PYTHONWARNINGS: ignore::UserWarning PYTHON_CORE_PKGS: wheel PYPI_ONLY: z3-solver - PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels PyQt6 + PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels PyQt6 pytest-qt CACHE_VER: v221013.1 NEOS_EMAIL: tests@pyomo.org SRC_REF: ${{ github.head_ref || github.ref }} From a93d793e49f9f209f188d5a7ee1a72829a7423c4 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 10 Jan 2024 14:33:02 -0500 Subject: [PATCH 0728/1204] change all ''' to """ --- pyomo/contrib/mindtpy/algorithm_base_class.py | 6 +++--- pyomo/contrib/mindtpy/extended_cutting_plane.py | 2 +- pyomo/contrib/mindtpy/feasibility_pump.py | 2 +- pyomo/contrib/mindtpy/global_outer_approximation.py | 2 +- pyomo/contrib/mindtpy/outer_approximation.py | 2 +- pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 05f1e4389d3..d732e95c422 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -519,9 +519,9 @@ def get_primal_integral(self): return primal_integral def get_integral_info(self): - ''' + """ Obtain primal integral, dual integral and primal dual gap integral. - ''' + """ self.primal_integral = self.get_primal_integral() self.dual_integral = self.get_dual_integral() self.primal_dual_gap_integral = self.primal_integral + self.dual_integral @@ -2598,7 +2598,7 @@ def fp_loop(self): self.working_model.MindtPy_utils.cuts.del_component('fp_orthogonality_cuts') def initialize_mip_problem(self): - '''Deactivate the nonlinear constraints to create the MIP problem.''' + """Deactivate the nonlinear constraints to create the MIP problem.""" # if single tree is activated, we need to add bounds for unbounded variables in nonlinear constraints to avoid unbounded main problem. config = self.config if config.single_tree: diff --git a/pyomo/contrib/mindtpy/extended_cutting_plane.py b/pyomo/contrib/mindtpy/extended_cutting_plane.py index f5fa205e091..ac13e352e35 100644 --- a/pyomo/contrib/mindtpy/extended_cutting_plane.py +++ b/pyomo/contrib/mindtpy/extended_cutting_plane.py @@ -84,7 +84,7 @@ def check_config(self): super().check_config() def initialize_mip_problem(self): - '''Deactivate the nonlinear constraints to create the MIP problem.''' + """Deactivate the nonlinear constraints to create the MIP problem.""" super().initialize_mip_problem() self.jacobians = calc_jacobians( self.mip.MindtPy_utils.nonlinear_constraint_list, diff --git a/pyomo/contrib/mindtpy/feasibility_pump.py b/pyomo/contrib/mindtpy/feasibility_pump.py index 9d5be89bab5..a34cceb014c 100644 --- a/pyomo/contrib/mindtpy/feasibility_pump.py +++ b/pyomo/contrib/mindtpy/feasibility_pump.py @@ -44,7 +44,7 @@ def check_config(self): super().check_config() def initialize_mip_problem(self): - '''Deactivate the nonlinear constraints to create the MIP problem.''' + """Deactivate the nonlinear constraints to create the MIP problem.""" super().initialize_mip_problem() self.jacobians = calc_jacobians( self.mip.MindtPy_utils.nonlinear_constraint_list, diff --git a/pyomo/contrib/mindtpy/global_outer_approximation.py b/pyomo/contrib/mindtpy/global_outer_approximation.py index 817fb0bf4a8..70fc4cffb90 100644 --- a/pyomo/contrib/mindtpy/global_outer_approximation.py +++ b/pyomo/contrib/mindtpy/global_outer_approximation.py @@ -67,7 +67,7 @@ def check_config(self): super().check_config() def initialize_mip_problem(self): - '''Deactivate the nonlinear constraints to create the MIP problem.''' + """Deactivate the nonlinear constraints to create the MIP problem.""" super().initialize_mip_problem() self.mip.MindtPy_utils.cuts.aff_cuts = ConstraintList(doc='Affine cuts') diff --git a/pyomo/contrib/mindtpy/outer_approximation.py b/pyomo/contrib/mindtpy/outer_approximation.py index 6d790ce70d0..f6e6147724e 100644 --- a/pyomo/contrib/mindtpy/outer_approximation.py +++ b/pyomo/contrib/mindtpy/outer_approximation.py @@ -94,7 +94,7 @@ def check_config(self): _MindtPyAlgorithm.check_config(self) def initialize_mip_problem(self): - '''Deactivate the nonlinear constraints to create the MIP problem.''' + """Deactivate the nonlinear constraints to create the MIP problem.""" super().initialize_mip_problem() self.jacobians = calc_jacobians( self.mip.MindtPy_utils.nonlinear_constraint_list, diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index 547efc0a74c..9c1f33e80cc 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -114,7 +114,7 @@ def evaluate_jacobian_equality_constraints(self): """Evaluate the Jacobian of the equality constraints.""" return None - ''' + """ def _extract_and_assemble_fim(self): M = np.zeros((self.n_parameters, self.n_parameters)) for i in range(self.n_parameters): @@ -122,7 +122,7 @@ def _extract_and_assemble_fim(self): M[i,k] = self._input_values[self.ele_to_order[(i,k)]] return M - ''' + """ def evaluate_jacobian_outputs(self): """Evaluate the Jacobian of the outputs.""" From 666cbaa4e776947032e86887473f1296a9baacff Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 10 Jan 2024 14:39:32 -0500 Subject: [PATCH 0729/1204] rename int_sol_2_cuts_ind to integer_solution_to_cuts_index --- pyomo/contrib/mindtpy/algorithm_base_class.py | 8 ++++---- pyomo/contrib/mindtpy/single_tree.py | 9 ++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index d732e95c422..7e8d390976c 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -102,14 +102,14 @@ def __init__(self, **kwds): self.fixed_nlp = None # We store bounds, timing info, iteration count, incumbent, and the - # expression of the original (possibly nonlinear) objective function. + # Expression of the original (possibly nonlinear) objective function. self.results = SolverResults() self.timing = Bunch() self.curr_int_sol = [] self.should_terminate = False self.integer_list = [] - # dictionary {integer solution (list): [cuts begin index, cuts end index] (list)} - self.int_sol_2_cuts_ind = dict() + # Dictionary {integer solution (list): [cuts begin index, cuts end index] (list)} + self.integer_solution_to_cuts_index = dict() # Set up iteration counters self.nlp_iter = 0 @@ -813,7 +813,7 @@ def MindtPy_initialization(self): self.integer_list.append(self.curr_int_sol) fixed_nlp, fixed_nlp_result = self.solve_subproblem() self.handle_nlp_subproblem_tc(fixed_nlp, fixed_nlp_result) - self.int_sol_2_cuts_ind[self.curr_int_sol] = [ + self.integer_solution_to_cuts_index[self.curr_int_sol] = [ 1, len(self.mip.MindtPy_utils.cuts.oa_cuts), ] diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index c4d49e3afd6..10b1f21ec5c 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -906,7 +906,7 @@ def LazyOACallback_gurobi(cb_m, cb_opt, cb_where, mindtpy_solver, config): # Your callback should be prepared to cut off solutions that violate any of your lazy constraints, including those that have already been added. Node solutions will usually respect previously added lazy constraints, but not always. # https://www.gurobi.com/documentation/current/refman/cs_cb_addlazy.html # If this happens, MindtPy will look for the index of corresponding cuts, instead of solving the fixed-NLP again. - begin_index, end_index = mindtpy_solver.int_sol_2_cuts_ind[ + begin_index, end_index = mindtpy_solver.integer_solution_to_cuts_index[ mindtpy_solver.curr_int_sol ] for ind in range(begin_index, end_index + 1): @@ -924,10 +924,9 @@ def LazyOACallback_gurobi(cb_m, cb_opt, cb_where, mindtpy_solver, config): mindtpy_solver.handle_nlp_subproblem_tc(fixed_nlp, fixed_nlp_result, cb_opt) if config.strategy == 'OA': # store the cut index corresponding to current integer solution. - mindtpy_solver.int_sol_2_cuts_ind[mindtpy_solver.curr_int_sol] = [ - cut_ind + 1, - len(mindtpy_solver.mip.MindtPy_utils.cuts.oa_cuts), - ] + mindtpy_solver.integer_solution_to_cuts_index[ + mindtpy_solver.curr_int_sol + ] = [cut_ind + 1, len(mindtpy_solver.mip.MindtPy_utils.cuts.oa_cuts)] def handle_lazy_main_feasible_solution_gurobi(cb_m, cb_opt, mindtpy_solver, config): From 14ebdcdc8a03c947164b64729a24e53c4b1b02db Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 10 Jan 2024 15:46:05 -0500 Subject: [PATCH 0730/1204] add one more comment to CPLEX lazy constraint callback --- pyomo/contrib/mindtpy/single_tree.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index 10b1f21ec5c..145d85e0d37 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -666,6 +666,7 @@ def __call__(self): main_mip = self.main_mip mindtpy_solver = self.mindtpy_solver + # The lazy constraint callback may be invoked during MIP start processing. In that case get_solution_source returns mip_start_solution. # Reference: https://www.ibm.com/docs/en/icos/22.1.1?topic=SSSA5P_22.1.1/ilog.odms.cplex.help/refpythoncplex/html/cplex.callbacks.SolutionSource-class.htm # Another solution source is user_solution = 118, but it will not be encountered in LazyConstraintCallback. config.logger.info( From b143e87de6eb464cfec2b190114c6f068c9433ae Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 10 Jan 2024 15:46:51 -0500 Subject: [PATCH 0731/1204] remove the finished TODO --- pyomo/contrib/mindtpy/single_tree.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index 145d85e0d37..09b5e704f75 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -274,7 +274,6 @@ def add_lazy_affine_cuts(self, mindtpy_solver, config, opt): 'Skipping constraint %s due to MCPP error' % (constr.name) ) continue # skip to the next constraint - # TODO: check if the value of ccSlope and cvSlope is not Nan or inf. If so, we skip this. ccSlope = mc_eqn.subcc() cvSlope = mc_eqn.subcv() ccStart = mc_eqn.concave() From 09bda47d460033736c2789e0a8e6d5dbaf581d6a Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 10 Jan 2024 15:48:44 -0500 Subject: [PATCH 0732/1204] add TODO for self.abort() --- pyomo/contrib/mindtpy/single_tree.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index 09b5e704f75..228810a8f90 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -689,6 +689,7 @@ def __call__(self): mindtpy_solver.mip_start_lazy_oa_cuts = [] if mindtpy_solver.should_terminate: + # TODO: check the performance difference if we don't use self.abort() and let cplex terminate by itself. self.abort() return self.handle_lazy_main_feasible_solution(main_mip, mindtpy_solver, config, opt) @@ -744,6 +745,7 @@ def __call__(self): ) ) mindtpy_solver.results.solver.termination_condition = tc.optimal + # TODO: check the performance difference if we don't use self.abort() and let cplex terminate by itself. self.abort() return From ac7938f99e105d3713b8d5b6d49e6affe00ca3db Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 10 Jan 2024 15:43:03 -0700 Subject: [PATCH 0733/1204] Track change in PyPy behavior starting in 7.3.14 --- pyomo/core/tests/unit/test_initializer.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/pyomo/core/tests/unit/test_initializer.py b/pyomo/core/tests/unit/test_initializer.py index 5767406bdf7..a79c9c0efe6 100644 --- a/pyomo/core/tests/unit/test_initializer.py +++ b/pyomo/core/tests/unit/test_initializer.py @@ -11,6 +11,7 @@ import functools import pickle +import platform import types import pyomo.common.unittest as unittest @@ -37,6 +38,9 @@ from pyomo.environ import ConcreteModel, Var +is_pypy = platform.python_implementation().lower().startswith("pypy") + + def _init_scalar(m): return 1 @@ -561,11 +565,18 @@ def test_no_argspec(self): self.assertFalse(a.contains_indices()) self.assertEqual(a('111', 2), 7) - # Special case: getfullargspec fails on int, so we assume it is - # always an IndexedCallInitializer + # Special case: getfullargspec fails for int under CPython and + # PyPy<7.3.14, so we assume it is an IndexedCallInitializer. basetwo = functools.partial(int, '101', base=2) a = Initializer(basetwo) - self.assertIs(type(a), IndexedCallInitializer) + if is_pypy and sys.pypy_version_info[:3] >= (7, 3, 14): + # PyPy behavior diverged from CPython in 7.3.14. Arguably + # this is "more correct", so we will allow the difference to + # persist through Pyomo's Initializer handling (and not + # special case it there) + self.assertIs(type(a), ScalarCallInitializer) + else: + self.assertIs(type(a), IndexedCallInitializer) self.assertFalse(a.constant()) self.assertFalse(a.verified) self.assertFalse(a.contains_indices()) From 8b542aee6ee5558314f4b625f49b272705baaf6b Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 10 Jan 2024 15:43:28 -0700 Subject: [PATCH 0734/1204] Standardize check for PyPy --- pyomo/core/tests/unit/test_pickle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/core/tests/unit/test_pickle.py b/pyomo/core/tests/unit/test_pickle.py index 808db2e45f3..861704a2f9c 100644 --- a/pyomo/core/tests/unit/test_pickle.py +++ b/pyomo/core/tests/unit/test_pickle.py @@ -35,7 +35,7 @@ ) -using_pypy = platform.python_implementation() == "PyPy" +is_pypy = platform.python_implementation().lower().startswith("pypy") def obj_rule(model): @@ -322,7 +322,7 @@ def rule2(model, i): model.con = Constraint(rule=rule1) model.con2 = Constraint(model.a, rule=rule2) instance = model.create_instance() - if using_pypy: + if is_pypy: str_ = pickle.dumps(instance) tmp_ = pickle.loads(str_) else: From c22276fc824338991faed503f472965a15ff1749 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 10 Jan 2024 16:52:09 -0700 Subject: [PATCH 0735/1204] Add missing import --- pyomo/core/tests/unit/test_initializer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/core/tests/unit/test_initializer.py b/pyomo/core/tests/unit/test_initializer.py index a79c9c0efe6..35479612698 100644 --- a/pyomo/core/tests/unit/test_initializer.py +++ b/pyomo/core/tests/unit/test_initializer.py @@ -12,6 +12,7 @@ import functools import pickle import platform +import sys import types import pyomo.common.unittest as unittest From 131fd3f2977000ceadc903d88e9deac2c724bb81 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 10 Jan 2024 19:46:08 -0700 Subject: [PATCH 0736/1204] fix test: ScalarCallInitializers are constant --- pyomo/core/tests/unit/test_initializer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/core/tests/unit/test_initializer.py b/pyomo/core/tests/unit/test_initializer.py index 35479612698..b334a6b857b 100644 --- a/pyomo/core/tests/unit/test_initializer.py +++ b/pyomo/core/tests/unit/test_initializer.py @@ -576,9 +576,10 @@ def test_no_argspec(self): # persist through Pyomo's Initializer handling (and not # special case it there) self.assertIs(type(a), ScalarCallInitializer) + self.assertTrue(a.constant()) else: self.assertIs(type(a), IndexedCallInitializer) - self.assertFalse(a.constant()) + self.assertFalse(a.constant()) self.assertFalse(a.verified) self.assertFalse(a.contains_indices()) # but this is not callable, as int won't accept the 'model' From 317dae89cdb4eac00e006bc808d32d887a14d787 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 11 Jan 2024 14:30:08 -0500 Subject: [PATCH 0737/1204] update the version of MindtPy --- pyomo/contrib/mindtpy/MindtPy.py | 8 ++++++++ pyomo/contrib/mindtpy/__init__.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/MindtPy.py b/pyomo/contrib/mindtpy/MindtPy.py index 6eb27c4c649..bd873d950fd 100644 --- a/pyomo/contrib/mindtpy/MindtPy.py +++ b/pyomo/contrib/mindtpy/MindtPy.py @@ -50,6 +50,14 @@ - Add single-tree implementation. - Add support for cplex_persistent solver. - Fix bug in OA cut expression in cut_generation.py. + +24.1.11 changes: +- fix gurobi single tree termination check bug +- fix Gurobi single tree cycle handling +- fix bug in feasibility pump method +- add special handling for infeasible relaxed NLP +- update the log format of infeasible fixed NLP subproblems +- create a new copy_var_list_values function """ from pyomo.contrib.mindtpy import __version__ diff --git a/pyomo/contrib/mindtpy/__init__.py b/pyomo/contrib/mindtpy/__init__.py index 8e2c2d9eaa4..8dcd085211f 100644 --- a/pyomo/contrib/mindtpy/__init__.py +++ b/pyomo/contrib/mindtpy/__init__.py @@ -1 +1 @@ -__version__ = (0, 1, 0) +__version__ = (1, 0, 0) From 55b77d83db3b03556b5cd8484fa7311e72195c37 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Thu, 11 Jan 2024 12:41:18 -0700 Subject: [PATCH 0738/1204] factor out the binary variable in gdp to minlp transformation --- pyomo/gdp/plugins/gdp_to_minlp.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/gdp/plugins/gdp_to_minlp.py b/pyomo/gdp/plugins/gdp_to_minlp.py index bec9160ceca..5add18fd1cc 100644 --- a/pyomo/gdp/plugins/gdp_to_minlp.py +++ b/pyomo/gdp/plugins/gdp_to_minlp.py @@ -150,7 +150,7 @@ def _add_constraint_expressions( if (c.equality or lb is ub) and lb is not None: # equality newConstraint.add( - (name, i, 'eq'), c.body * indicator_var - lb * indicator_var == 0 + (name, i, 'eq'), (c.body - lb) * indicator_var == 0 ) constraintMap['transformedConstraints'][c] = [newConstraint[name, i, 'eq']] constraintMap['srcConstraints'][newConstraint[name, i, 'eq']] = c @@ -158,7 +158,7 @@ def _add_constraint_expressions( # inequality if lb is not None: newConstraint.add( - (name, i, 'lb'), 0 <= c.body * indicator_var - lb * indicator_var + (name, i, 'lb'), 0 <= (c.body - lb) * indicator_var ) constraintMap['transformedConstraints'][c] = [ newConstraint[name, i, 'lb'] @@ -166,7 +166,7 @@ def _add_constraint_expressions( constraintMap['srcConstraints'][newConstraint[name, i, 'lb']] = c if ub is not None: newConstraint.add( - (name, i, 'ub'), c.body * indicator_var - ub * indicator_var <= 0 + (name, i, 'ub'), (c.body - ub) * indicator_var <= 0 ) transformed = constraintMap['transformedConstraints'].get(c) if transformed is not None: From 679018282f7868e4babf5588ef5e6a88836e9fd9 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 11 Jan 2024 14:31:15 -0700 Subject: [PATCH 0739/1204] Simplify baseline tester to better leverage assertStructuredAlmostEqual --- pyomo/common/unittest.py | 63 ++++------------------------------------ 1 file changed, 5 insertions(+), 58 deletions(-) diff --git a/pyomo/common/unittest.py b/pyomo/common/unittest.py index cd5f46d00f5..5173008c759 100644 --- a/pyomo/common/unittest.py +++ b/pyomo/common/unittest.py @@ -786,13 +786,9 @@ def filter_file_contents(self, lines): s = line.find("seconds") + 7 line = line[s:] + item_list = [] items = line.strip().split() for i in items: - if not i: - continue - if i.startswith('/') or i.startswith(":\\", 1): - continue - # A few substitutions to get tests passing on pypy3 if ".inf" in i: i = i.replace(".inf", "inf") @@ -800,9 +796,10 @@ def filter_file_contents(self, lines): i = i.replace("null", "None") try: - filtered.append(float(i)) + item_list.append(float(i)) except: - filtered.append(i) + item_list.append(i) + filtered.append(item_list) return filtered @@ -811,56 +808,6 @@ def compare_baselines(self, test_output, baseline, abstol=1e-6, reltol=None): out_filtered = self.filter_file_contents(test_output.strip().split('\n')) base_filtered = self.filter_file_contents(baseline.strip().split('\n')) - if len(out_filtered) != len(base_filtered): - # it is likely that a solver returned a (slightly) nonzero - # value for a variable that is normally 0. Try to look for - # sequences like "['varname:', 'Value:', 1e-9]" that appear - # in one result but not the other and remove them. - i = 0 - while i < min(len(out_filtered), len(base_filtered)): - try: - self.assertStructuredAlmostEqual( - out_filtered[i], - base_filtered[i], - abstol=abstol, - reltol=reltol, - allow_second_superset=False, - ) - i += 1 - continue - except self.failureException: - pass - - try: - index_of_out_i_in_base = base_filtered.index(out_filtered[i], i) - except ValueError: - index_of_out_i_in_base = float('inf') - try: - index_of_base_i_in_out = out_filtered.index(base_filtered[i], i) - except ValueError: - index_of_base_i_in_out = float('inf') - if index_of_out_i_in_base < index_of_base_i_in_out: - extra = base_filtered - n = index_of_out_i_in_base - else: - extra = out_filtered - n = index_of_base_i_in_out - if n == float('inf'): - n = None - extra_terms = extra[i:n] - try: - assert len(extra_terms) % 3 == 0 - assert all(str(_)[-1] == ":" for _ in extra_terms[0::3]) - assert all(str(_) == "Value:" for _ in extra_terms[1::3]) - assert all(abs(_) < abstol for _ in extra_terms[2::3]) - except: - # This does not match the pattern we are looking - # for: quit processing, and let the next - # assertStructuredAlmostEqual raise the appropriate - # failureException - break - extra[i:n] = [] - try: self.assertStructuredAlmostEqual( out_filtered, @@ -869,6 +816,7 @@ def compare_baselines(self, test_output, baseline, abstol=1e-6, reltol=None): reltol=reltol, allow_second_superset=False, ) + return True except self.failureException: # Print helpful information when file comparison fails print('---------------------------------') @@ -881,7 +829,6 @@ def compare_baselines(self, test_output, baseline, abstol=1e-6, reltol=None): print('---------------------------------') print(test_output) raise - return True def python_test_driver(self, tname, test_file, base_file): bname = os.path.basename(test_file) From a3981f43d1aeaa58fd98b1d7e57ca7cb62c24c0c Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 11 Jan 2024 14:31:46 -0700 Subject: [PATCH 0740/1204] Add option to bypass cleandoc in Pyomo's log formatter --- pyomo/common/log.py | 3 ++- pyomo/core/base/PyomoModel.py | 1 + pyomo/core/base/block.py | 3 ++- pyomo/scripting/util.py | 4 ++-- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pyomo/common/log.py b/pyomo/common/log.py index bf2ae1e4c96..3097fe1c6de 100644 --- a/pyomo/common/log.py +++ b/pyomo/common/log.py @@ -139,7 +139,8 @@ def format(self, record): # # A standard approach is to use inspect.cleandoc, which # allows for the first line to have 0 indent. - msg = inspect.cleandoc(msg) + if getattr(record, 'cleandoc', True): + msg = inspect.cleandoc(msg) # Split the formatted log message (that currently has _flag in # lieu of the actual message content) into lines, then diff --git a/pyomo/core/base/PyomoModel.py b/pyomo/core/base/PyomoModel.py index 6aacabeb183..055f6f8450a 100644 --- a/pyomo/core/base/PyomoModel.py +++ b/pyomo/core/base/PyomoModel.py @@ -877,6 +877,7 @@ def _initialize_component( str(data).strip(), type(err).__name__, err, + extra={'cleandoc': False}, ) raise diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index fd5322ba686..d3950575435 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -1186,11 +1186,12 @@ def add_component(self, name, val): except: err = sys.exc_info()[1] logger.error( - "Constructing component '%s' from data=%s failed:\n%s: %s", + "Constructing component '%s' from data=%s failed:\n %s: %s", str(val.name), str(data).strip(), type(err).__name__, err, + extra={'cleandoc': False}, ) raise if generate_debug_messages: diff --git a/pyomo/scripting/util.py b/pyomo/scripting/util.py index 3ec0feccd66..5bc65eb35ae 100644 --- a/pyomo/scripting/util.py +++ b/pyomo/scripting/util.py @@ -146,7 +146,7 @@ def pyomo_excepthook(etype, value, tb): if valueStr[0] == valueStr[-1] and valueStr[0] in "\"'": valueStr = valueStr[1:-1] - logger.error(msg + valueStr) + logger.error(msg + valueStr, extra={'cleandoc': False}) tb_list = traceback.extract_tb(tb, None) i = 0 @@ -1129,7 +1129,7 @@ def _run_command_impl(command, parser, args, name, data, options): if type(err) == KeyError and errStr != "None": errStr = str(err).replace(r"\n", "\n")[1:-1] - logger.error(msg + errStr) + logger.error(msg + errStr, extra={'cleandoc': False}) errorcode = 1 return retval, errorcode From 3bea8fc6ce5909d30203b2c0a44a168fc366732a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 12 Jan 2024 01:13:33 -0700 Subject: [PATCH 0741/1204] Remove "values" from results when they are within abstol of 0 --- pyomo/common/unittest.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/pyomo/common/unittest.py b/pyomo/common/unittest.py index 5173008c759..7af4ad99bda 100644 --- a/pyomo/common/unittest.py +++ b/pyomo/common/unittest.py @@ -764,7 +764,7 @@ def filter_fcn(self, line): return True return False - def filter_file_contents(self, lines): + def filter_file_contents(self, lines, abstol=None): filtered = [] deprecated = None for line in lines: @@ -799,14 +799,31 @@ def filter_file_contents(self, lines): item_list.append(float(i)) except: item_list.append(i) - filtered.append(item_list) + + # We can get printed results objects where the baseline is + # exactly 0 (and omitted) and the test is slightly non-zero. + # We will look for the pattern of values printed from + # results objects and remote them if they are within + # tolerance of 0 + if ( + len(item_list) == 2 + and item_list[0] == 'Value:' + and abs(item_list[1]) < (abstol or 0) + and len(filtered[-1]) == 1 + and filtered[-1][-1] == ':' + ): + filtered.pop() + else: + filtered.append(item_list) return filtered def compare_baselines(self, test_output, baseline, abstol=1e-6, reltol=None): # Filter files independently and then compare filtered contents - out_filtered = self.filter_file_contents(test_output.strip().split('\n')) - base_filtered = self.filter_file_contents(baseline.strip().split('\n')) + out_filtered = self.filter_file_contents( + test_output.strip().split('\n'), abstol + ) + base_filtered = self.filter_file_contents(baseline.strip().split('\n'), abstol) try: self.assertStructuredAlmostEqual( From 902caa287e4f59b94dc1bdd2b1253587969260f0 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 12 Jan 2024 01:40:26 -0700 Subject: [PATCH 0742/1204] Rename BaseLineTestDriver and compare_baselines --- doc/OnlineDocs/tests/test_examples.py | 8 ++++---- examples/pyomobook/test_book_examples.py | 8 ++++---- pyomo/common/unittest.py | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/doc/OnlineDocs/tests/test_examples.py b/doc/OnlineDocs/tests/test_examples.py index 46c258e91f4..e2563745b07 100644 --- a/doc/OnlineDocs/tests/test_examples.py +++ b/doc/OnlineDocs/tests/test_examples.py @@ -29,11 +29,11 @@ currdir = this_file_dir() -class TestOnlineDocExamples(unittest.BaseLineTestDriver, unittest.TestCase): +class TestOnlineDocExamples(unittest.BaselineTestDriver, unittest.TestCase): # Only test files in directories ending in -ch. These directories # contain the updated python and scripting files corresponding to # each chapter in the book. - py_tests, sh_tests = unittest.BaseLineTestDriver.gather_tests( + py_tests, sh_tests = unittest.BaselineTestDriver.gather_tests( list(filter(os.path.isdir, glob.glob(os.path.join(currdir, '*')))) ) @@ -57,13 +57,13 @@ class TestOnlineDocExamples(unittest.BaseLineTestDriver, unittest.TestCase): } @parameterized.parameterized.expand( - sh_tests, name_func=unittest.BaseLineTestDriver.custom_name_func + sh_tests, name_func=unittest.BaselineTestDriver.custom_name_func ) def test_sh(self, tname, test_file, base_file): self.shell_test_driver(tname, test_file, base_file) @parameterized.parameterized.expand( - py_tests, name_func=unittest.BaseLineTestDriver.custom_name_func + py_tests, name_func=unittest.BaselineTestDriver.custom_name_func ) def test_py(self, tname, test_file, base_file): self.python_test_driver(tname, test_file, base_file) diff --git a/examples/pyomobook/test_book_examples.py b/examples/pyomobook/test_book_examples.py index af7e9e33d20..fea9a63d572 100644 --- a/examples/pyomobook/test_book_examples.py +++ b/examples/pyomobook/test_book_examples.py @@ -29,11 +29,11 @@ currdir = this_file_dir() -class TestBookExamples(unittest.BaseLineTestDriver, unittest.TestCase): +class TestBookExamples(unittest.BaselineTestDriver, unittest.TestCase): # Only test files in directories ending in -ch. These directories # contain the updated python and scripting files corresponding to # each chapter in the book. - py_tests, sh_tests = unittest.BaseLineTestDriver.gather_tests( + py_tests, sh_tests = unittest.BaselineTestDriver.gather_tests( list(filter(os.path.isdir, glob.glob(os.path.join(currdir, '*-ch')))) ) @@ -142,13 +142,13 @@ def _check_gurobi_fully_licensed(self): self.__class__.solver_available['gurobi_license'] = False @parameterized.parameterized.expand( - sh_tests, name_func=unittest.BaseLineTestDriver.custom_name_func + sh_tests, name_func=unittest.BaselineTestDriver.custom_name_func ) def test_book_sh(self, tname, test_file, base_file): self.shell_test_driver(tname, test_file, base_file) @parameterized.parameterized.expand( - py_tests, name_func=unittest.BaseLineTestDriver.custom_name_func + py_tests, name_func=unittest.BaselineTestDriver.custom_name_func ) def test_book_py(self, tname, test_file, base_file): self.python_test_driver(tname, test_file, base_file) diff --git a/pyomo/common/unittest.py b/pyomo/common/unittest.py index 7af4ad99bda..77597d7b419 100644 --- a/pyomo/common/unittest.py +++ b/pyomo/common/unittest.py @@ -556,7 +556,7 @@ def assertRaisesRegex(self, expected_exception, expected_regex, *args, **kwargs) return context.handle('assertRaisesRegex', args, kwargs) -class BaseLineTestDriver(object): +class BaselineTestDriver(object): """Generic driver for performing baseline tests in bulk This test driver was originally crafted for testing the examples in @@ -818,7 +818,7 @@ def filter_file_contents(self, lines, abstol=None): return filtered - def compare_baselines(self, test_output, baseline, abstol=1e-6, reltol=None): + def compare_baseline(self, test_output, baseline, abstol=1e-6, reltol=None): # Filter files independently and then compare filtered contents out_filtered = self.filter_file_contents( test_output.strip().split('\n'), abstol @@ -875,7 +875,7 @@ def python_test_driver(self, tname, test_file, base_file): os.chdir(cwd) try: - self.compare_baselines(OUT.getvalue(), baseline) + self.compare_baseline(OUT.getvalue(), baseline) except: if os.environ.get('PYOMO_TEST_UPDATE_BASELINES', None): with open(base_file, 'w') as FILE: @@ -915,7 +915,7 @@ def shell_test_driver(self, tname, test_file, base_file): os.chdir(cwd) try: - self.compare_baselines(rc.stdout.decode(), baseline) + self.compare_baseline(rc.stdout.decode(), baseline) except: if os.environ.get('PYOMO_TEST_UPDATE_BASELINES', None): with open(base_file, 'w') as FILE: From b94c19d7a1a0d330b9685619962eccacb9da431a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 12 Jan 2024 01:40:41 -0700 Subject: [PATCH 0743/1204] Fix filter logic error --- pyomo/common/unittest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/common/unittest.py b/pyomo/common/unittest.py index 77597d7b419..6b416b82c2b 100644 --- a/pyomo/common/unittest.py +++ b/pyomo/common/unittest.py @@ -808,9 +808,10 @@ def filter_file_contents(self, lines, abstol=None): if ( len(item_list) == 2 and item_list[0] == 'Value:' + and type(item_list[1]) is float and abs(item_list[1]) < (abstol or 0) and len(filtered[-1]) == 1 - and filtered[-1][-1] == ':' + and filtered[-1][0][-1] == ':' ): filtered.pop() else: From 44a58b76bf91f7a1c1c7d011ea08e2c1a6c81070 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 12 Jan 2024 01:40:45 -0700 Subject: [PATCH 0744/1204] Add basic testing for BaselineTestDriver --- pyomo/common/tests/test_unittest.py | 193 ++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/pyomo/common/tests/test_unittest.py b/pyomo/common/tests/test_unittest.py index a87fc57da9e..ef97e73d062 100644 --- a/pyomo/common/tests/test_unittest.py +++ b/pyomo/common/tests/test_unittest.py @@ -236,5 +236,198 @@ def test_bound_function_require_fork(self): self.bound_function_require_fork() +baseline = """ +[ 0.00] Setting up Pyomo environment +[ 0.00] Applying Pyomo preprocessing actions +[ 0.00] Creating model +[ 0.00] Applying solver +[ 0.05] Processing results + Number of solutions: 1 + Solution Information + Gap: None + Status: optimal + Function Value: -9.99943939749e-05 + Solver results file: results.yml +[ 0.05] Applying Pyomo postprocessing actions +[ 0.05] Pyomo Finished +# ========================================================== +# = Solver Results = +# ========================================================== +# ---------------------------------------------------------- +# Problem Information +# ---------------------------------------------------------- +Problem: +- Lower bound: -inf + Upper bound: inf + Number of objectives: 1 + Number of constraints: 3 + Number of variables: 3 + Sense: unknown +# ---------------------------------------------------------- +# Solver Information +# ---------------------------------------------------------- +Solver: +- Status: ok + Message: Ipopt 3.12.3\x3a Optimal Solution Found + Termination condition: optimal + Id: 0 + Error rc: 0 + Time: 0.0408430099487 +# ---------------------------------------------------------- +# Solution Information +# ---------------------------------------------------------- +Solution: +- number of solutions: 1 + number of solutions displayed: 1 +- Gap: None + Status: optimal + Message: Ipopt 3.12.3\x3a Optimal Solution Found + Objective: + f1: + Value: -9.99943939749e-05 + Variable: + compl.v: + Value: 9.99943939749e-05 + y: + Value: 9.99943939749e-05 + Constraint: No values +""" + +pass_ref = """ +[ 0.00] Setting up Pyomo environment +[ 0.00] Applying Pyomo preprocessing actions +[ 0.00] Creating model +[ 0.00] Applying solver +[ 0.05] Processing results + Number of solutions: 1 + Solution Information + Gap: None + Status: optimal + Function Value: -0.00010001318188373491 + Solver results file: results.yml +[ 0.05] Applying Pyomo postprocessing actions +[ 0.05] Pyomo Finished +# ========================================================== +# = Solver Results = +# ========================================================== +# ---------------------------------------------------------- +# Problem Information +# ---------------------------------------------------------- +Problem: +- Lower bound: -inf + Upper bound: inf + Number of objectives: 1 + Number of constraints: 3 + Number of variables: 3 + Sense: unknown +# ---------------------------------------------------------- +# Solver Information +# ---------------------------------------------------------- +Solver: +- Status: ok + Message: Ipopt 3.14.13\x3a Optimal Solution Found + Termination condition: optimal + Id: 0 + Error rc: 0 + Time: 0.04224729537963867 +# ---------------------------------------------------------- +# Solution Information +# ---------------------------------------------------------- +Solution: +- number of solutions: 1 + number of solutions displayed: 1 +- Gap: None + Status: optimal + Message: Ipopt 3.14.13\x3a Optimal Solution Found + Objective: + f1: + Value: -0.00010001318188373491 + Variable: + compl.v: + Value: 9.99943939749205e-05 + x: + Value: -9.39395440720558e-09 + y: + Value: 9.99943939749205e-05 + Constraint: No values + +""" + +fail_ref = """ +[ 0.00] Setting up Pyomo environment +[ 0.00] Applying Pyomo preprocessing actions +[ 0.00] Creating model +[ 0.00] Applying solver +[ 0.05] Processing results + Number of solutions: 1 + Solution Information + Gap: None + Status: optimal + Function Value: -0.00010001318188373491 + Solver results file: results.yml +[ 0.05] Applying Pyomo postprocessing actions +[ 0.05] Pyomo Finished +# ========================================================== +# = Solver Results = +# ========================================================== +# ---------------------------------------------------------- +# Problem Information +# ---------------------------------------------------------- +Problem: +- Lower bound: -inf + Upper bound: inf + Number of objectives: 1 + Number of constraints: 3 + Number of variables: 3 + Sense: unknown +# ---------------------------------------------------------- +# Solver Information +# ---------------------------------------------------------- +Solver: +- Status: ok + Message: Ipopt 3.14.13\x3a Optimal Solution Found + Termination condition: optimal + Id: 0 + Error rc: 0 + Time: 0.04224729537963867 +# ---------------------------------------------------------- +# Solution Information +# ---------------------------------------------------------- +Solution: +- number of solutions: 1 + number of solutions displayed: 1 +- Gap: None + Status: optimal + Message: Ipopt 3.14.13\x3a Optimal Solution Found + Objective: + f1: + Value: -0.00010001318188373491 + Variable: + compl.v: + Value: 9.79943939749205e-05 + x: + Value: -9.39395440720558e-09 + y: + Value: 9.99943939749205e-05 + Constraint: No values + +""" + + +class TestBaselineTestDriver(unittest.BaselineTestDriver, unittest.TestCase): + solver_dependencies = {} + package_dependencies = {} + + def test_baseline_pass(self): + self.compare_baseline(pass_ref, baseline, abstol=1e-6) + + with self.assertRaises(self.failureException): + self.compare_baseline(pass_ref, baseline, None) + + def test_baseline_fail(self): + with self.assertRaises(self.failureException): + self.compare_baseline(fail_ref, baseline) + + if __name__ == '__main__': unittest.main() From 35050837a7e731e62519f4a7864f1aba49badb1d Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 12 Jan 2024 01:50:57 -0700 Subject: [PATCH 0745/1204] Update test headers to clarify matplotlib_available import --- doc/OnlineDocs/tests/test_examples.py | 10 ++++------ examples/pyomobook/test_book_examples.py | 10 ++++------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/doc/OnlineDocs/tests/test_examples.py b/doc/OnlineDocs/tests/test_examples.py index e2563745b07..33cbab2e8b4 100644 --- a/doc/OnlineDocs/tests/test_examples.py +++ b/doc/OnlineDocs/tests/test_examples.py @@ -12,22 +12,20 @@ import pyomo.common.unittest as unittest import glob import os -from pyomo.common.dependencies import attempt_import +from pyomo.common.dependencies import attempt_import, matplotlib_available from pyomo.common.fileutils import this_file_dir import pyomo.environ as pyo +currdir = this_file_dir() + parameterized, param_available = attempt_import('parameterized') if not param_available: raise unittest.SkipTest('Parameterized is not available.') -# Needed for testing (switches the matplotlib backend): -from pyomo.common.dependencies import matplotlib_available - +# Needed for testing (triggers matplotlib import and switches its backend): bool(matplotlib_available) -currdir = this_file_dir() - class TestOnlineDocExamples(unittest.BaselineTestDriver, unittest.TestCase): # Only test files in directories ending in -ch. These directories diff --git a/examples/pyomobook/test_book_examples.py b/examples/pyomobook/test_book_examples.py index fea9a63d572..e946864c1aa 100644 --- a/examples/pyomobook/test_book_examples.py +++ b/examples/pyomobook/test_book_examples.py @@ -12,22 +12,20 @@ import pyomo.common.unittest as unittest import glob import os -from pyomo.common.dependencies import attempt_import +from pyomo.common.dependencies import attempt_import, matplotlib_available from pyomo.common.fileutils import this_file_dir import pyomo.environ as pyo +currdir = this_file_dir() + parameterized, param_available = attempt_import('parameterized') if not param_available: raise unittest.SkipTest('Parameterized is not available.') -# Needed for testing (switches the matplotlib backend): -from pyomo.common.dependencies import matplotlib_available - +# Needed for testing (triggers matplotlib import and switches its backend): bool(matplotlib_available) -currdir = this_file_dir() - class TestBookExamples(unittest.BaselineTestDriver, unittest.TestCase): # Only test files in directories ending in -ch. These directories From 427bcd0242ae2ab1fa5523d7a0dfe7733c8a1260 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 12 Jan 2024 08:44:37 -0700 Subject: [PATCH 0746/1204] Move doc/OnlineDocs/tests -> doc/OnlineDocs/src --- doc/OnlineDocs/Makefile | 2 +- doc/OnlineDocs/conf.py | 4 +- .../expressions/design.rst | 8 +- .../developer_reference/expressions/index.rst | 2 +- .../expressions/managing.rst | 28 ++-- .../expressions/overview.rst | 14 +- .../expressions/performance.rst | 20 +-- .../pyomo_modeling_components/Constraints.rst | 6 +- .../pyomo_modeling_components/Expressions.rst | 18 +-- .../pyomo_modeling_components/Sets.rst | 4 +- .../pyomo_modeling_components/Variables.rst | 6 +- .../pyomo_overview/simple_examples.rst | 12 +- doc/OnlineDocs/{tests => src}/data/A.tab | 0 doc/OnlineDocs/{tests => src}/data/ABCD.tab | 0 doc/OnlineDocs/{tests => src}/data/ABCD.txt | 0 doc/OnlineDocs/{tests => src}/data/ABCD.xls | Bin doc/OnlineDocs/{tests => src}/data/ABCD1.dat | 0 doc/OnlineDocs/{tests => src}/data/ABCD1.py | 0 doc/OnlineDocs/{tests => src}/data/ABCD1.txt | 0 doc/OnlineDocs/{tests => src}/data/ABCD2.dat | 0 doc/OnlineDocs/{tests => src}/data/ABCD2.py | 0 doc/OnlineDocs/{tests => src}/data/ABCD2.txt | 0 doc/OnlineDocs/{tests => src}/data/ABCD3.dat | 0 doc/OnlineDocs/{tests => src}/data/ABCD3.py | 0 doc/OnlineDocs/{tests => src}/data/ABCD3.txt | 0 doc/OnlineDocs/{tests => src}/data/ABCD4.dat | 0 doc/OnlineDocs/{tests => src}/data/ABCD4.py | 0 doc/OnlineDocs/{tests => src}/data/ABCD4.txt | 0 doc/OnlineDocs/{tests => src}/data/ABCD5.dat | 0 doc/OnlineDocs/{tests => src}/data/ABCD5.py | 0 doc/OnlineDocs/{tests => src}/data/ABCD5.txt | 0 doc/OnlineDocs/{tests => src}/data/ABCD6.dat | 0 doc/OnlineDocs/{tests => src}/data/ABCD6.py | 0 doc/OnlineDocs/{tests => src}/data/ABCD6.txt | 0 doc/OnlineDocs/{tests => src}/data/ABCD7.dat | 0 doc/OnlineDocs/{tests => src}/data/ABCD7.py | 0 doc/OnlineDocs/{tests => src}/data/ABCD7.txt | 0 doc/OnlineDocs/{tests => src}/data/ABCD8.bad | 0 doc/OnlineDocs/{tests => src}/data/ABCD8.dat | 0 doc/OnlineDocs/{tests => src}/data/ABCD8.py | 0 doc/OnlineDocs/{tests => src}/data/ABCD9.bad | 0 doc/OnlineDocs/{tests => src}/data/ABCD9.dat | 0 doc/OnlineDocs/{tests => src}/data/ABCD9.py | 0 doc/OnlineDocs/{tests => src}/data/C.tab | 0 doc/OnlineDocs/{tests => src}/data/D.tab | 0 doc/OnlineDocs/{tests => src}/data/U.tab | 0 doc/OnlineDocs/{tests => src}/data/Y.tab | 0 doc/OnlineDocs/{tests => src}/data/Z.tab | 0 .../{tests => src}/data/data_managers.txt | 0 doc/OnlineDocs/{tests => src}/data/diet.dat | 0 doc/OnlineDocs/{tests => src}/data/diet.sql | 0 .../{tests => src}/data/diet.sqlite | Bin .../{tests => src}/data/diet.sqlite.dat | 0 doc/OnlineDocs/{tests => src}/data/diet1.py | 0 doc/OnlineDocs/{tests => src}/data/ex.dat | 0 doc/OnlineDocs/{tests => src}/data/ex.py | 0 doc/OnlineDocs/{tests => src}/data/ex.txt | 0 doc/OnlineDocs/{tests => src}/data/ex1.dat | 0 doc/OnlineDocs/{tests => src}/data/ex2.dat | 0 .../{tests => src}/data/import1.tab.dat | 0 .../{tests => src}/data/import1.tab.py | 0 .../{tests => src}/data/import1.tab.txt | 0 .../{tests => src}/data/import2.tab.dat | 0 .../{tests => src}/data/import2.tab.py | 0 .../{tests => src}/data/import2.tab.txt | 0 .../{tests => src}/data/import3.tab.dat | 0 .../{tests => src}/data/import3.tab.py | 0 .../{tests => src}/data/import3.tab.txt | 0 .../{tests => src}/data/import4.tab.dat | 0 .../{tests => src}/data/import4.tab.py | 0 .../{tests => src}/data/import4.tab.txt | 0 .../{tests => src}/data/import5.tab.dat | 0 .../{tests => src}/data/import5.tab.py | 0 .../{tests => src}/data/import5.tab.txt | 0 .../{tests => src}/data/import6.tab.dat | 0 .../{tests => src}/data/import6.tab.py | 0 .../{tests => src}/data/import6.tab.txt | 0 .../{tests => src}/data/import7.tab.dat | 0 .../{tests => src}/data/import7.tab.py | 0 .../{tests => src}/data/import7.tab.txt | 0 .../{tests => src}/data/import8.tab.dat | 0 .../{tests => src}/data/import8.tab.py | 0 .../{tests => src}/data/import8.tab.txt | 0 .../{tests => src}/data/namespace1.dat | 0 doc/OnlineDocs/{tests => src}/data/param1.dat | 0 doc/OnlineDocs/{tests => src}/data/param1.py | 0 doc/OnlineDocs/{tests => src}/data/param1.txt | 0 doc/OnlineDocs/{tests => src}/data/param2.dat | 0 doc/OnlineDocs/{tests => src}/data/param2.py | 0 doc/OnlineDocs/{tests => src}/data/param2.txt | 0 .../{tests => src}/data/param2a.dat | 0 doc/OnlineDocs/{tests => src}/data/param2a.py | 0 .../{tests => src}/data/param2a.txt | 0 doc/OnlineDocs/{tests => src}/data/param3.dat | 0 doc/OnlineDocs/{tests => src}/data/param3.py | 0 doc/OnlineDocs/{tests => src}/data/param3.txt | 0 .../{tests => src}/data/param3a.dat | 0 doc/OnlineDocs/{tests => src}/data/param3a.py | 0 .../{tests => src}/data/param3a.txt | 0 .../{tests => src}/data/param3b.dat | 0 doc/OnlineDocs/{tests => src}/data/param3b.py | 0 .../{tests => src}/data/param3b.txt | 0 .../{tests => src}/data/param3c.dat | 0 doc/OnlineDocs/{tests => src}/data/param3c.py | 0 .../{tests => src}/data/param3c.txt | 0 doc/OnlineDocs/{tests => src}/data/param4.dat | 0 doc/OnlineDocs/{tests => src}/data/param4.py | 0 doc/OnlineDocs/{tests => src}/data/param4.txt | 0 doc/OnlineDocs/{tests => src}/data/param5.dat | 0 doc/OnlineDocs/{tests => src}/data/param5.py | 0 doc/OnlineDocs/{tests => src}/data/param5.txt | 0 .../{tests => src}/data/param5a.dat | 0 doc/OnlineDocs/{tests => src}/data/param5a.py | 0 .../{tests => src}/data/param5a.txt | 0 doc/OnlineDocs/{tests => src}/data/param6.dat | 0 doc/OnlineDocs/{tests => src}/data/param6.py | 0 doc/OnlineDocs/{tests => src}/data/param6.txt | 0 .../{tests => src}/data/param6a.dat | 0 doc/OnlineDocs/{tests => src}/data/param6a.py | 0 .../{tests => src}/data/param6a.txt | 0 .../{tests => src}/data/param7a.dat | 0 doc/OnlineDocs/{tests => src}/data/param7a.py | 0 .../{tests => src}/data/param7a.txt | 0 .../{tests => src}/data/param7b.dat | 0 doc/OnlineDocs/{tests => src}/data/param7b.py | 0 .../{tests => src}/data/param7b.txt | 0 .../{tests => src}/data/param8a.dat | 0 doc/OnlineDocs/{tests => src}/data/param8a.py | 0 .../{tests => src}/data/param8a.txt | 0 .../{tests => src}/data/pyomo.diet1.sh | 0 .../{tests => src}/data/pyomo.diet1.txt | 0 .../{tests => src}/data/pyomo.diet2.sh | 0 .../{tests => src}/data/pyomo.diet2.txt | 0 doc/OnlineDocs/{tests => src}/data/set1.dat | 0 doc/OnlineDocs/{tests => src}/data/set1.py | 0 doc/OnlineDocs/{tests => src}/data/set1.txt | 0 doc/OnlineDocs/{tests => src}/data/set2.dat | 0 doc/OnlineDocs/{tests => src}/data/set2.py | 0 doc/OnlineDocs/{tests => src}/data/set2.txt | 0 doc/OnlineDocs/{tests => src}/data/set2a.dat | 0 doc/OnlineDocs/{tests => src}/data/set2a.py | 0 doc/OnlineDocs/{tests => src}/data/set2a.txt | 0 doc/OnlineDocs/{tests => src}/data/set3.dat | 0 doc/OnlineDocs/{tests => src}/data/set3.py | 0 doc/OnlineDocs/{tests => src}/data/set3.txt | 0 doc/OnlineDocs/{tests => src}/data/set4.dat | 0 doc/OnlineDocs/{tests => src}/data/set4.py | 0 doc/OnlineDocs/{tests => src}/data/set4.txt | 0 doc/OnlineDocs/{tests => src}/data/set5.dat | 0 doc/OnlineDocs/{tests => src}/data/set5.py | 0 doc/OnlineDocs/{tests => src}/data/set5.txt | 0 doc/OnlineDocs/{tests => src}/data/table0.dat | 0 doc/OnlineDocs/{tests => src}/data/table0.py | 0 doc/OnlineDocs/{tests => src}/data/table0.txt | 0 .../{tests => src}/data/table0.ul.dat | 0 .../{tests => src}/data/table0.ul.py | 0 .../{tests => src}/data/table0.ul.txt | 0 doc/OnlineDocs/{tests => src}/data/table1.dat | 0 doc/OnlineDocs/{tests => src}/data/table1.py | 0 doc/OnlineDocs/{tests => src}/data/table1.txt | 0 doc/OnlineDocs/{tests => src}/data/table2.dat | 0 doc/OnlineDocs/{tests => src}/data/table2.py | 0 doc/OnlineDocs/{tests => src}/data/table2.txt | 0 doc/OnlineDocs/{tests => src}/data/table3.dat | 0 doc/OnlineDocs/{tests => src}/data/table3.py | 0 doc/OnlineDocs/{tests => src}/data/table3.txt | 0 .../{tests => src}/data/table3.ul.dat | 0 .../{tests => src}/data/table3.ul.py | 0 .../{tests => src}/data/table3.ul.txt | 0 doc/OnlineDocs/{tests => src}/data/table4.dat | 0 doc/OnlineDocs/{tests => src}/data/table4.py | 0 doc/OnlineDocs/{tests => src}/data/table4.txt | 0 .../{tests => src}/data/table4.ul.dat | 0 .../{tests => src}/data/table4.ul.py | 0 .../{tests => src}/data/table4.ul.txt | 0 doc/OnlineDocs/{tests => src}/data/table5.dat | 0 doc/OnlineDocs/{tests => src}/data/table5.py | 0 doc/OnlineDocs/{tests => src}/data/table5.txt | 0 doc/OnlineDocs/{tests => src}/data/table6.dat | 0 doc/OnlineDocs/{tests => src}/data/table6.py | 0 doc/OnlineDocs/{tests => src}/data/table6.txt | 0 doc/OnlineDocs/{tests => src}/data/table7.dat | 0 doc/OnlineDocs/{tests => src}/data/table7.py | 0 doc/OnlineDocs/{tests => src}/data/table7.txt | 0 .../{tests => src}/dataportal/A.tab | 0 .../{tests => src}/dataportal/C.tab | 0 .../{tests => src}/dataportal/D.tab | 0 .../{tests => src}/dataportal/PP.csv | 0 .../{tests => src}/dataportal/PP.json | 0 .../{tests => src}/dataportal/PP.sqlite | Bin .../{tests => src}/dataportal/PP.tab | 0 .../{tests => src}/dataportal/PP.xml | 0 .../{tests => src}/dataportal/PP.yaml | 0 .../{tests => src}/dataportal/PP_sqlite.py | 0 .../{tests => src}/dataportal/Pyomo_mysql | 0 .../{tests => src}/dataportal/S.tab | 0 .../{tests => src}/dataportal/T.json | 0 .../{tests => src}/dataportal/T.yaml | 0 .../{tests => src}/dataportal/U.tab | 0 .../{tests => src}/dataportal/XW.tab | 0 .../{tests => src}/dataportal/Y.tab | 0 .../{tests => src}/dataportal/Z.tab | 0 .../dataportal/dataportal_tab.py | 0 .../dataportal/dataportal_tab.txt | 0 .../{tests => src}/dataportal/excel.xls | Bin .../dataportal/param_initialization.py | 0 .../dataportal/param_initialization.txt | 0 .../dataportal/set_initialization.py | 0 .../dataportal/set_initialization.txt | 0 doc/OnlineDocs/{tests => src}/expr/design.py | 0 doc/OnlineDocs/{tests => src}/expr/design.txt | 0 doc/OnlineDocs/{tests => src}/expr/index.py | 0 doc/OnlineDocs/{tests => src}/expr/index.txt | 0 .../{tests => src}/expr/managing.py | 0 .../{tests => src}/expr/managing.txt | 0 .../{tests => src}/expr/overview.py | 0 .../{tests => src}/expr/overview.txt | 0 .../{tests => src}/expr/performance.py | 0 .../{tests => src}/expr/performance.txt | 0 .../{tests => src}/expr/quicksum.log | 0 .../{tests => src}/expr/quicksum.py | 0 .../{tests => src}/kernel/examples.sh | 0 .../{tests => src}/kernel/examples.txt | 0 .../scripting/AbstractSuffixes.py | 0 .../{tests => src}/scripting/Isinglebuild.py | 0 .../{tests => src}/scripting/Isinglecomm.dat | 0 .../{tests => src}/scripting/NodesIn_init.py | 0 .../{tests => src}/scripting/Z_init.py | 0 .../{tests => src}/scripting/abstract1.dat | 0 .../{tests => src}/scripting/abstract2.dat | 0 .../{tests => src}/scripting/abstract2.py | 0 .../{tests => src}/scripting/abstract2a.dat | 0 .../scripting/abstract2piece.py | 0 .../scripting/abstract2piecebuild.py | 0 .../scripting/block_iter_example.py | 0 .../{tests => src}/scripting/concrete1.py | 0 .../{tests => src}/scripting/doubleA.py | 0 .../{tests => src}/scripting/driveabs2.py | 0 .../{tests => src}/scripting/driveconc1.py | 0 .../{tests => src}/scripting/iterative1.py | 0 .../{tests => src}/scripting/iterative2.py | 0 .../{tests => src}/scripting/noiteration1.py | 0 .../{tests => src}/scripting/parallel.py | 0 .../scripting/spy4Constraints.py | 0 .../scripting/spy4Expressions.py | 0 .../scripting/spy4PyomoCommand.py | 0 .../{tests => src}/scripting/spy4Variables.py | 0 .../{tests => src}/scripting/spy4scripts.py | 0 .../{tests => src}/strip_examples.py | 0 .../{tests => src}/test_examples.py | 0 .../working_abstractmodels/BuildAction.rst | 10 +- .../data/dataportals.rst | 74 ++++----- .../working_abstractmodels/data/datfiles.rst | 146 +++++++++--------- .../working_abstractmodels/data/native.rst | 16 +- .../working_abstractmodels/pyomo_command.rst | 2 +- doc/OnlineDocs/working_models.rst | 62 ++++---- 256 files changed, 217 insertions(+), 217 deletions(-) rename doc/OnlineDocs/{tests => src}/data/A.tab (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD.tab (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD.txt (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD.xls (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD1.dat (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD1.py (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD1.txt (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD2.dat (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD2.py (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD2.txt (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD3.dat (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD3.py (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD3.txt (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD4.dat (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD4.py (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD4.txt (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD5.dat (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD5.py (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD5.txt (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD6.dat (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD6.py (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD6.txt (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD7.dat (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD7.py (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD7.txt (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD8.bad (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD8.dat (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD8.py (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD9.bad (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD9.dat (100%) rename doc/OnlineDocs/{tests => src}/data/ABCD9.py (100%) rename doc/OnlineDocs/{tests => src}/data/C.tab (100%) rename doc/OnlineDocs/{tests => src}/data/D.tab (100%) rename doc/OnlineDocs/{tests => src}/data/U.tab (100%) rename doc/OnlineDocs/{tests => src}/data/Y.tab (100%) rename doc/OnlineDocs/{tests => src}/data/Z.tab (100%) rename doc/OnlineDocs/{tests => src}/data/data_managers.txt (100%) rename doc/OnlineDocs/{tests => src}/data/diet.dat (100%) rename doc/OnlineDocs/{tests => src}/data/diet.sql (100%) rename doc/OnlineDocs/{tests => src}/data/diet.sqlite (100%) rename doc/OnlineDocs/{tests => src}/data/diet.sqlite.dat (100%) rename doc/OnlineDocs/{tests => src}/data/diet1.py (100%) rename doc/OnlineDocs/{tests => src}/data/ex.dat (100%) rename doc/OnlineDocs/{tests => src}/data/ex.py (100%) rename doc/OnlineDocs/{tests => src}/data/ex.txt (100%) rename doc/OnlineDocs/{tests => src}/data/ex1.dat (100%) rename doc/OnlineDocs/{tests => src}/data/ex2.dat (100%) rename doc/OnlineDocs/{tests => src}/data/import1.tab.dat (100%) rename doc/OnlineDocs/{tests => src}/data/import1.tab.py (100%) rename doc/OnlineDocs/{tests => src}/data/import1.tab.txt (100%) rename doc/OnlineDocs/{tests => src}/data/import2.tab.dat (100%) rename doc/OnlineDocs/{tests => src}/data/import2.tab.py (100%) rename doc/OnlineDocs/{tests => src}/data/import2.tab.txt (100%) rename doc/OnlineDocs/{tests => src}/data/import3.tab.dat (100%) rename doc/OnlineDocs/{tests => src}/data/import3.tab.py (100%) rename doc/OnlineDocs/{tests => src}/data/import3.tab.txt (100%) rename doc/OnlineDocs/{tests => src}/data/import4.tab.dat (100%) rename doc/OnlineDocs/{tests => src}/data/import4.tab.py (100%) rename doc/OnlineDocs/{tests => src}/data/import4.tab.txt (100%) rename doc/OnlineDocs/{tests => src}/data/import5.tab.dat (100%) rename doc/OnlineDocs/{tests => src}/data/import5.tab.py (100%) rename doc/OnlineDocs/{tests => src}/data/import5.tab.txt (100%) rename doc/OnlineDocs/{tests => src}/data/import6.tab.dat (100%) rename doc/OnlineDocs/{tests => src}/data/import6.tab.py (100%) rename doc/OnlineDocs/{tests => src}/data/import6.tab.txt (100%) rename doc/OnlineDocs/{tests => src}/data/import7.tab.dat (100%) rename doc/OnlineDocs/{tests => src}/data/import7.tab.py (100%) rename doc/OnlineDocs/{tests => src}/data/import7.tab.txt (100%) rename doc/OnlineDocs/{tests => src}/data/import8.tab.dat (100%) rename doc/OnlineDocs/{tests => src}/data/import8.tab.py (100%) rename doc/OnlineDocs/{tests => src}/data/import8.tab.txt (100%) rename doc/OnlineDocs/{tests => src}/data/namespace1.dat (100%) rename doc/OnlineDocs/{tests => src}/data/param1.dat (100%) rename doc/OnlineDocs/{tests => src}/data/param1.py (100%) rename doc/OnlineDocs/{tests => src}/data/param1.txt (100%) rename doc/OnlineDocs/{tests => src}/data/param2.dat (100%) rename doc/OnlineDocs/{tests => src}/data/param2.py (100%) rename doc/OnlineDocs/{tests => src}/data/param2.txt (100%) rename doc/OnlineDocs/{tests => src}/data/param2a.dat (100%) rename doc/OnlineDocs/{tests => src}/data/param2a.py (100%) rename doc/OnlineDocs/{tests => src}/data/param2a.txt (100%) rename doc/OnlineDocs/{tests => src}/data/param3.dat (100%) rename doc/OnlineDocs/{tests => src}/data/param3.py (100%) rename doc/OnlineDocs/{tests => src}/data/param3.txt (100%) rename doc/OnlineDocs/{tests => src}/data/param3a.dat (100%) rename doc/OnlineDocs/{tests => src}/data/param3a.py (100%) rename doc/OnlineDocs/{tests => src}/data/param3a.txt (100%) rename doc/OnlineDocs/{tests => src}/data/param3b.dat (100%) rename doc/OnlineDocs/{tests => src}/data/param3b.py (100%) rename doc/OnlineDocs/{tests => src}/data/param3b.txt (100%) rename doc/OnlineDocs/{tests => src}/data/param3c.dat (100%) rename doc/OnlineDocs/{tests => src}/data/param3c.py (100%) rename doc/OnlineDocs/{tests => src}/data/param3c.txt (100%) rename doc/OnlineDocs/{tests => src}/data/param4.dat (100%) rename doc/OnlineDocs/{tests => src}/data/param4.py (100%) rename doc/OnlineDocs/{tests => src}/data/param4.txt (100%) rename doc/OnlineDocs/{tests => src}/data/param5.dat (100%) rename doc/OnlineDocs/{tests => src}/data/param5.py (100%) rename doc/OnlineDocs/{tests => src}/data/param5.txt (100%) rename doc/OnlineDocs/{tests => src}/data/param5a.dat (100%) rename doc/OnlineDocs/{tests => src}/data/param5a.py (100%) rename doc/OnlineDocs/{tests => src}/data/param5a.txt (100%) rename doc/OnlineDocs/{tests => src}/data/param6.dat (100%) rename doc/OnlineDocs/{tests => src}/data/param6.py (100%) rename doc/OnlineDocs/{tests => src}/data/param6.txt (100%) rename doc/OnlineDocs/{tests => src}/data/param6a.dat (100%) rename doc/OnlineDocs/{tests => src}/data/param6a.py (100%) rename doc/OnlineDocs/{tests => src}/data/param6a.txt (100%) rename doc/OnlineDocs/{tests => src}/data/param7a.dat (100%) rename doc/OnlineDocs/{tests => src}/data/param7a.py (100%) rename doc/OnlineDocs/{tests => src}/data/param7a.txt (100%) rename doc/OnlineDocs/{tests => src}/data/param7b.dat (100%) rename doc/OnlineDocs/{tests => src}/data/param7b.py (100%) rename doc/OnlineDocs/{tests => src}/data/param7b.txt (100%) rename doc/OnlineDocs/{tests => src}/data/param8a.dat (100%) rename doc/OnlineDocs/{tests => src}/data/param8a.py (100%) rename doc/OnlineDocs/{tests => src}/data/param8a.txt (100%) rename doc/OnlineDocs/{tests => src}/data/pyomo.diet1.sh (100%) rename doc/OnlineDocs/{tests => src}/data/pyomo.diet1.txt (100%) rename doc/OnlineDocs/{tests => src}/data/pyomo.diet2.sh (100%) rename doc/OnlineDocs/{tests => src}/data/pyomo.diet2.txt (100%) rename doc/OnlineDocs/{tests => src}/data/set1.dat (100%) rename doc/OnlineDocs/{tests => src}/data/set1.py (100%) rename doc/OnlineDocs/{tests => src}/data/set1.txt (100%) rename doc/OnlineDocs/{tests => src}/data/set2.dat (100%) rename doc/OnlineDocs/{tests => src}/data/set2.py (100%) rename doc/OnlineDocs/{tests => src}/data/set2.txt (100%) rename doc/OnlineDocs/{tests => src}/data/set2a.dat (100%) rename doc/OnlineDocs/{tests => src}/data/set2a.py (100%) rename doc/OnlineDocs/{tests => src}/data/set2a.txt (100%) rename doc/OnlineDocs/{tests => src}/data/set3.dat (100%) rename doc/OnlineDocs/{tests => src}/data/set3.py (100%) rename doc/OnlineDocs/{tests => src}/data/set3.txt (100%) rename doc/OnlineDocs/{tests => src}/data/set4.dat (100%) rename doc/OnlineDocs/{tests => src}/data/set4.py (100%) rename doc/OnlineDocs/{tests => src}/data/set4.txt (100%) rename doc/OnlineDocs/{tests => src}/data/set5.dat (100%) rename doc/OnlineDocs/{tests => src}/data/set5.py (100%) rename doc/OnlineDocs/{tests => src}/data/set5.txt (100%) rename doc/OnlineDocs/{tests => src}/data/table0.dat (100%) rename doc/OnlineDocs/{tests => src}/data/table0.py (100%) rename doc/OnlineDocs/{tests => src}/data/table0.txt (100%) rename doc/OnlineDocs/{tests => src}/data/table0.ul.dat (100%) rename doc/OnlineDocs/{tests => src}/data/table0.ul.py (100%) rename doc/OnlineDocs/{tests => src}/data/table0.ul.txt (100%) rename doc/OnlineDocs/{tests => src}/data/table1.dat (100%) rename doc/OnlineDocs/{tests => src}/data/table1.py (100%) rename doc/OnlineDocs/{tests => src}/data/table1.txt (100%) rename doc/OnlineDocs/{tests => src}/data/table2.dat (100%) rename doc/OnlineDocs/{tests => src}/data/table2.py (100%) rename doc/OnlineDocs/{tests => src}/data/table2.txt (100%) rename doc/OnlineDocs/{tests => src}/data/table3.dat (100%) rename doc/OnlineDocs/{tests => src}/data/table3.py (100%) rename doc/OnlineDocs/{tests => src}/data/table3.txt (100%) rename doc/OnlineDocs/{tests => src}/data/table3.ul.dat (100%) rename doc/OnlineDocs/{tests => src}/data/table3.ul.py (100%) rename doc/OnlineDocs/{tests => src}/data/table3.ul.txt (100%) rename doc/OnlineDocs/{tests => src}/data/table4.dat (100%) rename doc/OnlineDocs/{tests => src}/data/table4.py (100%) rename doc/OnlineDocs/{tests => src}/data/table4.txt (100%) rename doc/OnlineDocs/{tests => src}/data/table4.ul.dat (100%) rename doc/OnlineDocs/{tests => src}/data/table4.ul.py (100%) rename doc/OnlineDocs/{tests => src}/data/table4.ul.txt (100%) rename doc/OnlineDocs/{tests => src}/data/table5.dat (100%) rename doc/OnlineDocs/{tests => src}/data/table5.py (100%) rename doc/OnlineDocs/{tests => src}/data/table5.txt (100%) rename doc/OnlineDocs/{tests => src}/data/table6.dat (100%) rename doc/OnlineDocs/{tests => src}/data/table6.py (100%) rename doc/OnlineDocs/{tests => src}/data/table6.txt (100%) rename doc/OnlineDocs/{tests => src}/data/table7.dat (100%) rename doc/OnlineDocs/{tests => src}/data/table7.py (100%) rename doc/OnlineDocs/{tests => src}/data/table7.txt (100%) rename doc/OnlineDocs/{tests => src}/dataportal/A.tab (100%) rename doc/OnlineDocs/{tests => src}/dataportal/C.tab (100%) rename doc/OnlineDocs/{tests => src}/dataportal/D.tab (100%) rename doc/OnlineDocs/{tests => src}/dataportal/PP.csv (100%) rename doc/OnlineDocs/{tests => src}/dataportal/PP.json (100%) rename doc/OnlineDocs/{tests => src}/dataportal/PP.sqlite (100%) rename doc/OnlineDocs/{tests => src}/dataportal/PP.tab (100%) rename doc/OnlineDocs/{tests => src}/dataportal/PP.xml (100%) rename doc/OnlineDocs/{tests => src}/dataportal/PP.yaml (100%) rename doc/OnlineDocs/{tests => src}/dataportal/PP_sqlite.py (100%) rename doc/OnlineDocs/{tests => src}/dataportal/Pyomo_mysql (100%) rename doc/OnlineDocs/{tests => src}/dataportal/S.tab (100%) rename doc/OnlineDocs/{tests => src}/dataportal/T.json (100%) rename doc/OnlineDocs/{tests => src}/dataportal/T.yaml (100%) rename doc/OnlineDocs/{tests => src}/dataportal/U.tab (100%) rename doc/OnlineDocs/{tests => src}/dataportal/XW.tab (100%) rename doc/OnlineDocs/{tests => src}/dataportal/Y.tab (100%) rename doc/OnlineDocs/{tests => src}/dataportal/Z.tab (100%) rename doc/OnlineDocs/{tests => src}/dataportal/dataportal_tab.py (100%) rename doc/OnlineDocs/{tests => src}/dataportal/dataportal_tab.txt (100%) rename doc/OnlineDocs/{tests => src}/dataportal/excel.xls (100%) rename doc/OnlineDocs/{tests => src}/dataportal/param_initialization.py (100%) rename doc/OnlineDocs/{tests => src}/dataportal/param_initialization.txt (100%) rename doc/OnlineDocs/{tests => src}/dataportal/set_initialization.py (100%) rename doc/OnlineDocs/{tests => src}/dataportal/set_initialization.txt (100%) rename doc/OnlineDocs/{tests => src}/expr/design.py (100%) rename doc/OnlineDocs/{tests => src}/expr/design.txt (100%) rename doc/OnlineDocs/{tests => src}/expr/index.py (100%) rename doc/OnlineDocs/{tests => src}/expr/index.txt (100%) rename doc/OnlineDocs/{tests => src}/expr/managing.py (100%) rename doc/OnlineDocs/{tests => src}/expr/managing.txt (100%) rename doc/OnlineDocs/{tests => src}/expr/overview.py (100%) rename doc/OnlineDocs/{tests => src}/expr/overview.txt (100%) rename doc/OnlineDocs/{tests => src}/expr/performance.py (100%) rename doc/OnlineDocs/{tests => src}/expr/performance.txt (100%) rename doc/OnlineDocs/{tests => src}/expr/quicksum.log (100%) rename doc/OnlineDocs/{tests => src}/expr/quicksum.py (100%) rename doc/OnlineDocs/{tests => src}/kernel/examples.sh (100%) rename doc/OnlineDocs/{tests => src}/kernel/examples.txt (100%) rename doc/OnlineDocs/{tests => src}/scripting/AbstractSuffixes.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/Isinglebuild.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/Isinglecomm.dat (100%) rename doc/OnlineDocs/{tests => src}/scripting/NodesIn_init.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/Z_init.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/abstract1.dat (100%) rename doc/OnlineDocs/{tests => src}/scripting/abstract2.dat (100%) rename doc/OnlineDocs/{tests => src}/scripting/abstract2.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/abstract2a.dat (100%) rename doc/OnlineDocs/{tests => src}/scripting/abstract2piece.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/abstract2piecebuild.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/block_iter_example.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/concrete1.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/doubleA.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/driveabs2.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/driveconc1.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/iterative1.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/iterative2.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/noiteration1.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/parallel.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/spy4Constraints.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/spy4Expressions.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/spy4PyomoCommand.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/spy4Variables.py (100%) rename doc/OnlineDocs/{tests => src}/scripting/spy4scripts.py (100%) rename doc/OnlineDocs/{tests => src}/strip_examples.py (100%) rename doc/OnlineDocs/{tests => src}/test_examples.py (100%) diff --git a/doc/OnlineDocs/Makefile b/doc/OnlineDocs/Makefile index 604903631b2..3625325ef73 100644 --- a/doc/OnlineDocs/Makefile +++ b/doc/OnlineDocs/Makefile @@ -23,4 +23,4 @@ clean clean_tests: @$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) @echo "Removing *.spy, *.out" @find . -name \*.spy -delete - @find tests -name \*.out -delete + @find src -name \*.out -delete diff --git a/doc/OnlineDocs/conf.py b/doc/OnlineDocs/conf.py index d8939cf61dd..ef6510daedf 100644 --- a/doc/OnlineDocs/conf.py +++ b/doc/OnlineDocs/conf.py @@ -26,12 +26,12 @@ sys.path.insert(0, os.path.abspath('../..')) # -- Rebuild SPY files ---------------------------------------------------- -sys.path.insert(0, os.path.abspath('tests')) +sys.path.insert(0, os.path.abspath('src')) try: print("Regenerating SPY files...") from strip_examples import generate_spy_files - generate_spy_files(os.path.abspath('tests')) + generate_spy_files(os.path.abspath('src')) generate_spy_files( os.path.abspath(os.path.join('library_reference', 'kernel', 'examples')) ) diff --git a/doc/OnlineDocs/developer_reference/expressions/design.rst b/doc/OnlineDocs/developer_reference/expressions/design.rst index 9a6d5b9412f..ddecb39ad0c 100644 --- a/doc/OnlineDocs/developer_reference/expressions/design.rst +++ b/doc/OnlineDocs/developer_reference/expressions/design.rst @@ -73,7 +73,7 @@ Expression trees can be categorized in four different ways: These three categories are illustrated with the following example: -.. literalinclude:: ../../tests/expr/design_categories.spy +.. literalinclude:: ../../src/expr/design_categories.spy The following table describes four different simple expressions that consist of a single model component, and it shows how they @@ -107,7 +107,7 @@ Named expressions allow for changes to an expression after it has been constructed. For example, consider the expression ``f`` defined with the :class:`Expression ` component: -.. literalinclude:: ../../tests/expr/design_named_expression.spy +.. literalinclude:: ../../src/expr/design_named_expression.spy Although ``f`` is an immutable expression, whose definition is fixed, a sub-expressions is the named expression ``M.e``. Named @@ -227,7 +227,7 @@ The :data:`linear_expression ` object is a context manager that can be used to declare a linear sum. For example, consider the following two loops: -.. literalinclude:: ../../tests/expr/design_cm1.spy +.. literalinclude:: ../../src/expr/design_cm1.spy The first apparent difference in these loops is that the value of ``s`` is explicitly initialized while ``e`` is initialized when the @@ -250,7 +250,7 @@ construct different expressions with different context declarations. Finally, note that these context managers can be passed into the :attr:`start` method for the :func:`quicksum ` function. For example: -.. literalinclude:: ../../tests/expr/design_cm2.spy +.. literalinclude:: ../../src/expr/design_cm2.spy This sum contains terms for ``M.x[i]`` and ``M.y[i]``. The syntax in this example is not intuitive because the sum is being stored diff --git a/doc/OnlineDocs/developer_reference/expressions/index.rst b/doc/OnlineDocs/developer_reference/expressions/index.rst index 769639d50eb..685fde25173 100644 --- a/doc/OnlineDocs/developer_reference/expressions/index.rst +++ b/doc/OnlineDocs/developer_reference/expressions/index.rst @@ -21,7 +21,7 @@ nodes contain operators. Pyomo relies on so-called magic methods to automate the construction of symbolic expressions. For example, consider an expression ``e`` declared as follows: -.. literalinclude:: ../../tests/expr/index_simple.spy +.. literalinclude:: ../../src/expr/index_simple.spy Python determines that the magic method ``__mul__`` is called on the ``M.v`` object, with the argument ``2``. This method returns diff --git a/doc/OnlineDocs/developer_reference/expressions/managing.rst b/doc/OnlineDocs/developer_reference/expressions/managing.rst index 43c5ec34816..a4dd2a51436 100644 --- a/doc/OnlineDocs/developer_reference/expressions/managing.rst +++ b/doc/OnlineDocs/developer_reference/expressions/managing.rst @@ -23,7 +23,7 @@ mimics the Python operations used to construct an expression. The :data:`verbose` flag can be set to :const:`True` to generate a string representation that is a nested functional form. For example: -.. literalinclude:: ../../tests/expr/managing_ex1.spy +.. literalinclude:: ../../src/expr/managing_ex1.spy Labeler and Symbol Map ~~~~~~~~~~~~~~~~~~~~~~ @@ -37,7 +37,7 @@ the :class:`NumericLabeler` defines a functor that can be used to sequentially generate simple labels with a prefix followed by the variable count: -.. literalinclude:: ../../tests/expr/managing_ex2.spy +.. literalinclude:: ../../src/expr/managing_ex2.spy The :data:`smap` option is used to specify a symbol map object (:class:`SymbolMap `), which @@ -72,19 +72,19 @@ the expression have a value. The :func:`value ` function can be used to walk the expression tree and compute the value of an expression. For example: -.. literalinclude:: ../../tests/expr/managing_ex5.spy +.. literalinclude:: ../../src/expr/managing_ex5.spy Additionally, expressions define the :func:`__call__` method, so the following is another way to compute the value of an expression: -.. literalinclude:: ../../tests/expr/managing_ex6.spy +.. literalinclude:: ../../src/expr/managing_ex6.spy If a parameter or variable is undefined, then the :func:`value ` function and :func:`__call__` method will raise an exception. This exception can be suppressed using the :attr:`exception` option. For example: -.. literalinclude:: ../../tests/expr/managing_ex7.spy +.. literalinclude:: ../../src/expr/managing_ex7.spy This option is useful in contexts where adding a try block is inconvenient in your modeling script. @@ -108,7 +108,7 @@ functions that support this functionality. First, the function is a generator function that walks the expression tree and yields all nodes whose type is in a specified set of node types. For example: -.. literalinclude:: ../../tests/expr/managing_ex8.spy +.. literalinclude:: ../../src/expr/managing_ex8.spy The :func:`identify_variables ` function is a generator function that yields all nodes that are @@ -117,7 +117,7 @@ but this set of variable types does not need to be specified by the user. However, the :attr:`include_fixed` flag can be specified to omit fixed variables. For example: -.. literalinclude:: ../../tests/expr/managing_ex9.spy +.. literalinclude:: ../../src/expr/managing_ex9.spy Walking an Expression Tree with a Visitor Class ----------------------------------------------- @@ -223,14 +223,14 @@ In this example, we describe an visitor class that counts the number of nodes in an expression (including leaf nodes). Consider the following class: -.. literalinclude:: ../../tests/expr/managing_visitor1.spy +.. literalinclude:: ../../src/expr/managing_visitor1.spy The class constructor creates a counter, and the :func:`visit` method increments this counter for every node that is visited. The :func:`finalize` method returns the value of this counter after the tree has been walked. The following function illustrates this use of this visitor class: -.. literalinclude:: ../../tests/expr/managing_visitor2.spy +.. literalinclude:: ../../src/expr/managing_visitor2.spy ExpressionValueVisitor Example @@ -240,14 +240,14 @@ In this example, we describe an visitor class that clones the expression tree (including leaf nodes). Consider the following class: -.. literalinclude:: ../../tests/expr/managing_visitor3.spy +.. literalinclude:: ../../src/expr/managing_visitor3.spy The :func:`visit` method creates a new expression node with children specified by :attr:`values`. The :func:`visiting_potential_leaf` method performs a :func:`deepcopy` on leaf nodes, which are native Python types or non-expression objects. -.. literalinclude:: ../../tests/expr/managing_visitor4.spy +.. literalinclude:: ../../src/expr/managing_visitor4.spy ExpressionReplacementVisitor Example @@ -258,15 +258,15 @@ variables with scaled variables, using a mutable parameter that can be modified later. the following class: -.. literalinclude:: ../../tests/expr/managing_visitor5.spy +.. literalinclude:: ../../src/expr/managing_visitor5.spy No other method need to be defined. The :func:`beforeChild` method identifies variable nodes and returns a product expression that contains a mutable parameter. -.. literalinclude:: ../../tests/expr/managing_visitor6.spy +.. literalinclude:: ../../src/expr/managing_visitor6.spy The :func:`scale_expression` function is called with an expression and a dictionary, :attr:`scale`, that maps variable ID to model parameter. For example: -.. literalinclude:: ../../tests/expr/managing_visitor7.spy +.. literalinclude:: ../../src/expr/managing_visitor7.spy diff --git a/doc/OnlineDocs/developer_reference/expressions/overview.rst b/doc/OnlineDocs/developer_reference/expressions/overview.rst index 58808a813f1..c1962edec22 100644 --- a/doc/OnlineDocs/developer_reference/expressions/overview.rst +++ b/doc/OnlineDocs/developer_reference/expressions/overview.rst @@ -50,13 +50,13 @@ are: example, the following two loops had dramatically different runtime: - .. literalinclude:: ../../tests/expr/overview_example1.spy + .. literalinclude:: ../../src/expr/overview_example1.spy * Coopr3 eliminates side effects by automatically cloning sub-expressions. Unfortunately, this can easily lead to unexpected cloning in models, which can dramatically slow down Pyomo model generation. For example: - .. literalinclude:: ../../tests/expr/overview_example2.spy + .. literalinclude:: ../../src/expr/overview_example2.spy * Coopr3 leverages recursion in many operations, including expression cloning. Even simple non-linear expressions can result in deep @@ -82,7 +82,7 @@ control for how expressions are managed in Python. For example: * Python variables can point to the same expression tree - .. literalinclude:: ../../tests/expr/overview_tree1.spy + .. literalinclude:: ../../src/expr/overview_tree1.spy This is illustrated as follows: @@ -102,7 +102,7 @@ control for how expressions are managed in Python. For example: * A variable can point to a sub-tree that another variable points to - .. literalinclude:: ../../tests/expr/overview_tree2.spy + .. literalinclude:: ../../src/expr/overview_tree2.spy This is illustrated as follows: @@ -124,7 +124,7 @@ control for how expressions are managed in Python. For example: * Two expression trees can point to the same sub-tree - .. literalinclude:: ../../tests/expr/overview_tree3.spy + .. literalinclude:: ../../src/expr/overview_tree3.spy This is illustrated as follows: @@ -169,7 +169,7 @@ between expressions, we do not consider those expressions entangled. Expression entanglement is problematic because shared expressions complicate the expected behavior when sub-expressions are changed. Consider the following example: -.. literalinclude:: ../../tests/expr/overview_tree4.spy +.. literalinclude:: ../../src/expr/overview_tree4.spy What is the value of ``e`` after ``M.w`` is added to it? What is the value of ``f``? The answers to these questions are not immediately @@ -244,7 +244,7 @@ There is one important exception to the entanglement property described above. The ``Expression`` component is treated as a mutable expression when shared between expressions. For example: -.. literalinclude:: ../../tests/expr/overview_tree5.spy +.. literalinclude:: ../../src/expr/overview_tree5.spy Here, the expression ``M.e`` is a so-called *named expression* that the user has declared. Named expressions are explicitly intended diff --git a/doc/OnlineDocs/developer_reference/expressions/performance.rst b/doc/OnlineDocs/developer_reference/expressions/performance.rst index 4b2c691b729..8e344e50982 100644 --- a/doc/OnlineDocs/developer_reference/expressions/performance.rst +++ b/doc/OnlineDocs/developer_reference/expressions/performance.rst @@ -11,14 +11,14 @@ Expression Generation Pyomo expressions can be constructed using native binary operators in Python. For example, a sum can be created in a simple loop: -.. literalinclude:: ../../tests/expr/performance_loop1.spy +.. literalinclude:: ../../src/expr/performance_loop1.spy Additionally, Pyomo expressions can be constructed using functions that iteratively apply Python binary operators. For example, the Python :func:`sum` function can be used to replace the previous loop: -.. literalinclude:: ../../tests/expr/performance_loop2.spy +.. literalinclude:: ../../src/expr/performance_loop2.spy The :func:`sum` function is both more compact and more efficient. Using :func:`sum` avoids the creation of temporary variables, and @@ -47,7 +47,7 @@ expressions. For example, consider the following quadratic polynomial: -.. literalinclude:: ../../tests/expr/performance_loop3.spy +.. literalinclude:: ../../src/expr/performance_loop3.spy This quadratic polynomial is treated as a nonlinear expression unless the expression is explicitly processed to identify quadratic @@ -78,7 +78,7 @@ The :func:`prod ` function is analogous to the builtin argument list, :attr:`args`, which represents expressions that are multiplied together. For example: -.. literalinclude:: ../../tests/expr/performance_prod.spy +.. literalinclude:: ../../src/expr/performance_prod.spy quicksum ~~~~~~~~ @@ -89,7 +89,7 @@ generates a more compact Pyomo expression. Its main argument is a variable length argument list, :attr:`args`, which represents expressions that are summed together. For example: -.. literalinclude:: ../../tests/expr/performance_quicksum.spy +.. literalinclude:: ../../src/expr/performance_quicksum.spy The summation is customized based on the :attr:`start` and :attr:`linear` arguments. The :attr:`start` defines the initial @@ -111,13 +111,13 @@ more quickly. Consider the following example: -.. literalinclude:: ../../tests/expr/quicksum_runtime.spy +.. literalinclude:: ../../src/expr/quicksum_runtime.spy The sum consists of linear terms because the exponents are one. The following output illustrates that quicksum can identify this linear structure to generate expressions more quickly: -.. literalinclude:: ../../tests/expr/quicksum.log +.. literalinclude:: ../../src/expr/quicksum.log :language: none If :attr:`start` is not a numeric value, then the :func:`quicksum @@ -134,7 +134,7 @@ to be stored in an object that is passed into the function (e.g. the linear cont term in :attr:`args` is misleading. Consider the following example: - .. literalinclude:: ../../tests/expr/performance_warning.spy + .. literalinclude:: ../../src/expr/performance_warning.spy The first term created by the generator is linear, but the subsequent terms are nonlinear. Pyomo gracefully transitions @@ -153,12 +153,12 @@ calling :func:`quicksum `. If two or more components provided, then the result is the summation of their terms multiplied together. For example: -.. literalinclude:: ../../tests/expr/performance_sum_product1.spy +.. literalinclude:: ../../src/expr/performance_sum_product1.spy The :attr:`denom` argument specifies components whose terms are in the denominator. For example: -.. literalinclude:: ../../tests/expr/performance_sum_product2.spy +.. literalinclude:: ../../src/expr/performance_sum_product2.spy The terms summed by this function are explicitly specified, so :func:`sum_product ` can identify diff --git a/doc/OnlineDocs/pyomo_modeling_components/Constraints.rst b/doc/OnlineDocs/pyomo_modeling_components/Constraints.rst index a79d380dcab..0cc42cb2abe 100644 --- a/doc/OnlineDocs/pyomo_modeling_components/Constraints.rst +++ b/doc/OnlineDocs/pyomo_modeling_components/Constraints.rst @@ -6,7 +6,7 @@ that are created using a rule, which is a Python function. For example, if the variable ``model.x`` has the indexes 'butter' and 'scones', then this constraint limits the sum over these indexes to be exactly three: -.. literalinclude:: ../tests/scripting/spy4Constraints_Constraint_example.spy +.. literalinclude:: ../src/scripting/spy4Constraints_Constraint_example.spy :language: python Instead of expressions involving equality (==) or inequalities (`<=` or @@ -16,7 +16,7 @@ lb `<=` expr `<=` ub. Variables can appear only in the middle expr. For example, the following two constraint declarations have the same meaning: -.. literalinclude:: ../tests/scripting/spy4Constraints_Inequality_constraints_2expressions.spy +.. literalinclude:: ../src/scripting/spy4Constraints_Inequality_constraints_2expressions.spy :language: python For this simple example, it would also be possible to declare @@ -30,7 +30,7 @@ interpreted as placing a budget of :math:`i` on the :math:`i^{\mbox{th}}` item to buy where the cost per item is given by the parameter ``model.a``: -.. literalinclude:: ../tests/scripting/spy4Constraints_Passing_elements_crossproduct.spy +.. literalinclude:: ../src/scripting/spy4Constraints_Passing_elements_crossproduct.spy :language: python .. note:: diff --git a/doc/OnlineDocs/pyomo_modeling_components/Expressions.rst b/doc/OnlineDocs/pyomo_modeling_components/Expressions.rst index f0558621316..16c206e2fe8 100644 --- a/doc/OnlineDocs/pyomo_modeling_components/Expressions.rst +++ b/doc/OnlineDocs/pyomo_modeling_components/Expressions.rst @@ -20,7 +20,7 @@ possible to build up expressions. The following example illustrates this, along with a reference to global Python data in the form of a Python variable called ``switch``: -.. literalinclude:: ../tests/scripting/spy4Expressions_Buildup_expression_switch.spy +.. literalinclude:: ../src/scripting/spy4Expressions_Buildup_expression_switch.spy :language: python In this example, the constraint that is generated depends on the value @@ -33,7 +33,7 @@ otherwise, the ``model.d`` term is not present. Because model elements result in expressions, not values, the following does not work as expected in an abstract model! - .. literalinclude:: ../tests/scripting/spy4Expressions_Abstract_wrong_usage.spy + .. literalinclude:: ../src/scripting/spy4Expressions_Abstract_wrong_usage.spy :language: python The trouble is that ``model.d >= 2`` results in an expression, not @@ -58,7 +58,7 @@ as described in the paper [Vielma_et_al]_. There are two basic forms for the declaration of the constraint: -.. literalinclude:: ../tests/scripting/spy4Expressions_Declare_piecewise_constraints.spy +.. literalinclude:: ../src/scripting/spy4Expressions_Declare_piecewise_constraints.spy :language: python where ``pwconst`` can be replaced by a name appropriate for the @@ -124,7 +124,7 @@ Keywords: indexing set is used or when all indices use an identical piecewise function). Examples: - .. literalinclude:: ../tests/scripting/spy4Expressions_f_rule_Function_examples.spy + .. literalinclude:: ../src/scripting/spy4Expressions_f_rule_Function_examples.spy :language: python * **force_pw=True/False** @@ -163,7 +163,7 @@ Keywords: Here is an example of an assignment to a Python dictionary variable that has keywords for a picewise constraint: -.. literalinclude:: ../tests/scripting/spy4Expressions_Keyword_assignment_example.spy +.. literalinclude:: ../src/scripting/spy4Expressions_Keyword_assignment_example.spy :language: python Here is a simple example based on the example given earlier in @@ -175,7 +175,7 @@ whimsically just to make the example. The important thing to note is that variables that are going to appear as the independent variable in a piecewise constraint must have bounds. -.. literalinclude:: ../tests/scripting/abstract2piece.py +.. literalinclude:: ../src/scripting/abstract2piece.py :language: python A more advanced example is provided in abstract2piecebuild.py in @@ -193,13 +193,13 @@ variable x times the index. Later in the model file, just to illustrate how to do it, the expression is changed but just for the first index to be x squared. -.. literalinclude:: ../tests/scripting/spy4Expressions_Expression_objects_illustration.spy +.. literalinclude:: ../src/scripting/spy4Expressions_Expression_objects_illustration.spy :language: python An alternative is to create Python functions that, potentially, manipulate model objects. E.g., if you define a function -.. literalinclude:: ../tests/scripting/spy4Expressions_Define_python_function.spy +.. literalinclude:: ../src/scripting/spy4Expressions_Define_python_function.spy :language: python You can call this function with or without Pyomo modeling components as @@ -211,7 +211,7 @@ expression is used to generate another expression (e.g., f(model.x, 3) + 5), the initial expression is always cloned so that the new generated expression is independent of the old. For example: -.. literalinclude:: ../tests/scripting/spy4Expressions_Generate_new_expression.spy +.. literalinclude:: ../src/scripting/spy4Expressions_Generate_new_expression.spy :language: python If you want to create an expression that is shared between other diff --git a/doc/OnlineDocs/pyomo_modeling_components/Sets.rst b/doc/OnlineDocs/pyomo_modeling_components/Sets.rst index f9a692fcb10..73c3539d79d 100644 --- a/doc/OnlineDocs/pyomo_modeling_components/Sets.rst +++ b/doc/OnlineDocs/pyomo_modeling_components/Sets.rst @@ -443,13 +443,13 @@ model is: for this model, a toy data file (in AMPL "``.dat``" format) would be: -.. literalinclude:: ../tests/scripting/Isinglecomm.dat +.. literalinclude:: ../src/scripting/Isinglecomm.dat :language: text .. doctest:: :hide: - >>> inst = model.create_instance('tests/scripting/Isinglecomm.dat') + >>> inst = model.create_instance('src/scripting/Isinglecomm.dat') This can also be done somewhat more efficiently, and perhaps more clearly, using a :class:`BuildAction` (for more information, see :ref:`BuildAction`): diff --git a/doc/OnlineDocs/pyomo_modeling_components/Variables.rst b/doc/OnlineDocs/pyomo_modeling_components/Variables.rst index 038f5769705..7f7ee74af5f 100644 --- a/doc/OnlineDocs/pyomo_modeling_components/Variables.rst +++ b/doc/OnlineDocs/pyomo_modeling_components/Variables.rst @@ -20,13 +20,13 @@ declaring a *singleton* (i.e. unindexed) variable named ``model.LumberJack`` that will take on real values between zero and 6 and it initialized to be 1.5: -.. literalinclude:: ../tests/scripting/spy4Variables_Declare_singleton_variable.spy +.. literalinclude:: ../src/scripting/spy4Variables_Declare_singleton_variable.spy :language: python Instead of the ``initialize`` option, initialization is sometimes done with a Python assignment statement as in -.. literalinclude:: ../tests/scripting/spy4Variables_Assign_value.spy +.. literalinclude:: ../src/scripting/spy4Variables_Assign_value.spy :language: python For indexed variables, bounds and initial values are often specified by @@ -36,7 +36,7 @@ followed by the indexes. This is illustrated in the following code snippet that makes use of Python dictionaries declared as lb and ub that are used by a function to provide bounds: -.. literalinclude:: ../tests/scripting/spy4Variables_Declare_bounds.spy +.. literalinclude:: ../src/scripting/spy4Variables_Declare_bounds.spy :language: python .. note:: diff --git a/doc/OnlineDocs/pyomo_overview/simple_examples.rst b/doc/OnlineDocs/pyomo_overview/simple_examples.rst index 4358c87b678..11305884c54 100644 --- a/doc/OnlineDocs/pyomo_overview/simple_examples.rst +++ b/doc/OnlineDocs/pyomo_overview/simple_examples.rst @@ -91,7 +91,7 @@ One way to implement this in Pyomo is as shown as follows: :hide: >>> # Create an instance to verify that the rules fire correctly - >>> inst = model.create_instance('tests/scripting/abstract1.dat') + >>> inst = model.create_instance('src/scripting/abstract1.dat') .. note:: @@ -261,9 +261,9 @@ parameters. Here is one file that provides data (in AMPL "``.dat``" format). :hide: >>> # Create an instance to verify that the rules fire correctly - >>> inst = model.create_instance('tests/scripting/abstract1.dat') + >>> inst = model.create_instance('src/scripting/abstract1.dat') -.. literalinclude:: ../tests/scripting/abstract1.dat +.. literalinclude:: ../src/scripting/abstract1.dat :language: text There are multiple formats that can be used to provide data to a Pyomo @@ -327,18 +327,18 @@ the same model. To start with an illustration of general indexes, consider a slightly different Pyomo implementation of the model we just presented. -.. literalinclude:: ../tests/scripting/abstract2.py +.. literalinclude:: ../src/scripting/abstract2.py :language: python To get the same instantiated model, the following data file can be used. -.. literalinclude:: ../tests/scripting/abstract2a.dat +.. literalinclude:: ../src/scripting/abstract2a.dat :language: none However, this model can also be fed different data for problems of the same general form using meaningful indexes. -.. literalinclude:: ../tests/scripting/abstract2.dat +.. literalinclude:: ../src/scripting/abstract2.dat :language: none diff --git a/doc/OnlineDocs/tests/data/A.tab b/doc/OnlineDocs/src/data/A.tab similarity index 100% rename from doc/OnlineDocs/tests/data/A.tab rename to doc/OnlineDocs/src/data/A.tab diff --git a/doc/OnlineDocs/tests/data/ABCD.tab b/doc/OnlineDocs/src/data/ABCD.tab similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD.tab rename to doc/OnlineDocs/src/data/ABCD.tab diff --git a/doc/OnlineDocs/tests/data/ABCD.txt b/doc/OnlineDocs/src/data/ABCD.txt similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD.txt rename to doc/OnlineDocs/src/data/ABCD.txt diff --git a/doc/OnlineDocs/tests/data/ABCD.xls b/doc/OnlineDocs/src/data/ABCD.xls similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD.xls rename to doc/OnlineDocs/src/data/ABCD.xls diff --git a/doc/OnlineDocs/tests/data/ABCD1.dat b/doc/OnlineDocs/src/data/ABCD1.dat similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD1.dat rename to doc/OnlineDocs/src/data/ABCD1.dat diff --git a/doc/OnlineDocs/tests/data/ABCD1.py b/doc/OnlineDocs/src/data/ABCD1.py similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD1.py rename to doc/OnlineDocs/src/data/ABCD1.py diff --git a/doc/OnlineDocs/tests/data/ABCD1.txt b/doc/OnlineDocs/src/data/ABCD1.txt similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD1.txt rename to doc/OnlineDocs/src/data/ABCD1.txt diff --git a/doc/OnlineDocs/tests/data/ABCD2.dat b/doc/OnlineDocs/src/data/ABCD2.dat similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD2.dat rename to doc/OnlineDocs/src/data/ABCD2.dat diff --git a/doc/OnlineDocs/tests/data/ABCD2.py b/doc/OnlineDocs/src/data/ABCD2.py similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD2.py rename to doc/OnlineDocs/src/data/ABCD2.py diff --git a/doc/OnlineDocs/tests/data/ABCD2.txt b/doc/OnlineDocs/src/data/ABCD2.txt similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD2.txt rename to doc/OnlineDocs/src/data/ABCD2.txt diff --git a/doc/OnlineDocs/tests/data/ABCD3.dat b/doc/OnlineDocs/src/data/ABCD3.dat similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD3.dat rename to doc/OnlineDocs/src/data/ABCD3.dat diff --git a/doc/OnlineDocs/tests/data/ABCD3.py b/doc/OnlineDocs/src/data/ABCD3.py similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD3.py rename to doc/OnlineDocs/src/data/ABCD3.py diff --git a/doc/OnlineDocs/tests/data/ABCD3.txt b/doc/OnlineDocs/src/data/ABCD3.txt similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD3.txt rename to doc/OnlineDocs/src/data/ABCD3.txt diff --git a/doc/OnlineDocs/tests/data/ABCD4.dat b/doc/OnlineDocs/src/data/ABCD4.dat similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD4.dat rename to doc/OnlineDocs/src/data/ABCD4.dat diff --git a/doc/OnlineDocs/tests/data/ABCD4.py b/doc/OnlineDocs/src/data/ABCD4.py similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD4.py rename to doc/OnlineDocs/src/data/ABCD4.py diff --git a/doc/OnlineDocs/tests/data/ABCD4.txt b/doc/OnlineDocs/src/data/ABCD4.txt similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD4.txt rename to doc/OnlineDocs/src/data/ABCD4.txt diff --git a/doc/OnlineDocs/tests/data/ABCD5.dat b/doc/OnlineDocs/src/data/ABCD5.dat similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD5.dat rename to doc/OnlineDocs/src/data/ABCD5.dat diff --git a/doc/OnlineDocs/tests/data/ABCD5.py b/doc/OnlineDocs/src/data/ABCD5.py similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD5.py rename to doc/OnlineDocs/src/data/ABCD5.py diff --git a/doc/OnlineDocs/tests/data/ABCD5.txt b/doc/OnlineDocs/src/data/ABCD5.txt similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD5.txt rename to doc/OnlineDocs/src/data/ABCD5.txt diff --git a/doc/OnlineDocs/tests/data/ABCD6.dat b/doc/OnlineDocs/src/data/ABCD6.dat similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD6.dat rename to doc/OnlineDocs/src/data/ABCD6.dat diff --git a/doc/OnlineDocs/tests/data/ABCD6.py b/doc/OnlineDocs/src/data/ABCD6.py similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD6.py rename to doc/OnlineDocs/src/data/ABCD6.py diff --git a/doc/OnlineDocs/tests/data/ABCD6.txt b/doc/OnlineDocs/src/data/ABCD6.txt similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD6.txt rename to doc/OnlineDocs/src/data/ABCD6.txt diff --git a/doc/OnlineDocs/tests/data/ABCD7.dat b/doc/OnlineDocs/src/data/ABCD7.dat similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD7.dat rename to doc/OnlineDocs/src/data/ABCD7.dat diff --git a/doc/OnlineDocs/tests/data/ABCD7.py b/doc/OnlineDocs/src/data/ABCD7.py similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD7.py rename to doc/OnlineDocs/src/data/ABCD7.py diff --git a/doc/OnlineDocs/tests/data/ABCD7.txt b/doc/OnlineDocs/src/data/ABCD7.txt similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD7.txt rename to doc/OnlineDocs/src/data/ABCD7.txt diff --git a/doc/OnlineDocs/tests/data/ABCD8.bad b/doc/OnlineDocs/src/data/ABCD8.bad similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD8.bad rename to doc/OnlineDocs/src/data/ABCD8.bad diff --git a/doc/OnlineDocs/tests/data/ABCD8.dat b/doc/OnlineDocs/src/data/ABCD8.dat similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD8.dat rename to doc/OnlineDocs/src/data/ABCD8.dat diff --git a/doc/OnlineDocs/tests/data/ABCD8.py b/doc/OnlineDocs/src/data/ABCD8.py similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD8.py rename to doc/OnlineDocs/src/data/ABCD8.py diff --git a/doc/OnlineDocs/tests/data/ABCD9.bad b/doc/OnlineDocs/src/data/ABCD9.bad similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD9.bad rename to doc/OnlineDocs/src/data/ABCD9.bad diff --git a/doc/OnlineDocs/tests/data/ABCD9.dat b/doc/OnlineDocs/src/data/ABCD9.dat similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD9.dat rename to doc/OnlineDocs/src/data/ABCD9.dat diff --git a/doc/OnlineDocs/tests/data/ABCD9.py b/doc/OnlineDocs/src/data/ABCD9.py similarity index 100% rename from doc/OnlineDocs/tests/data/ABCD9.py rename to doc/OnlineDocs/src/data/ABCD9.py diff --git a/doc/OnlineDocs/tests/data/C.tab b/doc/OnlineDocs/src/data/C.tab similarity index 100% rename from doc/OnlineDocs/tests/data/C.tab rename to doc/OnlineDocs/src/data/C.tab diff --git a/doc/OnlineDocs/tests/data/D.tab b/doc/OnlineDocs/src/data/D.tab similarity index 100% rename from doc/OnlineDocs/tests/data/D.tab rename to doc/OnlineDocs/src/data/D.tab diff --git a/doc/OnlineDocs/tests/data/U.tab b/doc/OnlineDocs/src/data/U.tab similarity index 100% rename from doc/OnlineDocs/tests/data/U.tab rename to doc/OnlineDocs/src/data/U.tab diff --git a/doc/OnlineDocs/tests/data/Y.tab b/doc/OnlineDocs/src/data/Y.tab similarity index 100% rename from doc/OnlineDocs/tests/data/Y.tab rename to doc/OnlineDocs/src/data/Y.tab diff --git a/doc/OnlineDocs/tests/data/Z.tab b/doc/OnlineDocs/src/data/Z.tab similarity index 100% rename from doc/OnlineDocs/tests/data/Z.tab rename to doc/OnlineDocs/src/data/Z.tab diff --git a/doc/OnlineDocs/tests/data/data_managers.txt b/doc/OnlineDocs/src/data/data_managers.txt similarity index 100% rename from doc/OnlineDocs/tests/data/data_managers.txt rename to doc/OnlineDocs/src/data/data_managers.txt diff --git a/doc/OnlineDocs/tests/data/diet.dat b/doc/OnlineDocs/src/data/diet.dat similarity index 100% rename from doc/OnlineDocs/tests/data/diet.dat rename to doc/OnlineDocs/src/data/diet.dat diff --git a/doc/OnlineDocs/tests/data/diet.sql b/doc/OnlineDocs/src/data/diet.sql similarity index 100% rename from doc/OnlineDocs/tests/data/diet.sql rename to doc/OnlineDocs/src/data/diet.sql diff --git a/doc/OnlineDocs/tests/data/diet.sqlite b/doc/OnlineDocs/src/data/diet.sqlite similarity index 100% rename from doc/OnlineDocs/tests/data/diet.sqlite rename to doc/OnlineDocs/src/data/diet.sqlite diff --git a/doc/OnlineDocs/tests/data/diet.sqlite.dat b/doc/OnlineDocs/src/data/diet.sqlite.dat similarity index 100% rename from doc/OnlineDocs/tests/data/diet.sqlite.dat rename to doc/OnlineDocs/src/data/diet.sqlite.dat diff --git a/doc/OnlineDocs/tests/data/diet1.py b/doc/OnlineDocs/src/data/diet1.py similarity index 100% rename from doc/OnlineDocs/tests/data/diet1.py rename to doc/OnlineDocs/src/data/diet1.py diff --git a/doc/OnlineDocs/tests/data/ex.dat b/doc/OnlineDocs/src/data/ex.dat similarity index 100% rename from doc/OnlineDocs/tests/data/ex.dat rename to doc/OnlineDocs/src/data/ex.dat diff --git a/doc/OnlineDocs/tests/data/ex.py b/doc/OnlineDocs/src/data/ex.py similarity index 100% rename from doc/OnlineDocs/tests/data/ex.py rename to doc/OnlineDocs/src/data/ex.py diff --git a/doc/OnlineDocs/tests/data/ex.txt b/doc/OnlineDocs/src/data/ex.txt similarity index 100% rename from doc/OnlineDocs/tests/data/ex.txt rename to doc/OnlineDocs/src/data/ex.txt diff --git a/doc/OnlineDocs/tests/data/ex1.dat b/doc/OnlineDocs/src/data/ex1.dat similarity index 100% rename from doc/OnlineDocs/tests/data/ex1.dat rename to doc/OnlineDocs/src/data/ex1.dat diff --git a/doc/OnlineDocs/tests/data/ex2.dat b/doc/OnlineDocs/src/data/ex2.dat similarity index 100% rename from doc/OnlineDocs/tests/data/ex2.dat rename to doc/OnlineDocs/src/data/ex2.dat diff --git a/doc/OnlineDocs/tests/data/import1.tab.dat b/doc/OnlineDocs/src/data/import1.tab.dat similarity index 100% rename from doc/OnlineDocs/tests/data/import1.tab.dat rename to doc/OnlineDocs/src/data/import1.tab.dat diff --git a/doc/OnlineDocs/tests/data/import1.tab.py b/doc/OnlineDocs/src/data/import1.tab.py similarity index 100% rename from doc/OnlineDocs/tests/data/import1.tab.py rename to doc/OnlineDocs/src/data/import1.tab.py diff --git a/doc/OnlineDocs/tests/data/import1.tab.txt b/doc/OnlineDocs/src/data/import1.tab.txt similarity index 100% rename from doc/OnlineDocs/tests/data/import1.tab.txt rename to doc/OnlineDocs/src/data/import1.tab.txt diff --git a/doc/OnlineDocs/tests/data/import2.tab.dat b/doc/OnlineDocs/src/data/import2.tab.dat similarity index 100% rename from doc/OnlineDocs/tests/data/import2.tab.dat rename to doc/OnlineDocs/src/data/import2.tab.dat diff --git a/doc/OnlineDocs/tests/data/import2.tab.py b/doc/OnlineDocs/src/data/import2.tab.py similarity index 100% rename from doc/OnlineDocs/tests/data/import2.tab.py rename to doc/OnlineDocs/src/data/import2.tab.py diff --git a/doc/OnlineDocs/tests/data/import2.tab.txt b/doc/OnlineDocs/src/data/import2.tab.txt similarity index 100% rename from doc/OnlineDocs/tests/data/import2.tab.txt rename to doc/OnlineDocs/src/data/import2.tab.txt diff --git a/doc/OnlineDocs/tests/data/import3.tab.dat b/doc/OnlineDocs/src/data/import3.tab.dat similarity index 100% rename from doc/OnlineDocs/tests/data/import3.tab.dat rename to doc/OnlineDocs/src/data/import3.tab.dat diff --git a/doc/OnlineDocs/tests/data/import3.tab.py b/doc/OnlineDocs/src/data/import3.tab.py similarity index 100% rename from doc/OnlineDocs/tests/data/import3.tab.py rename to doc/OnlineDocs/src/data/import3.tab.py diff --git a/doc/OnlineDocs/tests/data/import3.tab.txt b/doc/OnlineDocs/src/data/import3.tab.txt similarity index 100% rename from doc/OnlineDocs/tests/data/import3.tab.txt rename to doc/OnlineDocs/src/data/import3.tab.txt diff --git a/doc/OnlineDocs/tests/data/import4.tab.dat b/doc/OnlineDocs/src/data/import4.tab.dat similarity index 100% rename from doc/OnlineDocs/tests/data/import4.tab.dat rename to doc/OnlineDocs/src/data/import4.tab.dat diff --git a/doc/OnlineDocs/tests/data/import4.tab.py b/doc/OnlineDocs/src/data/import4.tab.py similarity index 100% rename from doc/OnlineDocs/tests/data/import4.tab.py rename to doc/OnlineDocs/src/data/import4.tab.py diff --git a/doc/OnlineDocs/tests/data/import4.tab.txt b/doc/OnlineDocs/src/data/import4.tab.txt similarity index 100% rename from doc/OnlineDocs/tests/data/import4.tab.txt rename to doc/OnlineDocs/src/data/import4.tab.txt diff --git a/doc/OnlineDocs/tests/data/import5.tab.dat b/doc/OnlineDocs/src/data/import5.tab.dat similarity index 100% rename from doc/OnlineDocs/tests/data/import5.tab.dat rename to doc/OnlineDocs/src/data/import5.tab.dat diff --git a/doc/OnlineDocs/tests/data/import5.tab.py b/doc/OnlineDocs/src/data/import5.tab.py similarity index 100% rename from doc/OnlineDocs/tests/data/import5.tab.py rename to doc/OnlineDocs/src/data/import5.tab.py diff --git a/doc/OnlineDocs/tests/data/import5.tab.txt b/doc/OnlineDocs/src/data/import5.tab.txt similarity index 100% rename from doc/OnlineDocs/tests/data/import5.tab.txt rename to doc/OnlineDocs/src/data/import5.tab.txt diff --git a/doc/OnlineDocs/tests/data/import6.tab.dat b/doc/OnlineDocs/src/data/import6.tab.dat similarity index 100% rename from doc/OnlineDocs/tests/data/import6.tab.dat rename to doc/OnlineDocs/src/data/import6.tab.dat diff --git a/doc/OnlineDocs/tests/data/import6.tab.py b/doc/OnlineDocs/src/data/import6.tab.py similarity index 100% rename from doc/OnlineDocs/tests/data/import6.tab.py rename to doc/OnlineDocs/src/data/import6.tab.py diff --git a/doc/OnlineDocs/tests/data/import6.tab.txt b/doc/OnlineDocs/src/data/import6.tab.txt similarity index 100% rename from doc/OnlineDocs/tests/data/import6.tab.txt rename to doc/OnlineDocs/src/data/import6.tab.txt diff --git a/doc/OnlineDocs/tests/data/import7.tab.dat b/doc/OnlineDocs/src/data/import7.tab.dat similarity index 100% rename from doc/OnlineDocs/tests/data/import7.tab.dat rename to doc/OnlineDocs/src/data/import7.tab.dat diff --git a/doc/OnlineDocs/tests/data/import7.tab.py b/doc/OnlineDocs/src/data/import7.tab.py similarity index 100% rename from doc/OnlineDocs/tests/data/import7.tab.py rename to doc/OnlineDocs/src/data/import7.tab.py diff --git a/doc/OnlineDocs/tests/data/import7.tab.txt b/doc/OnlineDocs/src/data/import7.tab.txt similarity index 100% rename from doc/OnlineDocs/tests/data/import7.tab.txt rename to doc/OnlineDocs/src/data/import7.tab.txt diff --git a/doc/OnlineDocs/tests/data/import8.tab.dat b/doc/OnlineDocs/src/data/import8.tab.dat similarity index 100% rename from doc/OnlineDocs/tests/data/import8.tab.dat rename to doc/OnlineDocs/src/data/import8.tab.dat diff --git a/doc/OnlineDocs/tests/data/import8.tab.py b/doc/OnlineDocs/src/data/import8.tab.py similarity index 100% rename from doc/OnlineDocs/tests/data/import8.tab.py rename to doc/OnlineDocs/src/data/import8.tab.py diff --git a/doc/OnlineDocs/tests/data/import8.tab.txt b/doc/OnlineDocs/src/data/import8.tab.txt similarity index 100% rename from doc/OnlineDocs/tests/data/import8.tab.txt rename to doc/OnlineDocs/src/data/import8.tab.txt diff --git a/doc/OnlineDocs/tests/data/namespace1.dat b/doc/OnlineDocs/src/data/namespace1.dat similarity index 100% rename from doc/OnlineDocs/tests/data/namespace1.dat rename to doc/OnlineDocs/src/data/namespace1.dat diff --git a/doc/OnlineDocs/tests/data/param1.dat b/doc/OnlineDocs/src/data/param1.dat similarity index 100% rename from doc/OnlineDocs/tests/data/param1.dat rename to doc/OnlineDocs/src/data/param1.dat diff --git a/doc/OnlineDocs/tests/data/param1.py b/doc/OnlineDocs/src/data/param1.py similarity index 100% rename from doc/OnlineDocs/tests/data/param1.py rename to doc/OnlineDocs/src/data/param1.py diff --git a/doc/OnlineDocs/tests/data/param1.txt b/doc/OnlineDocs/src/data/param1.txt similarity index 100% rename from doc/OnlineDocs/tests/data/param1.txt rename to doc/OnlineDocs/src/data/param1.txt diff --git a/doc/OnlineDocs/tests/data/param2.dat b/doc/OnlineDocs/src/data/param2.dat similarity index 100% rename from doc/OnlineDocs/tests/data/param2.dat rename to doc/OnlineDocs/src/data/param2.dat diff --git a/doc/OnlineDocs/tests/data/param2.py b/doc/OnlineDocs/src/data/param2.py similarity index 100% rename from doc/OnlineDocs/tests/data/param2.py rename to doc/OnlineDocs/src/data/param2.py diff --git a/doc/OnlineDocs/tests/data/param2.txt b/doc/OnlineDocs/src/data/param2.txt similarity index 100% rename from doc/OnlineDocs/tests/data/param2.txt rename to doc/OnlineDocs/src/data/param2.txt diff --git a/doc/OnlineDocs/tests/data/param2a.dat b/doc/OnlineDocs/src/data/param2a.dat similarity index 100% rename from doc/OnlineDocs/tests/data/param2a.dat rename to doc/OnlineDocs/src/data/param2a.dat diff --git a/doc/OnlineDocs/tests/data/param2a.py b/doc/OnlineDocs/src/data/param2a.py similarity index 100% rename from doc/OnlineDocs/tests/data/param2a.py rename to doc/OnlineDocs/src/data/param2a.py diff --git a/doc/OnlineDocs/tests/data/param2a.txt b/doc/OnlineDocs/src/data/param2a.txt similarity index 100% rename from doc/OnlineDocs/tests/data/param2a.txt rename to doc/OnlineDocs/src/data/param2a.txt diff --git a/doc/OnlineDocs/tests/data/param3.dat b/doc/OnlineDocs/src/data/param3.dat similarity index 100% rename from doc/OnlineDocs/tests/data/param3.dat rename to doc/OnlineDocs/src/data/param3.dat diff --git a/doc/OnlineDocs/tests/data/param3.py b/doc/OnlineDocs/src/data/param3.py similarity index 100% rename from doc/OnlineDocs/tests/data/param3.py rename to doc/OnlineDocs/src/data/param3.py diff --git a/doc/OnlineDocs/tests/data/param3.txt b/doc/OnlineDocs/src/data/param3.txt similarity index 100% rename from doc/OnlineDocs/tests/data/param3.txt rename to doc/OnlineDocs/src/data/param3.txt diff --git a/doc/OnlineDocs/tests/data/param3a.dat b/doc/OnlineDocs/src/data/param3a.dat similarity index 100% rename from doc/OnlineDocs/tests/data/param3a.dat rename to doc/OnlineDocs/src/data/param3a.dat diff --git a/doc/OnlineDocs/tests/data/param3a.py b/doc/OnlineDocs/src/data/param3a.py similarity index 100% rename from doc/OnlineDocs/tests/data/param3a.py rename to doc/OnlineDocs/src/data/param3a.py diff --git a/doc/OnlineDocs/tests/data/param3a.txt b/doc/OnlineDocs/src/data/param3a.txt similarity index 100% rename from doc/OnlineDocs/tests/data/param3a.txt rename to doc/OnlineDocs/src/data/param3a.txt diff --git a/doc/OnlineDocs/tests/data/param3b.dat b/doc/OnlineDocs/src/data/param3b.dat similarity index 100% rename from doc/OnlineDocs/tests/data/param3b.dat rename to doc/OnlineDocs/src/data/param3b.dat diff --git a/doc/OnlineDocs/tests/data/param3b.py b/doc/OnlineDocs/src/data/param3b.py similarity index 100% rename from doc/OnlineDocs/tests/data/param3b.py rename to doc/OnlineDocs/src/data/param3b.py diff --git a/doc/OnlineDocs/tests/data/param3b.txt b/doc/OnlineDocs/src/data/param3b.txt similarity index 100% rename from doc/OnlineDocs/tests/data/param3b.txt rename to doc/OnlineDocs/src/data/param3b.txt diff --git a/doc/OnlineDocs/tests/data/param3c.dat b/doc/OnlineDocs/src/data/param3c.dat similarity index 100% rename from doc/OnlineDocs/tests/data/param3c.dat rename to doc/OnlineDocs/src/data/param3c.dat diff --git a/doc/OnlineDocs/tests/data/param3c.py b/doc/OnlineDocs/src/data/param3c.py similarity index 100% rename from doc/OnlineDocs/tests/data/param3c.py rename to doc/OnlineDocs/src/data/param3c.py diff --git a/doc/OnlineDocs/tests/data/param3c.txt b/doc/OnlineDocs/src/data/param3c.txt similarity index 100% rename from doc/OnlineDocs/tests/data/param3c.txt rename to doc/OnlineDocs/src/data/param3c.txt diff --git a/doc/OnlineDocs/tests/data/param4.dat b/doc/OnlineDocs/src/data/param4.dat similarity index 100% rename from doc/OnlineDocs/tests/data/param4.dat rename to doc/OnlineDocs/src/data/param4.dat diff --git a/doc/OnlineDocs/tests/data/param4.py b/doc/OnlineDocs/src/data/param4.py similarity index 100% rename from doc/OnlineDocs/tests/data/param4.py rename to doc/OnlineDocs/src/data/param4.py diff --git a/doc/OnlineDocs/tests/data/param4.txt b/doc/OnlineDocs/src/data/param4.txt similarity index 100% rename from doc/OnlineDocs/tests/data/param4.txt rename to doc/OnlineDocs/src/data/param4.txt diff --git a/doc/OnlineDocs/tests/data/param5.dat b/doc/OnlineDocs/src/data/param5.dat similarity index 100% rename from doc/OnlineDocs/tests/data/param5.dat rename to doc/OnlineDocs/src/data/param5.dat diff --git a/doc/OnlineDocs/tests/data/param5.py b/doc/OnlineDocs/src/data/param5.py similarity index 100% rename from doc/OnlineDocs/tests/data/param5.py rename to doc/OnlineDocs/src/data/param5.py diff --git a/doc/OnlineDocs/tests/data/param5.txt b/doc/OnlineDocs/src/data/param5.txt similarity index 100% rename from doc/OnlineDocs/tests/data/param5.txt rename to doc/OnlineDocs/src/data/param5.txt diff --git a/doc/OnlineDocs/tests/data/param5a.dat b/doc/OnlineDocs/src/data/param5a.dat similarity index 100% rename from doc/OnlineDocs/tests/data/param5a.dat rename to doc/OnlineDocs/src/data/param5a.dat diff --git a/doc/OnlineDocs/tests/data/param5a.py b/doc/OnlineDocs/src/data/param5a.py similarity index 100% rename from doc/OnlineDocs/tests/data/param5a.py rename to doc/OnlineDocs/src/data/param5a.py diff --git a/doc/OnlineDocs/tests/data/param5a.txt b/doc/OnlineDocs/src/data/param5a.txt similarity index 100% rename from doc/OnlineDocs/tests/data/param5a.txt rename to doc/OnlineDocs/src/data/param5a.txt diff --git a/doc/OnlineDocs/tests/data/param6.dat b/doc/OnlineDocs/src/data/param6.dat similarity index 100% rename from doc/OnlineDocs/tests/data/param6.dat rename to doc/OnlineDocs/src/data/param6.dat diff --git a/doc/OnlineDocs/tests/data/param6.py b/doc/OnlineDocs/src/data/param6.py similarity index 100% rename from doc/OnlineDocs/tests/data/param6.py rename to doc/OnlineDocs/src/data/param6.py diff --git a/doc/OnlineDocs/tests/data/param6.txt b/doc/OnlineDocs/src/data/param6.txt similarity index 100% rename from doc/OnlineDocs/tests/data/param6.txt rename to doc/OnlineDocs/src/data/param6.txt diff --git a/doc/OnlineDocs/tests/data/param6a.dat b/doc/OnlineDocs/src/data/param6a.dat similarity index 100% rename from doc/OnlineDocs/tests/data/param6a.dat rename to doc/OnlineDocs/src/data/param6a.dat diff --git a/doc/OnlineDocs/tests/data/param6a.py b/doc/OnlineDocs/src/data/param6a.py similarity index 100% rename from doc/OnlineDocs/tests/data/param6a.py rename to doc/OnlineDocs/src/data/param6a.py diff --git a/doc/OnlineDocs/tests/data/param6a.txt b/doc/OnlineDocs/src/data/param6a.txt similarity index 100% rename from doc/OnlineDocs/tests/data/param6a.txt rename to doc/OnlineDocs/src/data/param6a.txt diff --git a/doc/OnlineDocs/tests/data/param7a.dat b/doc/OnlineDocs/src/data/param7a.dat similarity index 100% rename from doc/OnlineDocs/tests/data/param7a.dat rename to doc/OnlineDocs/src/data/param7a.dat diff --git a/doc/OnlineDocs/tests/data/param7a.py b/doc/OnlineDocs/src/data/param7a.py similarity index 100% rename from doc/OnlineDocs/tests/data/param7a.py rename to doc/OnlineDocs/src/data/param7a.py diff --git a/doc/OnlineDocs/tests/data/param7a.txt b/doc/OnlineDocs/src/data/param7a.txt similarity index 100% rename from doc/OnlineDocs/tests/data/param7a.txt rename to doc/OnlineDocs/src/data/param7a.txt diff --git a/doc/OnlineDocs/tests/data/param7b.dat b/doc/OnlineDocs/src/data/param7b.dat similarity index 100% rename from doc/OnlineDocs/tests/data/param7b.dat rename to doc/OnlineDocs/src/data/param7b.dat diff --git a/doc/OnlineDocs/tests/data/param7b.py b/doc/OnlineDocs/src/data/param7b.py similarity index 100% rename from doc/OnlineDocs/tests/data/param7b.py rename to doc/OnlineDocs/src/data/param7b.py diff --git a/doc/OnlineDocs/tests/data/param7b.txt b/doc/OnlineDocs/src/data/param7b.txt similarity index 100% rename from doc/OnlineDocs/tests/data/param7b.txt rename to doc/OnlineDocs/src/data/param7b.txt diff --git a/doc/OnlineDocs/tests/data/param8a.dat b/doc/OnlineDocs/src/data/param8a.dat similarity index 100% rename from doc/OnlineDocs/tests/data/param8a.dat rename to doc/OnlineDocs/src/data/param8a.dat diff --git a/doc/OnlineDocs/tests/data/param8a.py b/doc/OnlineDocs/src/data/param8a.py similarity index 100% rename from doc/OnlineDocs/tests/data/param8a.py rename to doc/OnlineDocs/src/data/param8a.py diff --git a/doc/OnlineDocs/tests/data/param8a.txt b/doc/OnlineDocs/src/data/param8a.txt similarity index 100% rename from doc/OnlineDocs/tests/data/param8a.txt rename to doc/OnlineDocs/src/data/param8a.txt diff --git a/doc/OnlineDocs/tests/data/pyomo.diet1.sh b/doc/OnlineDocs/src/data/pyomo.diet1.sh similarity index 100% rename from doc/OnlineDocs/tests/data/pyomo.diet1.sh rename to doc/OnlineDocs/src/data/pyomo.diet1.sh diff --git a/doc/OnlineDocs/tests/data/pyomo.diet1.txt b/doc/OnlineDocs/src/data/pyomo.diet1.txt similarity index 100% rename from doc/OnlineDocs/tests/data/pyomo.diet1.txt rename to doc/OnlineDocs/src/data/pyomo.diet1.txt diff --git a/doc/OnlineDocs/tests/data/pyomo.diet2.sh b/doc/OnlineDocs/src/data/pyomo.diet2.sh similarity index 100% rename from doc/OnlineDocs/tests/data/pyomo.diet2.sh rename to doc/OnlineDocs/src/data/pyomo.diet2.sh diff --git a/doc/OnlineDocs/tests/data/pyomo.diet2.txt b/doc/OnlineDocs/src/data/pyomo.diet2.txt similarity index 100% rename from doc/OnlineDocs/tests/data/pyomo.diet2.txt rename to doc/OnlineDocs/src/data/pyomo.diet2.txt diff --git a/doc/OnlineDocs/tests/data/set1.dat b/doc/OnlineDocs/src/data/set1.dat similarity index 100% rename from doc/OnlineDocs/tests/data/set1.dat rename to doc/OnlineDocs/src/data/set1.dat diff --git a/doc/OnlineDocs/tests/data/set1.py b/doc/OnlineDocs/src/data/set1.py similarity index 100% rename from doc/OnlineDocs/tests/data/set1.py rename to doc/OnlineDocs/src/data/set1.py diff --git a/doc/OnlineDocs/tests/data/set1.txt b/doc/OnlineDocs/src/data/set1.txt similarity index 100% rename from doc/OnlineDocs/tests/data/set1.txt rename to doc/OnlineDocs/src/data/set1.txt diff --git a/doc/OnlineDocs/tests/data/set2.dat b/doc/OnlineDocs/src/data/set2.dat similarity index 100% rename from doc/OnlineDocs/tests/data/set2.dat rename to doc/OnlineDocs/src/data/set2.dat diff --git a/doc/OnlineDocs/tests/data/set2.py b/doc/OnlineDocs/src/data/set2.py similarity index 100% rename from doc/OnlineDocs/tests/data/set2.py rename to doc/OnlineDocs/src/data/set2.py diff --git a/doc/OnlineDocs/tests/data/set2.txt b/doc/OnlineDocs/src/data/set2.txt similarity index 100% rename from doc/OnlineDocs/tests/data/set2.txt rename to doc/OnlineDocs/src/data/set2.txt diff --git a/doc/OnlineDocs/tests/data/set2a.dat b/doc/OnlineDocs/src/data/set2a.dat similarity index 100% rename from doc/OnlineDocs/tests/data/set2a.dat rename to doc/OnlineDocs/src/data/set2a.dat diff --git a/doc/OnlineDocs/tests/data/set2a.py b/doc/OnlineDocs/src/data/set2a.py similarity index 100% rename from doc/OnlineDocs/tests/data/set2a.py rename to doc/OnlineDocs/src/data/set2a.py diff --git a/doc/OnlineDocs/tests/data/set2a.txt b/doc/OnlineDocs/src/data/set2a.txt similarity index 100% rename from doc/OnlineDocs/tests/data/set2a.txt rename to doc/OnlineDocs/src/data/set2a.txt diff --git a/doc/OnlineDocs/tests/data/set3.dat b/doc/OnlineDocs/src/data/set3.dat similarity index 100% rename from doc/OnlineDocs/tests/data/set3.dat rename to doc/OnlineDocs/src/data/set3.dat diff --git a/doc/OnlineDocs/tests/data/set3.py b/doc/OnlineDocs/src/data/set3.py similarity index 100% rename from doc/OnlineDocs/tests/data/set3.py rename to doc/OnlineDocs/src/data/set3.py diff --git a/doc/OnlineDocs/tests/data/set3.txt b/doc/OnlineDocs/src/data/set3.txt similarity index 100% rename from doc/OnlineDocs/tests/data/set3.txt rename to doc/OnlineDocs/src/data/set3.txt diff --git a/doc/OnlineDocs/tests/data/set4.dat b/doc/OnlineDocs/src/data/set4.dat similarity index 100% rename from doc/OnlineDocs/tests/data/set4.dat rename to doc/OnlineDocs/src/data/set4.dat diff --git a/doc/OnlineDocs/tests/data/set4.py b/doc/OnlineDocs/src/data/set4.py similarity index 100% rename from doc/OnlineDocs/tests/data/set4.py rename to doc/OnlineDocs/src/data/set4.py diff --git a/doc/OnlineDocs/tests/data/set4.txt b/doc/OnlineDocs/src/data/set4.txt similarity index 100% rename from doc/OnlineDocs/tests/data/set4.txt rename to doc/OnlineDocs/src/data/set4.txt diff --git a/doc/OnlineDocs/tests/data/set5.dat b/doc/OnlineDocs/src/data/set5.dat similarity index 100% rename from doc/OnlineDocs/tests/data/set5.dat rename to doc/OnlineDocs/src/data/set5.dat diff --git a/doc/OnlineDocs/tests/data/set5.py b/doc/OnlineDocs/src/data/set5.py similarity index 100% rename from doc/OnlineDocs/tests/data/set5.py rename to doc/OnlineDocs/src/data/set5.py diff --git a/doc/OnlineDocs/tests/data/set5.txt b/doc/OnlineDocs/src/data/set5.txt similarity index 100% rename from doc/OnlineDocs/tests/data/set5.txt rename to doc/OnlineDocs/src/data/set5.txt diff --git a/doc/OnlineDocs/tests/data/table0.dat b/doc/OnlineDocs/src/data/table0.dat similarity index 100% rename from doc/OnlineDocs/tests/data/table0.dat rename to doc/OnlineDocs/src/data/table0.dat diff --git a/doc/OnlineDocs/tests/data/table0.py b/doc/OnlineDocs/src/data/table0.py similarity index 100% rename from doc/OnlineDocs/tests/data/table0.py rename to doc/OnlineDocs/src/data/table0.py diff --git a/doc/OnlineDocs/tests/data/table0.txt b/doc/OnlineDocs/src/data/table0.txt similarity index 100% rename from doc/OnlineDocs/tests/data/table0.txt rename to doc/OnlineDocs/src/data/table0.txt diff --git a/doc/OnlineDocs/tests/data/table0.ul.dat b/doc/OnlineDocs/src/data/table0.ul.dat similarity index 100% rename from doc/OnlineDocs/tests/data/table0.ul.dat rename to doc/OnlineDocs/src/data/table0.ul.dat diff --git a/doc/OnlineDocs/tests/data/table0.ul.py b/doc/OnlineDocs/src/data/table0.ul.py similarity index 100% rename from doc/OnlineDocs/tests/data/table0.ul.py rename to doc/OnlineDocs/src/data/table0.ul.py diff --git a/doc/OnlineDocs/tests/data/table0.ul.txt b/doc/OnlineDocs/src/data/table0.ul.txt similarity index 100% rename from doc/OnlineDocs/tests/data/table0.ul.txt rename to doc/OnlineDocs/src/data/table0.ul.txt diff --git a/doc/OnlineDocs/tests/data/table1.dat b/doc/OnlineDocs/src/data/table1.dat similarity index 100% rename from doc/OnlineDocs/tests/data/table1.dat rename to doc/OnlineDocs/src/data/table1.dat diff --git a/doc/OnlineDocs/tests/data/table1.py b/doc/OnlineDocs/src/data/table1.py similarity index 100% rename from doc/OnlineDocs/tests/data/table1.py rename to doc/OnlineDocs/src/data/table1.py diff --git a/doc/OnlineDocs/tests/data/table1.txt b/doc/OnlineDocs/src/data/table1.txt similarity index 100% rename from doc/OnlineDocs/tests/data/table1.txt rename to doc/OnlineDocs/src/data/table1.txt diff --git a/doc/OnlineDocs/tests/data/table2.dat b/doc/OnlineDocs/src/data/table2.dat similarity index 100% rename from doc/OnlineDocs/tests/data/table2.dat rename to doc/OnlineDocs/src/data/table2.dat diff --git a/doc/OnlineDocs/tests/data/table2.py b/doc/OnlineDocs/src/data/table2.py similarity index 100% rename from doc/OnlineDocs/tests/data/table2.py rename to doc/OnlineDocs/src/data/table2.py diff --git a/doc/OnlineDocs/tests/data/table2.txt b/doc/OnlineDocs/src/data/table2.txt similarity index 100% rename from doc/OnlineDocs/tests/data/table2.txt rename to doc/OnlineDocs/src/data/table2.txt diff --git a/doc/OnlineDocs/tests/data/table3.dat b/doc/OnlineDocs/src/data/table3.dat similarity index 100% rename from doc/OnlineDocs/tests/data/table3.dat rename to doc/OnlineDocs/src/data/table3.dat diff --git a/doc/OnlineDocs/tests/data/table3.py b/doc/OnlineDocs/src/data/table3.py similarity index 100% rename from doc/OnlineDocs/tests/data/table3.py rename to doc/OnlineDocs/src/data/table3.py diff --git a/doc/OnlineDocs/tests/data/table3.txt b/doc/OnlineDocs/src/data/table3.txt similarity index 100% rename from doc/OnlineDocs/tests/data/table3.txt rename to doc/OnlineDocs/src/data/table3.txt diff --git a/doc/OnlineDocs/tests/data/table3.ul.dat b/doc/OnlineDocs/src/data/table3.ul.dat similarity index 100% rename from doc/OnlineDocs/tests/data/table3.ul.dat rename to doc/OnlineDocs/src/data/table3.ul.dat diff --git a/doc/OnlineDocs/tests/data/table3.ul.py b/doc/OnlineDocs/src/data/table3.ul.py similarity index 100% rename from doc/OnlineDocs/tests/data/table3.ul.py rename to doc/OnlineDocs/src/data/table3.ul.py diff --git a/doc/OnlineDocs/tests/data/table3.ul.txt b/doc/OnlineDocs/src/data/table3.ul.txt similarity index 100% rename from doc/OnlineDocs/tests/data/table3.ul.txt rename to doc/OnlineDocs/src/data/table3.ul.txt diff --git a/doc/OnlineDocs/tests/data/table4.dat b/doc/OnlineDocs/src/data/table4.dat similarity index 100% rename from doc/OnlineDocs/tests/data/table4.dat rename to doc/OnlineDocs/src/data/table4.dat diff --git a/doc/OnlineDocs/tests/data/table4.py b/doc/OnlineDocs/src/data/table4.py similarity index 100% rename from doc/OnlineDocs/tests/data/table4.py rename to doc/OnlineDocs/src/data/table4.py diff --git a/doc/OnlineDocs/tests/data/table4.txt b/doc/OnlineDocs/src/data/table4.txt similarity index 100% rename from doc/OnlineDocs/tests/data/table4.txt rename to doc/OnlineDocs/src/data/table4.txt diff --git a/doc/OnlineDocs/tests/data/table4.ul.dat b/doc/OnlineDocs/src/data/table4.ul.dat similarity index 100% rename from doc/OnlineDocs/tests/data/table4.ul.dat rename to doc/OnlineDocs/src/data/table4.ul.dat diff --git a/doc/OnlineDocs/tests/data/table4.ul.py b/doc/OnlineDocs/src/data/table4.ul.py similarity index 100% rename from doc/OnlineDocs/tests/data/table4.ul.py rename to doc/OnlineDocs/src/data/table4.ul.py diff --git a/doc/OnlineDocs/tests/data/table4.ul.txt b/doc/OnlineDocs/src/data/table4.ul.txt similarity index 100% rename from doc/OnlineDocs/tests/data/table4.ul.txt rename to doc/OnlineDocs/src/data/table4.ul.txt diff --git a/doc/OnlineDocs/tests/data/table5.dat b/doc/OnlineDocs/src/data/table5.dat similarity index 100% rename from doc/OnlineDocs/tests/data/table5.dat rename to doc/OnlineDocs/src/data/table5.dat diff --git a/doc/OnlineDocs/tests/data/table5.py b/doc/OnlineDocs/src/data/table5.py similarity index 100% rename from doc/OnlineDocs/tests/data/table5.py rename to doc/OnlineDocs/src/data/table5.py diff --git a/doc/OnlineDocs/tests/data/table5.txt b/doc/OnlineDocs/src/data/table5.txt similarity index 100% rename from doc/OnlineDocs/tests/data/table5.txt rename to doc/OnlineDocs/src/data/table5.txt diff --git a/doc/OnlineDocs/tests/data/table6.dat b/doc/OnlineDocs/src/data/table6.dat similarity index 100% rename from doc/OnlineDocs/tests/data/table6.dat rename to doc/OnlineDocs/src/data/table6.dat diff --git a/doc/OnlineDocs/tests/data/table6.py b/doc/OnlineDocs/src/data/table6.py similarity index 100% rename from doc/OnlineDocs/tests/data/table6.py rename to doc/OnlineDocs/src/data/table6.py diff --git a/doc/OnlineDocs/tests/data/table6.txt b/doc/OnlineDocs/src/data/table6.txt similarity index 100% rename from doc/OnlineDocs/tests/data/table6.txt rename to doc/OnlineDocs/src/data/table6.txt diff --git a/doc/OnlineDocs/tests/data/table7.dat b/doc/OnlineDocs/src/data/table7.dat similarity index 100% rename from doc/OnlineDocs/tests/data/table7.dat rename to doc/OnlineDocs/src/data/table7.dat diff --git a/doc/OnlineDocs/tests/data/table7.py b/doc/OnlineDocs/src/data/table7.py similarity index 100% rename from doc/OnlineDocs/tests/data/table7.py rename to doc/OnlineDocs/src/data/table7.py diff --git a/doc/OnlineDocs/tests/data/table7.txt b/doc/OnlineDocs/src/data/table7.txt similarity index 100% rename from doc/OnlineDocs/tests/data/table7.txt rename to doc/OnlineDocs/src/data/table7.txt diff --git a/doc/OnlineDocs/tests/dataportal/A.tab b/doc/OnlineDocs/src/dataportal/A.tab similarity index 100% rename from doc/OnlineDocs/tests/dataportal/A.tab rename to doc/OnlineDocs/src/dataportal/A.tab diff --git a/doc/OnlineDocs/tests/dataportal/C.tab b/doc/OnlineDocs/src/dataportal/C.tab similarity index 100% rename from doc/OnlineDocs/tests/dataportal/C.tab rename to doc/OnlineDocs/src/dataportal/C.tab diff --git a/doc/OnlineDocs/tests/dataportal/D.tab b/doc/OnlineDocs/src/dataportal/D.tab similarity index 100% rename from doc/OnlineDocs/tests/dataportal/D.tab rename to doc/OnlineDocs/src/dataportal/D.tab diff --git a/doc/OnlineDocs/tests/dataportal/PP.csv b/doc/OnlineDocs/src/dataportal/PP.csv similarity index 100% rename from doc/OnlineDocs/tests/dataportal/PP.csv rename to doc/OnlineDocs/src/dataportal/PP.csv diff --git a/doc/OnlineDocs/tests/dataportal/PP.json b/doc/OnlineDocs/src/dataportal/PP.json similarity index 100% rename from doc/OnlineDocs/tests/dataportal/PP.json rename to doc/OnlineDocs/src/dataportal/PP.json diff --git a/doc/OnlineDocs/tests/dataportal/PP.sqlite b/doc/OnlineDocs/src/dataportal/PP.sqlite similarity index 100% rename from doc/OnlineDocs/tests/dataportal/PP.sqlite rename to doc/OnlineDocs/src/dataportal/PP.sqlite diff --git a/doc/OnlineDocs/tests/dataportal/PP.tab b/doc/OnlineDocs/src/dataportal/PP.tab similarity index 100% rename from doc/OnlineDocs/tests/dataportal/PP.tab rename to doc/OnlineDocs/src/dataportal/PP.tab diff --git a/doc/OnlineDocs/tests/dataportal/PP.xml b/doc/OnlineDocs/src/dataportal/PP.xml similarity index 100% rename from doc/OnlineDocs/tests/dataportal/PP.xml rename to doc/OnlineDocs/src/dataportal/PP.xml diff --git a/doc/OnlineDocs/tests/dataportal/PP.yaml b/doc/OnlineDocs/src/dataportal/PP.yaml similarity index 100% rename from doc/OnlineDocs/tests/dataportal/PP.yaml rename to doc/OnlineDocs/src/dataportal/PP.yaml diff --git a/doc/OnlineDocs/tests/dataportal/PP_sqlite.py b/doc/OnlineDocs/src/dataportal/PP_sqlite.py similarity index 100% rename from doc/OnlineDocs/tests/dataportal/PP_sqlite.py rename to doc/OnlineDocs/src/dataportal/PP_sqlite.py diff --git a/doc/OnlineDocs/tests/dataportal/Pyomo_mysql b/doc/OnlineDocs/src/dataportal/Pyomo_mysql similarity index 100% rename from doc/OnlineDocs/tests/dataportal/Pyomo_mysql rename to doc/OnlineDocs/src/dataportal/Pyomo_mysql diff --git a/doc/OnlineDocs/tests/dataportal/S.tab b/doc/OnlineDocs/src/dataportal/S.tab similarity index 100% rename from doc/OnlineDocs/tests/dataportal/S.tab rename to doc/OnlineDocs/src/dataportal/S.tab diff --git a/doc/OnlineDocs/tests/dataportal/T.json b/doc/OnlineDocs/src/dataportal/T.json similarity index 100% rename from doc/OnlineDocs/tests/dataportal/T.json rename to doc/OnlineDocs/src/dataportal/T.json diff --git a/doc/OnlineDocs/tests/dataportal/T.yaml b/doc/OnlineDocs/src/dataportal/T.yaml similarity index 100% rename from doc/OnlineDocs/tests/dataportal/T.yaml rename to doc/OnlineDocs/src/dataportal/T.yaml diff --git a/doc/OnlineDocs/tests/dataportal/U.tab b/doc/OnlineDocs/src/dataportal/U.tab similarity index 100% rename from doc/OnlineDocs/tests/dataportal/U.tab rename to doc/OnlineDocs/src/dataportal/U.tab diff --git a/doc/OnlineDocs/tests/dataportal/XW.tab b/doc/OnlineDocs/src/dataportal/XW.tab similarity index 100% rename from doc/OnlineDocs/tests/dataportal/XW.tab rename to doc/OnlineDocs/src/dataportal/XW.tab diff --git a/doc/OnlineDocs/tests/dataportal/Y.tab b/doc/OnlineDocs/src/dataportal/Y.tab similarity index 100% rename from doc/OnlineDocs/tests/dataportal/Y.tab rename to doc/OnlineDocs/src/dataportal/Y.tab diff --git a/doc/OnlineDocs/tests/dataportal/Z.tab b/doc/OnlineDocs/src/dataportal/Z.tab similarity index 100% rename from doc/OnlineDocs/tests/dataportal/Z.tab rename to doc/OnlineDocs/src/dataportal/Z.tab diff --git a/doc/OnlineDocs/tests/dataportal/dataportal_tab.py b/doc/OnlineDocs/src/dataportal/dataportal_tab.py similarity index 100% rename from doc/OnlineDocs/tests/dataportal/dataportal_tab.py rename to doc/OnlineDocs/src/dataportal/dataportal_tab.py diff --git a/doc/OnlineDocs/tests/dataportal/dataportal_tab.txt b/doc/OnlineDocs/src/dataportal/dataportal_tab.txt similarity index 100% rename from doc/OnlineDocs/tests/dataportal/dataportal_tab.txt rename to doc/OnlineDocs/src/dataportal/dataportal_tab.txt diff --git a/doc/OnlineDocs/tests/dataportal/excel.xls b/doc/OnlineDocs/src/dataportal/excel.xls similarity index 100% rename from doc/OnlineDocs/tests/dataportal/excel.xls rename to doc/OnlineDocs/src/dataportal/excel.xls diff --git a/doc/OnlineDocs/tests/dataportal/param_initialization.py b/doc/OnlineDocs/src/dataportal/param_initialization.py similarity index 100% rename from doc/OnlineDocs/tests/dataportal/param_initialization.py rename to doc/OnlineDocs/src/dataportal/param_initialization.py diff --git a/doc/OnlineDocs/tests/dataportal/param_initialization.txt b/doc/OnlineDocs/src/dataportal/param_initialization.txt similarity index 100% rename from doc/OnlineDocs/tests/dataportal/param_initialization.txt rename to doc/OnlineDocs/src/dataportal/param_initialization.txt diff --git a/doc/OnlineDocs/tests/dataportal/set_initialization.py b/doc/OnlineDocs/src/dataportal/set_initialization.py similarity index 100% rename from doc/OnlineDocs/tests/dataportal/set_initialization.py rename to doc/OnlineDocs/src/dataportal/set_initialization.py diff --git a/doc/OnlineDocs/tests/dataportal/set_initialization.txt b/doc/OnlineDocs/src/dataportal/set_initialization.txt similarity index 100% rename from doc/OnlineDocs/tests/dataportal/set_initialization.txt rename to doc/OnlineDocs/src/dataportal/set_initialization.txt diff --git a/doc/OnlineDocs/tests/expr/design.py b/doc/OnlineDocs/src/expr/design.py similarity index 100% rename from doc/OnlineDocs/tests/expr/design.py rename to doc/OnlineDocs/src/expr/design.py diff --git a/doc/OnlineDocs/tests/expr/design.txt b/doc/OnlineDocs/src/expr/design.txt similarity index 100% rename from doc/OnlineDocs/tests/expr/design.txt rename to doc/OnlineDocs/src/expr/design.txt diff --git a/doc/OnlineDocs/tests/expr/index.py b/doc/OnlineDocs/src/expr/index.py similarity index 100% rename from doc/OnlineDocs/tests/expr/index.py rename to doc/OnlineDocs/src/expr/index.py diff --git a/doc/OnlineDocs/tests/expr/index.txt b/doc/OnlineDocs/src/expr/index.txt similarity index 100% rename from doc/OnlineDocs/tests/expr/index.txt rename to doc/OnlineDocs/src/expr/index.txt diff --git a/doc/OnlineDocs/tests/expr/managing.py b/doc/OnlineDocs/src/expr/managing.py similarity index 100% rename from doc/OnlineDocs/tests/expr/managing.py rename to doc/OnlineDocs/src/expr/managing.py diff --git a/doc/OnlineDocs/tests/expr/managing.txt b/doc/OnlineDocs/src/expr/managing.txt similarity index 100% rename from doc/OnlineDocs/tests/expr/managing.txt rename to doc/OnlineDocs/src/expr/managing.txt diff --git a/doc/OnlineDocs/tests/expr/overview.py b/doc/OnlineDocs/src/expr/overview.py similarity index 100% rename from doc/OnlineDocs/tests/expr/overview.py rename to doc/OnlineDocs/src/expr/overview.py diff --git a/doc/OnlineDocs/tests/expr/overview.txt b/doc/OnlineDocs/src/expr/overview.txt similarity index 100% rename from doc/OnlineDocs/tests/expr/overview.txt rename to doc/OnlineDocs/src/expr/overview.txt diff --git a/doc/OnlineDocs/tests/expr/performance.py b/doc/OnlineDocs/src/expr/performance.py similarity index 100% rename from doc/OnlineDocs/tests/expr/performance.py rename to doc/OnlineDocs/src/expr/performance.py diff --git a/doc/OnlineDocs/tests/expr/performance.txt b/doc/OnlineDocs/src/expr/performance.txt similarity index 100% rename from doc/OnlineDocs/tests/expr/performance.txt rename to doc/OnlineDocs/src/expr/performance.txt diff --git a/doc/OnlineDocs/tests/expr/quicksum.log b/doc/OnlineDocs/src/expr/quicksum.log similarity index 100% rename from doc/OnlineDocs/tests/expr/quicksum.log rename to doc/OnlineDocs/src/expr/quicksum.log diff --git a/doc/OnlineDocs/tests/expr/quicksum.py b/doc/OnlineDocs/src/expr/quicksum.py similarity index 100% rename from doc/OnlineDocs/tests/expr/quicksum.py rename to doc/OnlineDocs/src/expr/quicksum.py diff --git a/doc/OnlineDocs/tests/kernel/examples.sh b/doc/OnlineDocs/src/kernel/examples.sh similarity index 100% rename from doc/OnlineDocs/tests/kernel/examples.sh rename to doc/OnlineDocs/src/kernel/examples.sh diff --git a/doc/OnlineDocs/tests/kernel/examples.txt b/doc/OnlineDocs/src/kernel/examples.txt similarity index 100% rename from doc/OnlineDocs/tests/kernel/examples.txt rename to doc/OnlineDocs/src/kernel/examples.txt diff --git a/doc/OnlineDocs/tests/scripting/AbstractSuffixes.py b/doc/OnlineDocs/src/scripting/AbstractSuffixes.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/AbstractSuffixes.py rename to doc/OnlineDocs/src/scripting/AbstractSuffixes.py diff --git a/doc/OnlineDocs/tests/scripting/Isinglebuild.py b/doc/OnlineDocs/src/scripting/Isinglebuild.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/Isinglebuild.py rename to doc/OnlineDocs/src/scripting/Isinglebuild.py diff --git a/doc/OnlineDocs/tests/scripting/Isinglecomm.dat b/doc/OnlineDocs/src/scripting/Isinglecomm.dat similarity index 100% rename from doc/OnlineDocs/tests/scripting/Isinglecomm.dat rename to doc/OnlineDocs/src/scripting/Isinglecomm.dat diff --git a/doc/OnlineDocs/tests/scripting/NodesIn_init.py b/doc/OnlineDocs/src/scripting/NodesIn_init.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/NodesIn_init.py rename to doc/OnlineDocs/src/scripting/NodesIn_init.py diff --git a/doc/OnlineDocs/tests/scripting/Z_init.py b/doc/OnlineDocs/src/scripting/Z_init.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/Z_init.py rename to doc/OnlineDocs/src/scripting/Z_init.py diff --git a/doc/OnlineDocs/tests/scripting/abstract1.dat b/doc/OnlineDocs/src/scripting/abstract1.dat similarity index 100% rename from doc/OnlineDocs/tests/scripting/abstract1.dat rename to doc/OnlineDocs/src/scripting/abstract1.dat diff --git a/doc/OnlineDocs/tests/scripting/abstract2.dat b/doc/OnlineDocs/src/scripting/abstract2.dat similarity index 100% rename from doc/OnlineDocs/tests/scripting/abstract2.dat rename to doc/OnlineDocs/src/scripting/abstract2.dat diff --git a/doc/OnlineDocs/tests/scripting/abstract2.py b/doc/OnlineDocs/src/scripting/abstract2.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/abstract2.py rename to doc/OnlineDocs/src/scripting/abstract2.py diff --git a/doc/OnlineDocs/tests/scripting/abstract2a.dat b/doc/OnlineDocs/src/scripting/abstract2a.dat similarity index 100% rename from doc/OnlineDocs/tests/scripting/abstract2a.dat rename to doc/OnlineDocs/src/scripting/abstract2a.dat diff --git a/doc/OnlineDocs/tests/scripting/abstract2piece.py b/doc/OnlineDocs/src/scripting/abstract2piece.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/abstract2piece.py rename to doc/OnlineDocs/src/scripting/abstract2piece.py diff --git a/doc/OnlineDocs/tests/scripting/abstract2piecebuild.py b/doc/OnlineDocs/src/scripting/abstract2piecebuild.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/abstract2piecebuild.py rename to doc/OnlineDocs/src/scripting/abstract2piecebuild.py diff --git a/doc/OnlineDocs/tests/scripting/block_iter_example.py b/doc/OnlineDocs/src/scripting/block_iter_example.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/block_iter_example.py rename to doc/OnlineDocs/src/scripting/block_iter_example.py diff --git a/doc/OnlineDocs/tests/scripting/concrete1.py b/doc/OnlineDocs/src/scripting/concrete1.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/concrete1.py rename to doc/OnlineDocs/src/scripting/concrete1.py diff --git a/doc/OnlineDocs/tests/scripting/doubleA.py b/doc/OnlineDocs/src/scripting/doubleA.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/doubleA.py rename to doc/OnlineDocs/src/scripting/doubleA.py diff --git a/doc/OnlineDocs/tests/scripting/driveabs2.py b/doc/OnlineDocs/src/scripting/driveabs2.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/driveabs2.py rename to doc/OnlineDocs/src/scripting/driveabs2.py diff --git a/doc/OnlineDocs/tests/scripting/driveconc1.py b/doc/OnlineDocs/src/scripting/driveconc1.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/driveconc1.py rename to doc/OnlineDocs/src/scripting/driveconc1.py diff --git a/doc/OnlineDocs/tests/scripting/iterative1.py b/doc/OnlineDocs/src/scripting/iterative1.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/iterative1.py rename to doc/OnlineDocs/src/scripting/iterative1.py diff --git a/doc/OnlineDocs/tests/scripting/iterative2.py b/doc/OnlineDocs/src/scripting/iterative2.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/iterative2.py rename to doc/OnlineDocs/src/scripting/iterative2.py diff --git a/doc/OnlineDocs/tests/scripting/noiteration1.py b/doc/OnlineDocs/src/scripting/noiteration1.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/noiteration1.py rename to doc/OnlineDocs/src/scripting/noiteration1.py diff --git a/doc/OnlineDocs/tests/scripting/parallel.py b/doc/OnlineDocs/src/scripting/parallel.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/parallel.py rename to doc/OnlineDocs/src/scripting/parallel.py diff --git a/doc/OnlineDocs/tests/scripting/spy4Constraints.py b/doc/OnlineDocs/src/scripting/spy4Constraints.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/spy4Constraints.py rename to doc/OnlineDocs/src/scripting/spy4Constraints.py diff --git a/doc/OnlineDocs/tests/scripting/spy4Expressions.py b/doc/OnlineDocs/src/scripting/spy4Expressions.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/spy4Expressions.py rename to doc/OnlineDocs/src/scripting/spy4Expressions.py diff --git a/doc/OnlineDocs/tests/scripting/spy4PyomoCommand.py b/doc/OnlineDocs/src/scripting/spy4PyomoCommand.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/spy4PyomoCommand.py rename to doc/OnlineDocs/src/scripting/spy4PyomoCommand.py diff --git a/doc/OnlineDocs/tests/scripting/spy4Variables.py b/doc/OnlineDocs/src/scripting/spy4Variables.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/spy4Variables.py rename to doc/OnlineDocs/src/scripting/spy4Variables.py diff --git a/doc/OnlineDocs/tests/scripting/spy4scripts.py b/doc/OnlineDocs/src/scripting/spy4scripts.py similarity index 100% rename from doc/OnlineDocs/tests/scripting/spy4scripts.py rename to doc/OnlineDocs/src/scripting/spy4scripts.py diff --git a/doc/OnlineDocs/tests/strip_examples.py b/doc/OnlineDocs/src/strip_examples.py similarity index 100% rename from doc/OnlineDocs/tests/strip_examples.py rename to doc/OnlineDocs/src/strip_examples.py diff --git a/doc/OnlineDocs/tests/test_examples.py b/doc/OnlineDocs/src/test_examples.py similarity index 100% rename from doc/OnlineDocs/tests/test_examples.py rename to doc/OnlineDocs/src/test_examples.py diff --git a/doc/OnlineDocs/working_abstractmodels/BuildAction.rst b/doc/OnlineDocs/working_abstractmodels/BuildAction.rst index 5706ce97e6f..6840e15a1d5 100644 --- a/doc/OnlineDocs/working_abstractmodels/BuildAction.rst +++ b/doc/OnlineDocs/working_abstractmodels/BuildAction.rst @@ -13,7 +13,7 @@ trigger actions to be done as part of the model building process. The takes as arguments optional index sets and a function to perform the action. For example, -.. literalinclude:: ../tests/scripting/abstract2piecebuild_BuildAction_example.spy +.. literalinclude:: ../src/scripting/abstract2piecebuild_BuildAction_example.spy :language: python calls the function ``bpts_build`` for each member of ``model.J``. The @@ -21,14 +21,14 @@ function ``bpts_build`` should have the model and a variable for the members of ``model.J`` as formal arguments. In this example, the following would be a valid declaration for the function: -.. literalinclude:: ../tests/scripting/abstract2piecebuild_Function_valid_declaration.spy +.. literalinclude:: ../src/scripting/abstract2piecebuild_Function_valid_declaration.spy :language: python A full example, which extends the :ref:`abstract2.py` and :ref:`abstract2piece.py` examples, is -.. literalinclude:: ../tests/scripting/abstract2piecebuild.spy +.. literalinclude:: ../src/scripting/abstract2piecebuild.spy :language: python This example uses the build action to create a model component with @@ -51,13 +51,13 @@ clearer, to use a build action. The full model is: -.. literalinclude:: ../tests/scripting/Isinglebuild.py +.. literalinclude:: ../src/scripting/Isinglebuild.py :language: python for this model, the same data file can be used as for Isinglecomm.py in :ref:`Isinglecomm.py` such as the toy data file: -.. literalinclude:: ../tests/scripting/Isinglecomm.dat +.. literalinclude:: ../src/scripting/Isinglecomm.dat Build actions can also be a way to implement data validation, particularly when multiple Sets or Parameters must be analyzed. However, diff --git a/doc/OnlineDocs/working_abstractmodels/data/dataportals.rst b/doc/OnlineDocs/working_abstractmodels/data/dataportals.rst index 88b89653a37..5ce907fda2a 100644 --- a/doc/OnlineDocs/working_abstractmodels/data/dataportals.rst +++ b/doc/OnlineDocs/working_abstractmodels/data/dataportals.rst @@ -62,14 +62,14 @@ can be used to initialize both concrete and abstract Pyomo models. Consider the file ``A.tab``, which defines a simple set with a tabular format: -.. literalinclude:: ../../tests/dataportal/A.tab +.. literalinclude:: ../../src/dataportal/A.tab :language: none The ``load`` method is used to load data into a :class:`~pyomo.environ.DataPortal` object. Components in a concrete model can be explicitly initialized with data loaded by a :class:`~pyomo.environ.DataPortal` object: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_concrete1.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_concrete1.spy :language: python All data needed to initialize an abstract model *must* be provided by a @@ -77,7 +77,7 @@ All data needed to initialize an abstract model *must* be provided by a and the use of the :class:`~pyomo.environ.DataPortal` object to initialize components is automated for the user: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_load.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_load.spy :language: python Note the difference in the execution of the ``load`` method in these two @@ -126,7 +126,7 @@ that are loaded from different data sources. The ``[]`` operator is used to access set and parameter values. Consider the following example, which loads data and prints the value of the ``[]`` operator: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_getitem.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_getitem.spy :language: python The :class:`~pyomo.environ.DataPortal` @@ -162,12 +162,12 @@ with lists and dictionaries: For example, consider the following JSON file: -.. literalinclude:: ../../tests/dataportal/T.json +.. literalinclude:: ../../src/dataportal/T.json :language: none The data in this file can be used to load the following model: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_json1.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_json1.spy :language: python Note that no ``set`` or ``param`` option needs to be specified when @@ -178,13 +178,13 @@ needed for model construction is used. The following YAML file has a similar structure: -.. literalinclude:: ../../tests/dataportal/T.yaml +.. literalinclude:: ../../src/dataportal/T.yaml :language: none The data in this file can be used to load a Pyomo model with the same syntax as a JSON file: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_yaml1.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_yaml1.spy :language: python @@ -212,7 +212,7 @@ TAB files represent tabular data in an ascii file using whitespace as a delimiter. A TAB file consists of rows of values, where each row has the same length. For example, the file ``PP.tab`` has the format: -.. literalinclude:: ../../tests/dataportal/PP.tab +.. literalinclude:: ../../src/dataportal/PP.tab :language: none CSV files represent tabular data in a format that is very similar to TAB @@ -220,7 +220,7 @@ files. Pyomo assumes that a CSV file consists of rows of values, where each row has the same length. For example, the file ``PP.csv`` has the format: -.. literalinclude:: ../../tests/dataportal/PP.csv +.. literalinclude:: ../../src/dataportal/PP.csv :language: none Excel spreadsheets can express complex data relationships. A *range* is @@ -242,7 +242,7 @@ sub-element of a ``row`` element represents a different column, where each row has the same length. For example, the file ``PP.xml`` has the format: -.. literalinclude:: ../../tests/dataportal/PP.xml +.. literalinclude:: ../../src/dataportal/PP.xml :language: none Loading Set Data @@ -256,13 +256,13 @@ Loading a Simple Set Consider the file ``A.tab``, which defines a simple set: -.. literalinclude:: ../../tests/dataportal/A.tab +.. literalinclude:: ../../src/dataportal/A.tab :language: none In the following example, a :class:`~pyomo.environ.DataPortal` object loads data for a simple set ``A``: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_set1.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_set1.spy :language: python Loading a Set of Tuples @@ -270,13 +270,13 @@ Loading a Set of Tuples Consider the file ``C.tab``: -.. literalinclude:: ../../tests/dataportal/C.tab +.. literalinclude:: ../../src/dataportal/C.tab :language: none In the following example, a :class:`~pyomo.environ.DataPortal` object loads data for a two-dimensional set ``C``: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_set2.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_set2.spy :language: python In this example, the column titles do not directly impact the process of @@ -289,13 +289,13 @@ Loading a Set Array Consider the file ``D.tab``, which defines an array representation of a two-dimensional set: -.. literalinclude:: ../../tests/dataportal/D.tab +.. literalinclude:: ../../src/dataportal/D.tab :language: none In the following example, a :class:`~pyomo.environ.DataPortal` object loads data for a two-dimensional set ``D``: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_set3.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_set3.spy :language: python The ``format`` option indicates that the set data is declared in a array @@ -313,13 +313,13 @@ Loading a Simple Parameter The simplest parameter is simply a singleton value. Consider the file ``Z.tab``: -.. literalinclude:: ../../tests/dataportal/Z.tab +.. literalinclude:: ../../src/dataportal/Z.tab :language: none In the following example, a :class:`~pyomo.environ.DataPortal` object loads data for a simple parameter ``z``: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_param1.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_param1.spy :language: python Loading an Indexed Parameter @@ -328,13 +328,13 @@ Loading an Indexed Parameter An indexed parameter can be defined by a single column in a table. For example, consider the file ``Y.tab``: -.. literalinclude:: ../../tests/dataportal/Y.tab +.. literalinclude:: ../../src/dataportal/Y.tab :language: none In the following example, a :class:`~pyomo.environ.DataPortal` object loads data for an indexed parameter ``y``: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_param2.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_param2.spy :language: python When column names are not used to specify the index and parameter data, @@ -351,19 +351,19 @@ The index set can be loaded with the parameter data using the ``index`` option. In the following example, a :class:`~pyomo.environ.DataPortal` object loads data for set ``A`` and the indexed parameter ``y`` -.. literalinclude:: ../../tests/dataportal/dataportal_tab_param3.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_param3.spy :language: python An index set with multiple dimensions can also be loaded with an indexed parameter. Consider the file ``PP.tab``: -.. literalinclude:: ../../tests/dataportal/PP.tab +.. literalinclude:: ../../src/dataportal/PP.tab :language: none In the following example, a :class:`~pyomo.environ.DataPortal` object loads data for a tuple set and an indexed parameter: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_param10.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_param10.spy :language: python Loading a Parameter with Missing Values @@ -373,7 +373,7 @@ Missing parameter data can be expressed in two ways. First, parameter data can be defined with indices that are a subset of valid indices in the model. The following example loads the indexed parameter ``y``: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_param9.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_param9.spy :language: python The model defines an index set with four values, but only three @@ -382,13 +382,13 @@ parameter values are declared in the data file ``Y.tab``. Parameter data can also be declared with missing values using the period (``.``) symbol. For example, consider the file ``S.tab``: -.. literalinclude:: ../../tests/dataportal/PP.tab +.. literalinclude:: ../../src/dataportal/PP.tab :language: none In the following example, a :class:`~pyomo.environ.DataPortal` object loads data for the index set ``A`` and indexed parameter ``y``: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_param8.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_param8.spy :language: python The period (``.``) symbol indicates a missing parameter value, but the @@ -400,13 +400,13 @@ Loading Multiple Parameters Multiple parameters can be initialized at once by specifying a list (or tuple) of component parameters. Consider the file ``XW.tab``: -.. literalinclude:: ../../tests/dataportal/XW.tab +.. literalinclude:: ../../src/dataportal/XW.tab :language: none In the following example, a :class:`~pyomo.environ.DataPortal` object loads data for parameters ``x`` and ``w``: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_param4.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_param4.spy :language: python Selecting Parameter Columns @@ -421,7 +421,7 @@ component data. For example, consider the following load declaration: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_param5.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_param5.spy :language: python The columns ``A`` and ``W`` are selected from the file ``XW.tab``, and a @@ -433,20 +433,20 @@ Loading a Parameter Array Consider the file ``U.tab``, which defines an array representation of a multiply-indexed parameter: -.. literalinclude:: ../../tests/dataportal/U.tab +.. literalinclude:: ../../src/dataportal/U.tab :language: none In the following example, a :class:`~pyomo.environ.DataPortal` object loads data for a two-dimensional parameter ``u``: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_param6.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_param6.spy :language: python The ``format`` option indicates that the parameter data is declared in a array format. The ``format`` option can also indicate that the parameter data should be transposed. -.. literalinclude:: ../../tests/dataportal/dataportal_tab_param7.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_param7.spy :language: python Note that the transposed parameter data changes the index set for the @@ -467,7 +467,7 @@ the following range of cells, which is named ``PPtable``: In the following example, a :class:`~pyomo.environ.DataPortal` object loads the named range ``PPtable`` from the file ``excel.xls``: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_excel1.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_excel1.spy :language: python Note that the ``range`` option is required to specify the table of cell @@ -477,14 +477,14 @@ There are a variety of ways that data can be loaded from a relational database. In the simplest case, a table can be specified within a database: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_db1.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_db1.spy :language: python In this example, the interface ``sqlite3`` is used to load data from an SQLite database in the file ``PP.sqlite``. More generally, an SQL query can be specified to dynamically generate a table. For example: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_db2.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_db2.spy :language: python Data Namespaces @@ -524,7 +524,7 @@ components. For example, the following script generates two model instances from an abstract model using data loaded into different namespaces: -.. literalinclude:: ../../tests/dataportal/dataportal_tab_namespaces1.spy +.. literalinclude:: ../../src/dataportal/dataportal_tab_namespaces1.spy :language: python diff --git a/doc/OnlineDocs/working_abstractmodels/data/datfiles.rst b/doc/OnlineDocs/working_abstractmodels/data/datfiles.rst index 06d28093cf4..4982ed2fff0 100644 --- a/doc/OnlineDocs/working_abstractmodels/data/datfiles.rst +++ b/doc/OnlineDocs/working_abstractmodels/data/datfiles.rst @@ -104,7 +104,7 @@ A set may be empty, and it may contain any combination of numeric and non-numeric string values. For example, the following are valid ``set`` commands: -.. literalinclude:: ../../tests/data/set1.dat +.. literalinclude:: ../../src/data/set1.dat :language: python @@ -115,19 +115,19 @@ The ``set`` data command can also specify tuple data with the standard notation for tuples. For example, suppose that set ``A`` contains 3-tuples: -.. literalinclude:: ../../tests/data/set2_decl.spy +.. literalinclude:: ../../src/data/set2_decl.spy :language: python The following ``set`` data command then specifies that ``A`` is the set containing the tuples ``(1,2,3)`` and ``(4,5,6)``: -.. literalinclude:: ../../tests/data/set2a.dat +.. literalinclude:: ../../src/data/set2a.dat :language: none Alternatively, set data can simply be listed in the order that the tuple is represented: -.. literalinclude:: ../../tests/data/set2.dat +.. literalinclude:: ../../src/data/set2.dat :language: none Obviously, the number of data elements specified using this syntax @@ -138,7 +138,7 @@ membership. For example, the following ``set`` data command declares 2-tuples in ``A`` using plus (``+``) to denote valid tuples and minus (``-``) to denote invalid tuples: -.. literalinclude:: ../../tests/data/set4.dat +.. literalinclude:: ../../src/data/set4.dat :language: none This data command declares the following five 2-tuples: ``('A1',1)``, @@ -148,13 +148,13 @@ Finally, a set of tuple data can be concisely represented with tuple *templates* that represent a *slice* of tuple data. For example, suppose that the set ``A`` contains 4-tuples: -.. literalinclude:: ../../tests/data/set5_decl.spy +.. literalinclude:: ../../src/data/set5_decl.spy :language: python The following ``set`` data command declares groups of tuples that are defined by a template and data to complete this template: -.. literalinclude:: ../../tests/data/set5.dat +.. literalinclude:: ../../src/data/set5.dat :language: none A tuple template consists of a tuple that contains one or more asterisk @@ -163,7 +163,7 @@ tuple value is replaced by the values from the list of values that follows the tuple template. In this example, the following tuples are in set ``A``: -.. literalinclude:: ../../tests/data/set5.txt +.. literalinclude:: ../../src/data/set5.txt :language: none Set Arrays @@ -183,12 +183,12 @@ list of string values. Suppose that a set ``A`` is used to index a set ``B`` as follows: -.. literalinclude:: ../../tests/data/set3_decl.spy +.. literalinclude:: ../../src/data/set3_decl.spy :language: python Then set ``B`` is indexed using the values declared for set ``A``: -.. literalinclude:: ../../tests/data/set3.dat +.. literalinclude:: ../../src/data/set3.dat :language: none The ``param`` Command @@ -197,7 +197,7 @@ The ``param`` Command Simple or non-indexed parameters are declared in an obvious way, as shown by these examples: -.. literalinclude:: ../../tests/data/param1.dat +.. literalinclude:: ../../src/data/param1.dat :language: none Parameters can be defined with numeric data, simple strings and quoted @@ -213,33 +213,33 @@ parameter data. One-dimensional parameter data is indexed over a single set. Suppose that the parameter ``B`` is a parameter indexed by the set ``A``: -.. literalinclude:: ../../tests/data/param2_decl.spy +.. literalinclude:: ../../src/data/param2_decl.spy :language: python A ``param`` data command can specify values for ``B`` with a list of index-value pairs: -.. literalinclude:: ../../tests/data/param2.dat +.. literalinclude:: ../../src/data/param2.dat :language: none Because whitespace is ignored, this example data command file can be reorganized to specify the same data in a tabular format: -.. literalinclude:: ../../tests/data/param2a.dat +.. literalinclude:: ../../src/data/param2a.dat :language: none Multiple parameters can be defined using a single ``param`` data command. For example, suppose that parameters ``B``, ``C``, and ``D`` are one-dimensional parameters all indexed by the set ``A``: -.. literalinclude:: ../../tests/data/param3_decl.spy +.. literalinclude:: ../../src/data/param3_decl.spy :language: python Values for these parameters can be specified using a single ``param`` data command that declares these parameter names followed by a list of index and parameter values: -.. literalinclude:: ../../tests/data/param3.dat +.. literalinclude:: ../../src/data/param3.dat :language: none The values in the ``param`` data command are interpreted as a list of @@ -249,7 +249,7 @@ corresponding numeric value. Note that parameter values do not need to be defined for all indices. For example, the following data command file is valid: -.. literalinclude:: ../../tests/data/param3a.dat +.. literalinclude:: ../../src/data/param3a.dat :language: none The index ``g`` is omitted from the ``param`` command, and consequently @@ -259,7 +259,7 @@ More complex patterns of missing data can be specified using the period specifying multiple parameters that do not necessarily have the same index values: -.. literalinclude:: ../../tests/data/param3b.dat +.. literalinclude:: ../../src/data/param3b.dat :language: none This example provides a concise representation of parameters that share @@ -270,13 +270,13 @@ Note that this data file specifies the data for set ``A`` twice: defined. An alternate syntax for ``param`` allows the user to concisely specify the definition of an index set along with associated parameters: -.. literalinclude:: ../../tests/data/param3c.dat +.. literalinclude:: ../../src/data/param3c.dat :language: none Finally, we note that default values for missing data can also be specified using the ``default`` keyword: -.. literalinclude:: ../../tests/data/param4.dat +.. literalinclude:: ../../src/data/param4.dat :language: none Note that default values can only be specified in ``param`` commands @@ -290,58 +290,58 @@ Multi-dimensional parameter data is indexed over either multiple sets or a single multi-dimensional set. Suppose that parameter ``B`` is a parameter indexed by set ``A`` that has dimension 2: -.. literalinclude:: ../../tests/data/param5_decl.spy +.. literalinclude:: ../../src/data/param5_decl.spy :language: python The syntax of the ``param`` data command remains essentially the same when specifying values for ``B`` with a list of index and parameter values: -.. literalinclude:: ../../tests/data/param5.dat +.. literalinclude:: ../../src/data/param5.dat :language: none Missing and default values are also handled in the same way with multi-dimensional index sets: -.. literalinclude:: ../../tests/data/param5a.dat +.. literalinclude:: ../../src/data/param5a.dat :language: none Similarly, multiple parameters can defined with a single ``param`` data command. Suppose that parameters ``B``, ``C``, and ``D`` are parameters indexed over set ``A`` that has dimension 2: -.. literalinclude:: ../../tests/data/param6_decl.spy +.. literalinclude:: ../../src/data/param6_decl.spy :language: python These parameters can be defined with a single ``param`` command that declares the parameter names followed by a list of index and parameter values: -.. literalinclude:: ../../tests/data/param6.dat +.. literalinclude:: ../../src/data/param6.dat :language: none Similarly, the following ``param`` data command defines the index set along with the parameters: -.. literalinclude:: ../../tests/data/param6a.dat +.. literalinclude:: ../../src/data/param6a.dat :language: none The ``param`` command also supports a matrix syntax for specifying the values in a parameter that has a 2-dimensional index. Suppose parameter ``B`` is indexed over set ``A`` that has dimension 2: -.. literalinclude:: ../../tests/data/param7a_decl.spy +.. literalinclude:: ../../src/data/param7a_decl.spy :language: python The following ``param`` command defines a matrix of parameter values: -.. literalinclude:: ../../tests/data/param7a.dat +.. literalinclude:: ../../src/data/param7a.dat :language: none Additionally, the following syntax can be used to specify a transposed matrix of parameter values: -.. literalinclude:: ../../tests/data/param7b.dat +.. literalinclude:: ../../src/data/param7b.dat :language: none This functionality facilitates the presentation of parameter data in a @@ -355,13 +355,13 @@ be specified as a series of slices. Each slice is defined by a template followed by a list of index and parameter values. Suppose that parameter ``B`` is indexed over set ``A`` that has dimension 4: -.. literalinclude:: ../../tests/data/param8a_decl.spy +.. literalinclude:: ../../src/data/param8a_decl.spy :language: python The following ``param`` command defines a matrix of parameter values with multiple templates: -.. literalinclude:: ../../tests/data/param8a.dat +.. literalinclude:: ../../src/data/param8a.dat :language: none The ``B`` parameter consists of four values: ``B[a,1,a,1]=10``, @@ -376,7 +376,7 @@ data declaration than is possible with a ``param`` declaration. The following example illustrates a simple ``table`` command that declares data for a single parameter: -.. literalinclude:: ../../tests/data/table0.dat +.. literalinclude:: ../../src/data/table0.dat :language: none The parameter ``M`` is indexed by column ``A``, which must be @@ -385,20 +385,20 @@ are provided after the colon and before the colon-equal (``:=``). Subsequently, the table data is provided. The syntax is not sensitive to whitespace, so the following is an equivalent ``table`` command: -.. literalinclude:: ../../tests/data/table1.dat +.. literalinclude:: ../../src/data/table1.dat :language: none Multiple parameters can be declared by simply including additional parameter names. For example: -.. literalinclude:: ../../tests/data/table2.dat +.. literalinclude:: ../../src/data/table2.dat :language: none This example declares data for the ``M`` and ``N`` parameters, which have different indexing columns. The indexing columns represent set data, which is specified separately. For example: -.. literalinclude:: ../../tests/data/table3.dat +.. literalinclude:: ../../src/data/table3.dat :language: none This example declares data for the ``M`` and ``N`` parameters, along @@ -406,12 +406,12 @@ with the ``A`` and ``Z`` indexing sets. The correspondence between the index set ``Z`` and the indices of parameter ``N`` can be made more explicit by indexing ``N`` by ``Z``: -.. literalinclude:: ../../tests/data/table4.dat +.. literalinclude:: ../../src/data/table4.dat :language: none Set data can also be specified independent of parameter data: -.. literalinclude:: ../../tests/data/table5.dat +.. literalinclude:: ../../src/data/table5.dat :language: none .. warning:: @@ -423,13 +423,13 @@ Set data can also be specified independent of parameter data: that is initialized. For example, the ``table`` command initializes a set ``Z`` and a parameter ``M`` that are not related: - .. literalinclude:: ../../tests/data/table7.dat + .. literalinclude:: ../../src/data/table7.dat :language: none Finally, simple parameter values can also be specified with a ``table`` command: -.. literalinclude:: ../../tests/data/table6.dat +.. literalinclude:: ../../src/data/table6.dat :language: none The previous examples considered examples of the ``table`` command where @@ -437,7 +437,7 @@ column labels are provided. The ``table`` command can also be used without column labels. For example, the first example can be revised to omit column labels as follows: -.. literalinclude:: ../../tests/data/table0.ul.dat +.. literalinclude:: ../../src/data/table0.ul.dat :language: none The ``columns=4`` is a keyword-value pair that defines the number of @@ -450,12 +450,12 @@ braces syntax declares the column where the ``M`` data is provided. Similarly, set data can be declared referencing the integer column labels: -.. literalinclude:: ../../tests/data/table3.ul.dat +.. literalinclude:: ../../src/data/table3.ul.dat :language: none Declared set names can also be used to index parameters: -.. literalinclude:: ../../tests/data/table4.ul.dat +.. literalinclude:: ../../src/data/table4.ul.dat :language: none Finally, we compare and contrast the ``table`` and ``param`` commands. @@ -521,13 +521,13 @@ Simple Load Examples The simplest illustration of the ``load`` command is specifying data for an indexed parameter. Consider the file ``Y.tab``: -.. literalinclude:: ../../tests/data/Y.tab +.. literalinclude:: ../../src/data/Y.tab :language: none This file specifies the values of parameter ``Y`` which is indexed by set ``A``. The following ``load`` command loads the parameter data: -.. literalinclude:: ../../tests/data/import1.tab.dat +.. literalinclude:: ../../src/data/import1.tab.dat :language: none The first argument is the filename. The options after the colon @@ -538,7 +538,7 @@ indicates the parameter that is initialized. Similarly, the following load command loads both the parameter data as well as the index set ``A``: -.. literalinclude:: ../../tests/data/import2.tab.dat +.. literalinclude:: ../../src/data/import2.tab.dat :language: none The difference is the specification of the index set, ``A=[A]``, which @@ -548,24 +548,24 @@ ASCII table file. Set data can also be loaded from a ASCII table file that contains a single column of data: -.. literalinclude:: ../../tests/data/A.tab +.. literalinclude:: ../../src/data/A.tab :language: none The ``format`` option must be specified to denote the fact that the relational data is being interpreted as a set: -.. literalinclude:: ../../tests/data/import3.tab.dat +.. literalinclude:: ../../src/data/import3.tab.dat :language: none Note that this allows for specifying set data that contains tuples. Consider file ``C.tab``: -.. literalinclude:: ../../tests/data/C.tab +.. literalinclude:: ../../src/data/C.tab :language: none A similar ``load`` syntax will load this data into set ``C``: -.. literalinclude:: ../../tests/data/import4.tab.dat +.. literalinclude:: ../../src/data/import4.tab.dat :language: none Note that this example requires that ``C`` be declared with dimension @@ -609,7 +609,7 @@ describes different specifications and how they define how data is loaded into a model. Suppose file ``ABCD.tab`` defines the following relational table: -.. literalinclude:: ../../tests/data/ABCD.tab +.. literalinclude:: ../../src/data/ABCD.tab :language: none There are many ways to interpret this relational table. It could @@ -621,7 +621,7 @@ for specifying how a table is interpreted. A simple specification is to interpret the relational table as a set: -.. literalinclude:: ../../tests/data/ABCD1.dat +.. literalinclude:: ../../src/data/ABCD1.dat :language: none Note that ``Z`` is a set in the model that the data is being loaded @@ -631,7 +631,7 @@ data from this table. Another simple specification is to interpret the relational table as a parameter with indexed by 3-tuples: -.. literalinclude:: ../../tests/data/ABCD2.dat +.. literalinclude:: ../../src/data/ABCD2.dat :language: none Again, this requires that ``D`` be a parameter in the model that the @@ -639,14 +639,14 @@ data is being loaded into. Additionally, the index set for ``D`` must contain the indices that are specified in the table. The ``load`` command also allows for the specification of the index set: -.. literalinclude:: ../../tests/data/ABCD3.dat +.. literalinclude:: ../../src/data/ABCD3.dat :language: none This specifies that the index set is loaded into the ``Z`` set in the model. Similarly, data can be loaded into another parameter than what is specified in the relational table: -.. literalinclude:: ../../tests/data/ABCD4.dat +.. literalinclude:: ../../src/data/ABCD4.dat :language: none This specifies that the index set is loaded into the ``Z`` set and that @@ -658,13 +658,13 @@ specification of data mappings from columns in a relational table into index sets and parameters. For example, suppose that a model is defined with set ``Z`` and parameters ``Y`` and ``W``: -.. literalinclude:: ../../tests/data/ABCD5_decl.spy +.. literalinclude:: ../../src/data/ABCD5_decl.spy :language: python Then the following command defines how these data items are loaded using columns ``B``, ``C`` and ``D``: -.. literalinclude:: ../../tests/data/ABCD5.dat +.. literalinclude:: ../../src/data/ABCD5.dat :language: none When the ``using`` option is omitted the data manager is inferred from @@ -672,13 +672,13 @@ the filename suffix. However, the filename suffix does not always reflect the format of the data it contains. For example, consider the relational table in the file ``ABCD.txt``: -.. literalinclude:: ../../tests/data/ABCD.txt +.. literalinclude:: ../../src/data/ABCD.txt :language: none We can specify the ``using`` option to load from this file into parameter ``D`` and set ``Z``: -.. literalinclude:: ../../tests/data/ABCD6.dat +.. literalinclude:: ../../src/data/ABCD6.dat :language: none .. note:: @@ -692,7 +692,7 @@ parameter ``D`` and set ``Z``: The following data managers are supported in Pyomo 5.1: - .. literalinclude:: ../../tests/data/data_managers.txt + .. literalinclude:: ../../src/data/data_managers.txt :language: none Interpreting Tabular Data @@ -725,12 +725,12 @@ A table with a single value can be interpreted as a simple parameter using the ``param`` format value. Suppose that ``Z.tab`` contains the following table: -.. literalinclude:: ../../tests/data/Z.tab +.. literalinclude:: ../../src/data/Z.tab :language: none The following load command then loads this value into parameter ``p``: -.. literalinclude:: ../../tests/data/import6.tab.dat +.. literalinclude:: ../../src/data/import6.tab.dat :language: none Sets with 2-tuple data can be represented with a matrix format that @@ -739,12 +739,12 @@ relational table as a matrix that defines a set of 2-tuples where ``+`` denotes a valid tuple and ``-`` denotes an invalid tuple. Suppose that ``D.tab`` contains the following relational table: -.. literalinclude:: ../../tests/data/D.tab +.. literalinclude:: ../../src/data/D.tab :language: none Then the following load command loads data into set ``B``: -.. literalinclude:: ../../tests/data/import5.tab.dat +.. literalinclude:: ../../src/data/import5.tab.dat :language: none This command declares the following 2-tuples: ``('A1',1)``, @@ -754,19 +754,19 @@ Parameters with 2-tuple indices can be interpreted with a matrix format that where rows and columns are different indices. Suppose that ``U.tab`` contains the following table: -.. literalinclude:: ../../tests/data/U.tab +.. literalinclude:: ../../src/data/U.tab :language: none Then the following load command loads this value into parameter ``U`` with a 2-dimensional index using the ``array`` format value.: -.. literalinclude:: ../../tests/data/import7.tab.dat +.. literalinclude:: ../../src/data/import7.tab.dat :language: none The ``transpose_array`` format value also interprets the table as a matrix, but it loads the data in a transposed format: -.. literalinclude:: ../../tests/data/import8.tab.dat +.. literalinclude:: ../../src/data/import8.tab.dat :language: none Note that these format values do not support the initialization of the @@ -789,7 +789,7 @@ in the following figure: The following command loads this data to initialize parameter ``D`` and index ``Z``: -.. literalinclude:: ../../tests/data/ABCD7.dat +.. literalinclude:: ../../src/data/ABCD7.dat :language: none Thus, the syntax for loading data from spreadsheets only differs from @@ -809,7 +809,7 @@ command loads data from the Excel spreadsheet ``ABCD.xls`` using the ``pyodbc`` interface. The command loads this data to initialize parameter ``D`` and index ``Z``: -.. literalinclude:: ../../tests/data/ABCD8.dat +.. literalinclude:: ../../src/data/ABCD8.dat :language: none The ``using`` option specifies that the ``pyodbc`` package will be @@ -818,7 +818,7 @@ specifies that the table ``ABCD`` is loaded from this spreadsheet. Similarly, the following command specifies a data connection string to specify the ODBC driver explicitly: -.. literalinclude:: ../../tests/data/ABCD9.dat +.. literalinclude:: ../../src/data/ABCD9.dat :language: none ODBC drivers are generally tailored to the type of data source that @@ -836,7 +836,7 @@ task of minimizing the cost for a meal at a fast food restaurant -- they must purchase a sandwich, side, and a drink for the lowest cost. The following is a Pyomo model for this problem: -.. literalinclude:: ../../tests/data/diet1.py +.. literalinclude:: ../../src/data/diet1.py :language: python Suppose that the file ``diet1.sqlite`` be a SQLite database file that @@ -884,7 +884,7 @@ We can solve the ``diet1`` model using the Python definition in ``diet.sqlite.dat`` specifies a ``load`` command that uses that ``sqlite3`` data manager and embeds a SQL query to retrieve the data: -.. literalinclude:: ../../tests/data/diet.sqlite.dat +.. literalinclude:: ../../src/data/diet.sqlite.dat :language: none The PyODBC driver module will pass the SQL query through an Access ODBC @@ -904,7 +904,7 @@ The ``include`` command allows a data command file to execute data commands from another file. For example, the following command file executes data commands from ``ex1.dat`` and then ``ex2.dat``: -.. literalinclude:: ../../tests/data/ex.dat +.. literalinclude:: ../../src/data/ex.dat :language: none Pyomo is sensitive to the order of execution of data commands, since @@ -921,7 +921,7 @@ to structure the specification of Pyomo's data commands. Specifically, a namespace declaration is used to group data commands and to provide a group label. Consider the following data command file: -.. literalinclude:: ../../tests/data/namespace1.dat +.. literalinclude:: ../../src/data/namespace1.dat :language: none This data file defines two namespaces: ``ns1`` and ``ns2`` that diff --git a/doc/OnlineDocs/working_abstractmodels/data/native.rst b/doc/OnlineDocs/working_abstractmodels/data/native.rst index 2a52c8356aa..ed92d78d78e 100644 --- a/doc/OnlineDocs/working_abstractmodels/data/native.rst +++ b/doc/OnlineDocs/working_abstractmodels/data/native.rst @@ -34,29 +34,29 @@ can be initialized with: * list, set and tuple data: - .. literalinclude:: ../../tests/dataportal/set_initialization_decl2.spy + .. literalinclude:: ../../src/dataportal/set_initialization_decl2.spy :language: python * generators: - .. literalinclude:: ../../tests/dataportal/set_initialization_decl3.spy + .. literalinclude:: ../../src/dataportal/set_initialization_decl3.spy :language: python * numpy arrays: - .. literalinclude:: ../../tests/dataportal/set_initialization_decl4.spy + .. literalinclude:: ../../src/dataportal/set_initialization_decl4.spy :language: python Sets can also be indirectly initialized with functions that return native Python data: -.. literalinclude:: ../../tests/dataportal/set_initialization_decl5.spy +.. literalinclude:: ../../src/dataportal/set_initialization_decl5.spy :language: python Indexed sets can be initialized with dictionary data where the dictionary values are iterable data: -.. literalinclude:: ../../tests/dataportal/set_initialization_decl6.spy +.. literalinclude:: ../../src/dataportal/set_initialization_decl6.spy :language: python @@ -66,19 +66,19 @@ Parameter Components When a parameter is a single value, then a :class:`~pyomo.environ.Param` component can be simply initialized with a value: -.. literalinclude:: ../../tests/dataportal/param_initialization_decl1.spy +.. literalinclude:: ../../src/dataportal/param_initialization_decl1.spy :language: python More generally, :class:`~pyomo.environ.Param` components can be initialized with dictionary data where the dictionary values are single values: -.. literalinclude:: ../../tests/dataportal/param_initialization_decl2.spy +.. literalinclude:: ../../src/dataportal/param_initialization_decl2.spy :language: python Parameters can also be indirectly initialized with functions that return native Python data: -.. literalinclude:: ../../tests/dataportal/param_initialization_decl3.spy +.. literalinclude:: ../../src/dataportal/param_initialization_decl3.spy :language: python diff --git a/doc/OnlineDocs/working_abstractmodels/pyomo_command.rst b/doc/OnlineDocs/working_abstractmodels/pyomo_command.rst index 1d22798d8ce..aabfc8667f7 100644 --- a/doc/OnlineDocs/working_abstractmodels/pyomo_command.rst +++ b/doc/OnlineDocs/working_abstractmodels/pyomo_command.rst @@ -90,7 +90,7 @@ When there seem to be troubles expressing the model, it is often useful to embed print commands in the model in places that will yield helpful information. Consider the following snippet: -.. literalinclude:: ../tests/scripting/spy4PyomoCommand_Troubleshooting_printed_command.spy +.. literalinclude:: ../src/scripting/spy4PyomoCommand_Troubleshooting_printed_command.spy :language: python The effect will be to output every member of the set ``model.I`` at the diff --git a/doc/OnlineDocs/working_models.rst b/doc/OnlineDocs/working_models.rst index 2b9b664c548..dbd7aa383e3 100644 --- a/doc/OnlineDocs/working_models.rst +++ b/doc/OnlineDocs/working_models.rst @@ -58,7 +58,7 @@ computer to solve the problem or even to iterate over solutions. This example is provided just to illustrate some elementary aspects of scripting. -.. literalinclude:: tests/scripting/iterative1.spy +.. literalinclude:: src/scripting/iterative1.spy :language: python Let us now analyze this script. The first line is a comment that happens @@ -66,7 +66,7 @@ to give the name of the file. This is followed by two lines that import symbols for Pyomo. The pyomo namespace is imported as ``pyo``. Therefore, ``pyo.`` must precede each use of a Pyomo name. -.. literalinclude:: tests/scripting/iterative1_Import_symbols_for_pyomo.spy +.. literalinclude:: src/scripting/iterative1_Import_symbols_for_pyomo.spy :language: python An object to perform optimization is created by calling @@ -74,7 +74,7 @@ An object to perform optimization is created by calling argument would be ``'gurobi'`` if, e.g., Gurobi was desired instead of glpk: -.. literalinclude:: tests/scripting/iterative1_Call_SolverFactory_with_argument.spy +.. literalinclude:: src/scripting/iterative1_Call_SolverFactory_with_argument.spy :language: python The next lines after a comment create a model. For our discussion here, @@ -86,13 +86,13 @@ to keep it simple. Constraints could be present in the base model. Even though it is an abstract model, the base model is fully specified by these commands because it requires no external data: -.. literalinclude:: tests/scripting/iterative1_Create_base_model.spy +.. literalinclude:: src/scripting/iterative1_Create_base_model.spy :language: python The next line is not part of the base model specification. It creates an empty constraint list that the script will use to add constraints. -.. literalinclude:: tests/scripting/iterative1_Create_empty_constraint_list.spy +.. literalinclude:: src/scripting/iterative1_Create_empty_constraint_list.spy :language: python The next non-comment line creates the instantiated model and refers to @@ -103,19 +103,19 @@ the ``create`` function is called without arguments because none are needed; however, the name of a file with data commands is given as an argument in many scripts. -.. literalinclude:: tests/scripting/iterative1_Create_instantiated_model.spy +.. literalinclude:: src/scripting/iterative1_Create_instantiated_model.spy :language: python The next line invokes the solver and refers to the object contain results with the Python variable ``results``. -.. literalinclude:: tests/scripting/iterative1_Solve_and_refer_to_results.spy +.. literalinclude:: src/scripting/iterative1_Solve_and_refer_to_results.spy :language: python The solve function loads the results into the instance, so the next line writes out the updated values. -.. literalinclude:: tests/scripting/iterative1_Display_updated_value.spy +.. literalinclude:: src/scripting/iterative1_Display_updated_value.spy :language: python The next non-comment line is a Python iteration command that will @@ -123,7 +123,7 @@ successively assign the integers from 0 to 4 to the Python variable ``i``, although that variable is not used in script. This loop is what causes the script to generate five more solutions: -.. literalinclude:: tests/scripting/iterative1_Assign_integers.spy +.. literalinclude:: src/scripting/iterative1_Assign_integers.spy :language: python An expression is built up in the Python variable named ``expr``. The @@ -135,7 +135,7 @@ zero and the expression in ``expr`` is augmented accordingly. Although Pyomo expression when it is assigned expressions involving Pyomo variable objects: -.. literalinclude:: tests/scripting/iterative1_Iteratively_assign_and_test.spy +.. literalinclude:: src/scripting/iterative1_Iteratively_assign_and_test.spy :language: python During the first iteration (when ``i`` is 0), we know that all values of @@ -159,7 +159,7 @@ function to get it. The next line adds to the constraint list called ``c`` the requirement that the expression be greater than or equal to one: -.. literalinclude:: tests/scripting/iterative1_Add_expression_constraint.spy +.. literalinclude:: src/scripting/iterative1_Add_expression_constraint.spy :language: python The proof that this precludes the last solution is left as an exerise @@ -167,7 +167,7 @@ for the reader. The final lines in the outer for loop find a solution and display it: -.. literalinclude:: tests/scripting/iterative1_Find_and_display_solution.spy +.. literalinclude:: src/scripting/iterative1_Find_and_display_solution.spy :language: python .. note:: @@ -268,14 +268,14 @@ Fixing Variables and Re-solving Instead of changing model data, scripts are often used to fix variable values. The following example illustrates this. -.. literalinclude:: tests/scripting/iterative2.spy +.. literalinclude:: src/scripting/iterative2.spy :language: python In this example, the variables are binary. The model is solved and then the value of ``model.x[2]`` is flipped to the opposite value before solving the model again. The main lines of interest are: -.. literalinclude:: tests/scripting/iterative2_Flip_value_before_solve_again.spy +.. literalinclude:: src/scripting/iterative2_Flip_value_before_solve_again.spy :language: python This could also have been accomplished by setting the upper and lower @@ -430,7 +430,7 @@ Consider the following very simple example, which is similar to the iterative example. This is a concrete model. In this example, the value of ``x[2]`` is accessed. -.. literalinclude:: tests/scripting/noiteration1.py +.. literalinclude:: src/scripting/noiteration1.py :language: python .. note:: @@ -476,7 +476,7 @@ Another way to access all of the variables (particularly if there are blocks) is as follows (this particular snippet assumes that instead of `import pyomo.environ as pyo` `from pyo.environ import *` was used): -.. literalinclude:: tests/scripting/block_iter_example_compprintloop.spy +.. literalinclude:: src/scripting/block_iter_example_compprintloop.spy :language: python .. _ParamAccess: @@ -521,21 +521,21 @@ To signal that duals are desired, declare a Suffix component with the name "dual" on the model or instance with an IMPORT or IMPORT_EXPORT direction. -.. literalinclude:: tests/scripting/driveabs2_Create_dual_suffix_component.spy +.. literalinclude:: src/scripting/driveabs2_Create_dual_suffix_component.spy :language: python See the section on Suffixes :ref:`Suffixes` for more information on Pyomo's Suffix component. After the results are obtained and loaded into an instance, duals can be accessed in the following fashion. -.. literalinclude:: tests/scripting/driveabs2_Access_all_dual.spy +.. literalinclude:: src/scripting/driveabs2_Access_all_dual.spy :language: python The following snippet will only work, of course, if there is a constraint with the name ``AxbConstraint`` that has and index, which is the string ``Film``. -.. literalinclude:: tests/scripting/driveabs2_Access_one_dual.spy +.. literalinclude:: src/scripting/driveabs2_Access_one_dual.spy :language: python Here is a complete example that relies on the file ``abstract2.py`` to @@ -544,14 +544,14 @@ data. Note that the model in ``abstract2.py`` does contain a constraint named ``AxbConstraint`` and ``abstract2.dat`` does specify an index for it named ``Film``. -.. literalinclude:: tests/scripting/driveabs2.spy +.. literalinclude:: src/scripting/driveabs2.spy :language: python Concrete models are slightly different because the model is the instance. Here is a complete example that relies on the file ``concrete1.py`` to provide the model and instantiate it. -.. literalinclude:: tests/scripting/driveconc1.py +.. literalinclude:: src/scripting/driveconc1.py :language: python Accessing Slacks @@ -568,7 +568,7 @@ After a solve, the results object has a member ``Solution.Status`` that contains the solver status. The following snippet shows an example of access via a ``print`` statement: -.. literalinclude:: tests/scripting/spy4scripts_Print_solver_status.spy +.. literalinclude:: src/scripting/spy4scripts_Print_solver_status.spy :language: python The use of the Python ``str`` function to cast the value to a be string @@ -576,12 +576,12 @@ makes it easy to test it. In particular, the value 'optimal' indicates that the solver succeeded. It is also possible to access Pyomo data that can be compared with the solver status as in the following code snippet: -.. literalinclude:: tests/scripting/spy4scripts_Pyomo_data_comparedwith_solver_status_1.spy +.. literalinclude:: src/scripting/spy4scripts_Pyomo_data_comparedwith_solver_status_1.spy :language: python Alternatively, -.. literalinclude:: tests/scripting/spy4scripts_Pyomo_data_comparedwith_solver_status_2.spy +.. literalinclude:: src/scripting/spy4scripts_Pyomo_data_comparedwith_solver_status_2.spy :language: python .. _TeeTrue: @@ -592,7 +592,7 @@ Display of Solver Output To see the output of the solver, use the option ``tee=True`` as in -.. literalinclude:: tests/scripting/spy4scripts_See_solver_output.spy +.. literalinclude:: src/scripting/spy4scripts_See_solver_output.spy :language: python This can be useful for troubleshooting solver difficulties. @@ -607,7 +607,7 @@ solver. In scripts or callbacks, the options can be attached to the solver object by adding to its options dictionary as illustrated by this snippet: -.. literalinclude:: tests/scripting/spy4scripts_Add_option_to_solver.spy +.. literalinclude:: src/scripting/spy4scripts_Add_option_to_solver.spy :language: python If multiple options are needed, then multiple dictionary entries should @@ -616,7 +616,7 @@ be added. Sometimes it is desirable to pass options as part of the call to the solve function as in this snippet: -.. literalinclude:: tests/scripting/spy4scripts_Add_multiple_options_to_solver.spy +.. literalinclude:: src/scripting/spy4scripts_Add_multiple_options_to_solver.spy :language: python The quoted string is passed directly to the solver. If multiple options @@ -644,7 +644,7 @@ situations where they are not, the SolverFactory function accepts the keyword ``executable``, which you can use to set an absolute or relative path to a solver executable. E.g., -.. literalinclude:: tests/scripting/spy4scripts_Set_path_to_solver_executable.spy +.. literalinclude:: src/scripting/spy4scripts_Set_path_to_solver_executable.spy :language: python Warm Starts @@ -654,7 +654,7 @@ Some solvers support a warm start based on current values of variables. To use this feature, set the values of variables in the instance and pass ``warmstart=True`` to the ``solve()`` method. E.g., -.. literalinclude:: tests/scripting/spy4scripts_Pass_warmstart_to_solver.spy +.. literalinclude:: src/scripting/spy4scripts_Pass_warmstart_to_solver.spy :language: python .. note:: @@ -686,7 +686,7 @@ parallel. The example can be run with the following command: mpirun -np 2 python -m mpi4py parallel.py -.. literalinclude:: tests/scripting/parallel.py +.. literalinclude:: src/scripting/parallel.py :language: python @@ -700,5 +700,5 @@ The pyomo command-line ``--tempdir`` option propagates through to the TempFileManager service. One can accomplish the same through the following few lines of code in a script: -.. literalinclude:: tests/scripting/spy4scripts_Specify_temporary_directory_name.spy +.. literalinclude:: src/scripting/spy4scripts_Specify_temporary_directory_name.spy :language: python From fdeef16f8327be0cbde3e58248e5a78fb36650b1 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 12 Jan 2024 10:36:51 -0700 Subject: [PATCH 0747/1204] rename gdp_to_minlp to binary_multiplication --- pyomo/gdp/plugins/gdp_to_minlp.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/gdp/plugins/gdp_to_minlp.py b/pyomo/gdp/plugins/gdp_to_minlp.py index 5add18fd1cc..1ab6fd6b768 100644 --- a/pyomo/gdp/plugins/gdp_to_minlp.py +++ b/pyomo/gdp/plugins/gdp_to_minlp.py @@ -9,14 +9,14 @@ import logging -logger = logging.getLogger('pyomo.gdp.gdp_to_minlp') +logger = logging.getLogger('pyomo.gdp.binary_multiplication') @TransformationFactory.register( - 'gdp.gdp_to_minlp', doc="Reformulate the GDP as an MINLP." + 'gdp.binary_multiplication', doc="Reformulate the GDP as an MINLP by multiplying f(x) <= 0 by y to get f(x) * y <= 0." ) class GDPToMINLPTransformation(GDP_to_MIP_Transformation): - CONFIG = ConfigDict("gdp.gdp_to_minlp") + CONFIG = ConfigDict("gdp.binary_multiplication") CONFIG.declare( 'targets', ConfigValue( @@ -33,7 +33,7 @@ class GDPToMINLPTransformation(GDP_to_MIP_Transformation): ), ) - transformation_name = 'gdp_to_minlp' + transformation_name = 'binary_multiplication' def __init__(self): super().__init__(logger) From e16fa151b20bca0d3d0fa798e25ea94d37348134 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 12 Jan 2024 10:37:38 -0700 Subject: [PATCH 0748/1204] rename gdp_to_minlp to binary_multiplication --- pyomo/gdp/plugins/{gdp_to_minlp.py => binary_multiplication.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pyomo/gdp/plugins/{gdp_to_minlp.py => binary_multiplication.py} (100%) diff --git a/pyomo/gdp/plugins/gdp_to_minlp.py b/pyomo/gdp/plugins/binary_multiplication.py similarity index 100% rename from pyomo/gdp/plugins/gdp_to_minlp.py rename to pyomo/gdp/plugins/binary_multiplication.py From 94c2200b8e1bf6c841ecf1803eb0fc08ecd33e07 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 12 Jan 2024 10:39:58 -0700 Subject: [PATCH 0749/1204] rename gdp_to_minlp to binary_multiplication --- pyomo/gdp/plugins/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/gdp/plugins/__init__.py b/pyomo/gdp/plugins/__init__.py index 39761697a0f..2edb99bbe1b 100644 --- a/pyomo/gdp/plugins/__init__.py +++ b/pyomo/gdp/plugins/__init__.py @@ -22,4 +22,4 @@ def load(): import pyomo.gdp.plugins.multiple_bigm import pyomo.gdp.plugins.transform_current_disjunctive_state import pyomo.gdp.plugins.bound_pretransformation - import pyomo.gdp.plugins.gdp_to_minlp + import pyomo.gdp.plugins.binary_multiplication From bf64515bd9f404f3da3e29d4fa9d6bc98b52dcff Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 12 Jan 2024 10:42:46 -0700 Subject: [PATCH 0750/1204] rename gdp_to_minlp to binary_multiplication --- .../tests/{test_gdp_to_minlp.py => test_binary_multiplication.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pyomo/gdp/tests/{test_gdp_to_minlp.py => test_binary_multiplication.py} (100%) diff --git a/pyomo/gdp/tests/test_gdp_to_minlp.py b/pyomo/gdp/tests/test_binary_multiplication.py similarity index 100% rename from pyomo/gdp/tests/test_gdp_to_minlp.py rename to pyomo/gdp/tests/test_binary_multiplication.py From 71c4f1faa35ef6c5dd43f5b563fdf0e7d0f5be86 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 12 Jan 2024 10:49:19 -0700 Subject: [PATCH 0751/1204] rename gdp_to_minlp to binary_multiplication --- pyomo/gdp/tests/test_binary_multiplication.py | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/pyomo/gdp/tests/test_binary_multiplication.py b/pyomo/gdp/tests/test_binary_multiplication.py index 532922ee1cc..2c7d20f91a9 100644 --- a/pyomo/gdp/tests/test_binary_multiplication.py +++ b/pyomo/gdp/tests/test_binary_multiplication.py @@ -32,7 +32,7 @@ class CommonTests: def diff_apply_to_and_create_using(self, model): - ct.diff_apply_to_and_create_using(self, model, 'gdp.gdp_to_minlp') + ct.diff_apply_to_and_create_using(self, model, 'gdp.binary_multiplication') class TwoTermDisj(unittest.TestCase, CommonTests): @@ -42,10 +42,10 @@ def setUp(self): def test_new_block_created(self): m = models.makeTwoTermDisj() - TransformationFactory('gdp.gdp_to_minlp').apply_to(m) + TransformationFactory('gdp.binary_multiplication').apply_to(m) # we have a transformation block - transBlock = m.component("_pyomo_gdp_gdp_to_minlp_reformulation") + transBlock = m.component("_pyomo_gdp_binary_multiplication_reformulation") self.assertIsInstance(transBlock, Block) disjBlock = transBlock.component("relaxedDisjuncts") @@ -56,72 +56,72 @@ def test_new_block_created(self): self.assertIs(m.d[1].transformation_block, disjBlock[1]) def test_disjunction_deactivated(self): - ct.check_disjunction_deactivated(self, 'gdp_to_minlp') + ct.check_disjunction_deactivated(self, 'binary_multiplication') def test_disjunctDatas_deactivated(self): - ct.check_disjunctDatas_deactivated(self, 'gdp_to_minlp') + ct.check_disjunctDatas_deactivated(self, 'binary_multiplication') def test_do_not_transform_twice_if_disjunction_reactivated(self): - ct.check_do_not_transform_twice_if_disjunction_reactivated(self, 'gdp_to_minlp') + ct.check_do_not_transform_twice_if_disjunction_reactivated(self, 'binary_multiplication') def test_xor_constraint_mapping(self): - ct.check_xor_constraint_mapping(self, 'gdp_to_minlp') + ct.check_xor_constraint_mapping(self, 'binary_multiplication') def test_xor_constraint_mapping_two_disjunctions(self): - ct.check_xor_constraint_mapping_two_disjunctions(self, 'gdp_to_minlp') + ct.check_xor_constraint_mapping_two_disjunctions(self, 'binary_multiplication') def test_disjunct_mapping(self): - ct.check_disjunct_mapping(self, 'gdp_to_minlp') + ct.check_disjunct_mapping(self, 'binary_multiplication') def test_disjunct_and_constraint_maps(self): """Tests the actual data structures used to store the maps.""" m = models.makeTwoTermDisj() - gdp_to_minlp = TransformationFactory('gdp.gdp_to_minlp') - gdp_to_minlp.apply_to(m) - disjBlock = m._pyomo_gdp_gdp_to_minlp_reformulation.relaxedDisjuncts + binary_multiplication = TransformationFactory('gdp.binary_multiplication') + binary_multiplication.apply_to(m) + disjBlock = m._pyomo_gdp_binary_multiplication_reformulation.relaxedDisjuncts oldblock = m.component("d") # we are counting on the fact that the disjuncts get relaxed in the # same order every time. for i in [0, 1]: self.assertIs(oldblock[i].transformation_block, disjBlock[i]) - self.assertIs(gdp_to_minlp.get_src_disjunct(disjBlock[i]), oldblock[i]) + self.assertIs(binary_multiplication.get_src_disjunct(disjBlock[i]), oldblock[i]) # check constraint dict has right mapping - c1_list = gdp_to_minlp.get_transformed_constraints(oldblock[1].c1) + c1_list = binary_multiplication.get_transformed_constraints(oldblock[1].c1) # this is an equality self.assertEqual(len(c1_list), 1) self.assertIs(c1_list[0].parent_block(), disjBlock[1]) - self.assertIs(gdp_to_minlp.get_src_constraint(c1_list[0]), oldblock[1].c1) + self.assertIs(binary_multiplication.get_src_constraint(c1_list[0]), oldblock[1].c1) - c2_list = gdp_to_minlp.get_transformed_constraints(oldblock[1].c2) + c2_list = binary_multiplication.get_transformed_constraints(oldblock[1].c2) # just ub self.assertEqual(len(c2_list), 1) self.assertIs(c2_list[0].parent_block(), disjBlock[1]) - self.assertIs(gdp_to_minlp.get_src_constraint(c2_list[0]), oldblock[1].c2) + self.assertIs(binary_multiplication.get_src_constraint(c2_list[0]), oldblock[1].c2) - c_list = gdp_to_minlp.get_transformed_constraints(oldblock[0].c) + c_list = binary_multiplication.get_transformed_constraints(oldblock[0].c) # just lb self.assertEqual(len(c_list), 1) self.assertIs(c_list[0].parent_block(), disjBlock[0]) - self.assertIs(gdp_to_minlp.get_src_constraint(c_list[0]), oldblock[0].c) + self.assertIs(binary_multiplication.get_src_constraint(c_list[0]), oldblock[0].c) def test_new_block_nameCollision(self): - ct.check_transformation_block_name_collision(self, 'gdp_to_minlp') + ct.check_transformation_block_name_collision(self, 'binary_multiplication') def test_indicator_vars(self): - ct.check_indicator_vars(self, 'gdp_to_minlp') + ct.check_indicator_vars(self, 'binary_multiplication') def test_xor_constraints(self): - ct.check_xor_constraint(self, 'gdp_to_minlp') + ct.check_xor_constraint(self, 'binary_multiplication') def test_or_constraints(self): m = models.makeTwoTermDisj() m.disjunction.xor = False - TransformationFactory('gdp.gdp_to_minlp').apply_to(m) + TransformationFactory('gdp.binary_multiplication').apply_to(m) # check or constraint is an or (upper bound is None) - orcons = m._pyomo_gdp_gdp_to_minlp_reformulation.component("disjunction_xor") + orcons = m._pyomo_gdp_binary_multiplication_reformulation.component("disjunction_xor") self.assertIsInstance(orcons, Constraint) assertExpressionsEqual( self, @@ -137,35 +137,35 @@ def test_or_constraints(self): self.assertIsNone(orcons.upper) def test_deactivated_constraints(self): - ct.check_deactivated_constraints(self, 'gdp_to_minlp') + ct.check_deactivated_constraints(self, 'binary_multiplication') def test_transformed_constraints(self): m = models.makeTwoTermDisj() - gdp_to_minlp = TransformationFactory('gdp.gdp_to_minlp') - gdp_to_minlp.apply_to(m) - self.check_transformed_constraints(m, gdp_to_minlp, -3, 2, 7, 2) + binary_multiplication = TransformationFactory('gdp.binary_multiplication') + binary_multiplication.apply_to(m) + self.check_transformed_constraints(m, binary_multiplication, -3, 2, 7, 2) def test_do_not_transform_userDeactivated_disjuncts(self): - ct.check_user_deactivated_disjuncts(self, 'gdp_to_minlp') + ct.check_user_deactivated_disjuncts(self, 'binary_multiplication') def test_improperly_deactivated_disjuncts(self): - ct.check_improperly_deactivated_disjuncts(self, 'gdp_to_minlp') + ct.check_improperly_deactivated_disjuncts(self, 'binary_multiplication') def test_do_not_transform_userDeactivated_IndexedDisjunction(self): ct.check_do_not_transform_userDeactivated_indexedDisjunction( - self, 'gdp_to_minlp' + self, 'binary_multiplication' ) # helper method to check the M values in all of the transformed # constraints (m, M) is the tuple for M. This also relies on the # disjuncts being transformed in the same order every time. def check_transformed_constraints( - self, model, gdp_to_minlp, cons1lb, cons2lb, cons2ub, cons3ub + self, model, binary_multiplication, cons1lb, cons2lb, cons2ub, cons3ub ): - disjBlock = model._pyomo_gdp_gdp_to_minlp_reformulation.relaxedDisjuncts + disjBlock = model._pyomo_gdp_binary_multiplication_reformulation.relaxedDisjuncts # first constraint - c = gdp_to_minlp.get_transformed_constraints(model.d[0].c) + c = binary_multiplication.get_transformed_constraints(model.d[0].c) self.assertEqual(len(c), 1) c_lb = c[0] self.assertTrue(c[0].active) @@ -181,7 +181,7 @@ def check_transformed_constraints( self.assertIsNone(c[0].upper) # second constraint - c = gdp_to_minlp.get_transformed_constraints(model.d[1].c1) + c = binary_multiplication.get_transformed_constraints(model.d[1].c1) self.assertEqual(len(c), 1) c_eq = c[0] self.assertTrue(c[0].active) @@ -196,7 +196,7 @@ def check_transformed_constraints( self.assertEqual(c[0].upper, 0) # third constraint - c = gdp_to_minlp.get_transformed_constraints(model.d[1].c2) + c = binary_multiplication.get_transformed_constraints(model.d[1].c2) self.assertEqual(len(c), 1) c_ub = c[0] self.assertTrue(c_ub.active) @@ -230,8 +230,8 @@ def d_rule(d, j): m.d = Disjunct(m.I, rule=d_rule) m.disjunction = Disjunction(expr=[m.d[i] for i in m.I]) - TransformationFactory('gdp.gdp_to_minlp').apply_to(m) - transBlock = m._pyomo_gdp_gdp_to_minlp_reformulation + TransformationFactory('gdp.binary_multiplication').apply_to(m) + transBlock = m._pyomo_gdp_binary_multiplication_reformulation # 2 blocks: the original Disjunct and the transformation block self.assertEqual(len(list(m.component_objects(Block, descend_into=False))), 1) @@ -260,8 +260,8 @@ def d_rule(d, j): m.d = Disjunct(m.I, rule=d_rule) m.disjunction = Disjunction(expr=[m.d[i] for i in m.I]) - TransformationFactory('gdp.gdp_to_minlp').apply_to(m) - transBlock = m._pyomo_gdp_gdp_to_minlp_reformulation + TransformationFactory('gdp.binary_multiplication').apply_to(m) + transBlock = m._pyomo_gdp_binary_multiplication_reformulation # 2 blocks: the original Disjunct and the transformation block self.assertEqual(len(list(m.component_objects(Block, descend_into=False))), 1) @@ -278,12 +278,12 @@ def d_rule(d, j): def test_local_var(self): m = models.localVar() - gdp_to_minlp = TransformationFactory('gdp.gdp_to_minlp') - gdp_to_minlp.apply_to(m) + binary_multiplication = TransformationFactory('gdp.binary_multiplication') + binary_multiplication.apply_to(m) # we just need to make sure that constraint was transformed correctly, # which just means that the M values were correct. - transformedC = gdp_to_minlp.get_transformed_constraints(m.disj2.cons) + transformedC = binary_multiplication.get_transformed_constraints(m.disj2.cons) self.assertEqual(len(transformedC), 1) eq = transformedC[0] repn = generate_standard_repn(eq.body) From 0f2d6439e3e8fa9ead232e42f854fd22743ad9a0 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 12 Jan 2024 11:21:33 -0700 Subject: [PATCH 0752/1204] rename gdp_to_minlp to binary_multiplication --- pyomo/gdp/plugins/binary_multiplication.py | 15 ++++------ pyomo/gdp/tests/test_binary_multiplication.py | 28 ++++++++++++++----- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/pyomo/gdp/plugins/binary_multiplication.py b/pyomo/gdp/plugins/binary_multiplication.py index 1ab6fd6b768..2305f244f29 100644 --- a/pyomo/gdp/plugins/binary_multiplication.py +++ b/pyomo/gdp/plugins/binary_multiplication.py @@ -13,7 +13,8 @@ @TransformationFactory.register( - 'gdp.binary_multiplication', doc="Reformulate the GDP as an MINLP by multiplying f(x) <= 0 by y to get f(x) * y <= 0." + 'gdp.binary_multiplication', + doc="Reformulate the GDP as an MINLP by multiplying f(x) <= 0 by y to get f(x) * y <= 0.", ) class GDPToMINLPTransformation(GDP_to_MIP_Transformation): CONFIG = ConfigDict("gdp.binary_multiplication") @@ -149,25 +150,19 @@ def _add_constraint_expressions( lb, ub = c.lower, c.upper if (c.equality or lb is ub) and lb is not None: # equality - newConstraint.add( - (name, i, 'eq'), (c.body - lb) * indicator_var == 0 - ) + newConstraint.add((name, i, 'eq'), (c.body - lb) * indicator_var == 0) constraintMap['transformedConstraints'][c] = [newConstraint[name, i, 'eq']] constraintMap['srcConstraints'][newConstraint[name, i, 'eq']] = c else: # inequality if lb is not None: - newConstraint.add( - (name, i, 'lb'), 0 <= (c.body - lb) * indicator_var - ) + newConstraint.add((name, i, 'lb'), 0 <= (c.body - lb) * indicator_var) constraintMap['transformedConstraints'][c] = [ newConstraint[name, i, 'lb'] ] constraintMap['srcConstraints'][newConstraint[name, i, 'lb']] = c if ub is not None: - newConstraint.add( - (name, i, 'ub'), (c.body - ub) * indicator_var <= 0 - ) + newConstraint.add((name, i, 'ub'), (c.body - ub) * indicator_var <= 0) transformed = constraintMap['transformedConstraints'].get(c) if transformed is not None: constraintMap['transformedConstraints'][c].append( diff --git a/pyomo/gdp/tests/test_binary_multiplication.py b/pyomo/gdp/tests/test_binary_multiplication.py index 2c7d20f91a9..2c6e045f853 100644 --- a/pyomo/gdp/tests/test_binary_multiplication.py +++ b/pyomo/gdp/tests/test_binary_multiplication.py @@ -62,7 +62,9 @@ def test_disjunctDatas_deactivated(self): ct.check_disjunctDatas_deactivated(self, 'binary_multiplication') def test_do_not_transform_twice_if_disjunction_reactivated(self): - ct.check_do_not_transform_twice_if_disjunction_reactivated(self, 'binary_multiplication') + ct.check_do_not_transform_twice_if_disjunction_reactivated( + self, 'binary_multiplication' + ) def test_xor_constraint_mapping(self): ct.check_xor_constraint_mapping(self, 'binary_multiplication') @@ -85,26 +87,34 @@ def test_disjunct_and_constraint_maps(self): # same order every time. for i in [0, 1]: self.assertIs(oldblock[i].transformation_block, disjBlock[i]) - self.assertIs(binary_multiplication.get_src_disjunct(disjBlock[i]), oldblock[i]) + self.assertIs( + binary_multiplication.get_src_disjunct(disjBlock[i]), oldblock[i] + ) # check constraint dict has right mapping c1_list = binary_multiplication.get_transformed_constraints(oldblock[1].c1) # this is an equality self.assertEqual(len(c1_list), 1) self.assertIs(c1_list[0].parent_block(), disjBlock[1]) - self.assertIs(binary_multiplication.get_src_constraint(c1_list[0]), oldblock[1].c1) + self.assertIs( + binary_multiplication.get_src_constraint(c1_list[0]), oldblock[1].c1 + ) c2_list = binary_multiplication.get_transformed_constraints(oldblock[1].c2) # just ub self.assertEqual(len(c2_list), 1) self.assertIs(c2_list[0].parent_block(), disjBlock[1]) - self.assertIs(binary_multiplication.get_src_constraint(c2_list[0]), oldblock[1].c2) + self.assertIs( + binary_multiplication.get_src_constraint(c2_list[0]), oldblock[1].c2 + ) c_list = binary_multiplication.get_transformed_constraints(oldblock[0].c) # just lb self.assertEqual(len(c_list), 1) self.assertIs(c_list[0].parent_block(), disjBlock[0]) - self.assertIs(binary_multiplication.get_src_constraint(c_list[0]), oldblock[0].c) + self.assertIs( + binary_multiplication.get_src_constraint(c_list[0]), oldblock[0].c + ) def test_new_block_nameCollision(self): ct.check_transformation_block_name_collision(self, 'binary_multiplication') @@ -121,7 +131,9 @@ def test_or_constraints(self): TransformationFactory('gdp.binary_multiplication').apply_to(m) # check or constraint is an or (upper bound is None) - orcons = m._pyomo_gdp_binary_multiplication_reformulation.component("disjunction_xor") + orcons = m._pyomo_gdp_binary_multiplication_reformulation.component( + "disjunction_xor" + ) self.assertIsInstance(orcons, Constraint) assertExpressionsEqual( self, @@ -162,7 +174,9 @@ def test_do_not_transform_userDeactivated_IndexedDisjunction(self): def check_transformed_constraints( self, model, binary_multiplication, cons1lb, cons2lb, cons2ub, cons3ub ): - disjBlock = model._pyomo_gdp_binary_multiplication_reformulation.relaxedDisjuncts + disjBlock = ( + model._pyomo_gdp_binary_multiplication_reformulation.relaxedDisjuncts + ) # first constraint c = binary_multiplication.get_transformed_constraints(model.d[0].c) From f79202fa877325b3d8b75a9bb0dc9a7ee8c59ba5 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 12 Jan 2024 13:09:05 -0700 Subject: [PATCH 0753/1204] contrib.viewer uses PySide6; add tests_optional; remove codecov --- .github/workflows/test_branches.yml | 7 ++++--- .github/workflows/test_pr_and_main.yml | 7 ++++--- setup.py | 6 ++++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 53c41d4bbf5..b3ee2ce2fc4 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -22,7 +22,8 @@ env: PYTHONWARNINGS: ignore::UserWarning PYTHON_CORE_PKGS: wheel PYPI_ONLY: z3-solver - PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels PyQt6 pytest-qt + PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels pytest-qt PySide6 + CONDA_EXCLUDE: PySide6 pyside6 CACHE_VER: v221013.1 NEOS_EMAIL: tests@pyomo.org SRC_REF: ${{ github.head_ref || github.ref }} @@ -126,7 +127,7 @@ jobs: # Note: pandas 1.0.3 causes gams 29.1.0 import to fail in python 3.8 EXTRAS=tests if test -z "${{matrix.slim}}"; then - EXTRAS="$EXTRAS,docs,optional" + EXTRAS="$EXTRAS,tests_optional,docs,optional" fi echo "EXTRAS=$EXTRAS" >> $GITHUB_ENV PYTHON_PACKAGES="${{matrix.PACKAGES}}" @@ -321,7 +322,7 @@ jobs: if test "${{matrix.TARGET}}" == linux; then EXCLUDE="casadi numdifftools $EXCLUDE" fi - EXCLUDE=`echo "$EXCLUDE PyQt6 pyqt6" | xargs` + EXCLUDE=`echo "$EXCLUDE $CONDA_EXCLUDE" | xargs` if test -n "$EXCLUDE"; then for WORD in $EXCLUDE; do PACKAGES=${PACKAGES//$WORD / } diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 39aeed6f123..63c8cfdcb63 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -25,7 +25,8 @@ env: PYTHONWARNINGS: ignore::UserWarning PYTHON_CORE_PKGS: wheel PYPI_ONLY: z3-solver - PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels PyQt6 pytest-qt + PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels pytest-qt PySide6 + CONDA_EXCLUDE: PySide6 pyside6 CACHE_VER: v221013.1 NEOS_EMAIL: tests@pyomo.org SRC_REF: ${{ github.head_ref || github.ref }} @@ -156,7 +157,7 @@ jobs: # Note: pandas 1.0.3 causes gams 29.1.0 import to fail in python 3.8 EXTRAS=tests if test -z "${{matrix.slim}}"; then - EXTRAS="$EXTRAS,docs,optional" + EXTRAS="$EXTRAS,tests_optional,docs,optional" fi echo "EXTRAS=$EXTRAS" >> $GITHUB_ENV PYTHON_PACKAGES="${{matrix.PACKAGES}}" @@ -351,7 +352,7 @@ jobs: if test "${{matrix.TARGET}}" == linux; then EXCLUDE="casadi numdifftools $EXCLUDE" fi - EXCLUDE=`echo "$EXCLUDE PyQt6 pyqt6" | xargs` + EXCLUDE=`echo "$EXCLUDE $CONDA_EXCLUDE" | xargs` if test -n "$EXCLUDE"; then for WORD in $EXCLUDE; do PACKAGES=${PACKAGES//$WORD / } diff --git a/setup.py b/setup.py index dbc1a4a6b53..3d08eeca4bb 100644 --- a/setup.py +++ b/setup.py @@ -244,13 +244,15 @@ def __ne__(self, other): install_requires=['ply'], extras_require={ 'tests': [ - #'codecov', # useful for testing infrastructures, but not required 'coverage', 'parameterized', 'pybind11', 'pytest', 'pytest-parallel', ], + 'tests_optional': [ + 'pytest-qt', # contrib.viewer + ], 'docs': [ 'Sphinx>4', 'sphinx-copybutton', @@ -277,7 +279,7 @@ def __ne__(self, other): #'pathos', # requested for #963, but PR currently closed 'pint', # units 'plotly', # incidence_analysis - 'PyQt6', # contrib.viewer + 'PySide6', # contrib.viewer 'pytest-qt', # for testing contrib.viewer 'python-louvain', # community_detection 'pyyaml', # core From c255d4d8111a951ce0c4834be70b9eb2aa4410cf Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 12 Jan 2024 13:10:41 -0700 Subject: [PATCH 0754/1204] Illogical black formatting --- setup.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/setup.py b/setup.py index 3d08eeca4bb..2d43d8198b8 100644 --- a/setup.py +++ b/setup.py @@ -243,16 +243,8 @@ def __ne__(self, other): python_requires='>=3.8', install_requires=['ply'], extras_require={ - 'tests': [ - 'coverage', - 'parameterized', - 'pybind11', - 'pytest', - 'pytest-parallel', - ], - 'tests_optional': [ - 'pytest-qt', # contrib.viewer - ], + 'tests': ['coverage', 'parameterized', 'pybind11', 'pytest', 'pytest-parallel'], + 'tests_optional': ['pytest-qt'], # contrib.viewer 'docs': [ 'Sphinx>4', 'sphinx-copybutton', From d6b9f8548ed41dd5cc0c86db729820ae2f2c2c13 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 12 Jan 2024 13:22:59 -0700 Subject: [PATCH 0755/1204] Turn on pyvista; remove special linux packages --- .github/workflows/test_branches.yml | 4 ++-- .github/workflows/test_pr_and_main.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index b3ee2ce2fc4..48fd83f668c 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -194,7 +194,7 @@ jobs: # - ipopt needs: libopenblas-dev gfortran liblapack-dev # - contrib.viewer needs: libg1l libegl1 sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ - install libopenblas-dev gfortran liblapack-dev glpk-utils libgl1 libegl1 + install libopenblas-dev gfortran liblapack-dev glpk-utils sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os - name: Update Windows @@ -222,7 +222,7 @@ jobs: uses: pyvista/setup-headless-display-action@v2 with: qt: true - pyvista: false + pyvista: true # GitHub actions is very fragile when it comes to setting up various # Python interpreters, expecially the setup-miniconda interface. diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 63c8cfdcb63..6b105e30593 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -224,7 +224,7 @@ jobs: # - ipopt needs: libopenblas-dev gfortran liblapack-dev # - contrib.viewer needs: libg1l libegl1 sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ - install libopenblas-dev gfortran liblapack-dev glpk-utils libgl1 libegl1 + install libopenblas-dev gfortran liblapack-dev glpk-utils sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os - name: Update Windows @@ -252,7 +252,7 @@ jobs: uses: pyvista/setup-headless-display-action@v2 with: qt: true - pyvista: false + pyvista: true # GitHub actions is very fragile when it comes to setting up various # Python interpreters, expecially the setup-miniconda interface. From c63b62f46d90da2410f1bb38342b9aa1f28474a8 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 12 Jan 2024 13:39:56 -0700 Subject: [PATCH 0756/1204] Revert to non-action version --- .github/workflows/test_branches.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 48fd83f668c..62c6fe5c0f5 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -53,6 +53,9 @@ jobs: name: ${{ matrix.TARGET }}/${{ matrix.python }}${{ matrix.other }} runs-on: ${{ matrix.os }} timeout-minutes: 120 + env: + DISPLAY: ':99.0' + QT_SELECT: 'qt6' strategy: fail-fast: false matrix: @@ -195,7 +198,11 @@ jobs: # - contrib.viewer needs: libg1l libegl1 sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ install libopenblas-dev gfortran liblapack-dev glpk-utils + sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ + install -y xvfb x11-utils libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 xdotool sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os + # start xvfb in the background + sudo /usr/bin/Xvfb $DISPLAY -screen 0 1280x1024x24 & - name: Update Windows if: matrix.TARGET == 'win' @@ -215,15 +222,6 @@ jobs: auto-update-conda: false python-version: ${{ matrix.python }} - # This is necessary for qt (UI) tests; the package utilized here does not - # have support for OSX. - - name: Set up UI testing infrastructure - if: ${{ matrix.TARGET != 'osx' }} - uses: pyvista/setup-headless-display-action@v2 - with: - qt: true - pyvista: true - # GitHub actions is very fragile when it comes to setting up various # Python interpreters, expecially the setup-miniconda interface. # Per the setup-miniconda documentation, it is important to always From 339e2adcf3508cb67af783dce081cd6776b531a2 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 12 Jan 2024 13:50:53 -0700 Subject: [PATCH 0757/1204] Missing library --- .github/workflows/test_branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 62c6fe5c0f5..154a8d4ae40 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -199,7 +199,7 @@ jobs: sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ install libopenblas-dev gfortran liblapack-dev glpk-utils sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ - install -y xvfb x11-utils libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 xdotool + install -y xvfb x11-utils libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 xdotool libegl1 sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os # start xvfb in the background sudo /usr/bin/Xvfb $DISPLAY -screen 0 1280x1024x24 & From 7a4ec5041e13277168974bb5b73b7f17c028f30f Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 12 Jan 2024 14:05:33 -0700 Subject: [PATCH 0758/1204] Switch to PyQt5 --- .github/workflows/test_branches.yml | 3 +-- setup.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 154a8d4ae40..a5faf881d91 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -55,7 +55,6 @@ jobs: timeout-minutes: 120 env: DISPLAY: ':99.0' - QT_SELECT: 'qt6' strategy: fail-fast: false matrix: @@ -199,7 +198,7 @@ jobs: sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ install libopenblas-dev gfortran liblapack-dev glpk-utils sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ - install -y xvfb x11-utils libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 xdotool libegl1 + install -y xvfb x11-utils libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 xdotool sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os # start xvfb in the background sudo /usr/bin/Xvfb $DISPLAY -screen 0 1280x1024x24 & diff --git a/setup.py b/setup.py index 2d43d8198b8..58b2c8ec15a 100644 --- a/setup.py +++ b/setup.py @@ -271,7 +271,7 @@ def __ne__(self, other): #'pathos', # requested for #963, but PR currently closed 'pint', # units 'plotly', # incidence_analysis - 'PySide6', # contrib.viewer + 'PyQt5', # contrib.viewer 'pytest-qt', # for testing contrib.viewer 'python-louvain', # community_detection 'pyyaml', # core From 83432b6533c52cfaf18f6ea8fcb36dd2100b75da Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 12 Jan 2024 14:06:01 -0700 Subject: [PATCH 0759/1204] Switch to PyQt5 --- .github/workflows/test_branches.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index a5faf881d91..4c33cdfcad5 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -22,8 +22,8 @@ env: PYTHONWARNINGS: ignore::UserWarning PYTHON_CORE_PKGS: wheel PYPI_ONLY: z3-solver - PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels pytest-qt PySide6 - CONDA_EXCLUDE: PySide6 pyside6 + PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels pytest-qt PyQt5 + CONDA_EXCLUDE: PyQt5 pyqt5 CACHE_VER: v221013.1 NEOS_EMAIL: tests@pyomo.org SRC_REF: ${{ github.head_ref || github.ref }} From 63a09a986341b3914a15887663b494b5fc32b8fa Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 12 Jan 2024 14:17:30 -0700 Subject: [PATCH 0760/1204] Add testing of gather files, test drivers, and baseline updater --- pyomo/common/tests/test_unittest.py | 146 +++++++++++++++++++++++++--- 1 file changed, 135 insertions(+), 11 deletions(-) diff --git a/pyomo/common/tests/test_unittest.py b/pyomo/common/tests/test_unittest.py index ef97e73d062..882b5601552 100644 --- a/pyomo/common/tests/test_unittest.py +++ b/pyomo/common/tests/test_unittest.py @@ -11,11 +11,14 @@ import datetime import multiprocessing -from io import StringIO +import os import time +from io import StringIO import pyomo.common.unittest as unittest from pyomo.common.log import LoggingIntercept +from pyomo.common.tee import capture_output +from pyomo.common.tempfiles import TempfileManager from pyomo.environ import ConcreteModel, Var, Param @@ -296,17 +299,19 @@ def test_bound_function_require_fork(self): pass_ref = """ [ 0.00] Setting up Pyomo environment [ 0.00] Applying Pyomo preprocessing actions +WARNING: DEPRECATED: The Model.preprocess() method is deprecated and no longer + performs any actions (deprecated in 6.0) (called from :1) [ 0.00] Creating model -[ 0.00] Applying solver -[ 0.05] Processing results +[ 0.01] Applying solver +[ 0.06] Processing results Number of solutions: 1 Solution Information Gap: None Status: optimal Function Value: -0.00010001318188373491 Solver results file: results.yml -[ 0.05] Applying Pyomo postprocessing actions -[ 0.05] Pyomo Finished +[ 0.06] Applying Pyomo postprocessing actions +[ 0.06] Pyomo Finished # ========================================================== # = Solver Results = # ========================================================== @@ -357,16 +362,16 @@ def test_bound_function_require_fork(self): [ 0.00] Setting up Pyomo environment [ 0.00] Applying Pyomo preprocessing actions [ 0.00] Creating model -[ 0.00] Applying solver -[ 0.05] Processing results +[ 0.01] Applying solver +[ 0.06] Processing results Number of solutions: 1 Solution Information Gap: None Status: optimal Function Value: -0.00010001318188373491 Solver results file: results.yml -[ 0.05] Applying Pyomo postprocessing actions -[ 0.05] Pyomo Finished +[ 0.06] Applying Pyomo postprocessing actions +[ 0.06] Pyomo Finished # ========================================================== # = Solver Results = # ========================================================== @@ -422,11 +427,130 @@ def test_baseline_pass(self): self.compare_baseline(pass_ref, baseline, abstol=1e-6) with self.assertRaises(self.failureException): - self.compare_baseline(pass_ref, baseline, None) + with capture_output() as OUT: + self.compare_baseline(pass_ref, baseline, None) + self.assertEqual( + OUT.getvalue(), + f"""--------------------------------- +BASELINE FILE +--------------------------------- +{baseline} +================================= +--------------------------------- +TEST OUTPUT FILE +--------------------------------- +{pass_ref} +""", + ) def test_baseline_fail(self): with self.assertRaises(self.failureException): - self.compare_baseline(fail_ref, baseline) + with capture_output() as OUT: + self.compare_baseline(fail_ref, baseline) + self.assertEqual( + OUT.getvalue(), + f"""--------------------------------- +BASELINE FILE +--------------------------------- +{baseline} +================================= +--------------------------------- +TEST OUTPUT FILE +--------------------------------- +{fail_ref} +""", + ) + + def test_testcase_collection(self): + with TempfileManager.new_context() as TMP: + tmpdir = TMP.create_tempdir() + for fname in ( + 'a.py', + 'b.py', + 'b.txt', + 'c.py', + 'c.sh', + 'c.yml', + 'd.sh', + 'd.yml', + 'e.sh', + ): + with open(os.path.join(tmpdir, fname), 'w'): + pass + + py_tests, sh_tests = unittest.BaselineTestDriver.gather_tests([tmpdir]) + self.assertEqual( + py_tests, + [ + ( + os.path.basename(tmpdir) + '_b', + os.path.join(tmpdir, 'b.py'), + os.path.join(tmpdir, 'b.txt'), + ) + ], + ) + self.assertEqual( + sh_tests, + [ + ( + os.path.basename(tmpdir) + '_c', + os.path.join(tmpdir, 'c.sh'), + os.path.join(tmpdir, 'c.yml'), + ), + ( + os.path.basename(tmpdir) + '_d', + os.path.join(tmpdir, 'd.sh'), + os.path.join(tmpdir, 'd.txt'), + ), + ], + ) + + self.python_test_driver(*py_tests[0]) + + _update_baselines = os.environ.pop('PYOMO_TEST_UPDATE_BASELINES', None) + try: + with open(os.path.join(tmpdir, 'b.py'), 'w') as FILE: + FILE.write('print("Hello, World")\n') + + with self.assertRaises(self.failureException): + self.python_test_driver(*py_tests[0]) + with open(os.path.join(tmpdir, 'b.txt'), 'r') as FILE: + self.assertEqual(FILE.read(), "") + + os.environ['PYOMO_TEST_UPDATE_BASELINES'] = '1' + + with self.assertRaises(self.failureException): + self.python_test_driver(*py_tests[0]) + with open(os.path.join(tmpdir, 'b.txt'), 'r') as FILE: + self.assertEqual(FILE.read(), "Hello, World\n") + + finally: + os.environ.pop('PYOMO_TEST_UPDATE_BASELINES', None) + if _update_baselines is not None: + os.environ['PYOMO_TEST_UPDATE_BASELINES'] = _update_baselines + + self.shell_test_driver(*sh_tests[1]) + _update_baselines = os.environ.pop('PYOMO_TEST_UPDATE_BASELINES', None) + try: + with open(os.path.join(tmpdir, 'd.sh'), 'w') as FILE: + FILE.write('echo "Hello, World"\n') + + with self.assertRaises(self.failureException): + self.shell_test_driver(*sh_tests[1]) + with open(os.path.join(tmpdir, 'd.txt'), 'r') as FILE: + self.assertEqual(FILE.read(), "") + + os.environ['PYOMO_TEST_UPDATE_BASELINES'] = '1' + + with self.assertRaises(self.failureException): + self.shell_test_driver(*sh_tests[1]) + with open(os.path.join(tmpdir, 'd.txt'), 'r') as FILE: + self.assertEqual(FILE.read(), "Hello, World\n") + + finally: + os.environ.pop('PYOMO_TEST_UPDATE_BASELINES', None) + if _update_baselines is not None: + os.environ['PYOMO_TEST_UPDATE_BASELINES'] = _update_baselines if __name__ == '__main__': From f827e22c1ee21de3e9a7a992bf173c605f92bc0b Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 12 Jan 2024 14:20:25 -0700 Subject: [PATCH 0761/1204] Switch back to UI GHA --- .github/workflows/test_branches.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 4c33cdfcad5..cdfd229d504 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -53,8 +53,6 @@ jobs: name: ${{ matrix.TARGET }}/${{ matrix.python }}${{ matrix.other }} runs-on: ${{ matrix.os }} timeout-minutes: 120 - env: - DISPLAY: ':99.0' strategy: fail-fast: false matrix: @@ -197,11 +195,7 @@ jobs: # - contrib.viewer needs: libg1l libegl1 sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ install libopenblas-dev gfortran liblapack-dev glpk-utils - sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ - install -y xvfb x11-utils libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 xdotool sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os - # start xvfb in the background - sudo /usr/bin/Xvfb $DISPLAY -screen 0 1280x1024x24 & - name: Update Windows if: matrix.TARGET == 'win' @@ -221,6 +215,15 @@ jobs: auto-update-conda: false python-version: ${{ matrix.python }} + # This is necessary for qt (UI) tests; the package utilized here does not + # have support for OSX. + - name: Set up UI testing infrastructure + if: ${{ matrix.TARGET != 'osx' }} + uses: pyvista/setup-headless-display-action@v2 + with: + qt: true + pyvista: false + # GitHub actions is very fragile when it comes to setting up various # Python interpreters, expecially the setup-miniconda interface. # Per the setup-miniconda documentation, it is important to always From 0fb24df8293deffa29a934acfb0add441a5f891f Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 12 Jan 2024 14:44:06 -0700 Subject: [PATCH 0762/1204] Sync branch and pr workflow files --- .github/workflows/test_branches.yml | 1 - .github/workflows/test_pr_and_main.yml | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index cdfd229d504..b5fd299fbaf 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -192,7 +192,6 @@ jobs: # Notes: # - install glpk # - ipopt needs: libopenblas-dev gfortran liblapack-dev - # - contrib.viewer needs: libg1l libegl1 sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ install libopenblas-dev gfortran liblapack-dev glpk-utils sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 6b105e30593..254b97cfb55 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -25,8 +25,8 @@ env: PYTHONWARNINGS: ignore::UserWarning PYTHON_CORE_PKGS: wheel PYPI_ONLY: z3-solver - PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels pytest-qt PySide6 - CONDA_EXCLUDE: PySide6 pyside6 + PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels pytest-qt PyQt5 + CONDA_EXCLUDE: PyQt5 pyqt5 CACHE_VER: v221013.1 NEOS_EMAIL: tests@pyomo.org SRC_REF: ${{ github.head_ref || github.ref }} @@ -222,7 +222,6 @@ jobs: # Notes: # - install glpk # - ipopt needs: libopenblas-dev gfortran liblapack-dev - # - contrib.viewer needs: libg1l libegl1 sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \ install libopenblas-dev gfortran liblapack-dev glpk-utils sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os @@ -252,7 +251,7 @@ jobs: uses: pyvista/setup-headless-display-action@v2 with: qt: true - pyvista: true + pyvista: false # GitHub actions is very fragile when it comes to setting up various # Python interpreters, expecially the setup-miniconda interface. From 23cb13f9d1cf97acf14465c64cae59a0b131bb4f Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 12 Jan 2024 14:48:19 -0700 Subject: [PATCH 0763/1204] pytest-qt is sneaky and still around --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 58b2c8ec15a..8f36277d6b0 100644 --- a/setup.py +++ b/setup.py @@ -272,7 +272,6 @@ def __ne__(self, other): 'pint', # units 'plotly', # incidence_analysis 'PyQt5', # contrib.viewer - 'pytest-qt', # for testing contrib.viewer 'python-louvain', # community_detection 'pyyaml', # core 'qtconsole', # contrib.viewer From ba5f5f20a702ec4ff904b9e3d45caf2261a52141 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 12 Jan 2024 16:14:57 -0700 Subject: [PATCH 0764/1204] Fix typo in test --- pyomo/common/tests/test_unittest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/common/tests/test_unittest.py b/pyomo/common/tests/test_unittest.py index 882b5601552..e3779e6f86e 100644 --- a/pyomo/common/tests/test_unittest.py +++ b/pyomo/common/tests/test_unittest.py @@ -472,7 +472,7 @@ def test_testcase_collection(self): 'c.sh', 'c.yml', 'd.sh', - 'd.yml', + 'd.txt', 'e.sh', ): with open(os.path.join(tmpdir, fname), 'w'): From 32bf34a01b687df4df119436386605373cf89115 Mon Sep 17 00:00:00 2001 From: "David L. Woodruff" Date: Sun, 14 Jan 2024 14:18:02 -0800 Subject: [PATCH 0765/1204] fix an error in the documenation for LinearExpression --- doc/OnlineDocs/advanced_topics/linearexpression.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/OnlineDocs/advanced_topics/linearexpression.rst b/doc/OnlineDocs/advanced_topics/linearexpression.rst index abadc7869da..b974607d0da 100644 --- a/doc/OnlineDocs/advanced_topics/linearexpression.rst +++ b/doc/OnlineDocs/advanced_topics/linearexpression.rst @@ -38,5 +38,5 @@ syntax. This example creates two constraints that are the same: .. warning:: - The lists that are passed to ``LinearModel`` are not copied, so caution must + The lists that are passed to ``LinearExpression`` are not copied, so caution must be excercised if they are modified after the component is constructed. From 47e13be9e6dbec31a26a0487d1cac1e9d3baeb59 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 15 Jan 2024 10:44:07 -0700 Subject: [PATCH 0766/1204] Deactivate qt tests for pip envs --- .github/workflows/test_branches.yml | 5 ++--- .github/workflows/test_pr_and_main.yml | 5 ++--- setup.py | 1 - 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index b5fd299fbaf..ee4ac0f9e8e 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -22,8 +22,7 @@ env: PYTHONWARNINGS: ignore::UserWarning PYTHON_CORE_PKGS: wheel PYPI_ONLY: z3-solver - PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels pytest-qt PyQt5 - CONDA_EXCLUDE: PyQt5 pyqt5 + PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels pytest-qt CACHE_VER: v221013.1 NEOS_EMAIL: tests@pyomo.org SRC_REF: ${{ github.head_ref || github.ref }} @@ -321,7 +320,7 @@ jobs: if test "${{matrix.TARGET}}" == linux; then EXCLUDE="casadi numdifftools $EXCLUDE" fi - EXCLUDE=`echo "$EXCLUDE $CONDA_EXCLUDE" | xargs` + EXCLUDE=`echo "$EXCLUDE" | xargs` if test -n "$EXCLUDE"; then for WORD in $EXCLUDE; do PACKAGES=${PACKAGES//$WORD / } diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 254b97cfb55..3c8a573b284 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -25,8 +25,7 @@ env: PYTHONWARNINGS: ignore::UserWarning PYTHON_CORE_PKGS: wheel PYPI_ONLY: z3-solver - PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels pytest-qt PyQt5 - CONDA_EXCLUDE: PyQt5 pyqt5 + PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels pytest-qt CACHE_VER: v221013.1 NEOS_EMAIL: tests@pyomo.org SRC_REF: ${{ github.head_ref || github.ref }} @@ -351,7 +350,7 @@ jobs: if test "${{matrix.TARGET}}" == linux; then EXCLUDE="casadi numdifftools $EXCLUDE" fi - EXCLUDE=`echo "$EXCLUDE $CONDA_EXCLUDE" | xargs` + EXCLUDE=`echo "$EXCLUDE" | xargs` if test -n "$EXCLUDE"; then for WORD in $EXCLUDE; do PACKAGES=${PACKAGES//$WORD / } diff --git a/setup.py b/setup.py index 8f36277d6b0..a4713f5706e 100644 --- a/setup.py +++ b/setup.py @@ -271,7 +271,6 @@ def __ne__(self, other): #'pathos', # requested for #963, but PR currently closed 'pint', # units 'plotly', # incidence_analysis - 'PyQt5', # contrib.viewer 'python-louvain', # community_detection 'pyyaml', # core 'qtconsole', # contrib.viewer From d47f3f5cd66b33984109ef0bd8a91903128b196b Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 15 Jan 2024 10:56:23 -0700 Subject: [PATCH 0767/1204] Change dep on pytest-qt --- .github/workflows/test_branches.yml | 8 ++++---- .github/workflows/test_pr_and_main.yml | 8 ++++---- setup.py | 12 +++++++----- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index ee4ac0f9e8e..8406553733f 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -22,7 +22,7 @@ env: PYTHONWARNINGS: ignore::UserWarning PYTHON_CORE_PKGS: wheel PYPI_ONLY: z3-solver - PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels pytest-qt + PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels CACHE_VER: v221013.1 NEOS_EMAIL: tests@pyomo.org SRC_REF: ${{ github.head_ref || github.ref }} @@ -75,7 +75,7 @@ jobs: python: 3.9 TARGET: win PYENV: conda - PACKAGES: glpk + PACKAGES: glpk pytest-qt - os: ubuntu-latest python: '3.11' @@ -83,7 +83,7 @@ jobs: skip_doctest: 1 TARGET: linux PYENV: conda - PACKAGES: + PACKAGES: pytest-qt - os: ubuntu-latest python: 3.9 @@ -126,7 +126,7 @@ jobs: # Note: pandas 1.0.3 causes gams 29.1.0 import to fail in python 3.8 EXTRAS=tests if test -z "${{matrix.slim}}"; then - EXTRAS="$EXTRAS,tests_optional,docs,optional" + EXTRAS="$EXTRAS,docs,optional" fi echo "EXTRAS=$EXTRAS" >> $GITHUB_ENV PYTHON_PACKAGES="${{matrix.PACKAGES}}" diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 3c8a573b284..b350882a3d1 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -25,7 +25,7 @@ env: PYTHONWARNINGS: ignore::UserWarning PYTHON_CORE_PKGS: wheel PYPI_ONLY: z3-solver - PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels pytest-qt + PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels CACHE_VER: v221013.1 NEOS_EMAIL: tests@pyomo.org SRC_REF: ${{ github.head_ref || github.ref }} @@ -76,7 +76,7 @@ jobs: - os: windows-latest TARGET: win PYENV: conda - PACKAGES: glpk + PACKAGES: glpk pytest-qt - os: ubuntu-latest python: '3.11' @@ -84,7 +84,7 @@ jobs: skip_doctest: 1 TARGET: linux PYENV: conda - PACKAGES: + PACKAGES: pytest-qt - os: ubuntu-latest python: 3.9 @@ -156,7 +156,7 @@ jobs: # Note: pandas 1.0.3 causes gams 29.1.0 import to fail in python 3.8 EXTRAS=tests if test -z "${{matrix.slim}}"; then - EXTRAS="$EXTRAS,tests_optional,docs,optional" + EXTRAS="$EXTRAS,docs,optional" fi echo "EXTRAS=$EXTRAS" >> $GITHUB_ENV PYTHON_PACKAGES="${{matrix.PACKAGES}}" diff --git a/setup.py b/setup.py index a4713f5706e..e2d702db010 100644 --- a/setup.py +++ b/setup.py @@ -243,8 +243,10 @@ def __ne__(self, other): python_requires='>=3.8', install_requires=['ply'], extras_require={ + # There are certain tests that also require pytest-qt, but because those + # tests are so environment/machine specific, we are leaving these out of + # the dependencies. 'tests': ['coverage', 'parameterized', 'pybind11', 'pytest', 'pytest-parallel'], - 'tests_optional': ['pytest-qt'], # contrib.viewer 'docs': [ 'Sphinx>4', 'sphinx-copybutton', @@ -262,7 +264,7 @@ def __ne__(self, other): 'matplotlib!=3.6.1', # network, incidence_analysis, community_detection # Note: networkx 3.2 is Python>-3.9, but there is a broken - # 3.2 package on conda-forgethat will get implicitly + # 3.2 package on conda-forge that will get implicitly # installed on python 3.8 'networkx<3.2; python_version<"3.9"', 'networkx; python_version>="3.9"', @@ -273,6 +275,8 @@ def __ne__(self, other): 'plotly', # incidence_analysis 'python-louvain', # community_detection 'pyyaml', # core + # qtconsole also requires a supported Qt version (PyQt5 or PySide6). + # Because those are environment specific, we have left that out here. 'qtconsole', # contrib.viewer 'scipy', 'sympy', # differentiation @@ -286,9 +290,7 @@ def __ne__(self, other): # The following optional dependencies are difficult to # install on PyPy (binary wheels are not available), so we # will only "require" them on other (CPython) platforms: - # - # DAE can use casadi - 'casadi; implementation_name!="pypy"', + 'casadi; implementation_name!="pypy"', # dae 'numdifftools; implementation_name!="pypy"', # pynumero 'pandas; implementation_name!="pypy"', 'seaborn; implementation_name!="pypy"', # parmest.graphics From 7828311676ecad4ae60600104333eac0a9c01a2a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 15 Jan 2024 11:49:14 -0700 Subject: [PATCH 0768/1204] Typo correction --- pyomo/solver/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/solver/base.py b/pyomo/solver/base.py index 202b0422cee..d7f4adabf56 100644 --- a/pyomo/solver/base.py +++ b/pyomo/solver/base.py @@ -405,7 +405,7 @@ def solve( self.config.symbolic_solver_labels = symbolic_solver_labels self.config.time_limit = timelimit self.config.report_timing = report_timing - # This is a new flag in the interface. To preserve backwards compability, + # This is a new flag in the interface. To preserve backwards compatibility, # its default is set to "False" self.config.raise_exception_on_nonoptimal_result = ( raise_exception_on_nonoptimal_result From 5c452be23adc9cb9864c56176b60da32e068d2bc Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 15 Jan 2024 12:07:54 -0700 Subject: [PATCH 0769/1204] minor updates --- pyomo/solver/config.py | 1 + pyomo/solver/ipopt.py | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pyomo/solver/config.py b/pyomo/solver/config.py index 54a497cee0c..ef0114ba439 100644 --- a/pyomo/solver/config.py +++ b/pyomo/solver/config.py @@ -85,6 +85,7 @@ def __init__( ConfigValue( domain=NonNegativeInt, description="Number of threads to be used by a solver.", + default=None, ), ) self.time_limit: Optional[float] = self.declare( diff --git a/pyomo/solver/ipopt.py b/pyomo/solver/ipopt.py index 406f4291c44..51f48ec4881 100644 --- a/pyomo/solver/ipopt.py +++ b/pyomo/solver/ipopt.py @@ -22,6 +22,7 @@ from pyomo.common.tempfiles import TempfileManager from pyomo.core.base import Objective from pyomo.core.base.label import NumericLabeler +from pyomo.core.staleflag import StaleFlagManager from pyomo.repn.plugins.nl_writer import NLWriter, NLWriterInfo, AMPLRepn from pyomo.solver.base import SolverBase, SymbolMap from pyomo.solver.config import SolverConfig @@ -83,9 +84,6 @@ def __init__( self.log_level = self.declare( 'log_level', ConfigValue(domain=NonNegativeInt, default=logging.INFO) ) - self.presolve: bool = self.declare( - 'presolve', ConfigValue(domain=bool, default=True) - ) class ipoptResults(Results): @@ -208,6 +206,10 @@ def version(self): version = tuple(int(i) for i in version.split('.')) return version + @property + def writer(self): + return self._writer + @property def config(self): return self._config @@ -269,14 +271,14 @@ def solve(self, model, **kwds): raise ipoptSolverError( f'Solver {self.__class__} is not available ({avail}).' ) + StaleFlagManager.mark_all_as_stale() # Update configuration options, based on keywords passed to solve config: ipoptConfig = self.config(kwds.pop('options', {})) config.set_value(kwds) - self._writer.config.linear_presolve = config.presolve if config.threads: logger.log( logging.WARNING, - msg=f"The `threads` option was specified, but this has not yet been implemented for {self.__class__}.", + msg=f"The `threads` option was specified, but but is not used by {self.__class__}.", ) results = ipoptResults() with TempfileManager.new_context() as tempfile: From 7491594ec30af978b56f7a21783a057ef70e9711 Mon Sep 17 00:00:00 2001 From: David L Woodruff Date: Mon, 15 Jan 2024 12:27:41 -0800 Subject: [PATCH 0770/1204] Update linearexpression.rst Indicate that improvements can only sometimes be made --- doc/OnlineDocs/advanced_topics/linearexpression.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/OnlineDocs/advanced_topics/linearexpression.rst b/doc/OnlineDocs/advanced_topics/linearexpression.rst index 51ae5432e6d..0dddbf84d93 100644 --- a/doc/OnlineDocs/advanced_topics/linearexpression.rst +++ b/doc/OnlineDocs/advanced_topics/linearexpression.rst @@ -2,7 +2,7 @@ LinearExpression ================ Significant speed -improvements can be obtained using the ``LinearExpression`` object +improvements can sometimes be obtained using the ``LinearExpression`` object when there are long, dense, linear expressions. The arguments are :: From db03f0cdbde8aa81ff1c86279e0230b1e7cb4e80 Mon Sep 17 00:00:00 2001 From: David L Woodruff Date: Mon, 15 Jan 2024 12:47:01 -0800 Subject: [PATCH 0771/1204] Update linearexpression.rst I took a stab at a vague statement (as distinct from a cryptic statement) about the lack of need for LinearExpression. --- doc/OnlineDocs/advanced_topics/linearexpression.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/OnlineDocs/advanced_topics/linearexpression.rst b/doc/OnlineDocs/advanced_topics/linearexpression.rst index 0dddbf84d93..8b43c3fa03a 100644 --- a/doc/OnlineDocs/advanced_topics/linearexpression.rst +++ b/doc/OnlineDocs/advanced_topics/linearexpression.rst @@ -11,7 +11,9 @@ when there are long, dense, linear expressions. The arguments are where the second and third arguments are lists that must be of the same length. Here is a simple example that illustrates the -syntax. This example creates two constraints that are the same: +syntax. This example creates two constraints that are the same; in this +particular case the LinearExpression component would offer very little improvement +because Pyomo would be able to detect that `campe2` is a linear expression: .. doctest:: From 8e168cfc3d6a55460f3a92f779d0340f649c7c6e Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 15 Jan 2024 15:21:44 -0700 Subject: [PATCH 0772/1204] MOVE: Shift pyomo.solver to pyomo.contrib.solver --- pyomo/contrib/appsi/fbbt.py | 2 +- pyomo/contrib/appsi/plugins.py | 2 +- pyomo/contrib/appsi/solvers/cbc.py | 8 ++++---- pyomo/contrib/appsi/solvers/cplex.py | 8 ++++---- pyomo/contrib/appsi/solvers/gurobi.py | 10 +++++----- pyomo/contrib/appsi/solvers/highs.py | 10 +++++----- pyomo/contrib/appsi/solvers/ipopt.py | 8 ++++---- .../appsi/solvers/tests/test_gurobi_persistent.py | 2 +- .../appsi/solvers/tests/test_persistent_solvers.py | 4 ++-- .../appsi/solvers/tests/test_wntr_persistent.py | 2 +- pyomo/contrib/appsi/solvers/wntr.py | 10 +++++----- pyomo/contrib/appsi/writers/lp_writer.py | 2 +- pyomo/contrib/appsi/writers/nl_writer.py | 2 +- pyomo/{ => contrib}/solver/__init__.py | 0 pyomo/{ => contrib}/solver/base.py | 6 +++--- pyomo/{ => contrib}/solver/config.py | 0 pyomo/{ => contrib}/solver/factory.py | 2 +- pyomo/{ => contrib}/solver/ipopt.py | 12 ++++++------ pyomo/{ => contrib}/solver/plugins.py | 0 pyomo/{ => contrib}/solver/results.py | 2 +- pyomo/{ => contrib}/solver/solution.py | 0 pyomo/{ => contrib}/solver/tests/__init__.py | 0 .../{ => contrib}/solver/tests/solvers/test_ipopt.py | 4 ++-- pyomo/{ => contrib}/solver/tests/unit/test_base.py | 0 pyomo/{ => contrib}/solver/tests/unit/test_config.py | 2 +- .../{ => contrib}/solver/tests/unit/test_results.py | 0 .../{ => contrib}/solver/tests/unit/test_solution.py | 0 pyomo/{ => contrib}/solver/tests/unit/test_util.py | 2 +- pyomo/{ => contrib}/solver/util.py | 4 ++-- pyomo/environ/__init__.py | 1 - 30 files changed, 52 insertions(+), 53 deletions(-) rename pyomo/{ => contrib}/solver/__init__.py (100%) rename pyomo/{ => contrib}/solver/base.py (99%) rename pyomo/{ => contrib}/solver/config.py (100%) rename pyomo/{ => contrib}/solver/factory.py (94%) rename pyomo/{ => contrib}/solver/ipopt.py (97%) rename pyomo/{ => contrib}/solver/plugins.py (100%) rename pyomo/{ => contrib}/solver/results.py (99%) rename pyomo/{ => contrib}/solver/solution.py (100%) rename pyomo/{ => contrib}/solver/tests/__init__.py (100%) rename pyomo/{ => contrib}/solver/tests/solvers/test_ipopt.py (94%) rename pyomo/{ => contrib}/solver/tests/unit/test_base.py (100%) rename pyomo/{ => contrib}/solver/tests/unit/test_config.py (96%) rename pyomo/{ => contrib}/solver/tests/unit/test_results.py (100%) rename pyomo/{ => contrib}/solver/tests/unit/test_solution.py (100%) rename pyomo/{ => contrib}/solver/tests/unit/test_util.py (97%) rename pyomo/{ => contrib}/solver/util.py (99%) diff --git a/pyomo/contrib/appsi/fbbt.py b/pyomo/contrib/appsi/fbbt.py index cff1085de0d..ccbb3819554 100644 --- a/pyomo/contrib/appsi/fbbt.py +++ b/pyomo/contrib/appsi/fbbt.py @@ -1,4 +1,4 @@ -from pyomo.solver.util import PersistentSolverUtils +from pyomo.contrib.solver.util import PersistentSolverUtils from pyomo.common.config import ( ConfigDict, ConfigValue, diff --git a/pyomo/contrib/appsi/plugins.py b/pyomo/contrib/appsi/plugins.py index 3a132b74395..ebccba09ab2 100644 --- a/pyomo/contrib/appsi/plugins.py +++ b/pyomo/contrib/appsi/plugins.py @@ -1,5 +1,5 @@ from pyomo.common.extensions import ExtensionBuilderFactory -from pyomo.solver.factory import SolverFactory +from pyomo.contrib.solver.factory import SolverFactory from .solvers import Gurobi, Ipopt, Cbc, Cplex, Highs from .build import AppsiBuilder diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index 62404890d0b..141c6de57bd 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -22,10 +22,10 @@ from pyomo.common.errors import PyomoException from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager -from pyomo.solver.base import PersistentSolverBase -from pyomo.solver.config import SolverConfig -from pyomo.solver.results import TerminationCondition, Results -from pyomo.solver.solution import PersistentSolutionLoader +from pyomo.contrib.solver.base import PersistentSolverBase +from pyomo.contrib.solver.config import SolverConfig +from pyomo.contrib.solver.results import TerminationCondition, Results +from pyomo.contrib.solver.solution import PersistentSolutionLoader logger = logging.getLogger(__name__) diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 1837b5690a0..6f02ac12eb1 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -19,10 +19,10 @@ from pyomo.common.errors import PyomoException from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager -from pyomo.solver.base import PersistentSolverBase -from pyomo.solver.config import BranchAndBoundConfig -from pyomo.solver.results import TerminationCondition, Results -from pyomo.solver.solution import PersistentSolutionLoader +from pyomo.contrib.solver.base import PersistentSolverBase +from pyomo.contrib.solver.config import BranchAndBoundConfig +from pyomo.contrib.solver.results import TerminationCondition, Results +from pyomo.contrib.solver.solution import PersistentSolutionLoader logger = logging.getLogger(__name__) diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index 99fa19820a5..a947c8d7d7d 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -22,11 +22,11 @@ from pyomo.repn import generate_standard_repn from pyomo.core.expr.numeric_expr import NPV_MaxExpression, NPV_MinExpression from pyomo.core.staleflag import StaleFlagManager -from pyomo.solver.base import PersistentSolverBase -from pyomo.solver.config import BranchAndBoundConfig -from pyomo.solver.results import TerminationCondition, Results -from pyomo.solver.solution import PersistentSolutionLoader -from pyomo.solver.util import PersistentSolverUtils +from pyomo.contrib.solver.base import PersistentSolverBase +from pyomo.contrib.solver.config import BranchAndBoundConfig +from pyomo.contrib.solver.results import TerminationCondition, Results +from pyomo.contrib.solver.solution import PersistentSolutionLoader +from pyomo.contrib.solver.util import PersistentSolverUtils logger = logging.getLogger(__name__) diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index b270e4f2700..1680831471c 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -20,11 +20,11 @@ from pyomo.core.expr.numeric_expr import NPV_MaxExpression, NPV_MinExpression from pyomo.common.dependencies import numpy as np from pyomo.core.staleflag import StaleFlagManager -from pyomo.solver.base import PersistentSolverBase -from pyomo.solver.config import BranchAndBoundConfig -from pyomo.solver.results import TerminationCondition, Results -from pyomo.solver.solution import PersistentSolutionLoader -from pyomo.solver.util import PersistentSolverUtils +from pyomo.contrib.solver.base import PersistentSolverBase +from pyomo.contrib.solver.config import BranchAndBoundConfig +from pyomo.contrib.solver.results import TerminationCondition, Results +from pyomo.contrib.solver.solution import PersistentSolutionLoader +from pyomo.contrib.solver.util import PersistentSolverUtils logger = logging.getLogger(__name__) diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index 569bb98457f..ec59b827192 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -26,10 +26,10 @@ from pyomo.common.errors import PyomoException from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager -from pyomo.solver.base import PersistentSolverBase -from pyomo.solver.config import SolverConfig -from pyomo.solver.results import TerminationCondition, Results -from pyomo.solver.solution import PersistentSolutionLoader +from pyomo.contrib.solver.base import PersistentSolverBase +from pyomo.contrib.solver.config import SolverConfig +from pyomo.contrib.solver.results import TerminationCondition, Results +from pyomo.contrib.solver.solution import PersistentSolutionLoader logger = logging.getLogger(__name__) diff --git a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py index c1825879dbe..4619a1c5452 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py @@ -1,7 +1,7 @@ from pyomo.common import unittest import pyomo.environ as pe from pyomo.contrib.appsi.solvers.gurobi import Gurobi -from pyomo.solver.results import TerminationCondition +from pyomo.contrib.solver.results import TerminationCondition from pyomo.core.expr.taylor_series import taylor_series_expansion diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index b50a072abbd..6731eb645fa 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -4,8 +4,8 @@ parameterized, param_available = attempt_import('parameterized') parameterized = parameterized.parameterized -from pyomo.solver.base import PersistentSolverBase -from pyomo.solver.results import TerminationCondition, Results +from pyomo.contrib.solver.base import PersistentSolverBase +from pyomo.contrib.solver.results import TerminationCondition, Results from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.contrib.appsi.solvers import Gurobi, Ipopt, Highs from typing import Type diff --git a/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py index 971305001a9..e09865294eb 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py @@ -1,6 +1,6 @@ import pyomo.environ as pe import pyomo.common.unittest as unittest -from pyomo.solver.results import TerminationCondition, SolutionStatus +from pyomo.contrib.solver.results import TerminationCondition, SolutionStatus from pyomo.contrib.appsi.solvers.wntr import Wntr, wntr_available import math diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py index aaa130f8631..04f54530c1b 100644 --- a/pyomo/contrib/appsi/solvers/wntr.py +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -1,8 +1,8 @@ -from pyomo.solver.base import PersistentSolverBase -from pyomo.solver.util import PersistentSolverUtils -from pyomo.solver.config import SolverConfig -from pyomo.solver.results import Results, TerminationCondition, SolutionStatus -from pyomo.solver.solution import PersistentSolutionLoader +from pyomo.contrib.solver.base import PersistentSolverBase +from pyomo.contrib.solver.util import PersistentSolverUtils +from pyomo.contrib.solver.config import SolverConfig +from pyomo.contrib.solver.results import Results, TerminationCondition, SolutionStatus +from pyomo.contrib.solver.solution import PersistentSolutionLoader from pyomo.core.expr.numeric_expr import ( ProductExpression, DivisionExpression, diff --git a/pyomo/contrib/appsi/writers/lp_writer.py b/pyomo/contrib/appsi/writers/lp_writer.py index 8deb92640c1..9d0b71fe794 100644 --- a/pyomo/contrib/appsi/writers/lp_writer.py +++ b/pyomo/contrib/appsi/writers/lp_writer.py @@ -8,7 +8,7 @@ from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler from pyomo.common.timing import HierarchicalTimer from pyomo.core.kernel.objective import minimize -from pyomo.solver.util import PersistentSolverUtils +from pyomo.contrib.solver.util import PersistentSolverUtils from .config import WriterConfig from ..cmodel import cmodel, cmodel_available diff --git a/pyomo/contrib/appsi/writers/nl_writer.py b/pyomo/contrib/appsi/writers/nl_writer.py index 1be657ba762..a9b44e63f36 100644 --- a/pyomo/contrib/appsi/writers/nl_writer.py +++ b/pyomo/contrib/appsi/writers/nl_writer.py @@ -13,7 +13,7 @@ from pyomo.core.kernel.objective import minimize from pyomo.common.collections import OrderedSet from pyomo.repn.plugins.ampl.ampl_ import set_pyomo_amplfunc_env -from pyomo.solver.util import PersistentSolverUtils +from pyomo.contrib.solver.util import PersistentSolverUtils from .config import WriterConfig from ..cmodel import cmodel, cmodel_available diff --git a/pyomo/solver/__init__.py b/pyomo/contrib/solver/__init__.py similarity index 100% rename from pyomo/solver/__init__.py rename to pyomo/contrib/solver/__init__.py diff --git a/pyomo/solver/base.py b/pyomo/contrib/solver/base.py similarity index 99% rename from pyomo/solver/base.py rename to pyomo/contrib/solver/base.py index d7f4adabf56..69ad921b182 100644 --- a/pyomo/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -26,9 +26,9 @@ from pyomo.core.kernel.objective import minimize from pyomo.core.base import SymbolMap from pyomo.core.staleflag import StaleFlagManager -from pyomo.solver.config import UpdateConfig -from pyomo.solver.util import get_objective -from pyomo.solver.results import ( +from pyomo.contrib.solver.config import UpdateConfig +from pyomo.contrib.solver.util import get_objective +from pyomo.contrib.solver.results import ( Results, legacy_solver_status_map, legacy_termination_condition_map, diff --git a/pyomo/solver/config.py b/pyomo/contrib/solver/config.py similarity index 100% rename from pyomo/solver/config.py rename to pyomo/contrib/solver/config.py diff --git a/pyomo/solver/factory.py b/pyomo/contrib/solver/factory.py similarity index 94% rename from pyomo/solver/factory.py rename to pyomo/contrib/solver/factory.py index 23a66acd9cb..fa3e2611667 100644 --- a/pyomo/solver/factory.py +++ b/pyomo/contrib/solver/factory.py @@ -12,7 +12,7 @@ from pyomo.opt.base import SolverFactory as LegacySolverFactory from pyomo.common.factory import Factory -from pyomo.solver.base import LegacySolverInterface +from pyomo.contrib.solver.base import LegacySolverInterface class SolverFactoryClass(Factory): diff --git a/pyomo/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py similarity index 97% rename from pyomo/solver/ipopt.py rename to pyomo/contrib/solver/ipopt.py index 51f48ec4881..cb70938a074 100644 --- a/pyomo/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -24,16 +24,16 @@ from pyomo.core.base.label import NumericLabeler from pyomo.core.staleflag import StaleFlagManager from pyomo.repn.plugins.nl_writer import NLWriter, NLWriterInfo, AMPLRepn -from pyomo.solver.base import SolverBase, SymbolMap -from pyomo.solver.config import SolverConfig -from pyomo.solver.factory import SolverFactory -from pyomo.solver.results import ( +from pyomo.contrib.solver.base import SolverBase, SymbolMap +from pyomo.contrib.solver.config import SolverConfig +from pyomo.contrib.solver.factory import SolverFactory +from pyomo.contrib.solver.results import ( Results, TerminationCondition, SolutionStatus, parse_sol_file, ) -from pyomo.solver.solution import SolutionLoaderBase, SolutionLoader +from pyomo.contrib.solver.solution import SolutionLoaderBase, SolutionLoader from pyomo.common.tee import TeeStream from pyomo.common.log import LogStream from pyomo.core.expr.visitor import replace_expressions @@ -278,7 +278,7 @@ def solve(self, model, **kwds): if config.threads: logger.log( logging.WARNING, - msg=f"The `threads` option was specified, but but is not used by {self.__class__}.", + msg=f"The `threads` option was specified, but this is not used by {self.__class__}.", ) results = ipoptResults() with TempfileManager.new_context() as tempfile: diff --git a/pyomo/solver/plugins.py b/pyomo/contrib/solver/plugins.py similarity index 100% rename from pyomo/solver/plugins.py rename to pyomo/contrib/solver/plugins.py diff --git a/pyomo/solver/results.py b/pyomo/contrib/solver/results.py similarity index 99% rename from pyomo/solver/results.py rename to pyomo/contrib/solver/results.py index e99db52073b..c24053e6358 100644 --- a/pyomo/solver/results.py +++ b/pyomo/contrib/solver/results.py @@ -31,7 +31,7 @@ TerminationCondition as LegacyTerminationCondition, SolverStatus as LegacySolverStatus, ) -from pyomo.solver.solution import SolutionLoaderBase +from pyomo.contrib.solver.solution import SolutionLoaderBase from pyomo.repn.plugins.nl_writer import NLWriterInfo diff --git a/pyomo/solver/solution.py b/pyomo/contrib/solver/solution.py similarity index 100% rename from pyomo/solver/solution.py rename to pyomo/contrib/solver/solution.py diff --git a/pyomo/solver/tests/__init__.py b/pyomo/contrib/solver/tests/__init__.py similarity index 100% rename from pyomo/solver/tests/__init__.py rename to pyomo/contrib/solver/tests/__init__.py diff --git a/pyomo/solver/tests/solvers/test_ipopt.py b/pyomo/contrib/solver/tests/solvers/test_ipopt.py similarity index 94% rename from pyomo/solver/tests/solvers/test_ipopt.py rename to pyomo/contrib/solver/tests/solvers/test_ipopt.py index d9fccbb84fc..c1aecba05fc 100644 --- a/pyomo/solver/tests/solvers/test_ipopt.py +++ b/pyomo/contrib/solver/tests/solvers/test_ipopt.py @@ -13,8 +13,8 @@ import pyomo.environ as pyo from pyomo.common.fileutils import ExecutableData from pyomo.common.config import ConfigDict -from pyomo.solver.ipopt import ipoptConfig -from pyomo.solver.factory import SolverFactory +from pyomo.contrib.solver.ipopt import ipoptConfig +from pyomo.contrib.solver.factory import SolverFactory from pyomo.common import unittest diff --git a/pyomo/solver/tests/unit/test_base.py b/pyomo/contrib/solver/tests/unit/test_base.py similarity index 100% rename from pyomo/solver/tests/unit/test_base.py rename to pyomo/contrib/solver/tests/unit/test_base.py diff --git a/pyomo/solver/tests/unit/test_config.py b/pyomo/contrib/solver/tests/unit/test_config.py similarity index 96% rename from pyomo/solver/tests/unit/test_config.py rename to pyomo/contrib/solver/tests/unit/test_config.py index c705c7cb8ac..1051825f4e5 100644 --- a/pyomo/solver/tests/unit/test_config.py +++ b/pyomo/contrib/solver/tests/unit/test_config.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ from pyomo.common import unittest -from pyomo.solver.config import SolverConfig, BranchAndBoundConfig +from pyomo.contrib.solver.config import SolverConfig, BranchAndBoundConfig class TestSolverConfig(unittest.TestCase): diff --git a/pyomo/solver/tests/unit/test_results.py b/pyomo/contrib/solver/tests/unit/test_results.py similarity index 100% rename from pyomo/solver/tests/unit/test_results.py rename to pyomo/contrib/solver/tests/unit/test_results.py diff --git a/pyomo/solver/tests/unit/test_solution.py b/pyomo/contrib/solver/tests/unit/test_solution.py similarity index 100% rename from pyomo/solver/tests/unit/test_solution.py rename to pyomo/contrib/solver/tests/unit/test_solution.py diff --git a/pyomo/solver/tests/unit/test_util.py b/pyomo/contrib/solver/tests/unit/test_util.py similarity index 97% rename from pyomo/solver/tests/unit/test_util.py rename to pyomo/contrib/solver/tests/unit/test_util.py index 737a271d603..9bf92af72cf 100644 --- a/pyomo/solver/tests/unit/test_util.py +++ b/pyomo/contrib/solver/tests/unit/test_util.py @@ -11,7 +11,7 @@ from pyomo.common import unittest import pyomo.environ as pyo -from pyomo.solver.util import collect_vars_and_named_exprs, get_objective +from pyomo.contrib.solver.util import collect_vars_and_named_exprs, get_objective from typing import Callable from pyomo.common.gsl import find_GSL diff --git a/pyomo/solver/util.py b/pyomo/contrib/solver/util.py similarity index 99% rename from pyomo/solver/util.py rename to pyomo/contrib/solver/util.py index c0c99a00747..9f0c607a0db 100644 --- a/pyomo/solver/util.py +++ b/pyomo/contrib/solver/util.py @@ -22,8 +22,8 @@ from pyomo.common.collections import ComponentMap from pyomo.common.timing import HierarchicalTimer from pyomo.core.expr.numvalue import NumericConstant -from pyomo.solver.config import UpdateConfig -from pyomo.solver.results import TerminationCondition, SolutionStatus +from pyomo.contrib.solver.config import UpdateConfig +from pyomo.contrib.solver.results import TerminationCondition, SolutionStatus def get_objective(block): diff --git a/pyomo/environ/__init__.py b/pyomo/environ/__init__.py index 2cd562edb2b..51c68449247 100644 --- a/pyomo/environ/__init__.py +++ b/pyomo/environ/__init__.py @@ -30,7 +30,6 @@ def _do_import(pkg_name): 'pyomo.repn', 'pyomo.neos', 'pyomo.solvers', - 'pyomo.solver', 'pyomo.gdp', 'pyomo.mpec', 'pyomo.dae', From aa28193717ea2f93b920fe8e9104832a98f079b4 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 15 Jan 2024 15:31:42 -0700 Subject: [PATCH 0773/1204] Missed several imports --- pyomo/contrib/appsi/examples/getting_started.py | 2 +- pyomo/contrib/solver/tests/unit/test_base.py | 2 +- pyomo/contrib/solver/tests/unit/test_results.py | 4 ++-- pyomo/contrib/solver/tests/unit/test_solution.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/appsi/examples/getting_started.py b/pyomo/contrib/appsi/examples/getting_started.py index 52f4992b37b..15c3fcb2058 100644 --- a/pyomo/contrib/appsi/examples/getting_started.py +++ b/pyomo/contrib/appsi/examples/getting_started.py @@ -1,7 +1,7 @@ import pyomo.environ as pe from pyomo.contrib import appsi from pyomo.common.timing import HierarchicalTimer -from pyomo.solver import results +from pyomo.contrib.solver import results def main(plot=True, n_points=200): diff --git a/pyomo/contrib/solver/tests/unit/test_base.py b/pyomo/contrib/solver/tests/unit/test_base.py index b501f8d3dd3..71690b7aa0e 100644 --- a/pyomo/contrib/solver/tests/unit/test_base.py +++ b/pyomo/contrib/solver/tests/unit/test_base.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ from pyomo.common import unittest -from pyomo.solver import base +from pyomo.contrib.solver import base class TestSolverBase(unittest.TestCase): diff --git a/pyomo/contrib/solver/tests/unit/test_results.py b/pyomo/contrib/solver/tests/unit/test_results.py index 0c0b4bb18db..e7d02751f7d 100644 --- a/pyomo/contrib/solver/tests/unit/test_results.py +++ b/pyomo/contrib/solver/tests/unit/test_results.py @@ -11,8 +11,8 @@ from pyomo.common import unittest from pyomo.common.config import ConfigDict -from pyomo.solver import results -from pyomo.solver import solution +from pyomo.contrib.solver import results +from pyomo.contrib.solver import solution import pyomo.environ as pyo from pyomo.core.base.var import ScalarVar diff --git a/pyomo/contrib/solver/tests/unit/test_solution.py b/pyomo/contrib/solver/tests/unit/test_solution.py index f4c33a60c84..dc53f1e4543 100644 --- a/pyomo/contrib/solver/tests/unit/test_solution.py +++ b/pyomo/contrib/solver/tests/unit/test_solution.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ from pyomo.common import unittest -from pyomo.solver import solution +from pyomo.contrib.solver import solution class TestPersistentSolverBase(unittest.TestCase): From f0d9685b006d7ab40cceeeaaf2a118e778add56d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 15 Jan 2024 15:52:34 -0700 Subject: [PATCH 0774/1204] Missing init files --- pyomo/contrib/solver/tests/solvers/__init__.py | 11 +++++++++++ pyomo/contrib/solver/tests/unit/__init__.py | 11 +++++++++++ 2 files changed, 22 insertions(+) create mode 100644 pyomo/contrib/solver/tests/solvers/__init__.py create mode 100644 pyomo/contrib/solver/tests/unit/__init__.py diff --git a/pyomo/contrib/solver/tests/solvers/__init__.py b/pyomo/contrib/solver/tests/solvers/__init__.py new file mode 100644 index 00000000000..9320e403e95 --- /dev/null +++ b/pyomo/contrib/solver/tests/solvers/__init__.py @@ -0,0 +1,11 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + diff --git a/pyomo/contrib/solver/tests/unit/__init__.py b/pyomo/contrib/solver/tests/unit/__init__.py new file mode 100644 index 00000000000..9320e403e95 --- /dev/null +++ b/pyomo/contrib/solver/tests/unit/__init__.py @@ -0,0 +1,11 @@ +# ___________________________________________________________________________ +# +# 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 7a1a0d50c7c83204faf343d6524eaede0fad8e1f Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 15 Jan 2024 16:26:09 -0700 Subject: [PATCH 0775/1204] Black --- pyomo/contrib/solver/tests/solvers/__init__.py | 1 - pyomo/contrib/solver/tests/unit/__init__.py | 1 - 2 files changed, 2 deletions(-) diff --git a/pyomo/contrib/solver/tests/solvers/__init__.py b/pyomo/contrib/solver/tests/solvers/__init__.py index 9320e403e95..d93cfd77b3c 100644 --- a/pyomo/contrib/solver/tests/solvers/__init__.py +++ b/pyomo/contrib/solver/tests/solvers/__init__.py @@ -8,4 +8,3 @@ # rights in this software. # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ - diff --git a/pyomo/contrib/solver/tests/unit/__init__.py b/pyomo/contrib/solver/tests/unit/__init__.py index 9320e403e95..d93cfd77b3c 100644 --- a/pyomo/contrib/solver/tests/unit/__init__.py +++ b/pyomo/contrib/solver/tests/unit/__init__.py @@ -8,4 +8,3 @@ # rights in this software. # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ - From cc76d9ed0fb94ce7707ef69f6df0dfaa8664837f Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 15 Jan 2024 17:12:30 -0700 Subject: [PATCH 0776/1204] Assigning numpy to Param should trigger numpy registrations --- pyomo/common/numeric_types.py | 4 +- pyomo/core/base/range.py | 92 +++++++++++++++++++------- pyomo/core/tests/unit/test_numvalue.py | 4 +- 3 files changed, 72 insertions(+), 28 deletions(-) diff --git a/pyomo/common/numeric_types.py b/pyomo/common/numeric_types.py index bd71b29f005..19718b308b6 100644 --- a/pyomo/common/numeric_types.py +++ b/pyomo/common/numeric_types.py @@ -212,8 +212,8 @@ def check_if_numeric_type(obj): # trigger the resolution of numpy_available and check if this # type was automatically registered bool(numpy_available) - if obj_class in native_numeric_types: - return True + if obj_class in native_types: + return obj_class in native_numeric_types try: obj_plus_0 = obj + 0 diff --git a/pyomo/core/base/range.py b/pyomo/core/base/range.py index f650680df26..023c889b3b0 100644 --- a/pyomo/core/base/range.py +++ b/pyomo/core/base/range.py @@ -12,6 +12,8 @@ import math from collections.abc import Sequence +from pyomo.common.numeric_types import check_if_numeric_type + try: from math import remainder except ImportError: @@ -27,6 +29,41 @@ def remainder(a, b): _infinite = {_inf, -_inf} +def _check_comparable_to_int(value): + if check_if_numeric_type(value): + self._types_comparable_to_int.add(value.__class__) + return True + + # Special case: because numpy is fond of returning scalars + # as length-1 ndarrays, we will include a special case that + # will unpack things that look like single element arrays. + try: + # Note: trap "value[0] is not value" to catch things like + # single-character strings + if ( + hasattr(value, '__len__') + and hasattr(value, '__getitem__') + and len(value) == 1 + and value[0] is not value + ): + return value[0] in self + except: + pass + # See if this class behaves like a "normal" number: both + # comparable and creatable + try: + if not (bool(value - 0 > 0) ^ bool(value - 0 <= 0)): + return False + elif value.__class__(0) != 0 or not value.__class__(0) == 0: + return False + else: + self._types_comparable_to_int.add(value.__class__) + return True + except: + pass + return False + + class RangeDifferenceError(ValueError): pass @@ -180,32 +217,37 @@ def __ne__(self, other): def __contains__(self, value): # NumericRanges must hold items that are comparable to ints if value.__class__ not in self._types_comparable_to_int: - # Special case: because numpy is fond of returning scalars - # as length-1 ndarrays, we will include a special case that - # will unpack things that look like single element arrays. - try: - # Note: trap "value[0] is not value" to catch things like - # single-character strings - if ( - hasattr(value, '__len__') - and hasattr(value, '__getitem__') - and len(value) == 1 - and value[0] is not value - ): - return value[0] in self - except: - pass - # See if this class behaves like a "normal" number: both - # comparable and creatable - try: - if not (bool(value - 0 > 0) ^ bool(value - 0 <= 0)): - return False - elif value.__class__(0) != 0 or not value.__class__(0) == 0: + # Build on numeric_type.check_if_numeric_type to cleanly + # handle numpy registrations + if check_if_numeric_type(value): + self._types_comparable_to_int.add(value.__class__) + else: + # Special case: because numpy is fond of returning scalars + # as length-1 ndarrays, we will include a special case that + # will unpack things that look like single element arrays. + try: + # Note: trap "value[0] is not value" to catch things like + # single-character strings + if ( + hasattr(value, '__len__') + and hasattr(value, '__getitem__') + and len(value) == 1 + and value[0] is not value + ): + return value[0] in self + except: + pass + # See if this class behaves like a "normal" number: both + # comparable and creatable + try: + if not (bool(value - 0 > 0) ^ bool(value - 0 <= 0)): + return False + elif value.__class__(0) != 0 or not value.__class__(0) == 0: + return False + else: + self._types_comparable_to_int.add(value.__class__) + except: return False - else: - self._types_comparable_to_int.add(value.__class__) - except: - return False if self.step: _dir = int(math.copysign(1, self.step)) diff --git a/pyomo/core/tests/unit/test_numvalue.py b/pyomo/core/tests/unit/test_numvalue.py index 8c6b0e02ed4..74df1d29522 100644 --- a/pyomo/core/tests/unit/test_numvalue.py +++ b/pyomo/core/tests/unit/test_numvalue.py @@ -562,7 +562,7 @@ def test_numpy_basic_bool_registration(self): @unittest.skipUnless(numpy_available, "This test requires NumPy") def test_automatic_numpy_registration(self): cmd = ( - 'import pyomo; from pyomo.core.base import Var; import numpy as np; ' + 'import pyomo; from pyomo.core.base import Var, Param; import numpy as np; ' 'print(np.float64 in pyomo.common.numeric_types.native_numeric_types); ' '%s; print(np.float64 in pyomo.common.numeric_types.native_numeric_types)' ) @@ -580,6 +580,8 @@ def _tester(expr): _tester('np.float64(5) <= Var()') _tester('np.float64(5) + Var()') _tester('Var() + np.float64(5)') + _tester('v = Var(); v.construct(); v.value = np.float64(5)') + _tester('p = Param(mutable=True); p.construct(); p.value = np.float64(5)') if __name__ == "__main__": From 5842fccab8aad947faad99d81cc62e01a655b331 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 15 Jan 2024 17:23:48 -0700 Subject: [PATCH 0777/1204] NFC: apply black --- pyomo/core/base/range.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyomo/core/base/range.py b/pyomo/core/base/range.py index 023c889b3b0..e40f191c12a 100644 --- a/pyomo/core/base/range.py +++ b/pyomo/core/base/range.py @@ -41,10 +41,10 @@ def _check_comparable_to_int(value): # Note: trap "value[0] is not value" to catch things like # single-character strings if ( - hasattr(value, '__len__') - and hasattr(value, '__getitem__') - and len(value) == 1 - and value[0] is not value + hasattr(value, '__len__') + and hasattr(value, '__getitem__') + and len(value) == 1 + and value[0] is not value ): return value[0] in self except: @@ -229,10 +229,10 @@ def __contains__(self, value): # Note: trap "value[0] is not value" to catch things like # single-character strings if ( - hasattr(value, '__len__') - and hasattr(value, '__getitem__') - and len(value) == 1 - and value[0] is not value + hasattr(value, '__len__') + and hasattr(value, '__getitem__') + and len(value) == 1 + and value[0] is not value ): return value[0] in self except: From 960493103806d41cc1ac70c35ba7f2bf895e3f6c Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 15 Jan 2024 18:05:12 -0700 Subject: [PATCH 0778/1204] Add missing solver dependency flags for OnlineDocs tests --- doc/OnlineDocs/src/test_examples.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/OnlineDocs/src/test_examples.py b/doc/OnlineDocs/src/test_examples.py index 33cbab2e8b4..a7991eadf19 100644 --- a/doc/OnlineDocs/src/test_examples.py +++ b/doc/OnlineDocs/src/test_examples.py @@ -35,7 +35,11 @@ class TestOnlineDocExamples(unittest.BaselineTestDriver, unittest.TestCase): list(filter(os.path.isdir, glob.glob(os.path.join(currdir, '*')))) ) - solver_dependencies = {} + solver_dependencies = { + 'test_data_pyomo_diet1': ['glpk'], + 'test_data_pyomo_diet2': ['glpk'], + 'test_kernel_examples': ['glpk'], + } # Note on package dependencies: two tests actually need # pyutilib.excel.spreadsheet; however, the pyutilib importer is # broken on Python>=3.12, so instead of checking for spreadsheet, we From c299be04c8eb9168a082216baa01de4c436481d4 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 15 Jan 2024 18:55:02 -0700 Subject: [PATCH 0779/1204] Remove (unused) development function --- pyomo/core/base/range.py | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/pyomo/core/base/range.py b/pyomo/core/base/range.py index e40f191c12a..9df4828f550 100644 --- a/pyomo/core/base/range.py +++ b/pyomo/core/base/range.py @@ -29,41 +29,6 @@ def remainder(a, b): _infinite = {_inf, -_inf} -def _check_comparable_to_int(value): - if check_if_numeric_type(value): - self._types_comparable_to_int.add(value.__class__) - return True - - # Special case: because numpy is fond of returning scalars - # as length-1 ndarrays, we will include a special case that - # will unpack things that look like single element arrays. - try: - # Note: trap "value[0] is not value" to catch things like - # single-character strings - if ( - hasattr(value, '__len__') - and hasattr(value, '__getitem__') - and len(value) == 1 - and value[0] is not value - ): - return value[0] in self - except: - pass - # See if this class behaves like a "normal" number: both - # comparable and creatable - try: - if not (bool(value - 0 > 0) ^ bool(value - 0 <= 0)): - return False - elif value.__class__(0) != 0 or not value.__class__(0) == 0: - return False - else: - self._types_comparable_to_int.add(value.__class__) - return True - except: - pass - return False - - class RangeDifferenceError(ValueError): pass From c1049540b5dca5d711bddee0bef3ce96f6f10684 Mon Sep 17 00:00:00 2001 From: jasherma Date: Mon, 15 Jan 2024 22:55:29 -0500 Subject: [PATCH 0780/1204] Fix changelog date --- pyomo/contrib/pyros/CHANGELOG.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/CHANGELOG.txt b/pyomo/contrib/pyros/CHANGELOG.txt index 2a0b782a9b4..7d4678f0ba3 100644 --- a/pyomo/contrib/pyros/CHANGELOG.txt +++ b/pyomo/contrib/pyros/CHANGELOG.txt @@ -3,7 +3,7 @@ PyROS CHANGELOG =============== ------------------------------------------------------------------------------- -PyROS 1.2.9 12 Oct 2023 +PyROS 1.2.9 15 Dec 2023 ------------------------------------------------------------------------------- - Fix DR polishing optimality constraint for case of nominal objective focus - Use previous separation solution to initialize second-stage and state From 4087d1adb3cfea55c6e85547ce0697b5b860601d Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 15 Jan 2024 23:49:20 -0700 Subject: [PATCH 0781/1204] solver refactor: various updates --- pyomo/contrib/solver/base.py | 61 ++++++++++---------------------- pyomo/contrib/solver/config.py | 15 ++++---- pyomo/contrib/solver/ipopt.py | 57 +++++++++++++---------------- pyomo/contrib/solver/results.py | 21 +++-------- pyomo/contrib/solver/solution.py | 50 ++------------------------ pyomo/repn/plugins/nl_writer.py | 2 +- 6 files changed, 58 insertions(+), 148 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 69ad921b182..961187179f2 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -14,6 +14,8 @@ from typing import Sequence, Dict, Optional, Mapping, NoReturn, List, Tuple import os +from .config import SolverConfig + from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.param import _ParamData @@ -49,6 +51,11 @@ class SolverBase(abc.ABC): - is_persistent: Set to false for all direct solvers. """ + CONFIG = SolverConfig() + + def __init__(self, **kwds) -> None: + self.config = self.CONFIG(value=kwds) + # # Support "with" statements. Forgetting to call deactivate # on Plugins is a common source of memory leaks @@ -146,19 +153,6 @@ def version(self) -> Tuple: A tuple representing the version """ - @property - @abc.abstractmethod - def config(self): - """ - An object for configuring solve options. - - Returns - ------- - SolverConfig - An object for configuring pyomo solve options such as the time limit. - These options are mostly independent of the solver. - """ - def is_persistent(self): """ Returns @@ -187,7 +181,7 @@ def is_persistent(self): """ return True - def load_vars( + def _load_vars( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None ) -> NoReturn: """ @@ -199,12 +193,12 @@ def load_vars( A list of the variables whose solution should be loaded. If vars_to_load is None, then the solution to all primal variables will be loaded. """ - for v, val in self.get_primals(vars_to_load=vars_to_load).items(): + for v, val in self._get_primals(vars_to_load=vars_to_load).items(): v.set_value(val, skip_validation=True) StaleFlagManager.mark_all_as_stale(delayed=True) @abc.abstractmethod - def get_primals( + def _get_primals( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None ) -> Mapping[_GeneralVarData, float]: """ @@ -224,7 +218,7 @@ def get_primals( '{0} does not support the get_primals method'.format(type(self)) ) - def get_duals( + def _get_duals( self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None ) -> Dict[_GeneralConstraintData, float]: """ @@ -245,26 +239,7 @@ def get_duals( '{0} does not support the get_duals method'.format(type(self)) ) - def get_slacks( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: - """ - Parameters - ---------- - cons_to_load: list - A list of the constraints whose slacks should be loaded. If cons_to_load is None, then the slacks for all - constraints will be loaded. - - Returns - ------- - slacks: dict - Maps constraints to slack values - """ - raise NotImplementedError( - '{0} does not support the get_slacks method'.format(type(self)) - ) - - def get_reduced_costs( + def _get_reduced_costs( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None ) -> Mapping[_GeneralVarData, float]: """ @@ -296,6 +271,12 @@ def set_instance(self, model): Set an instance of the model """ + @abc.abstractmethod + def set_objective(self, obj: _GeneralObjectiveData): + """ + Set current objective for the model + """ + @abc.abstractmethod def add_variables(self, variables: List[_GeneralVarData]): """ @@ -344,12 +325,6 @@ def remove_block(self, block: _BlockData): Remove a block from the model """ - @abc.abstractmethod - def set_objective(self, obj: _GeneralObjectiveData): - """ - Set current objective for the model - """ - @abc.abstractmethod def update_variables(self, variables: List[_GeneralVarData]): """ diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index ef0114ba439..738338d3718 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -16,7 +16,9 @@ ConfigValue, NonNegativeFloat, NonNegativeInt, + ADVANCED_OPTION, ) +from pyomo.common.timing import HierarchicalTimer class SolverConfig(ConfigDict): @@ -72,12 +74,11 @@ def __init__( description="If True, the names given to the solver will reflect the names of the Pyomo components. Cannot be changed after set_instance is called.", ), ) - self.report_timing: bool = self.declare( - 'report_timing', + self.timer: HierarchicalTimer = self.declare( + 'timer', ConfigValue( - domain=bool, - default=False, - description="If True, timing information will be printed at the end of a solve call.", + default=None, + description="A HierarchicalTimer.", ), ) self.threads: Optional[int] = self.declare( @@ -133,9 +134,6 @@ def __init__( self.abs_gap: Optional[float] = self.declare( 'abs_gap', ConfigValue(domain=NonNegativeFloat) ) - self.relax_integrality: bool = self.declare( - 'relax_integrality', ConfigValue(domain=bool, default=False) - ) class UpdateConfig(ConfigDict): @@ -283,6 +281,7 @@ def __init__( ConfigValue( domain=bool, default=True, + visibility=ADVANCED_OPTION, doc=""" This is an advanced option that should only be used in special circumstances. With the default setting of True, fixed variables will be treated like parameters. diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index cb70938a074..475ce6e6f0b 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -20,6 +20,7 @@ from pyomo.common.config import ConfigValue, NonNegativeInt, NonNegativeFloat from pyomo.common.errors import PyomoException from pyomo.common.tempfiles import TempfileManager +from pyomo.common.timing import HierarchicalTimer from pyomo.core.base import Objective from pyomo.core.base.label import NumericLabeler from pyomo.core.staleflag import StaleFlagManager @@ -84,6 +85,10 @@ def __init__( self.log_level = self.declare( 'log_level', ConfigValue(domain=NonNegativeInt, default=logging.INFO) ) + self.writer_config = self.declare( + 'writer_config', + ConfigValue(default=NLWriter.CONFIG()) + ) class ipoptResults(Results): @@ -183,10 +188,8 @@ class ipopt(SolverBase): CONFIG = ipoptConfig() def __init__(self, **kwds): - self._config = self.CONFIG(kwds) + super().__init__(**kwds) self._writer = NLWriter() - self._writer.config.skip_trivial_constraints = True - self._solver_options = self._config.solver_options def available(self): if self.config.executable.path() is None: @@ -206,26 +209,6 @@ def version(self): version = tuple(int(i) for i in version.split('.')) return version - @property - def writer(self): - return self._writer - - @property - def config(self): - return self._config - - @config.setter - def config(self, val): - self._config = val - - @property - def solver_options(self): - return self._solver_options - - @solver_options.setter - def solver_options(self, val: Dict): - self._solver_options = val - @property def symbol_map(self): return self._symbol_map @@ -250,14 +233,14 @@ def _create_command_line(self, basename: str, config: ipoptConfig, opt_file: boo cmd = [str(config.executable), basename + '.nl', '-AMPL'] if opt_file: cmd.append('option_file_name=' + basename + '.opt') - if 'option_file_name' in self.solver_options: + if 'option_file_name' in config.solver_options: raise ValueError( 'Pyomo generates the ipopt options file as part of the solve method. ' 'Add all options to ipopt.config.solver_options instead.' ) - if config.time_limit is not None and 'max_cpu_time' not in self.solver_options: - self.solver_options['max_cpu_time'] = config.time_limit - for k, val in self.solver_options.items(): + if config.time_limit is not None and 'max_cpu_time' not in config.solver_options: + config.solver_options['max_cpu_time'] = config.time_limit + for k, val in config.solver_options.items(): if k in ipopt_command_line_options: cmd.append(str(k) + '=' + str(val)) return cmd @@ -271,15 +254,18 @@ def solve(self, model, **kwds): raise ipoptSolverError( f'Solver {self.__class__} is not available ({avail}).' ) - StaleFlagManager.mark_all_as_stale() # Update configuration options, based on keywords passed to solve - config: ipoptConfig = self.config(kwds.pop('options', {})) - config.set_value(kwds) + config: ipoptConfig = self.config(value=kwds) if config.threads: logger.log( logging.WARNING, msg=f"The `threads` option was specified, but this is not used by {self.__class__}.", ) + if config.timer is None: + timer = HierarchicalTimer() + else: + timer = config.timer + StaleFlagManager.mark_all_as_stale() results = ipoptResults() with TempfileManager.new_context() as tempfile: if config.temp_dir is None: @@ -296,6 +282,8 @@ def solve(self, model, **kwds): with open(basename + '.nl', 'w') as nl_file, open( basename + '.row', 'w' ) as row_file, open(basename + '.col', 'w') as col_file: + timer.start('write_nl_file') + self._writer.config.set_value(config.writer_config) nl_info = self._writer.write( model, nl_file, @@ -303,6 +291,7 @@ def solve(self, model, **kwds): col_file, symbolic_solver_labels=config.symbolic_solver_labels, ) + timer.stop('write_nl_file') # Get a copy of the environment to pass to the subprocess env = os.environ.copy() if nl_info.external_function_libraries: @@ -318,7 +307,7 @@ def solve(self, model, **kwds): # Write the opt_file, if there should be one; return a bool to say # whether or not we have one (so we can correctly build the command line) opt_file = self._write_options_file( - filename=basename, options=self.solver_options + filename=basename, options=config.solver_options ) # Call ipopt - passing the files via the subprocess cmd = self._create_command_line( @@ -343,6 +332,7 @@ def solve(self, model, **kwds): ) ) with TeeStream(*ostreams) as t: + timer.start('subprocess') process = subprocess.run( cmd, timeout=timeout, @@ -351,6 +341,7 @@ def solve(self, model, **kwds): stdout=t.STDOUT, stderr=t.STDERR, ) + timer.stop('subprocess') # This is the stuff we need to parse to get the iterations # and time iters, ipopt_time_nofunc, ipopt_time_func = self._parse_ipopt_output( @@ -362,7 +353,9 @@ def solve(self, model, **kwds): results.solution_loader = SolutionLoader(None, None, None, None) else: with open(basename + '.sol', 'r') as sol_file: + timer.start('parse_sol') results = self._parse_solution(sol_file, nl_info, results) + timer.stop('parse_sol') results.iteration_count = iters results.timing_info.no_function_solve_time = ipopt_time_nofunc results.timing_info.function_solve_time = ipopt_time_func @@ -427,8 +420,6 @@ def solve(self, model, **kwds): results.timing_info.wall_time = ( end_timestamp - start_timestamp ).total_seconds() - if config.report_timing: - results.report_timing() return results def _parse_ipopt_output(self, stream: io.StringIO): diff --git a/pyomo/contrib/solver/results.py b/pyomo/contrib/solver/results.py index c24053e6358..2f839580a43 100644 --- a/pyomo/contrib/solver/results.py +++ b/pyomo/contrib/solver/results.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ import enum -from typing import Optional, Tuple, Dict, Any, Sequence, List +from typing import Optional, Tuple, Dict, Any, Sequence, List, Type from datetime import datetime import io @@ -21,6 +21,7 @@ NonNegativeInt, In, NonNegativeFloat, + ADVANCED_OPTION, ) from pyomo.common.errors import PyomoException from pyomo.core.base.var import _GeneralVarData @@ -224,7 +225,7 @@ def __init__( self.iteration_count: Optional[int] = self.declare( 'iteration_count', ConfigValue(domain=NonNegativeInt, default=None) ) - self.timing_info: ConfigDict = self.declare('timing_info', ConfigDict()) + self.timing_info: ConfigDict = self.declare('timing_info', ConfigDict(implicit=True)) self.timing_info.start_timestamp: datetime = self.timing_info.declare( 'start_timestamp', ConfigValue(domain=Datetime) @@ -235,20 +236,8 @@ def __init__( self.extra_info: ConfigDict = self.declare( 'extra_info', ConfigDict(implicit=True) ) - - def __str__(self): - s = '' - s += 'termination_condition: ' + str(self.termination_condition) + '\n' - s += 'solution_status: ' + str(self.solution_status) + '\n' - s += 'incumbent_objective: ' + str(self.incumbent_objective) + '\n' - s += 'objective_bound: ' + str(self.objective_bound) - return s - - def report_timing(self): - print('Timing Information: ') - print('-' * 50) - self.timing_info.display() - print('-' * 50) + self.solver_configuration: ConfigDict = self.declare('solver_configuration', ConfigDict(doc="A copy of the config object used in the solve", visibility=ADVANCED_OPTION)) + self.solver_log: str = self.declare('solver_log', ConfigValue(domain=str, default=None, visibility=ADVANCED_OPTION)) class ResultsReader: diff --git a/pyomo/contrib/solver/solution.py b/pyomo/contrib/solver/solution.py index 068677ea580..4ec3f98cd08 100644 --- a/pyomo/contrib/solver/solution.py +++ b/pyomo/contrib/solver/solution.py @@ -95,27 +95,6 @@ def get_duals( """ raise NotImplementedError(f'{type(self)} does not support the get_duals method') - def get_slacks( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: - """ - Returns a dictionary mapping constraint to slack. - - Parameters - ---------- - cons_to_load: list - A list of the constraints whose duals should be loaded. If cons_to_load is None, then the duals for all - constraints will be loaded. - - Returns - ------- - slacks: dict - Maps constraints to slacks - """ - raise NotImplementedError( - f'{type(self)} does not support the get_slacks method' - ) - def get_reduced_costs( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None ) -> Mapping[_GeneralVarData, float]: @@ -198,23 +177,6 @@ def get_duals( duals[c] = self._duals[c] return duals - def get_slacks( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: - if self._slacks is None: - raise RuntimeError( - 'Solution loader does not currently have valid slacks. Please ' - 'check the termination condition and ensure the solver returns slacks ' - 'for the given problem type.' - ) - if cons_to_load is None: - slacks = dict(self._slacks) - else: - slacks = {} - for c in cons_to_load: - slacks[c] = self._slacks[c] - return slacks - def get_reduced_costs( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None ) -> Mapping[_GeneralVarData, float]: @@ -244,25 +206,19 @@ def _assert_solution_still_valid(self): def get_primals(self, vars_to_load=None): self._assert_solution_still_valid() - return self._solver.get_primals(vars_to_load=vars_to_load) + return self._solver._get_primals(vars_to_load=vars_to_load) def get_duals( self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None ) -> Dict[_GeneralConstraintData, float]: self._assert_solution_still_valid() - return self._solver.get_duals(cons_to_load=cons_to_load) - - def get_slacks( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: - self._assert_solution_still_valid() - return self._solver.get_slacks(cons_to_load=cons_to_load) + return self._solver._get_duals(cons_to_load=cons_to_load) def get_reduced_costs( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None ) -> Mapping[_GeneralVarData, float]: self._assert_solution_still_valid() - return self._solver.get_reduced_costs(vars_to_load=vars_to_load) + return self._solver._get_reduced_costs(vars_to_load=vars_to_load) def invalidate(self): self._valid = False diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 187d3176bb7..3b94963e858 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -214,7 +214,7 @@ class NLWriter(object): CONFIG.declare( 'skip_trivial_constraints', ConfigValue( - default=False, + default=True, domain=bool, description='Skip writing constraints whose body is constant', ), From e5c46edc0dbee83b2e75569ec0105cd750fe1e0e Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 16 Jan 2024 00:00:18 -0700 Subject: [PATCH 0782/1204] solver refactor: various updates --- pyomo/contrib/solver/results.py | 90 +++++++++++++++------------------ 1 file changed, 41 insertions(+), 49 deletions(-) diff --git a/pyomo/contrib/solver/results.py b/pyomo/contrib/solver/results.py index 2f839580a43..c7edc8c2f2e 100644 --- a/pyomo/contrib/solver/results.py +++ b/pyomo/contrib/solver/results.py @@ -257,10 +257,8 @@ def __init__(self) -> None: def parse_sol_file( sol_file: io.TextIOBase, nl_info: NLWriterInfo, - suffixes_to_read: Sequence[str], result: Results, ) -> Tuple[Results, SolFileData]: - suffixes_to_read = set(suffixes_to_read) sol_data = SolFileData() # @@ -368,9 +366,8 @@ def parse_sol_file( if result.solution_status != SolutionStatus.noSolution: for v, val in zip(nl_info.variables, variable_vals): sol_data.primals[id(v)] = (v, val) - if "dual" in suffixes_to_read: - for c, val in zip(nl_info.constraints, duals): - sol_data.duals[c] = val + for c, val in zip(nl_info.constraints, duals): + sol_data.duals[c] = val ### Read suffixes ### line = sol_file.readline() while line: @@ -400,51 +397,46 @@ def parse_sol_file( # tablen = int(line[4]) tabline = int(line[5]) suffix_name = sol_file.readline().strip() - if suffix_name in suffixes_to_read: - # ignore translation of the table number to string value for now, - # this information can be obtained from the solver documentation - for n in range(tabline): - sol_file.readline() - if kind == 0: # Var - sol_data.var_suffixes[suffix_name] = dict() - for cnt in range(nvalues): - suf_line = sol_file.readline().split() - var_ndx = int(suf_line[0]) - var = nl_info.variables[var_ndx] - sol_data.var_suffixes[suffix_name][id(var)] = ( - var, - convert_function(suf_line[1]), - ) - elif kind == 1: # Con - sol_data.con_suffixes[suffix_name] = dict() - for cnt in range(nvalues): - suf_line = sol_file.readline().split() - con_ndx = int(suf_line[0]) - con = nl_info.constraints[con_ndx] - sol_data.con_suffixes[suffix_name][con] = convert_function( - suf_line[1] - ) - elif kind == 2: # Obj - sol_data.obj_suffixes[suffix_name] = dict() - for cnt in range(nvalues): - suf_line = sol_file.readline().split() - obj_ndx = int(suf_line[0]) - obj = nl_info.objectives[obj_ndx] - sol_data.obj_suffixes[suffix_name][id(obj)] = ( - obj, - convert_function(suf_line[1]), - ) - elif kind == 3: # Prob - sol_data.problem_suffixes[suffix_name] = list() - for cnt in range(nvalues): - suf_line = sol_file.readline().split() - sol_data.problem_suffixes[suffix_name].append( - convert_function(suf_line[1]) - ) - else: - # do not store the suffix in the solution object + # ignore translation of the table number to string value for now, + # this information can be obtained from the solver documentation + for n in range(tabline): + sol_file.readline() + if kind == 0: # Var + sol_data.var_suffixes[suffix_name] = dict() for cnt in range(nvalues): - sol_file.readline() + suf_line = sol_file.readline().split() + var_ndx = int(suf_line[0]) + var = nl_info.variables[var_ndx] + sol_data.var_suffixes[suffix_name][id(var)] = ( + var, + convert_function(suf_line[1]), + ) + elif kind == 1: # Con + sol_data.con_suffixes[suffix_name] = dict() + for cnt in range(nvalues): + suf_line = sol_file.readline().split() + con_ndx = int(suf_line[0]) + con = nl_info.constraints[con_ndx] + sol_data.con_suffixes[suffix_name][con] = convert_function( + suf_line[1] + ) + elif kind == 2: # Obj + sol_data.obj_suffixes[suffix_name] = dict() + for cnt in range(nvalues): + suf_line = sol_file.readline().split() + obj_ndx = int(suf_line[0]) + obj = nl_info.objectives[obj_ndx] + sol_data.obj_suffixes[suffix_name][id(obj)] = ( + obj, + convert_function(suf_line[1]), + ) + elif kind == 3: # Prob + sol_data.problem_suffixes[suffix_name] = list() + for cnt in range(nvalues): + suf_line = sol_file.readline().split() + sol_data.problem_suffixes[suffix_name].append( + convert_function(suf_line[1]) + ) line = sol_file.readline() return result, sol_data From dac6bef9459e38be840428005b59ab0947ffaaa2 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 16 Jan 2024 00:14:27 -0700 Subject: [PATCH 0783/1204] solver refactor: use caching in available and version --- pyomo/contrib/solver/ipopt.py | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 475ce6e6f0b..878fe7cb264 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -190,24 +190,31 @@ class ipopt(SolverBase): def __init__(self, **kwds): super().__init__(**kwds) self._writer = NLWriter() + self._available_cache = None + self._version_cache = None def available(self): - if self.config.executable.path() is None: - return self.Availability.NotFound - return self.Availability.FullLicense + if self._available_cache is None: + if self.config.executable.path() is None: + self._available_cache = self.Availability.NotFound + else: + self._available_cache = self.Availability.FullLicense + return self._available_cache def version(self): - results = subprocess.run( - [str(self.config.executable), '--version'], - timeout=1, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True, - ) - version = results.stdout.splitlines()[0] - version = version.split(' ')[1].strip() - version = tuple(int(i) for i in version.split('.')) - return version + if self._version_cache is None: + results = subprocess.run( + [str(self.config.executable), '--version'], + timeout=1, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + ) + version = results.stdout.splitlines()[0] + version = version.split(' ')[1].strip() + version = tuple(int(i) for i in version.split('.')) + self._version_cache = version + return self._version_cache @property def symbol_map(self): From 4df0a8dd7f6518e12f24920d60b5e1fc10114d3f Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 16 Jan 2024 00:26:32 -0700 Subject: [PATCH 0784/1204] solver refactor: config updates --- pyomo/contrib/solver/base.py | 7 - pyomo/contrib/solver/config.py | 272 +++++++++++++++++++-------------- 2 files changed, 156 insertions(+), 123 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 961187179f2..216bf28ac4a 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -258,13 +258,6 @@ def _get_reduced_costs( '{0} does not support the get_reduced_costs method'.format(type(self)) ) - @property - @abc.abstractmethod - def update_config(self) -> UpdateConfig: - """ - Updates the solver config - """ - @abc.abstractmethod def set_instance(self, model): """ diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index 738338d3718..0a2478d44ff 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -21,122 +21,7 @@ from pyomo.common.timing import HierarchicalTimer -class SolverConfig(ConfigDict): - """ - Base config values for all solver interfaces - """ - - def __init__( - self, - description=None, - doc=None, - implicit=False, - implicit_domain=None, - visibility=0, - ): - super().__init__( - description=description, - doc=doc, - implicit=implicit, - implicit_domain=implicit_domain, - visibility=visibility, - ) - - self.tee: bool = self.declare( - 'tee', - ConfigValue( - domain=bool, - default=False, - description="If True, the solver log prints to stdout.", - ), - ) - self.load_solution: bool = self.declare( - 'load_solution', - ConfigValue( - domain=bool, - default=True, - description="If True, the values of the primal variables will be loaded into the model.", - ), - ) - self.raise_exception_on_nonoptimal_result: bool = self.declare( - 'raise_exception_on_nonoptimal_result', - ConfigValue( - domain=bool, - default=True, - description="If False, the `solve` method will continue processing even if the returned result is nonoptimal.", - ), - ) - self.symbolic_solver_labels: bool = self.declare( - 'symbolic_solver_labels', - ConfigValue( - domain=bool, - default=False, - description="If True, the names given to the solver will reflect the names of the Pyomo components. Cannot be changed after set_instance is called.", - ), - ) - self.timer: HierarchicalTimer = self.declare( - 'timer', - ConfigValue( - default=None, - description="A HierarchicalTimer.", - ), - ) - self.threads: Optional[int] = self.declare( - 'threads', - ConfigValue( - domain=NonNegativeInt, - description="Number of threads to be used by a solver.", - default=None, - ), - ) - self.time_limit: Optional[float] = self.declare( - 'time_limit', - ConfigValue( - domain=NonNegativeFloat, description="Time limit applied to the solver." - ), - ) - self.solver_options: ConfigDict = self.declare( - 'solver_options', - ConfigDict(implicit=True, description="Options to pass to the solver."), - ) - - -class BranchAndBoundConfig(SolverConfig): - """ - Attributes - ---------- - mip_gap: float - Solver will terminate if the mip gap is less than mip_gap - relax_integrality: bool - If True, all integer variables will be relaxed to continuous - variables before solving - """ - - def __init__( - self, - description=None, - doc=None, - implicit=False, - implicit_domain=None, - visibility=0, - ): - super().__init__( - description=description, - doc=doc, - implicit=implicit, - implicit_domain=implicit_domain, - visibility=visibility, - ) - - self.rel_gap: Optional[float] = self.declare( - 'rel_gap', ConfigValue(domain=NonNegativeFloat) - ) - self.abs_gap: Optional[float] = self.declare( - 'abs_gap', ConfigValue(domain=NonNegativeFloat) - ) - - -class UpdateConfig(ConfigDict): +class AutoUpdateConfig(ConfigDict): """ This is necessary for persistent solvers. @@ -294,3 +179,158 @@ def __init__( updating the values of fixed variables is much faster this way.""", ), ) + + +class SolverConfig(ConfigDict): + """ + Base config values for all solver interfaces + """ + + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + super().__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, + ) + + self.tee: bool = self.declare( + 'tee', + ConfigValue( + domain=bool, + default=False, + description="If True, the solver log prints to stdout.", + ), + ) + self.load_solution: bool = self.declare( + 'load_solution', + ConfigValue( + domain=bool, + default=True, + description="If True, the values of the primal variables will be loaded into the model.", + ), + ) + self.raise_exception_on_nonoptimal_result: bool = self.declare( + 'raise_exception_on_nonoptimal_result', + ConfigValue( + domain=bool, + default=True, + description="If False, the `solve` method will continue processing even if the returned result is nonoptimal.", + ), + ) + self.symbolic_solver_labels: bool = self.declare( + 'symbolic_solver_labels', + ConfigValue( + domain=bool, + default=False, + description="If True, the names given to the solver will reflect the names of the Pyomo components. Cannot be changed after set_instance is called.", + ), + ) + self.timer: HierarchicalTimer = self.declare( + 'timer', + ConfigValue( + default=None, + description="A HierarchicalTimer.", + ), + ) + self.threads: Optional[int] = self.declare( + 'threads', + ConfigValue( + domain=NonNegativeInt, + description="Number of threads to be used by a solver.", + default=None, + ), + ) + self.time_limit: Optional[float] = self.declare( + 'time_limit', + ConfigValue( + domain=NonNegativeFloat, description="Time limit applied to the solver." + ), + ) + self.solver_options: ConfigDict = self.declare( + 'solver_options', + ConfigDict(implicit=True, description="Options to pass to the solver."), + ) + + +class BranchAndBoundConfig(SolverConfig): + """ + Attributes + ---------- + mip_gap: float + Solver will terminate if the mip gap is less than mip_gap + relax_integrality: bool + If True, all integer variables will be relaxed to continuous + variables before solving + """ + + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + super().__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, + ) + + self.rel_gap: Optional[float] = self.declare( + 'rel_gap', ConfigValue(domain=NonNegativeFloat) + ) + self.abs_gap: Optional[float] = self.declare( + 'abs_gap', ConfigValue(domain=NonNegativeFloat) + ) + + +class PersistentSolverConfig(SolverConfig): + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + super().__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, + ) + + self.auto_updats: AutoUpdateConfig = self.declare('auto_updates', AutoUpdateConfig()) + + +class PersistentBranchAndBoundConfig(BranchAndBoundConfig): + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + super().__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, + ) + + self.auto_updats: AutoUpdateConfig = self.declare('auto_updates', AutoUpdateConfig()) From b94477913d92c5c346906bf0ca297862ac40baae Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 16 Jan 2024 00:27:33 -0700 Subject: [PATCH 0785/1204] solver refactor: typo --- pyomo/contrib/solver/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index 0a2478d44ff..8fe627cbcc1 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -313,7 +313,7 @@ def __init__( visibility=visibility, ) - self.auto_updats: AutoUpdateConfig = self.declare('auto_updates', AutoUpdateConfig()) + self.auto_updates: AutoUpdateConfig = self.declare('auto_updates', AutoUpdateConfig()) class PersistentBranchAndBoundConfig(BranchAndBoundConfig): @@ -333,4 +333,4 @@ def __init__( visibility=visibility, ) - self.auto_updats: AutoUpdateConfig = self.declare('auto_updates', AutoUpdateConfig()) + self.auto_updates: AutoUpdateConfig = self.declare('auto_updates', AutoUpdateConfig()) From fe41e220167ac95381bd5ef40852d1180cdb466f Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 16 Jan 2024 00:44:33 -0700 Subject: [PATCH 0786/1204] solver refactor: various fixes --- pyomo/contrib/solver/base.py | 1 - pyomo/contrib/solver/ipopt.py | 5 +++-- pyomo/contrib/solver/results.py | 2 +- pyomo/contrib/solver/util.py | 26 ++++++++------------------ 4 files changed, 12 insertions(+), 22 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 216bf28ac4a..56292859b1a 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -28,7 +28,6 @@ from pyomo.core.kernel.objective import minimize from pyomo.core.base import SymbolMap from pyomo.core.staleflag import StaleFlagManager -from pyomo.contrib.solver.config import UpdateConfig from pyomo.contrib.solver.util import get_objective from pyomo.contrib.solver.results import ( Results, diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 878fe7cb264..5176291ab42 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -421,6 +421,9 @@ def solve(self, model, **kwds): remove_named_expressions=True, ) + results.solver_configuration = config + results.solver_log = ostreams[0].getvalue() + # Capture/record end-time / wall-time end_timestamp = datetime.datetime.now(datetime.timezone.utc) results.timing_info.start_timestamp = start_timestamp @@ -462,11 +465,9 @@ def _parse_ipopt_output(self, stream: io.StringIO): def _parse_solution( self, instream: io.TextIOBase, nl_info: NLWriterInfo, result: ipoptResults ): - suffixes_to_read = ['dual', 'ipopt_zL_out', 'ipopt_zU_out'] res, sol_data = parse_sol_file( sol_file=instream, nl_info=nl_info, - suffixes_to_read=suffixes_to_read, result=result, ) diff --git a/pyomo/contrib/solver/results.py b/pyomo/contrib/solver/results.py index c7edc8c2f2e..e63aa351f64 100644 --- a/pyomo/contrib/solver/results.py +++ b/pyomo/contrib/solver/results.py @@ -236,7 +236,7 @@ def __init__( self.extra_info: ConfigDict = self.declare( 'extra_info', ConfigDict(implicit=True) ) - self.solver_configuration: ConfigDict = self.declare('solver_configuration', ConfigDict(doc="A copy of the config object used in the solve", visibility=ADVANCED_OPTION)) + self.solver_configuration: ConfigDict = self.declare('solver_configuration', ConfigValue(doc="A copy of the config object used in the solve", visibility=ADVANCED_OPTION)) self.solver_log: str = self.declare('solver_log', ConfigValue(domain=str, default=None, visibility=ADVANCED_OPTION)) diff --git a/pyomo/contrib/solver/util.py b/pyomo/contrib/solver/util.py index 9f0c607a0db..727d9c354e2 100644 --- a/pyomo/contrib/solver/util.py +++ b/pyomo/contrib/solver/util.py @@ -22,7 +22,6 @@ from pyomo.common.collections import ComponentMap from pyomo.common.timing import HierarchicalTimer from pyomo.core.expr.numvalue import NumericConstant -from pyomo.contrib.solver.config import UpdateConfig from pyomo.contrib.solver.results import TerminationCondition, SolutionStatus @@ -154,7 +153,6 @@ def __init__(self, only_child_vars=False): ) # maps constraint to list of tuples (named_expr, named_expr.expr) self._external_functions = ComponentMap() self._obj_named_expressions = [] - self._update_config = UpdateConfig() self._referenced_variables = ( {} ) # var_id: [dict[constraints, None], dict[sos constraints, None], None or objective] @@ -163,18 +161,10 @@ def __init__(self, only_child_vars=False): self._expr_types = None self._only_child_vars = only_child_vars - @property - def update_config(self): - return self._update_config - - @update_config.setter - def update_config(self, val: UpdateConfig): - self._update_config = val - def set_instance(self, model): - saved_update_config = self.update_config + saved_config = self.config self.__init__(only_child_vars=self._only_child_vars) - self.update_config = saved_update_config + self.config = saved_config self._model = model self.add_block(model) if self._objective is None: @@ -249,7 +239,7 @@ def add_constraints(self, cons: List[_GeneralConstraintData]): self._vars_referenced_by_con[con] = variables for v in variables: self._referenced_variables[id(v)][0][con] = None - if not self.update_config.treat_fixed_vars_as_params: + if not self.config.auto_updates.treat_fixed_vars_as_params: for v in fixed_vars: v.unfix() all_fixed_vars[id(v)] = v @@ -302,7 +292,7 @@ def set_objective(self, obj: _GeneralObjectiveData): self._vars_referenced_by_obj = variables for v in variables: self._referenced_variables[id(v)][2] = obj - if not self.update_config.treat_fixed_vars_as_params: + if not self.config.auto_updates.treat_fixed_vars_as_params: for v in fixed_vars: v.unfix() self._set_objective(obj) @@ -483,7 +473,7 @@ def update_params(self): def update(self, timer: HierarchicalTimer = None): if timer is None: timer = HierarchicalTimer() - config = self.update_config + config = self.config.auto_updates new_vars = [] old_vars = [] new_params = [] @@ -634,7 +624,7 @@ def update(self, timer: HierarchicalTimer = None): _v, lb, ub, fixed, domain_interval, value = self._vars[id(v)] if (fixed != v.fixed) or (fixed and (value != v.value)): vars_to_update.append(v) - if self.update_config.treat_fixed_vars_as_params: + if self.config.auto_updates.treat_fixed_vars_as_params: for c in self._referenced_variables[id(v)][0]: cons_to_remove_and_add[c] = None if self._referenced_variables[id(v)][2] is not None: @@ -670,13 +660,13 @@ def update(self, timer: HierarchicalTimer = None): break timer.stop('named expressions') timer.start('objective') - if self.update_config.check_for_new_objective: + if self.config.auto_updates.check_for_new_objective: pyomo_obj = get_objective(self._model) if pyomo_obj is not self._objective: need_to_set_objective = True else: pyomo_obj = self._objective - if self.update_config.update_objective: + if self.config.auto_updates.update_objective: if pyomo_obj is not None and pyomo_obj.expr is not self._objective_expr: need_to_set_objective = True elif pyomo_obj is not None and pyomo_obj.sense is not self._objective_sense: From 3d32f4a1aa098e06d3193a3258164101599fffe0 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 16 Jan 2024 01:45:57 -0700 Subject: [PATCH 0787/1204] move sol reader to separate file --- pyomo/contrib/solver/ipopt.py | 2 +- pyomo/contrib/solver/results.py | 205 +--------------------------- pyomo/contrib/solver/sol_reader.py | 206 +++++++++++++++++++++++++++++ 3 files changed, 208 insertions(+), 205 deletions(-) create mode 100644 pyomo/contrib/solver/sol_reader.py diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 5176291ab42..c7a932eb883 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -32,8 +32,8 @@ Results, TerminationCondition, SolutionStatus, - parse_sol_file, ) +from .sol_reader import parse_sol_file from pyomo.contrib.solver.solution import SolutionLoaderBase, SolutionLoader from pyomo.common.tee import TeeStream from pyomo.common.log import LogStream diff --git a/pyomo/contrib/solver/results.py b/pyomo/contrib/solver/results.py index e63aa351f64..3beb3aede81 100644 --- a/pyomo/contrib/solver/results.py +++ b/pyomo/contrib/solver/results.py @@ -10,9 +10,8 @@ # ___________________________________________________________________________ import enum -from typing import Optional, Tuple, Dict, Any, Sequence, List, Type +from typing import Optional, Tuple from datetime import datetime -import io from pyomo.common.config import ( ConfigDict, @@ -24,16 +23,12 @@ ADVANCED_OPTION, ) from pyomo.common.errors import PyomoException -from pyomo.core.base.var import _GeneralVarData -from pyomo.core.base.constraint import _ConstraintData -from pyomo.core.base.objective import _ObjectiveData from pyomo.opt.results.solution import SolutionStatus as LegacySolutionStatus from pyomo.opt.results.solver import ( TerminationCondition as LegacyTerminationCondition, SolverStatus as LegacySolverStatus, ) from pyomo.contrib.solver.solution import SolutionLoaderBase -from pyomo.repn.plugins.nl_writer import NLWriterInfo class SolverResultsError(PyomoException): @@ -244,204 +239,6 @@ class ResultsReader: pass -class SolFileData: - def __init__(self) -> None: - self.primals: Dict[int, Tuple[_GeneralVarData, float]] = dict() - self.duals: Dict[_ConstraintData, float] = dict() - self.var_suffixes: Dict[str, Dict[int, Tuple[_GeneralVarData, Any]]] = dict() - self.con_suffixes: Dict[str, Dict[_ConstraintData, Any]] = dict() - self.obj_suffixes: Dict[str, Dict[int, Tuple[_ObjectiveData, Any]]] = dict() - self.problem_suffixes: Dict[str, List[Any]] = dict() - - -def parse_sol_file( - sol_file: io.TextIOBase, - nl_info: NLWriterInfo, - result: Results, -) -> Tuple[Results, SolFileData]: - sol_data = SolFileData() - - # - # Some solvers (minto) do not write a message. We will assume - # all non-blank lines up the 'Options' line is the message. - # For backwards compatibility and general safety, we will parse all - # lines until "Options" appears. Anything before "Options" we will - # consider to be the solver message. - message = [] - for line in sol_file: - if not line: - break - line = line.strip() - if "Options" in line: - break - message.append(line) - message = '\n'.join(message) - # Once "Options" appears, we must now read the content under it. - model_objects = [] - if "Options" in line: - line = sol_file.readline() - number_of_options = int(line) - need_tolerance = False - if ( - number_of_options > 4 - ): # MRM: Entirely unclear why this is necessary, or if it even is - number_of_options -= 2 - need_tolerance = True - for i in range(number_of_options + 4): - line = sol_file.readline() - model_objects.append(int(line)) - if ( - need_tolerance - ): # MRM: Entirely unclear why this is necessary, or if it even is - line = sol_file.readline() - model_objects.append(float(line)) - else: - raise SolverResultsError("ERROR READING `sol` FILE. No 'Options' line found.") - # Identify the total number of variables and constraints - number_of_cons = model_objects[number_of_options + 1] - number_of_vars = model_objects[number_of_options + 3] - assert number_of_cons == len(nl_info.constraints) - assert number_of_vars == len(nl_info.variables) - - duals = [float(sol_file.readline()) for i in range(number_of_cons)] - variable_vals = [float(sol_file.readline()) for i in range(number_of_vars)] - - # Parse the exit code line and capture it - exit_code = [0, 0] - line = sol_file.readline() - if line and ('objno' in line): - exit_code_line = line.split() - if len(exit_code_line) != 3: - raise SolverResultsError( - f"ERROR READING `sol` FILE. Expected two numbers in `objno` line; received {line}." - ) - exit_code = [int(exit_code_line[1]), int(exit_code_line[2])] - else: - raise SolverResultsError( - f"ERROR READING `sol` FILE. Expected `objno`; received {line}." - ) - result.extra_info.solver_message = message.strip().replace('\n', '; ') - exit_code_message = '' - if (exit_code[1] >= 0) and (exit_code[1] <= 99): - result.solution_status = SolutionStatus.optimal - result.termination_condition = TerminationCondition.convergenceCriteriaSatisfied - elif (exit_code[1] >= 100) and (exit_code[1] <= 199): - exit_code_message = "Optimal solution indicated, but ERROR LIKELY!" - result.solution_status = SolutionStatus.feasible - result.termination_condition = TerminationCondition.error - elif (exit_code[1] >= 200) and (exit_code[1] <= 299): - exit_code_message = "INFEASIBLE SOLUTION: constraints cannot be satisfied!" - result.solution_status = SolutionStatus.infeasible - # TODO: this is solver dependent - # But this was the way in the previous version - and has been fine thus far? - result.termination_condition = TerminationCondition.locallyInfeasible - elif (exit_code[1] >= 300) and (exit_code[1] <= 399): - exit_code_message = ( - "UNBOUNDED PROBLEM: the objective can be improved without limit!" - ) - result.solution_status = SolutionStatus.noSolution - result.termination_condition = TerminationCondition.unbounded - elif (exit_code[1] >= 400) and (exit_code[1] <= 499): - exit_code_message = ( - "EXCEEDED MAXIMUM NUMBER OF ITERATIONS: the solver " - "was stopped by a limit that you set!" - ) - # TODO: this is solver dependent - # But this was the way in the previous version - and has been fine thus far? - result.solution_status = SolutionStatus.infeasible - result.termination_condition = TerminationCondition.iterationLimit - elif (exit_code[1] >= 500) and (exit_code[1] <= 599): - exit_code_message = ( - "FAILURE: the solver stopped by an error condition " - "in the solver routines!" - ) - result.termination_condition = TerminationCondition.error - - if result.extra_info.solver_message: - if exit_code_message: - result.extra_info.solver_message += '; ' + exit_code_message - else: - result.extra_info.solver_message = exit_code_message - - if result.solution_status != SolutionStatus.noSolution: - for v, val in zip(nl_info.variables, variable_vals): - sol_data.primals[id(v)] = (v, val) - for c, val in zip(nl_info.constraints, duals): - sol_data.duals[c] = val - ### Read suffixes ### - line = sol_file.readline() - while line: - line = line.strip() - if line == "": - continue - line = line.split() - # Some sort of garbage we tag onto the solver message, assuming we are past the suffixes - if line[0] != 'suffix': - # We assume this is the start of a - # section like kestrel_option, which - # comes after all suffixes. - remaining = "" - line = sol_file.readline() - while line: - remaining += line.strip() + "; " - line = sol_file.readline() - result.extra_info.solver_message += remaining - break - unmasked_kind = int(line[1]) - kind = unmasked_kind & 3 # 0-var, 1-con, 2-obj, 3-prob - convert_function = int - if (unmasked_kind & 4) == 4: - convert_function = float - nvalues = int(line[2]) - # namelen = int(line[3]) - # tablen = int(line[4]) - tabline = int(line[5]) - suffix_name = sol_file.readline().strip() - # ignore translation of the table number to string value for now, - # this information can be obtained from the solver documentation - for n in range(tabline): - sol_file.readline() - if kind == 0: # Var - sol_data.var_suffixes[suffix_name] = dict() - for cnt in range(nvalues): - suf_line = sol_file.readline().split() - var_ndx = int(suf_line[0]) - var = nl_info.variables[var_ndx] - sol_data.var_suffixes[suffix_name][id(var)] = ( - var, - convert_function(suf_line[1]), - ) - elif kind == 1: # Con - sol_data.con_suffixes[suffix_name] = dict() - for cnt in range(nvalues): - suf_line = sol_file.readline().split() - con_ndx = int(suf_line[0]) - con = nl_info.constraints[con_ndx] - sol_data.con_suffixes[suffix_name][con] = convert_function( - suf_line[1] - ) - elif kind == 2: # Obj - sol_data.obj_suffixes[suffix_name] = dict() - for cnt in range(nvalues): - suf_line = sol_file.readline().split() - obj_ndx = int(suf_line[0]) - obj = nl_info.objectives[obj_ndx] - sol_data.obj_suffixes[suffix_name][id(obj)] = ( - obj, - convert_function(suf_line[1]), - ) - elif kind == 3: # Prob - sol_data.problem_suffixes[suffix_name] = list() - for cnt in range(nvalues): - suf_line = sol_file.readline().split() - sol_data.problem_suffixes[suffix_name].append( - convert_function(suf_line[1]) - ) - line = sol_file.readline() - - return result, sol_data - - def parse_yaml(): pass diff --git a/pyomo/contrib/solver/sol_reader.py b/pyomo/contrib/solver/sol_reader.py new file mode 100644 index 00000000000..f1fc7998179 --- /dev/null +++ b/pyomo/contrib/solver/sol_reader.py @@ -0,0 +1,206 @@ +from typing import Tuple, Dict, Any, List +import io + +from pyomo.core.base.var import _GeneralVarData +from pyomo.core.base.constraint import _ConstraintData +from pyomo.core.base.objective import _ObjectiveData +from pyomo.repn.plugins.nl_writer import NLWriterInfo +from .results import Results, SolverResultsError, SolutionStatus, TerminationCondition + + +class SolFileData: + def __init__(self) -> None: + self.primals: Dict[int, Tuple[_GeneralVarData, float]] = dict() + self.duals: Dict[_ConstraintData, float] = dict() + self.var_suffixes: Dict[str, Dict[int, Tuple[_GeneralVarData, Any]]] = dict() + self.con_suffixes: Dict[str, Dict[_ConstraintData, Any]] = dict() + self.obj_suffixes: Dict[str, Dict[int, Tuple[_ObjectiveData, Any]]] = dict() + self.problem_suffixes: Dict[str, List[Any]] = dict() + + +def parse_sol_file( + sol_file: io.TextIOBase, + nl_info: NLWriterInfo, + result: Results, +) -> Tuple[Results, SolFileData]: + sol_data = SolFileData() + + # + # Some solvers (minto) do not write a message. We will assume + # all non-blank lines up the 'Options' line is the message. + # For backwards compatibility and general safety, we will parse all + # lines until "Options" appears. Anything before "Options" we will + # consider to be the solver message. + message = [] + for line in sol_file: + if not line: + break + line = line.strip() + if "Options" in line: + break + message.append(line) + message = '\n'.join(message) + # Once "Options" appears, we must now read the content under it. + model_objects = [] + if "Options" in line: + line = sol_file.readline() + number_of_options = int(line) + need_tolerance = False + if ( + number_of_options > 4 + ): # MRM: Entirely unclear why this is necessary, or if it even is + number_of_options -= 2 + need_tolerance = True + for i in range(number_of_options + 4): + line = sol_file.readline() + model_objects.append(int(line)) + if ( + need_tolerance + ): # MRM: Entirely unclear why this is necessary, or if it even is + line = sol_file.readline() + model_objects.append(float(line)) + else: + raise SolverResultsError("ERROR READING `sol` FILE. No 'Options' line found.") + # Identify the total number of variables and constraints + number_of_cons = model_objects[number_of_options + 1] + number_of_vars = model_objects[number_of_options + 3] + assert number_of_cons == len(nl_info.constraints) + assert number_of_vars == len(nl_info.variables) + + duals = [float(sol_file.readline()) for i in range(number_of_cons)] + variable_vals = [float(sol_file.readline()) for i in range(number_of_vars)] + + # Parse the exit code line and capture it + exit_code = [0, 0] + line = sol_file.readline() + if line and ('objno' in line): + exit_code_line = line.split() + if len(exit_code_line) != 3: + raise SolverResultsError( + f"ERROR READING `sol` FILE. Expected two numbers in `objno` line; received {line}." + ) + exit_code = [int(exit_code_line[1]), int(exit_code_line[2])] + else: + raise SolverResultsError( + f"ERROR READING `sol` FILE. Expected `objno`; received {line}." + ) + result.extra_info.solver_message = message.strip().replace('\n', '; ') + exit_code_message = '' + if (exit_code[1] >= 0) and (exit_code[1] <= 99): + result.solution_status = SolutionStatus.optimal + result.termination_condition = TerminationCondition.convergenceCriteriaSatisfied + elif (exit_code[1] >= 100) and (exit_code[1] <= 199): + exit_code_message = "Optimal solution indicated, but ERROR LIKELY!" + result.solution_status = SolutionStatus.feasible + result.termination_condition = TerminationCondition.error + elif (exit_code[1] >= 200) and (exit_code[1] <= 299): + exit_code_message = "INFEASIBLE SOLUTION: constraints cannot be satisfied!" + result.solution_status = SolutionStatus.infeasible + # TODO: this is solver dependent + # But this was the way in the previous version - and has been fine thus far? + result.termination_condition = TerminationCondition.locallyInfeasible + elif (exit_code[1] >= 300) and (exit_code[1] <= 399): + exit_code_message = ( + "UNBOUNDED PROBLEM: the objective can be improved without limit!" + ) + result.solution_status = SolutionStatus.noSolution + result.termination_condition = TerminationCondition.unbounded + elif (exit_code[1] >= 400) and (exit_code[1] <= 499): + exit_code_message = ( + "EXCEEDED MAXIMUM NUMBER OF ITERATIONS: the solver " + "was stopped by a limit that you set!" + ) + # TODO: this is solver dependent + # But this was the way in the previous version - and has been fine thus far? + result.solution_status = SolutionStatus.infeasible + result.termination_condition = TerminationCondition.iterationLimit + elif (exit_code[1] >= 500) and (exit_code[1] <= 599): + exit_code_message = ( + "FAILURE: the solver stopped by an error condition " + "in the solver routines!" + ) + result.termination_condition = TerminationCondition.error + + if result.extra_info.solver_message: + if exit_code_message: + result.extra_info.solver_message += '; ' + exit_code_message + else: + result.extra_info.solver_message = exit_code_message + + if result.solution_status != SolutionStatus.noSolution: + for v, val in zip(nl_info.variables, variable_vals): + sol_data.primals[id(v)] = (v, val) + for c, val in zip(nl_info.constraints, duals): + sol_data.duals[c] = val + ### Read suffixes ### + line = sol_file.readline() + while line: + line = line.strip() + if line == "": + continue + line = line.split() + # Some sort of garbage we tag onto the solver message, assuming we are past the suffixes + if line[0] != 'suffix': + # We assume this is the start of a + # section like kestrel_option, which + # comes after all suffixes. + remaining = "" + line = sol_file.readline() + while line: + remaining += line.strip() + "; " + line = sol_file.readline() + result.extra_info.solver_message += remaining + break + unmasked_kind = int(line[1]) + kind = unmasked_kind & 3 # 0-var, 1-con, 2-obj, 3-prob + convert_function = int + if (unmasked_kind & 4) == 4: + convert_function = float + nvalues = int(line[2]) + # namelen = int(line[3]) + # tablen = int(line[4]) + tabline = int(line[5]) + suffix_name = sol_file.readline().strip() + # ignore translation of the table number to string value for now, + # this information can be obtained from the solver documentation + for n in range(tabline): + sol_file.readline() + if kind == 0: # Var + sol_data.var_suffixes[suffix_name] = dict() + for cnt in range(nvalues): + suf_line = sol_file.readline().split() + var_ndx = int(suf_line[0]) + var = nl_info.variables[var_ndx] + sol_data.var_suffixes[suffix_name][id(var)] = ( + var, + convert_function(suf_line[1]), + ) + elif kind == 1: # Con + sol_data.con_suffixes[suffix_name] = dict() + for cnt in range(nvalues): + suf_line = sol_file.readline().split() + con_ndx = int(suf_line[0]) + con = nl_info.constraints[con_ndx] + sol_data.con_suffixes[suffix_name][con] = convert_function( + suf_line[1] + ) + elif kind == 2: # Obj + sol_data.obj_suffixes[suffix_name] = dict() + for cnt in range(nvalues): + suf_line = sol_file.readline().split() + obj_ndx = int(suf_line[0]) + obj = nl_info.objectives[obj_ndx] + sol_data.obj_suffixes[suffix_name][id(obj)] = ( + obj, + convert_function(suf_line[1]), + ) + elif kind == 3: # Prob + sol_data.problem_suffixes[suffix_name] = list() + for cnt in range(nvalues): + suf_line = sol_file.readline().split() + sol_data.problem_suffixes[suffix_name].append( + convert_function(suf_line[1]) + ) + line = sol_file.readline() + + return result, sol_data From 6c5d83c5ff23c175229e511088d2fe8b569daa48 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 16 Jan 2024 01:59:52 -0700 Subject: [PATCH 0788/1204] remove symbol map when it is not necessary --- pyomo/contrib/solver/base.py | 6 ++---- pyomo/contrib/solver/ipopt.py | 13 +------------ 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 56292859b1a..962d35582a1 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -27,6 +27,7 @@ from pyomo.opt.results.solution import Solution as LegacySolution from pyomo.core.kernel.objective import minimize from pyomo.core.base import SymbolMap +from pyomo.core.base.label import NumericLabeler from pyomo.core.staleflag import StaleFlagManager from pyomo.contrib.solver.util import get_objective from pyomo.contrib.solver.results import ( @@ -425,10 +426,7 @@ def solve( legacy_soln.gap = None symbol_map = SymbolMap() - symbol_map.byObject = dict(symbol_map.byObject) - symbol_map.bySymbol = dict(symbol_map.bySymbol) - symbol_map.aliases = dict(symbol_map.aliases) - symbol_map.default_labeler = symbol_map.default_labeler + symbol_map.default_labeler = NumericLabeler('x') model.solutions.add_symbol_map(symbol_map) legacy_results._smap_id = id(symbol_map) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index c7a932eb883..5e84fd8796c 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -22,10 +22,9 @@ from pyomo.common.tempfiles import TempfileManager from pyomo.common.timing import HierarchicalTimer from pyomo.core.base import Objective -from pyomo.core.base.label import NumericLabeler from pyomo.core.staleflag import StaleFlagManager from pyomo.repn.plugins.nl_writer import NLWriter, NLWriterInfo, AMPLRepn -from pyomo.contrib.solver.base import SolverBase, SymbolMap +from pyomo.contrib.solver.base import SolverBase from pyomo.contrib.solver.config import SolverConfig from pyomo.contrib.solver.factory import SolverFactory from pyomo.contrib.solver.results import ( @@ -216,10 +215,6 @@ def version(self): self._version_cache = version return self._version_cache - @property - def symbol_map(self): - return self._symbol_map - def _write_options_file(self, filename: str, options: Mapping): # First we need to determine if we even need to create a file. # If options is empty, then we return False @@ -305,12 +300,6 @@ def solve(self, model, **kwds): if env.get('AMPLFUNC'): nl_info.external_function_libraries.append(env.get('AMPLFUNC')) env['AMPLFUNC'] = "\n".join(nl_info.external_function_libraries) - symbol_map = self._symbol_map = SymbolMap() - labeler = NumericLabeler('component') - for v in nl_info.variables: - symbol_map.getSymbol(v, labeler) - for c in nl_info.constraints: - symbol_map.getSymbol(c, labeler) # Write the opt_file, if there should be one; return a bool to say # whether or not we have one (so we can correctly build the command line) opt_file = self._write_options_file( From c800776eebfdebbe1988be3d9dec15bf9763103c Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 16 Jan 2024 02:12:06 -0700 Subject: [PATCH 0789/1204] reorg --- pyomo/contrib/solver/ipopt.py | 25 +------------------------ pyomo/contrib/solver/sol_reader.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 5e84fd8796c..23464e40cb2 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -392,11 +392,7 @@ def solve(self, model, **kwds): if results.solution_status in { SolutionStatus.feasible, SolutionStatus.optimal, - } and len( - list( - model.component_data_objects(Objective, descend_into=True, active=True) - ) - ): + } and len(nl_info.objectives) > 0: if config.load_solution: results.incumbent_objective = value(nl_info.objectives[0]) else: @@ -476,14 +472,6 @@ def _parse_solution( if abs(zu) > abs(rc[v_id][1]): rc[v_id] = (v, zu) - if len(nl_info.eliminated_vars) > 0: - sub_map = {k: v[1] for k, v in sol_data.primals.items()} - for v, v_expr in nl_info.eliminated_vars: - val = evaluate_ampl_repn(v_expr, sub_map) - v_id = id(v) - sub_map[v_id] = val - sol_data.primals[v_id] = (v, val) - res.solution_loader = SolutionLoader( primals=sol_data.primals, duals=sol_data.duals, @@ -492,14 +480,3 @@ def _parse_solution( ) return res - - -def evaluate_ampl_repn(repn: AMPLRepn, sub_map): - assert not repn.nonlinear - assert repn.nl is None - val = repn.const - if repn.linear is not None: - for v_id, v_coef in repn.linear.items(): - val += v_coef * sub_map[v_id] - val *= repn.mult - return val diff --git a/pyomo/contrib/solver/sol_reader.py b/pyomo/contrib/solver/sol_reader.py index f1fc7998179..93fb6d39da3 100644 --- a/pyomo/contrib/solver/sol_reader.py +++ b/pyomo/contrib/solver/sol_reader.py @@ -6,6 +6,18 @@ from pyomo.core.base.objective import _ObjectiveData from pyomo.repn.plugins.nl_writer import NLWriterInfo from .results import Results, SolverResultsError, SolutionStatus, TerminationCondition +from pyomo.repn.plugins.nl_writer import AMPLRepn + + +def evaluate_ampl_repn(repn: AMPLRepn, sub_map): + assert not repn.nonlinear + assert repn.nl is None + val = repn.const + if repn.linear is not None: + for v_id, v_coef in repn.linear.items(): + val += v_coef * sub_map[v_id] + val *= repn.mult + return val class SolFileData: @@ -203,4 +215,12 @@ def parse_sol_file( ) line = sol_file.readline() + if len(nl_info.eliminated_vars) > 0: + sub_map = {k: v[1] for k, v in sol_data.primals.items()} + for v, v_expr in nl_info.eliminated_vars: + val = evaluate_ampl_repn(v_expr, sub_map) + v_id = id(v) + sub_map[v_id] = val + sol_data.primals[v_id] = (v, val) + return result, sol_data From 952e8e77bca81c65d914fc35fc2a43fdea69a9cc Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 16 Jan 2024 07:44:41 -0700 Subject: [PATCH 0790/1204] solver refactor: various fixes --- pyomo/contrib/solver/ipopt.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 23464e40cb2..2705abec7c6 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -71,9 +71,6 @@ def __init__( self.executable = self.declare( 'executable', ConfigValue(default=Executable('ipopt')) ) - self.save_solver_io: bool = self.declare( - 'save_solver_io', ConfigValue(domain=bool, default=False) - ) # TODO: Add in a deprecation here for keepfiles self.temp_dir: str = self.declare( 'temp_dir', ConfigValue(domain=str, default=None) From 1b1875c49600d4cdbf4f4e0d8536dd5d2b6895b3 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 16 Jan 2024 08:24:46 -0700 Subject: [PATCH 0791/1204] gdp.binary_multiplication: cleanup --- pyomo/gdp/plugins/binary_multiplication.py | 15 +++++------ pyomo/gdp/tests/test_binary_multiplication.py | 27 +++---------------- 2 files changed, 11 insertions(+), 31 deletions(-) diff --git a/pyomo/gdp/plugins/binary_multiplication.py b/pyomo/gdp/plugins/binary_multiplication.py index 2305f244f29..8489fa04808 100644 --- a/pyomo/gdp/plugins/binary_multiplication.py +++ b/pyomo/gdp/plugins/binary_multiplication.py @@ -3,30 +3,28 @@ from pyomo.core.base import TransformationFactory from pyomo.core.util import target_list from pyomo.gdp import Disjunction -from pyomo.gdp.plugins.gdp_to_mip_transformation import GDP_to_MIP_Transformation -from pyomo.core.util import target_list from weakref import ref as weakref_ref import logging -logger = logging.getLogger('pyomo.gdp.binary_multiplication') +logger = logging.getLogger(__name__) @TransformationFactory.register( 'gdp.binary_multiplication', - doc="Reformulate the GDP as an MINLP by multiplying f(x) <= 0 by y to get f(x) * y <= 0.", + doc="Reformulate the GDP as an MINLP by multiplying f(x) <= 0 by y to get f(x) * y <= 0 where y is the binary corresponding to the Boolean indicator var of the Disjunct containing f(x) <= 0.", ) -class GDPToMINLPTransformation(GDP_to_MIP_Transformation): +class GDPBinaryMultiplicationTransformation(GDP_to_MIP_Transformation): CONFIG = ConfigDict("gdp.binary_multiplication") CONFIG.declare( 'targets', ConfigValue( default=None, domain=target_list, - description="target or list of targets that will be relaxed", + description="target or list of targets that will be transformed", doc=""" - This specifies the list of components to relax. If None (default), the + This specifies the list of components to transform. 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 @@ -81,7 +79,8 @@ def _transform_disjunctionData( or_expr += disjunct.binary_indicator_var self._transform_disjunct(disjunct, transBlock) - rhs = 1 if parent_disjunct is None else parent_disjunct.binary_indicator_var + # rhs = 1 if parent_disjunct is None else parent_disjunct.binary_indicator_var + rhs = 1 if obj.xor: xorConstraint[index] = or_expr == rhs else: diff --git a/pyomo/gdp/tests/test_binary_multiplication.py b/pyomo/gdp/tests/test_binary_multiplication.py index 2c6e045f853..9a515ef830a 100644 --- a/pyomo/gdp/tests/test_binary_multiplication.py +++ b/pyomo/gdp/tests/test_binary_multiplication.py @@ -22,6 +22,7 @@ from pyomo.gdp import Disjunct, Disjunction from pyomo.core.expr.compare import assertExpressionsEqual from pyomo.repn import generate_standard_repn +from pyomo.core.expr.compare import assertExpressionsEqual import pyomo.core.expr as EXPR import pyomo.gdp.tests.models as models @@ -168,9 +169,6 @@ def test_do_not_transform_userDeactivated_IndexedDisjunction(self): self, 'binary_multiplication' ) - # helper method to check the M values in all of the transformed - # constraints (m, M) is the tuple for M. This also relies on the - # disjuncts being transformed in the same order every time. def check_transformed_constraints( self, model, binary_multiplication, cons1lb, cons2lb, cons2ub, cons3ub ): @@ -183,14 +181,8 @@ def check_transformed_constraints( self.assertEqual(len(c), 1) c_lb = c[0] self.assertTrue(c[0].active) - repn = generate_standard_repn(c[0].body) - self.assertIsNone(repn.nonlinear_expr) - self.assertEqual(len(repn.quadratic_coefs), 1) - self.assertEqual(len(repn.linear_coefs), 1) ind_var = model.d[0].indicator_var - ct.check_quadratic_coef(self, repn, model.a, ind_var, 1) - ct.check_linear_coef(self, repn, ind_var, -model.d[0].c.lower) - self.assertEqual(repn.constant, 0) + assertExpressionsEqual(self, c[0].body, (model.a - model.d[0].c.lower)*ind_var) self.assertEqual(c[0].lower, 0) self.assertIsNone(c[0].upper) @@ -199,13 +191,8 @@ def check_transformed_constraints( self.assertEqual(len(c), 1) c_eq = c[0] self.assertTrue(c[0].active) - repn = generate_standard_repn(c[0].body) - self.assertTrue(repn.nonlinear_expr is None) - self.assertEqual(len(repn.linear_coefs), 0) - self.assertEqual(len(repn.quadratic_coefs), 1) ind_var = model.d[1].indicator_var - ct.check_quadratic_coef(self, repn, model.a, ind_var, 1) - self.assertEqual(repn.constant, 0) + assertExpressionsEqual(self, c[0].body, model.a*ind_var) self.assertEqual(c[0].lower, 0) self.assertEqual(c[0].upper, 0) @@ -214,13 +201,7 @@ def check_transformed_constraints( self.assertEqual(len(c), 1) c_ub = c[0] self.assertTrue(c_ub.active) - repn = generate_standard_repn(c_ub.body) - self.assertIsNone(repn.nonlinear_expr) - self.assertEqual(len(repn.linear_coefs), 1) - self.assertEqual(len(repn.quadratic_coefs), 1) - ct.check_quadratic_coef(self, repn, model.x, ind_var, 1) - ct.check_linear_coef(self, repn, ind_var, -model.d[1].c2.upper) - self.assertEqual(repn.constant, 0) + assertExpressionsEqual(self, c_ub.body, (model.x - model.d[1].c2.upper)*ind_var) self.assertIsNone(c_ub.lower) self.assertEqual(c_ub.upper, 0) From 15c521794ff53fd38409096eaa89dba0020534e0 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 16 Jan 2024 08:31:26 -0700 Subject: [PATCH 0792/1204] run black --- pyomo/gdp/tests/test_binary_multiplication.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pyomo/gdp/tests/test_binary_multiplication.py b/pyomo/gdp/tests/test_binary_multiplication.py index 9a515ef830a..6b3ba87fa21 100644 --- a/pyomo/gdp/tests/test_binary_multiplication.py +++ b/pyomo/gdp/tests/test_binary_multiplication.py @@ -182,7 +182,9 @@ def check_transformed_constraints( c_lb = c[0] self.assertTrue(c[0].active) ind_var = model.d[0].indicator_var - assertExpressionsEqual(self, c[0].body, (model.a - model.d[0].c.lower)*ind_var) + assertExpressionsEqual( + self, c[0].body, (model.a - model.d[0].c.lower) * ind_var + ) self.assertEqual(c[0].lower, 0) self.assertIsNone(c[0].upper) @@ -192,7 +194,7 @@ def check_transformed_constraints( c_eq = c[0] self.assertTrue(c[0].active) ind_var = model.d[1].indicator_var - assertExpressionsEqual(self, c[0].body, model.a*ind_var) + assertExpressionsEqual(self, c[0].body, model.a * ind_var) self.assertEqual(c[0].lower, 0) self.assertEqual(c[0].upper, 0) @@ -201,7 +203,9 @@ def check_transformed_constraints( self.assertEqual(len(c), 1) c_ub = c[0] self.assertTrue(c_ub.active) - assertExpressionsEqual(self, c_ub.body, (model.x - model.d[1].c2.upper)*ind_var) + assertExpressionsEqual( + self, c_ub.body, (model.x - model.d[1].c2.upper) * ind_var + ) self.assertIsNone(c_ub.lower) self.assertEqual(c_ub.upper, 0) From e256ab19c56375bbe760fc1f81db3f8e57a27b6b Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 16 Jan 2024 08:48:00 -0700 Subject: [PATCH 0793/1204] make skip_trivial_constraints True by default --- pyomo/repn/plugins/nl_writer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 3b94963e858..897b906c181 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -338,6 +338,9 @@ def __call__(self, model, filename, solver_capability, io_options): config.scale_model = False config.linear_presolve = False + # just for backwards compatibility + config.skip_trivial_constraints = False + if config.symbolic_solver_labels: _open = lambda fname: open(fname, 'w') else: From 5b7919ec202c1b16ae0ab8adf6f479eac5b2fb36 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 16 Jan 2024 09:24:35 -0700 Subject: [PATCH 0794/1204] Apply black; convert doc -> description for ConfigDicts --- pyomo/contrib/solver/config.py | 254 ++++++++++++++--------------- pyomo/contrib/solver/ipopt.py | 26 ++- pyomo/contrib/solver/results.py | 17 +- pyomo/contrib/solver/sol_reader.py | 4 +- 4 files changed, 153 insertions(+), 148 deletions(-) diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index 8fe627cbcc1..84b1c2d2c87 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -21,6 +21,117 @@ from pyomo.common.timing import HierarchicalTimer +class SolverConfig(ConfigDict): + """ + Base config values for all solver interfaces + """ + + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + super().__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, + ) + + self.tee: bool = self.declare( + 'tee', + ConfigValue( + domain=bool, + default=False, + description="If True, the solver log prints to stdout.", + ), + ) + self.load_solution: bool = self.declare( + 'load_solution', + ConfigValue( + domain=bool, + default=True, + description="If True, the values of the primal variables will be loaded into the model.", + ), + ) + self.raise_exception_on_nonoptimal_result: bool = self.declare( + 'raise_exception_on_nonoptimal_result', + ConfigValue( + domain=bool, + default=True, + description="If False, the `solve` method will continue processing even if the returned result is nonoptimal.", + ), + ) + self.symbolic_solver_labels: bool = self.declare( + 'symbolic_solver_labels', + ConfigValue( + domain=bool, + default=False, + description="If True, the names given to the solver will reflect the names of the Pyomo components. Cannot be changed after set_instance is called.", + ), + ) + self.timer: HierarchicalTimer = self.declare( + 'timer', ConfigValue(default=None, description="A HierarchicalTimer.") + ) + self.threads: Optional[int] = self.declare( + 'threads', + ConfigValue( + domain=NonNegativeInt, + description="Number of threads to be used by a solver.", + default=None, + ), + ) + self.time_limit: Optional[float] = self.declare( + 'time_limit', + ConfigValue( + domain=NonNegativeFloat, description="Time limit applied to the solver." + ), + ) + self.solver_options: ConfigDict = self.declare( + 'solver_options', + ConfigDict(implicit=True, description="Options to pass to the solver."), + ) + + +class BranchAndBoundConfig(SolverConfig): + """ + Attributes + ---------- + mip_gap: float + Solver will terminate if the mip gap is less than mip_gap + relax_integrality: bool + If True, all integer variables will be relaxed to continuous + variables before solving + """ + + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + super().__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, + ) + + self.rel_gap: Optional[float] = self.declare( + 'rel_gap', ConfigValue(domain=NonNegativeFloat) + ) + self.abs_gap: Optional[float] = self.declare( + 'abs_gap', ConfigValue(domain=NonNegativeFloat) + ) + + class AutoUpdateConfig(ConfigDict): """ This is necessary for persistent solvers. @@ -59,7 +170,7 @@ def __init__( ConfigValue( domain=bool, default=True, - doc=""" + description=""" If False, new/old constraints will not be automatically detected on subsequent solves. Use False only when manually updating the solver with opt.add_constraints() and opt.remove_constraints() or when you are certain constraints are not being @@ -71,7 +182,7 @@ def __init__( ConfigValue( domain=bool, default=True, - doc=""" + description=""" If False, new/old variables will not be automatically detected on subsequent solves. Use False only when manually updating the solver with opt.add_variables() and opt.remove_variables() or when you are certain variables are not being added to / @@ -83,7 +194,7 @@ def __init__( ConfigValue( domain=bool, default=True, - doc=""" + description=""" If False, new/old parameters will not be automatically detected on subsequent solves. Use False only when manually updating the solver with opt.add_params() and opt.remove_params() or when you are certain parameters are not being added to / @@ -95,7 +206,7 @@ def __init__( ConfigValue( domain=bool, default=True, - doc=""" + description=""" If False, new/old objectives will not be automatically detected on subsequent solves. Use False only when manually updating the solver with opt.set_objective() or when you are certain objectives are not being added to / removed from the model.""", @@ -106,7 +217,7 @@ def __init__( ConfigValue( domain=bool, default=True, - doc=""" + description=""" If False, changes to existing constraints will not be automatically detected on subsequent solves. This includes changes to the lower, body, and upper attributes of constraints. Use False only when manually updating the solver with @@ -119,7 +230,7 @@ def __init__( ConfigValue( domain=bool, default=True, - doc=""" + description=""" If False, changes to existing variables will not be automatically detected on subsequent solves. This includes changes to the lb, ub, domain, and fixed attributes of variables. Use False only when manually updating the solver with @@ -131,7 +242,7 @@ def __init__( ConfigValue( domain=bool, default=True, - doc=""" + description=""" If False, changes to parameter values will not be automatically detected on subsequent solves. Use False only when manually updating the solver with opt.update_params() or when you are certain parameters are not being modified.""", @@ -142,7 +253,7 @@ def __init__( ConfigValue( domain=bool, default=True, - doc=""" + description=""" If False, changes to Expressions will not be automatically detected on subsequent solves. Use False only when manually updating the solver with opt.remove_constraints() and opt.add_constraints() or when you are certain @@ -154,7 +265,7 @@ def __init__( ConfigValue( domain=bool, default=True, - doc=""" + description=""" If False, changes to objectives will not be automatically detected on subsequent solves. This includes the expr and sense attributes of objectives. Use False only when manually updating the solver with opt.set_objective() or when you are @@ -167,7 +278,7 @@ def __init__( domain=bool, default=True, visibility=ADVANCED_OPTION, - doc=""" + description=""" This is an advanced option that should only be used in special circumstances. With the default setting of True, fixed variables will be treated like parameters. This means that z == x*y will be linear if x or y is fixed and the constraint @@ -181,121 +292,6 @@ def __init__( ) -class SolverConfig(ConfigDict): - """ - Base config values for all solver interfaces - """ - - def __init__( - self, - description=None, - doc=None, - implicit=False, - implicit_domain=None, - visibility=0, - ): - super().__init__( - description=description, - doc=doc, - implicit=implicit, - implicit_domain=implicit_domain, - visibility=visibility, - ) - - self.tee: bool = self.declare( - 'tee', - ConfigValue( - domain=bool, - default=False, - description="If True, the solver log prints to stdout.", - ), - ) - self.load_solution: bool = self.declare( - 'load_solution', - ConfigValue( - domain=bool, - default=True, - description="If True, the values of the primal variables will be loaded into the model.", - ), - ) - self.raise_exception_on_nonoptimal_result: bool = self.declare( - 'raise_exception_on_nonoptimal_result', - ConfigValue( - domain=bool, - default=True, - description="If False, the `solve` method will continue processing even if the returned result is nonoptimal.", - ), - ) - self.symbolic_solver_labels: bool = self.declare( - 'symbolic_solver_labels', - ConfigValue( - domain=bool, - default=False, - description="If True, the names given to the solver will reflect the names of the Pyomo components. Cannot be changed after set_instance is called.", - ), - ) - self.timer: HierarchicalTimer = self.declare( - 'timer', - ConfigValue( - default=None, - description="A HierarchicalTimer.", - ), - ) - self.threads: Optional[int] = self.declare( - 'threads', - ConfigValue( - domain=NonNegativeInt, - description="Number of threads to be used by a solver.", - default=None, - ), - ) - self.time_limit: Optional[float] = self.declare( - 'time_limit', - ConfigValue( - domain=NonNegativeFloat, description="Time limit applied to the solver." - ), - ) - self.solver_options: ConfigDict = self.declare( - 'solver_options', - ConfigDict(implicit=True, description="Options to pass to the solver."), - ) - - -class BranchAndBoundConfig(SolverConfig): - """ - Attributes - ---------- - mip_gap: float - Solver will terminate if the mip gap is less than mip_gap - relax_integrality: bool - If True, all integer variables will be relaxed to continuous - variables before solving - """ - - def __init__( - self, - description=None, - doc=None, - implicit=False, - implicit_domain=None, - visibility=0, - ): - super().__init__( - description=description, - doc=doc, - implicit=implicit, - implicit_domain=implicit_domain, - visibility=visibility, - ) - - self.rel_gap: Optional[float] = self.declare( - 'rel_gap', ConfigValue(domain=NonNegativeFloat) - ) - self.abs_gap: Optional[float] = self.declare( - 'abs_gap', ConfigValue(domain=NonNegativeFloat) - ) - - class PersistentSolverConfig(SolverConfig): def __init__( self, @@ -313,7 +309,9 @@ def __init__( visibility=visibility, ) - self.auto_updates: AutoUpdateConfig = self.declare('auto_updates', AutoUpdateConfig()) + self.auto_updates: AutoUpdateConfig = self.declare( + 'auto_updates', AutoUpdateConfig() + ) class PersistentBranchAndBoundConfig(BranchAndBoundConfig): @@ -333,4 +331,6 @@ def __init__( visibility=visibility, ) - self.auto_updates: AutoUpdateConfig = self.declare('auto_updates', AutoUpdateConfig()) + self.auto_updates: AutoUpdateConfig = self.declare( + 'auto_updates', AutoUpdateConfig() + ) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 2705abec7c6..63dca0af0d9 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -27,11 +27,7 @@ from pyomo.contrib.solver.base import SolverBase from pyomo.contrib.solver.config import SolverConfig from pyomo.contrib.solver.factory import SolverFactory -from pyomo.contrib.solver.results import ( - Results, - TerminationCondition, - SolutionStatus, -) +from pyomo.contrib.solver.results import Results, TerminationCondition, SolutionStatus from .sol_reader import parse_sol_file from pyomo.contrib.solver.solution import SolutionLoaderBase, SolutionLoader from pyomo.common.tee import TeeStream @@ -82,8 +78,7 @@ def __init__( 'log_level', ConfigValue(domain=NonNegativeInt, default=logging.INFO) ) self.writer_config = self.declare( - 'writer_config', - ConfigValue(default=NLWriter.CONFIG()) + 'writer_config', ConfigValue(default=NLWriter.CONFIG()) ) @@ -237,7 +232,10 @@ def _create_command_line(self, basename: str, config: ipoptConfig, opt_file: boo 'Pyomo generates the ipopt options file as part of the solve method. ' 'Add all options to ipopt.config.solver_options instead.' ) - if config.time_limit is not None and 'max_cpu_time' not in config.solver_options: + if ( + config.time_limit is not None + and 'max_cpu_time' not in config.solver_options + ): config.solver_options['max_cpu_time'] = config.time_limit for k, val in config.solver_options.items(): if k in ipopt_command_line_options: @@ -386,10 +384,10 @@ def solve(self, model, **kwds): ): model.rc.update(results.solution_loader.get_reduced_costs()) - if results.solution_status in { - SolutionStatus.feasible, - SolutionStatus.optimal, - } and len(nl_info.objectives) > 0: + if ( + results.solution_status in {SolutionStatus.feasible, SolutionStatus.optimal} + and len(nl_info.objectives) > 0 + ): if config.load_solution: results.incumbent_objective = value(nl_info.objectives[0]) else: @@ -448,9 +446,7 @@ def _parse_solution( self, instream: io.TextIOBase, nl_info: NLWriterInfo, result: ipoptResults ): res, sol_data = parse_sol_file( - sol_file=instream, - nl_info=nl_info, - result=result, + sol_file=instream, nl_info=nl_info, result=result ) if res.solution_status == SolutionStatus.noSolution: diff --git a/pyomo/contrib/solver/results.py b/pyomo/contrib/solver/results.py index 3beb3aede81..e21adcc35cc 100644 --- a/pyomo/contrib/solver/results.py +++ b/pyomo/contrib/solver/results.py @@ -220,7 +220,9 @@ def __init__( self.iteration_count: Optional[int] = self.declare( 'iteration_count', ConfigValue(domain=NonNegativeInt, default=None) ) - self.timing_info: ConfigDict = self.declare('timing_info', ConfigDict(implicit=True)) + self.timing_info: ConfigDict = self.declare( + 'timing_info', ConfigDict(implicit=True) + ) self.timing_info.start_timestamp: datetime = self.timing_info.declare( 'start_timestamp', ConfigValue(domain=Datetime) @@ -231,8 +233,17 @@ def __init__( self.extra_info: ConfigDict = self.declare( 'extra_info', ConfigDict(implicit=True) ) - self.solver_configuration: ConfigDict = self.declare('solver_configuration', ConfigValue(doc="A copy of the config object used in the solve", visibility=ADVANCED_OPTION)) - self.solver_log: str = self.declare('solver_log', ConfigValue(domain=str, default=None, visibility=ADVANCED_OPTION)) + self.solver_configuration: ConfigDict = self.declare( + 'solver_configuration', + ConfigValue( + description="A copy of the config object used in the solve", + visibility=ADVANCED_OPTION, + ), + ) + self.solver_log: str = self.declare( + 'solver_log', + ConfigValue(domain=str, default=None, visibility=ADVANCED_OPTION), + ) class ResultsReader: diff --git a/pyomo/contrib/solver/sol_reader.py b/pyomo/contrib/solver/sol_reader.py index 93fb6d39da3..92761246241 100644 --- a/pyomo/contrib/solver/sol_reader.py +++ b/pyomo/contrib/solver/sol_reader.py @@ -31,9 +31,7 @@ def __init__(self) -> None: def parse_sol_file( - sol_file: io.TextIOBase, - nl_info: NLWriterInfo, - result: Results, + sol_file: io.TextIOBase, nl_info: NLWriterInfo, result: Results ) -> Tuple[Results, SolFileData]: sol_data = SolFileData() From 78c08d09c8396ccc85b480116c698568b8c088b4 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 16 Jan 2024 10:06:20 -0700 Subject: [PATCH 0795/1204] restore changes to appsi --- pyomo/contrib/appsi/__init__.py | 1 + pyomo/contrib/appsi/base.py | 1695 +++++++++++++++++ pyomo/contrib/appsi/build.py | 6 +- .../contrib/appsi/examples/getting_started.py | 14 +- .../appsi/examples/tests/test_examples.py | 15 +- pyomo/contrib/appsi/fbbt.py | 23 +- pyomo/contrib/appsi/plugins.py | 2 +- pyomo/contrib/appsi/solvers/cbc.py | 90 +- pyomo/contrib/appsi/solvers/cplex.py | 90 +- pyomo/contrib/appsi/solvers/gurobi.py | 149 +- pyomo/contrib/appsi/solvers/highs.py | 139 +- pyomo/contrib/appsi/solvers/ipopt.py | 86 +- .../solvers/tests/test_gurobi_persistent.py | 33 +- .../solvers/tests/test_highs_persistent.py | 11 +- .../solvers/tests/test_ipopt_persistent.py | 6 +- .../solvers/tests/test_persistent_solvers.py | 431 ++--- .../solvers/tests/test_wntr_persistent.py | 97 +- pyomo/contrib/appsi/solvers/wntr.py | 32 +- pyomo/contrib/appsi/tests/test_base.py | 91 + pyomo/contrib/appsi/tests/test_interval.py | 4 +- pyomo/contrib/appsi/utils/__init__.py | 2 + .../utils/collect_vars_and_named_exprs.py | 50 + pyomo/contrib/appsi/utils/get_objective.py | 12 + pyomo/contrib/appsi/utils/tests/__init__.py | 0 .../test_collect_vars_and_named_exprs.py | 56 + pyomo/contrib/appsi/writers/config.py | 2 +- pyomo/contrib/appsi/writers/lp_writer.py | 22 +- pyomo/contrib/appsi/writers/nl_writer.py | 33 +- .../appsi/writers/tests/test_nl_writer.py | 2 +- 29 files changed, 2493 insertions(+), 701 deletions(-) create mode 100644 pyomo/contrib/appsi/base.py create mode 100644 pyomo/contrib/appsi/tests/test_base.py create mode 100644 pyomo/contrib/appsi/utils/__init__.py create mode 100644 pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py create mode 100644 pyomo/contrib/appsi/utils/get_objective.py create mode 100644 pyomo/contrib/appsi/utils/tests/__init__.py create mode 100644 pyomo/contrib/appsi/utils/tests/test_collect_vars_and_named_exprs.py diff --git a/pyomo/contrib/appsi/__init__.py b/pyomo/contrib/appsi/__init__.py index 0134a96f363..df3ba212448 100644 --- a/pyomo/contrib/appsi/__init__.py +++ b/pyomo/contrib/appsi/__init__.py @@ -1,3 +1,4 @@ +from . import base from . import solvers from . import writers from . import fbbt diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py new file mode 100644 index 00000000000..e6186eeedd2 --- /dev/null +++ b/pyomo/contrib/appsi/base.py @@ -0,0 +1,1695 @@ +import abc +import enum +from typing import ( + Sequence, + Dict, + Optional, + Mapping, + NoReturn, + List, + Tuple, + MutableMapping, +) +from pyomo.core.base.constraint import _GeneralConstraintData, Constraint +from pyomo.core.base.sos import _SOSConstraintData, SOSConstraint +from pyomo.core.base.var import _GeneralVarData, Var +from pyomo.core.base.param import _ParamData, Param +from pyomo.core.base.block import _BlockData, Block +from pyomo.core.base.objective import _GeneralObjectiveData +from pyomo.common.collections import ComponentMap +from .utils.get_objective import get_objective +from .utils.collect_vars_and_named_exprs import collect_vars_and_named_exprs +from pyomo.common.timing import HierarchicalTimer +from pyomo.common.config import ConfigDict, ConfigValue, NonNegativeFloat +from pyomo.common.errors import ApplicationError +from pyomo.opt.base import SolverFactory as LegacySolverFactory +from pyomo.common.factory import Factory +import os +from pyomo.opt.results.results_ import SolverResults as LegacySolverResults +from pyomo.opt.results.solution import ( + Solution as LegacySolution, + SolutionStatus as LegacySolutionStatus, +) +from pyomo.opt.results.solver import ( + TerminationCondition as LegacyTerminationCondition, + SolverStatus as LegacySolverStatus, +) +from pyomo.core.kernel.objective import minimize +from pyomo.core.base import SymbolMap +import weakref +from .cmodel import cmodel, cmodel_available +from pyomo.core.staleflag import StaleFlagManager +from pyomo.core.expr.numvalue import NumericConstant + + +class TerminationCondition(enum.Enum): + """ + An enumeration for checking the termination condition of solvers + """ + + unknown = 0 + """unknown serves as both a default value, and it is used when no other enum member makes sense""" + + maxTimeLimit = 1 + """The solver exited due to a time limit""" + + maxIterations = 2 + """The solver exited due to an iteration limit """ + + objectiveLimit = 3 + """The solver exited due to an objective limit""" + + minStepLength = 4 + """The solver exited due to a minimum step length""" + + optimal = 5 + """The solver exited with the optimal solution""" + + unbounded = 8 + """The solver exited because the problem is unbounded""" + + infeasible = 9 + """The solver exited because the problem is infeasible""" + + infeasibleOrUnbounded = 10 + """The solver exited because the problem is either infeasible or unbounded""" + + error = 11 + """The solver exited due to an error""" + + interrupted = 12 + """The solver exited because it was interrupted""" + + licensingProblems = 13 + """The solver exited due to licensing problems""" + + +class SolverConfig(ConfigDict): + """ + Attributes + ---------- + time_limit: float + Time limit for the solver + stream_solver: bool + If True, then the solver log goes to stdout + load_solution: bool + If False, then the values of the primal variables will not be + loaded into the model + symbolic_solver_labels: bool + If True, the names given to the solver will reflect the names + of the pyomo components. Cannot be changed after set_instance + is called. + report_timing: bool + If True, then some timing information will be printed at the + end of the solve. + """ + + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + super(SolverConfig, self).__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, + ) + + self.declare('time_limit', ConfigValue(domain=NonNegativeFloat)) + self.declare('stream_solver', ConfigValue(domain=bool)) + self.declare('load_solution', ConfigValue(domain=bool)) + self.declare('symbolic_solver_labels', ConfigValue(domain=bool)) + self.declare('report_timing', ConfigValue(domain=bool)) + + self.time_limit: Optional[float] = None + self.stream_solver: bool = False + self.load_solution: bool = True + self.symbolic_solver_labels: bool = False + self.report_timing: bool = False + + +class MIPSolverConfig(SolverConfig): + """ + Attributes + ---------- + mip_gap: float + Solver will terminate if the mip gap is less than mip_gap + relax_integrality: bool + If True, all integer variables will be relaxed to continuous + variables before solving + """ + + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + super(MIPSolverConfig, self).__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, + ) + + self.declare('mip_gap', ConfigValue(domain=NonNegativeFloat)) + self.declare('relax_integrality', ConfigValue(domain=bool)) + + self.mip_gap: Optional[float] = None + self.relax_integrality: bool = False + + +class SolutionLoaderBase(abc.ABC): + def load_vars( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> NoReturn: + """ + Load the solution of the primal variables into the value attribute of the variables. + + Parameters + ---------- + vars_to_load: list + A list of the variables whose solution should be loaded. If vars_to_load is None, then the solution + to all primal variables will be loaded. + """ + for v, val in self.get_primals(vars_to_load=vars_to_load).items(): + v.set_value(val, skip_validation=True) + StaleFlagManager.mark_all_as_stale(delayed=True) + + @abc.abstractmethod + def get_primals( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> Mapping[_GeneralVarData, float]: + """ + Returns a ComponentMap mapping variable to var value. + + Parameters + ---------- + vars_to_load: list + A list of the variables whose solution value should be retrieved. If vars_to_load is None, + then the values for all variables will be retrieved. + + Returns + ------- + primals: ComponentMap + Maps variables to solution values + """ + pass + + def get_duals( + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None + ) -> Dict[_GeneralConstraintData, float]: + """ + Returns a dictionary mapping constraint to dual value. + + Parameters + ---------- + cons_to_load: list + A list of the constraints whose duals should be retrieved. If cons_to_load is None, then the duals for all + constraints will be retrieved. + + Returns + ------- + duals: dict + Maps constraints to dual values + """ + raise NotImplementedError(f'{type(self)} does not support the get_duals method') + + def get_slacks( + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None + ) -> Dict[_GeneralConstraintData, float]: + """ + Returns a dictionary mapping constraint to slack. + + Parameters + ---------- + cons_to_load: list + A list of the constraints whose duals should be loaded. If cons_to_load is None, then the duals for all + constraints will be loaded. + + Returns + ------- + slacks: dict + Maps constraints to slacks + """ + raise NotImplementedError( + f'{type(self)} does not support the get_slacks method' + ) + + def get_reduced_costs( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> Mapping[_GeneralVarData, float]: + """ + Returns a ComponentMap mapping variable to reduced cost. + + Parameters + ---------- + vars_to_load: list + A list of the variables whose reduced cost should be retrieved. If vars_to_load is None, then the + reduced costs for all variables will be loaded. + + Returns + ------- + reduced_costs: ComponentMap + Maps variables to reduced costs + """ + raise NotImplementedError( + f'{type(self)} does not support the get_reduced_costs method' + ) + + +class SolutionLoader(SolutionLoaderBase): + def __init__( + self, + primals: Optional[MutableMapping], + duals: Optional[MutableMapping], + slacks: Optional[MutableMapping], + reduced_costs: Optional[MutableMapping], + ): + """ + Parameters + ---------- + primals: dict + maps id(Var) to (var, value) + duals: dict + maps Constraint to dual value + slacks: dict + maps Constraint to slack value + reduced_costs: dict + maps id(Var) to (var, reduced_cost) + """ + self._primals = primals + self._duals = duals + self._slacks = slacks + self._reduced_costs = reduced_costs + + def get_primals( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> Mapping[_GeneralVarData, float]: + if self._primals is None: + raise RuntimeError( + 'Solution loader does not currently have a valid solution. Please ' + 'check the termination condition.' + ) + if vars_to_load is None: + return ComponentMap(self._primals.values()) + else: + primals = ComponentMap() + for v in vars_to_load: + primals[v] = self._primals[id(v)][1] + return primals + + def get_duals( + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None + ) -> Dict[_GeneralConstraintData, float]: + if self._duals is None: + raise RuntimeError( + 'Solution loader does not currently have valid duals. Please ' + 'check the termination condition and ensure the solver returns duals ' + 'for the given problem type.' + ) + if cons_to_load is None: + duals = dict(self._duals) + else: + duals = dict() + for c in cons_to_load: + duals[c] = self._duals[c] + return duals + + def get_slacks( + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None + ) -> Dict[_GeneralConstraintData, float]: + if self._slacks is None: + raise RuntimeError( + 'Solution loader does not currently have valid slacks. Please ' + 'check the termination condition and ensure the solver returns slacks ' + 'for the given problem type.' + ) + if cons_to_load is None: + slacks = dict(self._slacks) + else: + slacks = dict() + for c in cons_to_load: + slacks[c] = self._slacks[c] + return slacks + + def get_reduced_costs( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> Mapping[_GeneralVarData, float]: + if self._reduced_costs is None: + raise RuntimeError( + 'Solution loader does not currently have valid reduced costs. Please ' + 'check the termination condition and ensure the solver returns reduced ' + 'costs for the given problem type.' + ) + if vars_to_load is None: + rc = ComponentMap(self._reduced_costs.values()) + else: + rc = ComponentMap() + for v in vars_to_load: + rc[v] = self._reduced_costs[id(v)][1] + return rc + + +class Results(object): + """ + Attributes + ---------- + termination_condition: TerminationCondition + The reason the solver exited. This is a member of the + TerminationCondition enum. + best_feasible_objective: float + If a feasible solution was found, this is the objective value of + the best solution found. If no feasible solution was found, this is + None. + best_objective_bound: float + The best objective bound found. For minimization problems, this is + the lower bound. For maximization problems, this is the upper bound. + For solvers that do not provide an objective bound, this should be -inf + (minimization) or inf (maximization) + + Here is an example workflow: + + >>> import pyomo.environ as pe + >>> from pyomo.contrib import appsi + >>> m = pe.ConcreteModel() + >>> m.x = pe.Var() + >>> m.obj = pe.Objective(expr=m.x**2) + >>> opt = appsi.solvers.Ipopt() + >>> opt.config.load_solution = False + >>> results = opt.solve(m) #doctest:+SKIP + >>> if results.termination_condition == appsi.base.TerminationCondition.optimal: #doctest:+SKIP + ... print('optimal solution found: ', results.best_feasible_objective) #doctest:+SKIP + ... results.solution_loader.load_vars() #doctest:+SKIP + ... print('the optimal value of x is ', m.x.value) #doctest:+SKIP + ... elif results.best_feasible_objective is not None: #doctest:+SKIP + ... print('sub-optimal but feasible solution found: ', results.best_feasible_objective) #doctest:+SKIP + ... results.solution_loader.load_vars(vars_to_load=[m.x]) #doctest:+SKIP + ... print('The value of x in the feasible solution is ', m.x.value) #doctest:+SKIP + ... elif results.termination_condition in {appsi.base.TerminationCondition.maxIterations, appsi.base.TerminationCondition.maxTimeLimit}: #doctest:+SKIP + ... print('No feasible solution was found. The best lower bound found was ', results.best_objective_bound) #doctest:+SKIP + ... else: #doctest:+SKIP + ... print('The following termination condition was encountered: ', results.termination_condition) #doctest:+SKIP + """ + + def __init__(self): + self.solution_loader: SolutionLoaderBase = SolutionLoader( + None, None, None, None + ) + self.termination_condition: TerminationCondition = TerminationCondition.unknown + self.best_feasible_objective: Optional[float] = None + self.best_objective_bound: Optional[float] = None + + def __str__(self): + s = '' + s += 'termination_condition: ' + str(self.termination_condition) + '\n' + s += 'best_feasible_objective: ' + str(self.best_feasible_objective) + '\n' + s += 'best_objective_bound: ' + str(self.best_objective_bound) + return s + + +class UpdateConfig(ConfigDict): + """ + Attributes + ---------- + check_for_new_or_removed_constraints: bool + check_for_new_or_removed_vars: bool + check_for_new_or_removed_params: bool + update_constraints: bool + update_vars: bool + update_params: bool + update_named_expressions: bool + """ + + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + if doc is None: + doc = 'Configuration options to detect changes in model between solves' + super(UpdateConfig, self).__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, + ) + + self.declare( + 'check_for_new_or_removed_constraints', + ConfigValue( + domain=bool, + default=True, + doc=""" + If False, new/old constraints will not be automatically detected on subsequent + solves. Use False only when manually updating the solver with opt.add_constraints() + and opt.remove_constraints() or when you are certain constraints are not being + added to/removed from the model.""", + ), + ) + self.declare( + 'check_for_new_or_removed_vars', + ConfigValue( + domain=bool, + default=True, + doc=""" + If False, new/old variables will not be automatically detected on subsequent + solves. Use False only when manually updating the solver with opt.add_variables() and + opt.remove_variables() or when you are certain variables are not being added to / + removed from the model.""", + ), + ) + self.declare( + 'check_for_new_or_removed_params', + ConfigValue( + domain=bool, + default=True, + doc=""" + If False, new/old parameters will not be automatically detected on subsequent + solves. Use False only when manually updating the solver with opt.add_params() and + opt.remove_params() or when you are certain parameters are not being added to / + removed from the model.""", + ), + ) + self.declare( + 'check_for_new_objective', + ConfigValue( + domain=bool, + default=True, + doc=""" + If False, new/old objectives will not be automatically detected on subsequent + solves. Use False only when manually updating the solver with opt.set_objective() or + when you are certain objectives are not being added to / removed from the model.""", + ), + ) + self.declare( + 'update_constraints', + ConfigValue( + domain=bool, + default=True, + doc=""" + If False, changes to existing constraints will not be automatically detected on + subsequent solves. This includes changes to the lower, body, and upper attributes of + constraints. Use False only when manually updating the solver with + opt.remove_constraints() and opt.add_constraints() or when you are certain constraints + are not being modified.""", + ), + ) + self.declare( + 'update_vars', + ConfigValue( + domain=bool, + default=True, + doc=""" + If False, changes to existing variables will not be automatically detected on + subsequent solves. This includes changes to the lb, ub, domain, and fixed + attributes of variables. Use False only when manually updating the solver with + opt.update_variables() or when you are certain variables are not being modified.""", + ), + ) + self.declare( + 'update_params', + ConfigValue( + domain=bool, + default=True, + doc=""" + If False, changes to parameter values will not be automatically detected on + subsequent solves. Use False only when manually updating the solver with + opt.update_params() or when you are certain parameters are not being modified.""", + ), + ) + self.declare( + 'update_named_expressions', + ConfigValue( + domain=bool, + default=True, + doc=""" + If False, changes to Expressions will not be automatically detected on + subsequent solves. Use False only when manually updating the solver with + opt.remove_constraints() and opt.add_constraints() or when you are certain + Expressions are not being modified.""", + ), + ) + self.declare( + 'update_objective', + ConfigValue( + domain=bool, + default=True, + doc=""" + If False, changes to objectives will not be automatically detected on + subsequent solves. This includes the expr and sense attributes of objectives. Use + False only when manually updating the solver with opt.set_objective() or when you are + certain objectives are not being modified.""", + ), + ) + self.declare( + 'treat_fixed_vars_as_params', + ConfigValue( + domain=bool, + default=True, + doc=""" + This is an advanced option that should only be used in special circumstances. + With the default setting of True, fixed variables will be treated like parameters. + This means that z == x*y will be linear if x or y is fixed and the constraint + can be written to an LP file. If the value of the fixed variable gets changed, we have + to completely reprocess all constraints using that variable. If + treat_fixed_vars_as_params is False, then constraints will be processed as if fixed + variables are not fixed, and the solver will be told the variable is fixed. This means + z == x*y could not be written to an LP file even if x and/or y is fixed. However, + updating the values of fixed variables is much faster this way.""", + ), + ) + + self.check_for_new_or_removed_constraints: bool = True + self.check_for_new_or_removed_vars: bool = True + self.check_for_new_or_removed_params: bool = True + self.check_for_new_objective: bool = True + self.update_constraints: bool = True + self.update_vars: bool = True + self.update_params: bool = True + self.update_named_expressions: bool = True + self.update_objective: bool = True + self.treat_fixed_vars_as_params: bool = True + + +class Solver(abc.ABC): + class Availability(enum.IntEnum): + NotFound = 0 + BadVersion = -1 + BadLicense = -2 + FullLicense = 1 + LimitedLicense = 2 + NeedsCompiledExtension = -3 + + def __bool__(self): + return self._value_ > 0 + + def __format__(self, format_spec): + # We want general formatting of this Enum to return the + # formatted string value and not the int (which is the + # default implementation from IntEnum) + return format(self.name, format_spec) + + def __str__(self): + # Note: Python 3.11 changed the core enums so that the + # "mixin" type for standard enums overrides the behavior + # specified in __format__. We will override str() here to + # preserve the previous behavior + return self.name + + @abc.abstractmethod + def solve(self, model: _BlockData, timer: HierarchicalTimer = None) -> Results: + """ + Solve a Pyomo model. + + Parameters + ---------- + model: _BlockData + The Pyomo model to be solved + timer: HierarchicalTimer + An option timer for reporting timing + + Returns + ------- + results: Results + A results object + """ + pass + + @abc.abstractmethod + def available(self): + """Test if the solver is available on this system. + + Nominally, this will return True if the solver interface is + valid and can be used to solve problems and False if it cannot. + + Note that for licensed solvers there are a number of "levels" of + available: depending on the license, the solver may be available + with limitations on problem size or runtime (e.g., 'demo' + vs. 'community' vs. 'full'). In these cases, the solver may + return a subclass of enum.IntEnum, with members that resolve to + True if the solver is available (possibly with limitations). + The Enum may also have multiple members that all resolve to + False indicating the reason why the interface is not available + (not found, bad license, unsupported version, etc). + + Returns + ------- + available: Solver.Availability + An enum that indicates "how available" the solver is. + Note that the enum can be cast to bool, which will + be True if the solver is runable at all and False + otherwise. + """ + pass + + @abc.abstractmethod + def version(self) -> Tuple: + """ + Returns + ------- + version: tuple + A tuple representing the version + """ + + @property + @abc.abstractmethod + def config(self): + """ + An object for configuring solve options. + + Returns + ------- + SolverConfig + An object for configuring pyomo solve options such as the time limit. + These options are mostly independent of the solver. + """ + pass + + @property + @abc.abstractmethod + def symbol_map(self): + pass + + def is_persistent(self): + """ + Returns + ------- + is_persistent: bool + True if the solver is a persistent solver. + """ + return False + + +class PersistentSolver(Solver): + def is_persistent(self): + return True + + def load_vars( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> NoReturn: + """ + Load the solution of the primal variables into the value attribute of the variables. + + Parameters + ---------- + vars_to_load: list + A list of the variables whose solution should be loaded. If vars_to_load is None, then the solution + to all primal variables will be loaded. + """ + for v, val in self.get_primals(vars_to_load=vars_to_load).items(): + v.set_value(val, skip_validation=True) + StaleFlagManager.mark_all_as_stale(delayed=True) + + @abc.abstractmethod + def get_primals( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> Mapping[_GeneralVarData, float]: + pass + + def get_duals( + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None + ) -> Dict[_GeneralConstraintData, float]: + """ + Declare sign convention in docstring here. + + Parameters + ---------- + cons_to_load: list + A list of the constraints whose duals should be loaded. If cons_to_load is None, then the duals for all + constraints will be loaded. + + Returns + ------- + duals: dict + Maps constraints to dual values + """ + raise NotImplementedError( + '{0} does not support the get_duals method'.format(type(self)) + ) + + def get_slacks( + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None + ) -> Dict[_GeneralConstraintData, float]: + """ + Parameters + ---------- + cons_to_load: list + A list of the constraints whose slacks should be loaded. If cons_to_load is None, then the slacks for all + constraints will be loaded. + + Returns + ------- + slacks: dict + Maps constraints to slack values + """ + raise NotImplementedError( + '{0} does not support the get_slacks method'.format(type(self)) + ) + + def get_reduced_costs( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> Mapping[_GeneralVarData, float]: + """ + Parameters + ---------- + vars_to_load: list + A list of the variables whose reduced cost should be loaded. If vars_to_load is None, then all reduced costs + will be loaded. + + Returns + ------- + reduced_costs: ComponentMap + Maps variable to reduced cost + """ + raise NotImplementedError( + '{0} does not support the get_reduced_costs method'.format(type(self)) + ) + + @property + @abc.abstractmethod + def update_config(self) -> UpdateConfig: + pass + + @abc.abstractmethod + def set_instance(self, model): + pass + + @abc.abstractmethod + def add_variables(self, variables: List[_GeneralVarData]): + pass + + @abc.abstractmethod + def add_params(self, params: List[_ParamData]): + pass + + @abc.abstractmethod + def add_constraints(self, cons: List[_GeneralConstraintData]): + pass + + @abc.abstractmethod + def add_block(self, block: _BlockData): + pass + + @abc.abstractmethod + def remove_variables(self, variables: List[_GeneralVarData]): + pass + + @abc.abstractmethod + def remove_params(self, params: List[_ParamData]): + pass + + @abc.abstractmethod + def remove_constraints(self, cons: List[_GeneralConstraintData]): + pass + + @abc.abstractmethod + def remove_block(self, block: _BlockData): + pass + + @abc.abstractmethod + def set_objective(self, obj: _GeneralObjectiveData): + pass + + @abc.abstractmethod + def update_variables(self, variables: List[_GeneralVarData]): + pass + + @abc.abstractmethod + def update_params(self): + pass + + +class PersistentSolutionLoader(SolutionLoaderBase): + def __init__(self, solver: PersistentSolver): + self._solver = solver + self._valid = True + + def _assert_solution_still_valid(self): + if not self._valid: + raise RuntimeError('The results in the solver are no longer valid.') + + def get_primals(self, vars_to_load=None): + self._assert_solution_still_valid() + return self._solver.get_primals(vars_to_load=vars_to_load) + + def get_duals( + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None + ) -> Dict[_GeneralConstraintData, float]: + self._assert_solution_still_valid() + return self._solver.get_duals(cons_to_load=cons_to_load) + + def get_slacks( + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None + ) -> Dict[_GeneralConstraintData, float]: + self._assert_solution_still_valid() + return self._solver.get_slacks(cons_to_load=cons_to_load) + + def get_reduced_costs( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> Mapping[_GeneralVarData, float]: + self._assert_solution_still_valid() + return self._solver.get_reduced_costs(vars_to_load=vars_to_load) + + def invalidate(self): + self._valid = False + + +""" +What can change in a pyomo model? +- variables added or removed +- constraints added or removed +- objective changed +- objective expr changed +- params added or removed +- variable modified + - lb + - ub + - fixed or unfixed + - domain + - value +- constraint modified + - lower + - upper + - body + - active or not +- named expressions modified + - expr +- param modified + - value + +Ideas: +- Consider explicitly handling deactivated constraints; favor deactivation over removal + and activation over addition + +Notes: +- variable bounds cannot be updated with mutable params; you must call update_variables +""" + + +class PersistentBase(abc.ABC): + def __init__(self, only_child_vars=False): + self._model = None + self._active_constraints = dict() # maps constraint to (lower, body, upper) + self._vars = dict() # maps var id to (var, lb, ub, fixed, domain, value) + self._params = dict() # maps param id to param + self._objective = None + self._objective_expr = None + self._objective_sense = None + self._named_expressions = ( + dict() + ) # maps constraint to list of tuples (named_expr, named_expr.expr) + self._external_functions = ComponentMap() + self._obj_named_expressions = list() + self._update_config = UpdateConfig() + self._referenced_variables = ( + dict() + ) # var_id: [dict[constraints, None], dict[sos constraints, None], None or objective] + self._vars_referenced_by_con = dict() + self._vars_referenced_by_obj = list() + self._expr_types = None + self.use_extensions = False + self._only_child_vars = only_child_vars + + @property + def update_config(self): + return self._update_config + + @update_config.setter + def update_config(self, val: UpdateConfig): + self._update_config = val + + def set_instance(self, model): + saved_update_config = self.update_config + self.__init__(only_child_vars=self._only_child_vars) + self.update_config = saved_update_config + self._model = model + if self.use_extensions and cmodel_available: + self._expr_types = cmodel.PyomoExprTypes() + self.add_block(model) + if self._objective is None: + self.set_objective(None) + + @abc.abstractmethod + def _add_variables(self, variables: List[_GeneralVarData]): + pass + + def add_variables(self, variables: List[_GeneralVarData]): + for v in variables: + if id(v) in self._referenced_variables: + raise ValueError( + 'variable {name} has already been added'.format(name=v.name) + ) + self._referenced_variables[id(v)] = [dict(), dict(), None] + self._vars[id(v)] = ( + v, + v._lb, + v._ub, + v.fixed, + v.domain.get_interval(), + v.value, + ) + self._add_variables(variables) + + @abc.abstractmethod + def _add_params(self, params: List[_ParamData]): + pass + + def add_params(self, params: List[_ParamData]): + for p in params: + self._params[id(p)] = p + self._add_params(params) + + @abc.abstractmethod + def _add_constraints(self, cons: List[_GeneralConstraintData]): + pass + + def _check_for_new_vars(self, variables: List[_GeneralVarData]): + new_vars = dict() + for v in variables: + v_id = id(v) + if v_id not in self._referenced_variables: + new_vars[v_id] = v + self.add_variables(list(new_vars.values())) + + def _check_to_remove_vars(self, variables: List[_GeneralVarData]): + vars_to_remove = dict() + for v in variables: + v_id = id(v) + ref_cons, ref_sos, ref_obj = self._referenced_variables[v_id] + if len(ref_cons) == 0 and len(ref_sos) == 0 and ref_obj is None: + vars_to_remove[v_id] = v + self.remove_variables(list(vars_to_remove.values())) + + def add_constraints(self, cons: List[_GeneralConstraintData]): + all_fixed_vars = dict() + for con in cons: + if con in self._named_expressions: + raise ValueError( + 'constraint {name} has already been added'.format(name=con.name) + ) + self._active_constraints[con] = (con.lower, con.body, con.upper) + if self.use_extensions and cmodel_available: + tmp = cmodel.prep_for_repn(con.body, self._expr_types) + else: + tmp = collect_vars_and_named_exprs(con.body) + named_exprs, variables, fixed_vars, external_functions = tmp + if not self._only_child_vars: + self._check_for_new_vars(variables) + self._named_expressions[con] = [(e, e.expr) for e in named_exprs] + if len(external_functions) > 0: + self._external_functions[con] = external_functions + self._vars_referenced_by_con[con] = variables + for v in variables: + self._referenced_variables[id(v)][0][con] = None + if not self.update_config.treat_fixed_vars_as_params: + for v in fixed_vars: + v.unfix() + all_fixed_vars[id(v)] = v + self._add_constraints(cons) + for v in all_fixed_vars.values(): + v.fix() + + @abc.abstractmethod + def _add_sos_constraints(self, cons: List[_SOSConstraintData]): + pass + + def add_sos_constraints(self, cons: List[_SOSConstraintData]): + for con in cons: + if con in self._vars_referenced_by_con: + raise ValueError( + 'constraint {name} has already been added'.format(name=con.name) + ) + self._active_constraints[con] = tuple() + variables = con.get_variables() + if not self._only_child_vars: + self._check_for_new_vars(variables) + self._named_expressions[con] = list() + self._vars_referenced_by_con[con] = variables + for v in variables: + self._referenced_variables[id(v)][1][con] = None + self._add_sos_constraints(cons) + + @abc.abstractmethod + def _set_objective(self, obj: _GeneralObjectiveData): + pass + + def set_objective(self, obj: _GeneralObjectiveData): + if self._objective is not None: + for v in self._vars_referenced_by_obj: + self._referenced_variables[id(v)][2] = None + if not self._only_child_vars: + self._check_to_remove_vars(self._vars_referenced_by_obj) + self._external_functions.pop(self._objective, None) + if obj is not None: + self._objective = obj + self._objective_expr = obj.expr + self._objective_sense = obj.sense + if self.use_extensions and cmodel_available: + tmp = cmodel.prep_for_repn(obj.expr, self._expr_types) + else: + tmp = collect_vars_and_named_exprs(obj.expr) + named_exprs, variables, fixed_vars, external_functions = tmp + if not self._only_child_vars: + self._check_for_new_vars(variables) + self._obj_named_expressions = [(i, i.expr) for i in named_exprs] + if len(external_functions) > 0: + self._external_functions[obj] = external_functions + self._vars_referenced_by_obj = variables + for v in variables: + self._referenced_variables[id(v)][2] = obj + if not self.update_config.treat_fixed_vars_as_params: + for v in fixed_vars: + v.unfix() + self._set_objective(obj) + for v in fixed_vars: + v.fix() + else: + self._vars_referenced_by_obj = list() + self._objective = None + self._objective_expr = None + self._objective_sense = None + self._obj_named_expressions = list() + self._set_objective(obj) + + def add_block(self, block): + param_dict = dict() + for p in block.component_objects(Param, descend_into=True): + if p.mutable: + for _p in p.values(): + param_dict[id(_p)] = _p + self.add_params(list(param_dict.values())) + if self._only_child_vars: + self.add_variables( + list( + dict( + (id(var), var) + for var in block.component_data_objects(Var, descend_into=True) + ).values() + ) + ) + self.add_constraints( + [ + con + for con in block.component_data_objects( + Constraint, descend_into=True, active=True + ) + ] + ) + self.add_sos_constraints( + [ + con + for con in block.component_data_objects( + SOSConstraint, descend_into=True, active=True + ) + ] + ) + obj = get_objective(block) + if obj is not None: + self.set_objective(obj) + + @abc.abstractmethod + def _remove_constraints(self, cons: List[_GeneralConstraintData]): + pass + + def remove_constraints(self, cons: List[_GeneralConstraintData]): + self._remove_constraints(cons) + for con in cons: + if con not in self._named_expressions: + raise ValueError( + 'cannot remove constraint {name} - it was not added'.format( + name=con.name + ) + ) + for v in self._vars_referenced_by_con[con]: + self._referenced_variables[id(v)][0].pop(con) + if not self._only_child_vars: + self._check_to_remove_vars(self._vars_referenced_by_con[con]) + del self._active_constraints[con] + del self._named_expressions[con] + self._external_functions.pop(con, None) + del self._vars_referenced_by_con[con] + + @abc.abstractmethod + def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): + pass + + def remove_sos_constraints(self, cons: List[_SOSConstraintData]): + self._remove_sos_constraints(cons) + for con in cons: + if con not in self._vars_referenced_by_con: + raise ValueError( + 'cannot remove constraint {name} - it was not added'.format( + name=con.name + ) + ) + for v in self._vars_referenced_by_con[con]: + self._referenced_variables[id(v)][1].pop(con) + self._check_to_remove_vars(self._vars_referenced_by_con[con]) + del self._active_constraints[con] + del self._named_expressions[con] + del self._vars_referenced_by_con[con] + + @abc.abstractmethod + def _remove_variables(self, variables: List[_GeneralVarData]): + pass + + def remove_variables(self, variables: List[_GeneralVarData]): + self._remove_variables(variables) + for v in variables: + v_id = id(v) + if v_id not in self._referenced_variables: + raise ValueError( + 'cannot remove variable {name} - it has not been added'.format( + name=v.name + ) + ) + cons_using, sos_using, obj_using = self._referenced_variables[v_id] + if cons_using or sos_using or (obj_using is not None): + raise ValueError( + 'cannot remove variable {name} - it is still being used by constraints or the objective'.format( + name=v.name + ) + ) + del self._referenced_variables[v_id] + del self._vars[v_id] + + @abc.abstractmethod + def _remove_params(self, params: List[_ParamData]): + pass + + def remove_params(self, params: List[_ParamData]): + self._remove_params(params) + for p in params: + del self._params[id(p)] + + def remove_block(self, block): + self.remove_constraints( + [ + con + for con in block.component_data_objects( + ctype=Constraint, descend_into=True, active=True + ) + ] + ) + self.remove_sos_constraints( + [ + con + for con in block.component_data_objects( + ctype=SOSConstraint, descend_into=True, active=True + ) + ] + ) + if self._only_child_vars: + self.remove_variables( + list( + dict( + (id(var), var) + for var in block.component_data_objects( + ctype=Var, descend_into=True + ) + ).values() + ) + ) + self.remove_params( + list( + dict( + (id(p), p) + for p in block.component_data_objects( + ctype=Param, descend_into=True + ) + ).values() + ) + ) + + @abc.abstractmethod + def _update_variables(self, variables: List[_GeneralVarData]): + pass + + def update_variables(self, variables: List[_GeneralVarData]): + for v in variables: + self._vars[id(v)] = ( + v, + v._lb, + v._ub, + v.fixed, + v.domain.get_interval(), + v.value, + ) + self._update_variables(variables) + + @abc.abstractmethod + def update_params(self): + pass + + def update(self, timer: HierarchicalTimer = None): + if timer is None: + timer = HierarchicalTimer() + config = self.update_config + new_vars = list() + old_vars = list() + new_params = list() + old_params = list() + new_cons = list() + old_cons = list() + old_sos = list() + new_sos = list() + current_vars_dict = dict() + current_cons_dict = dict() + current_sos_dict = dict() + timer.start('vars') + if self._only_child_vars and ( + config.check_for_new_or_removed_vars or config.update_vars + ): + current_vars_dict = { + id(v): v + for v in self._model.component_data_objects(Var, descend_into=True) + } + for v_id, v in current_vars_dict.items(): + if v_id not in self._vars: + new_vars.append(v) + for v_id, v_tuple in self._vars.items(): + if v_id not in current_vars_dict: + old_vars.append(v_tuple[0]) + elif config.update_vars: + start_vars = {v_id: v_tuple[0] for v_id, v_tuple in self._vars.items()} + timer.stop('vars') + timer.start('params') + if config.check_for_new_or_removed_params: + current_params_dict = dict() + for p in self._model.component_objects(Param, descend_into=True): + if p.mutable: + for _p in p.values(): + current_params_dict[id(_p)] = _p + for p_id, p in current_params_dict.items(): + if p_id not in self._params: + new_params.append(p) + for p_id, p in self._params.items(): + if p_id not in current_params_dict: + old_params.append(p) + timer.stop('params') + timer.start('cons') + if config.check_for_new_or_removed_constraints or config.update_constraints: + current_cons_dict = { + c: None + for c in self._model.component_data_objects( + Constraint, descend_into=True, active=True + ) + } + current_sos_dict = { + c: None + for c in self._model.component_data_objects( + SOSConstraint, descend_into=True, active=True + ) + } + for c in current_cons_dict.keys(): + if c not in self._vars_referenced_by_con: + new_cons.append(c) + for c in current_sos_dict.keys(): + if c not in self._vars_referenced_by_con: + new_sos.append(c) + for c in self._vars_referenced_by_con.keys(): + if c not in current_cons_dict and c not in current_sos_dict: + if (c.ctype is Constraint) or ( + c.ctype is None and isinstance(c, _GeneralConstraintData) + ): + old_cons.append(c) + else: + assert (c.ctype is SOSConstraint) or ( + c.ctype is None and isinstance(c, _SOSConstraintData) + ) + old_sos.append(c) + self.remove_constraints(old_cons) + self.remove_sos_constraints(old_sos) + timer.stop('cons') + timer.start('params') + self.remove_params(old_params) + + # sticking this between removal and addition + # is important so that we don't do unnecessary work + if config.update_params: + self.update_params() + + self.add_params(new_params) + timer.stop('params') + timer.start('vars') + self.add_variables(new_vars) + timer.stop('vars') + timer.start('cons') + self.add_constraints(new_cons) + self.add_sos_constraints(new_sos) + new_cons_set = set(new_cons) + new_sos_set = set(new_sos) + new_vars_set = set(id(v) for v in new_vars) + cons_to_remove_and_add = dict() + need_to_set_objective = False + if config.update_constraints: + cons_to_update = list() + sos_to_update = list() + for c in current_cons_dict.keys(): + if c not in new_cons_set: + cons_to_update.append(c) + for c in current_sos_dict.keys(): + if c not in new_sos_set: + sos_to_update.append(c) + for c in cons_to_update: + lower, body, upper = self._active_constraints[c] + new_lower, new_body, new_upper = c.lower, c.body, c.upper + if new_body is not body: + cons_to_remove_and_add[c] = None + continue + if new_lower is not lower: + if ( + type(new_lower) is NumericConstant + and type(lower) is NumericConstant + and new_lower.value == lower.value + ): + pass + else: + cons_to_remove_and_add[c] = None + continue + if new_upper is not upper: + if ( + type(new_upper) is NumericConstant + and type(upper) is NumericConstant + and new_upper.value == upper.value + ): + pass + else: + cons_to_remove_and_add[c] = None + continue + self.remove_sos_constraints(sos_to_update) + self.add_sos_constraints(sos_to_update) + timer.stop('cons') + timer.start('vars') + if self._only_child_vars and config.update_vars: + vars_to_check = list() + for v_id, v in current_vars_dict.items(): + if v_id not in new_vars_set: + vars_to_check.append(v) + elif config.update_vars: + end_vars = {v_id: v_tuple[0] for v_id, v_tuple in self._vars.items()} + vars_to_check = [v for v_id, v in end_vars.items() if v_id in start_vars] + if config.update_vars: + vars_to_update = list() + for v in vars_to_check: + _v, lb, ub, fixed, domain_interval, value = self._vars[id(v)] + if (fixed != v.fixed) or (fixed and (value != v.value)): + vars_to_update.append(v) + if self.update_config.treat_fixed_vars_as_params: + for c in self._referenced_variables[id(v)][0]: + cons_to_remove_and_add[c] = None + if self._referenced_variables[id(v)][2] is not None: + need_to_set_objective = True + elif lb is not v._lb: + vars_to_update.append(v) + elif ub is not v._ub: + vars_to_update.append(v) + elif domain_interval != v.domain.get_interval(): + vars_to_update.append(v) + self.update_variables(vars_to_update) + timer.stop('vars') + timer.start('cons') + cons_to_remove_and_add = list(cons_to_remove_and_add.keys()) + self.remove_constraints(cons_to_remove_and_add) + self.add_constraints(cons_to_remove_and_add) + timer.stop('cons') + timer.start('named expressions') + if config.update_named_expressions: + cons_to_update = list() + for c, expr_list in self._named_expressions.items(): + if c in new_cons_set: + continue + for named_expr, old_expr in expr_list: + if named_expr.expr is not old_expr: + cons_to_update.append(c) + break + self.remove_constraints(cons_to_update) + self.add_constraints(cons_to_update) + for named_expr, old_expr in self._obj_named_expressions: + if named_expr.expr is not old_expr: + need_to_set_objective = True + break + timer.stop('named expressions') + timer.start('objective') + if self.update_config.check_for_new_objective: + pyomo_obj = get_objective(self._model) + if pyomo_obj is not self._objective: + need_to_set_objective = True + else: + pyomo_obj = self._objective + if self.update_config.update_objective: + if pyomo_obj is not None and pyomo_obj.expr is not self._objective_expr: + need_to_set_objective = True + elif pyomo_obj is not None and pyomo_obj.sense is not self._objective_sense: + # we can definitely do something faster here than resetting the whole objective + need_to_set_objective = True + if need_to_set_objective: + self.set_objective(pyomo_obj) + timer.stop('objective') + + # this has to be done after the objective and constraints in case the + # old objective/constraints use old variables + timer.start('vars') + self.remove_variables(old_vars) + timer.stop('vars') + + +legacy_termination_condition_map = { + TerminationCondition.unknown: LegacyTerminationCondition.unknown, + TerminationCondition.maxTimeLimit: LegacyTerminationCondition.maxTimeLimit, + TerminationCondition.maxIterations: LegacyTerminationCondition.maxIterations, + TerminationCondition.objectiveLimit: LegacyTerminationCondition.minFunctionValue, + TerminationCondition.minStepLength: LegacyTerminationCondition.minStepLength, + TerminationCondition.optimal: LegacyTerminationCondition.optimal, + TerminationCondition.unbounded: LegacyTerminationCondition.unbounded, + TerminationCondition.infeasible: LegacyTerminationCondition.infeasible, + TerminationCondition.infeasibleOrUnbounded: LegacyTerminationCondition.infeasibleOrUnbounded, + TerminationCondition.error: LegacyTerminationCondition.error, + TerminationCondition.interrupted: LegacyTerminationCondition.resourceInterrupt, + TerminationCondition.licensingProblems: LegacyTerminationCondition.licensingProblems, +} + + +legacy_solver_status_map = { + TerminationCondition.unknown: LegacySolverStatus.unknown, + TerminationCondition.maxTimeLimit: LegacySolverStatus.aborted, + TerminationCondition.maxIterations: LegacySolverStatus.aborted, + TerminationCondition.objectiveLimit: LegacySolverStatus.aborted, + TerminationCondition.minStepLength: LegacySolverStatus.error, + TerminationCondition.optimal: LegacySolverStatus.ok, + TerminationCondition.unbounded: LegacySolverStatus.error, + TerminationCondition.infeasible: LegacySolverStatus.error, + TerminationCondition.infeasibleOrUnbounded: LegacySolverStatus.error, + TerminationCondition.error: LegacySolverStatus.error, + TerminationCondition.interrupted: LegacySolverStatus.aborted, + TerminationCondition.licensingProblems: LegacySolverStatus.error, +} + + +legacy_solution_status_map = { + TerminationCondition.unknown: LegacySolutionStatus.unknown, + TerminationCondition.maxTimeLimit: LegacySolutionStatus.stoppedByLimit, + TerminationCondition.maxIterations: LegacySolutionStatus.stoppedByLimit, + TerminationCondition.objectiveLimit: LegacySolutionStatus.stoppedByLimit, + TerminationCondition.minStepLength: LegacySolutionStatus.error, + TerminationCondition.optimal: LegacySolutionStatus.optimal, + TerminationCondition.unbounded: LegacySolutionStatus.unbounded, + TerminationCondition.infeasible: LegacySolutionStatus.infeasible, + TerminationCondition.infeasibleOrUnbounded: LegacySolutionStatus.unsure, + TerminationCondition.error: LegacySolutionStatus.error, + TerminationCondition.interrupted: LegacySolutionStatus.error, + TerminationCondition.licensingProblems: LegacySolutionStatus.error, +} + + +class LegacySolverInterface(object): + def solve( + self, + model: _BlockData, + tee: bool = False, + load_solutions: bool = True, + logfile: Optional[str] = None, + solnfile: Optional[str] = None, + timelimit: Optional[float] = None, + report_timing: bool = False, + solver_io: Optional[str] = None, + suffixes: Optional[Sequence] = None, + options: Optional[Dict] = None, + keepfiles: bool = False, + symbolic_solver_labels: bool = False, + ): + original_config = self.config + self.config = self.config() + self.config.stream_solver = tee + self.config.load_solution = load_solutions + self.config.symbolic_solver_labels = symbolic_solver_labels + self.config.time_limit = timelimit + self.config.report_timing = report_timing + if solver_io is not None: + raise NotImplementedError('Still working on this') + if suffixes is not None: + raise NotImplementedError('Still working on this') + if logfile is not None: + raise NotImplementedError('Still working on this') + if 'keepfiles' in self.config: + self.config.keepfiles = keepfiles + if solnfile is not None: + if 'filename' in self.config: + filename = os.path.splitext(solnfile)[0] + self.config.filename = filename + original_options = self.options + if options is not None: + self.options = options + + results: Results = super(LegacySolverInterface, self).solve(model) + + legacy_results = LegacySolverResults() + legacy_soln = LegacySolution() + legacy_results.solver.status = legacy_solver_status_map[ + results.termination_condition + ] + legacy_results.solver.termination_condition = legacy_termination_condition_map[ + results.termination_condition + ] + legacy_soln.status = legacy_solution_status_map[results.termination_condition] + legacy_results.solver.termination_message = str(results.termination_condition) + + obj = get_objective(model) + legacy_results.problem.sense = obj.sense + + if obj.sense == minimize: + legacy_results.problem.lower_bound = results.best_objective_bound + legacy_results.problem.upper_bound = results.best_feasible_objective + else: + legacy_results.problem.upper_bound = results.best_objective_bound + legacy_results.problem.lower_bound = results.best_feasible_objective + if ( + results.best_feasible_objective is not None + and results.best_objective_bound is not None + ): + legacy_soln.gap = abs( + results.best_feasible_objective - results.best_objective_bound + ) + else: + legacy_soln.gap = None + + symbol_map = SymbolMap() + symbol_map.byObject = dict(self.symbol_map.byObject) + symbol_map.bySymbol = dict(self.symbol_map.bySymbol) + symbol_map.aliases = dict(self.symbol_map.aliases) + symbol_map.default_labeler = self.symbol_map.default_labeler + model.solutions.add_symbol_map(symbol_map) + legacy_results._smap_id = id(symbol_map) + + delete_legacy_soln = True + if load_solutions: + if hasattr(model, 'dual') and model.dual.import_enabled(): + for c, val in results.solution_loader.get_duals().items(): + model.dual[c] = val + if hasattr(model, 'slack') and model.slack.import_enabled(): + for c, val in results.solution_loader.get_slacks().items(): + model.slack[c] = val + if hasattr(model, 'rc') and model.rc.import_enabled(): + for v, val in results.solution_loader.get_reduced_costs().items(): + model.rc[v] = val + elif results.best_feasible_objective is not None: + delete_legacy_soln = False + for v, val in results.solution_loader.get_primals().items(): + legacy_soln.variable[symbol_map.getSymbol(v)] = {'Value': val} + if hasattr(model, 'dual') and model.dual.import_enabled(): + for c, val in results.solution_loader.get_duals().items(): + legacy_soln.constraint[symbol_map.getSymbol(c)] = {'Dual': val} + if hasattr(model, 'slack') and model.slack.import_enabled(): + for c, val in results.solution_loader.get_slacks().items(): + symbol = symbol_map.getSymbol(c) + if symbol in legacy_soln.constraint: + legacy_soln.constraint[symbol]['Slack'] = val + if hasattr(model, 'rc') and model.rc.import_enabled(): + for v, val in results.solution_loader.get_reduced_costs().items(): + legacy_soln.variable['Rc'] = val + + legacy_results.solution.insert(legacy_soln) + if delete_legacy_soln: + legacy_results.solution.delete(0) + + self.config = original_config + self.options = original_options + + return legacy_results + + def available(self, exception_flag=True): + ans = super(LegacySolverInterface, self).available() + if exception_flag and not ans: + raise ApplicationError(f'Solver {self.__class__} is not available ({ans}).') + return bool(ans) + + def license_is_valid(self) -> bool: + """Test if the solver license is valid on this system. + + Note that this method is included for compatibility with the + legacy SolverFactory interface. Unlicensed or open source + solvers will return True by definition. Licensed solvers will + return True if a valid license is found. + + Returns + ------- + available: bool + True if the solver license is valid. Otherwise, False. + + """ + return bool(self.available()) + + @property + def options(self): + for solver_name in ['gurobi', 'ipopt', 'cplex', 'cbc', 'highs']: + if hasattr(self, solver_name + '_options'): + return getattr(self, solver_name + '_options') + raise NotImplementedError('Could not find the correct options') + + @options.setter + def options(self, val): + found = False + for solver_name in ['gurobi', 'ipopt', 'cplex', 'cbc', 'highs']: + if hasattr(self, solver_name + '_options'): + setattr(self, solver_name + '_options', val) + found = True + if not found: + raise NotImplementedError('Could not find the correct options') + + def __enter__(self): + return self + + def __exit__(self, t, v, traceback): + pass + + +class SolverFactoryClass(Factory): + def register(self, name, doc=None): + def decorator(cls): + self._cls[name] = cls + self._doc[name] = doc + + class LegacySolver(LegacySolverInterface, cls): + pass + + LegacySolverFactory.register(name, doc)(LegacySolver) + + return cls + + return decorator + + +SolverFactory = SolverFactoryClass() diff --git a/pyomo/contrib/appsi/build.py b/pyomo/contrib/appsi/build.py index c00da19eae8..2c8d02dd3ac 100644 --- a/pyomo/contrib/appsi/build.py +++ b/pyomo/contrib/appsi/build.py @@ -9,9 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -import errno import shutil -import stat import glob import os import sys @@ -81,7 +79,7 @@ def run(self): print("Building in '%s'" % tmpdir) os.chdir(tmpdir) try: - super().run() + super(appsi_build_ext, self).run() if not self.inplace: library = glob.glob("build/*/appsi_cmodel.*")[0] target = os.path.join( @@ -118,7 +116,7 @@ def run(self): pybind11.setup_helpers.MACOS = original_pybind11_setup_helpers_macos -class AppsiBuilder: +class AppsiBuilder(object): def __call__(self, parallel): return build_appsi() diff --git a/pyomo/contrib/appsi/examples/getting_started.py b/pyomo/contrib/appsi/examples/getting_started.py index 15c3fcb2058..de22d28e0a4 100644 --- a/pyomo/contrib/appsi/examples/getting_started.py +++ b/pyomo/contrib/appsi/examples/getting_started.py @@ -1,7 +1,6 @@ import pyomo.environ as pe from pyomo.contrib import appsi from pyomo.common.timing import HierarchicalTimer -from pyomo.contrib.solver import results def main(plot=True, n_points=200): @@ -17,7 +16,7 @@ def main(plot=True, n_points=200): m.c1 = pe.Constraint(expr=m.y >= (m.x + 1) ** 2) m.c2 = pe.Constraint(expr=m.y >= (m.x - m.p) ** 2) - opt = appsi.solvers.Ipopt() # create an APPSI solver interface + opt = appsi.solvers.Cplex() # create an APPSI solver interface opt.config.load_solution = False # modify the config options # change how automatic updates are handled opt.update_config.check_for_new_or_removed_vars = False @@ -25,18 +24,15 @@ def main(plot=True, n_points=200): # write a for loop to vary the value of parameter p from 1 to 10 p_values = [float(i) for i in np.linspace(1, 10, n_points)] - obj_values = [] - x_values = [] + obj_values = list() + x_values = list() timer = HierarchicalTimer() # create a timer for some basic profiling timer.start('p loop') for p_val in p_values: m.p.value = p_val res = opt.solve(m, timer=timer) - assert ( - res.termination_condition - == results.TerminationCondition.convergenceCriteriaSatisfied - ) - obj_values.append(res.incumbent_objective) + assert res.termination_condition == appsi.base.TerminationCondition.optimal + obj_values.append(res.best_feasible_objective) opt.load_vars([m.x]) x_values.append(m.x.value) timer.stop('p loop') diff --git a/pyomo/contrib/appsi/examples/tests/test_examples.py b/pyomo/contrib/appsi/examples/tests/test_examples.py index 7c577366c41..d2c88224a7d 100644 --- a/pyomo/contrib/appsi/examples/tests/test_examples.py +++ b/pyomo/contrib/appsi/examples/tests/test_examples.py @@ -1,17 +1,18 @@ from pyomo.contrib.appsi.examples import getting_started -from pyomo.common import unittest -from pyomo.common.dependencies import attempt_import +import pyomo.common.unittest as unittest +import pyomo.environ as pe from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.contrib import appsi -numpy, numpy_available = attempt_import('numpy') - @unittest.skipUnless(cmodel_available, 'appsi extensions are not available') -@unittest.skipUnless(numpy_available, 'numpy is not available') class TestExamples(unittest.TestCase): def test_getting_started(self): - opt = appsi.solvers.Ipopt() + try: + import numpy as np + except: + raise unittest.SkipTest('numpy is not available') + opt = appsi.solvers.Cplex() if not opt.available(): - raise unittest.SkipTest('ipopt is not available') + raise unittest.SkipTest('cplex is not available') getting_started.main(plot=False, n_points=10) diff --git a/pyomo/contrib/appsi/fbbt.py b/pyomo/contrib/appsi/fbbt.py index ccbb3819554..92a0e0c8cbc 100644 --- a/pyomo/contrib/appsi/fbbt.py +++ b/pyomo/contrib/appsi/fbbt.py @@ -1,4 +1,4 @@ -from pyomo.contrib.solver.util import PersistentSolverUtils +from pyomo.contrib.appsi.base import PersistentBase from pyomo.common.config import ( ConfigDict, ConfigValue, @@ -11,9 +11,10 @@ from pyomo.core.base.param import _ParamData from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.sos import _SOSConstraintData -from pyomo.core.base.objective import _GeneralObjectiveData, minimize +from pyomo.core.base.objective import _GeneralObjectiveData, minimize, maximize from pyomo.core.base.block import _BlockData from pyomo.core.base import SymbolMap, TextLabeler +from pyomo.common.errors import InfeasibleConstraintException class IntervalConfig(ConfigDict): @@ -34,7 +35,7 @@ def __init__( implicit_domain=None, visibility=0, ): - super().__init__( + super(IntervalConfig, self).__init__( description=description, doc=doc, implicit=implicit, @@ -59,16 +60,16 @@ def __init__( ) -class IntervalTightener(PersistentSolverUtils): +class IntervalTightener(PersistentBase): def __init__(self): - super().__init__() + super(IntervalTightener, self).__init__() self._config = IntervalConfig() self._cmodel = None - self._var_map = {} - self._con_map = {} - self._param_map = {} - self._rvar_map = {} - self._rcon_map = {} + self._var_map = dict() + self._con_map = dict() + self._param_map = dict() + self._rvar_map = dict() + self._rcon_map = dict() self._pyomo_expr_types = cmodel.PyomoExprTypes() self._symbolic_solver_labels: bool = False self._symbol_map = SymbolMap() @@ -253,7 +254,7 @@ def _update_pyomo_var_bounds(self): self._vars[v_id] = (_v, _lb, cv_ub, _fixed, _domain, _value) def _deactivate_satisfied_cons(self): - cons_to_deactivate = [] + cons_to_deactivate = list() if self.config.deactivate_satisfied_constraints: for c, cc in self._con_map.items(): if not cc.active: diff --git a/pyomo/contrib/appsi/plugins.py b/pyomo/contrib/appsi/plugins.py index ebccba09ab2..5333158239e 100644 --- a/pyomo/contrib/appsi/plugins.py +++ b/pyomo/contrib/appsi/plugins.py @@ -1,5 +1,5 @@ from pyomo.common.extensions import ExtensionBuilderFactory -from pyomo.contrib.solver.factory import SolverFactory +from .base import SolverFactory from .solvers import Gurobi, Ipopt, Cbc, Cplex, Highs from .build import AppsiBuilder diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index 141c6de57bd..a3aae2a9213 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -1,16 +1,20 @@ -import logging -import math -import subprocess -import sys -from typing import Optional, Sequence, Dict, List, Mapping - - from pyomo.common.tempfiles import TempfileManager from pyomo.common.fileutils import Executable +from pyomo.contrib.appsi.base import ( + PersistentSolver, + Results, + TerminationCondition, + SolverConfig, + PersistentSolutionLoader, +) from pyomo.contrib.appsi.writers import LPWriter from pyomo.common.log import LogStream +import logging +import subprocess from pyomo.core.kernel.objective import minimize, maximize +import math from pyomo.common.collections import ComponentMap +from typing import Optional, Sequence, NoReturn, List, Mapping from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.block import _BlockData @@ -18,14 +22,12 @@ from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.common.timing import HierarchicalTimer from pyomo.common.tee import TeeStream +import sys +from typing import Dict from pyomo.common.config import ConfigValue, NonNegativeInt from pyomo.common.errors import PyomoException from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager -from pyomo.contrib.solver.base import PersistentSolverBase -from pyomo.contrib.solver.config import SolverConfig -from pyomo.contrib.solver.results import TerminationCondition, Results -from pyomo.contrib.solver.solution import PersistentSolutionLoader logger = logging.getLogger(__name__) @@ -40,7 +42,7 @@ def __init__( implicit_domain=None, visibility=0, ): - super().__init__( + super(CbcConfig, self).__init__( description=description, doc=doc, implicit=implicit, @@ -61,15 +63,15 @@ def __init__( self.log_level = logging.INFO -class Cbc(PersistentSolverBase): +class Cbc(PersistentSolver): def __init__(self, only_child_vars=False): self._config = CbcConfig() - self._solver_options = {} + self._solver_options = dict() self._writer = LPWriter(only_child_vars=only_child_vars) self._filename = None - self._dual_sol = {} - self._primal_sol = {} - self._reduced_costs = {} + self._dual_sol = dict() + self._primal_sol = dict() + self._reduced_costs = dict() self._last_results_object: Optional[Results] = None def available(self): @@ -230,19 +232,17 @@ def _parse_soln(self): termination_line = all_lines[0].lower() obj_val = None if termination_line.startswith('optimal'): - results.termination_condition = ( - TerminationCondition.convergenceCriteriaSatisfied - ) + results.termination_condition = TerminationCondition.optimal obj_val = float(termination_line.split()[-1]) elif 'infeasible' in termination_line: - results.termination_condition = TerminationCondition.provenInfeasible + results.termination_condition = TerminationCondition.infeasible elif 'unbounded' in termination_line: results.termination_condition = TerminationCondition.unbounded elif termination_line.startswith('stopped on time'): results.termination_condition = TerminationCondition.maxTimeLimit obj_val = float(termination_line.split()[-1]) elif termination_line.startswith('stopped on iterations'): - results.termination_condition = TerminationCondition.iterationLimit + results.termination_condition = TerminationCondition.maxIterations obj_val = float(termination_line.split()[-1]) else: results.termination_condition = TerminationCondition.unknown @@ -261,9 +261,9 @@ def _parse_soln(self): first_var_line = ndx last_var_line = len(all_lines) - 1 - self._dual_sol = {} - self._primal_sol = {} - self._reduced_costs = {} + self._dual_sol = dict() + self._primal_sol = dict() + self._reduced_costs = dict() symbol_map = self._writer.symbol_map @@ -307,30 +307,26 @@ def _parse_soln(self): self._reduced_costs[v_id] = (v, -rc_val) if ( - results.termination_condition - == TerminationCondition.convergenceCriteriaSatisfied + results.termination_condition == TerminationCondition.optimal and self.config.load_solution ): for v_id, (v, val) in self._primal_sol.items(): v.set_value(val, skip_validation=True) if self._writer.get_active_objective() is None: - results.incumbent_objective = None + results.best_feasible_objective = None else: - results.incumbent_objective = obj_val - elif ( - results.termination_condition - == TerminationCondition.convergenceCriteriaSatisfied - ): + results.best_feasible_objective = obj_val + elif results.termination_condition == TerminationCondition.optimal: if self._writer.get_active_objective() is None: - results.incumbent_objective = None + results.best_feasible_objective = None else: - results.incumbent_objective = obj_val + results.best_feasible_objective = obj_val elif self.config.load_solution: raise RuntimeError( 'A feasible solution was not found, so no solution can be loaded.' 'Please set opt.config.load_solution=False and check ' 'results.termination_condition and ' - 'results.incumbent_objective before loading a solution.' + 'results.best_feasible_objective before loading a solution.' ) return results @@ -366,7 +362,7 @@ def _check_and_escape_options(): yield tmp_k, tmp_v cmd = [str(config.executable)] - action_options = [] + action_options = list() if config.time_limit is not None: cmd.extend(['-sec', str(config.time_limit)]) cmd.extend(['-timeMode', 'elapsed']) @@ -387,7 +383,7 @@ def _check_and_escape_options(): level=self.config.log_level, logger=self.config.solver_output_logger ) ] - if self.config.tee: + if self.config.stream_solver: ostreams.append(sys.stdout) with TeeStream(*ostreams) as t: @@ -407,24 +403,24 @@ def _check_and_escape_options(): 'A feasible solution was not found, so no solution can be loaded.' 'Please set opt.config.load_solution=False and check ' 'results.termination_condition and ' - 'results.incumbent_objective before loading a solution.' + 'results.best_feasible_objective before loading a solution.' ) results = Results() results.termination_condition = TerminationCondition.error - results.incumbent_objective = None + results.best_feasible_objective = None else: timer.start('parse solution') results = self._parse_soln() timer.stop('parse solution') if self._writer.get_active_objective() is None: - results.incumbent_objective = None - results.objective_bound = None + results.best_feasible_objective = None + results.best_objective_bound = None else: if self._writer.get_active_objective().sense == minimize: - results.objective_bound = -math.inf + results.best_objective_bound = -math.inf else: - results.objective_bound = math.inf + results.best_objective_bound = math.inf results.solution_loader = PersistentSolutionLoader(solver=self) @@ -435,7 +431,7 @@ def get_primals( ) -> Mapping[_GeneralVarData, float]: if ( self._last_results_object is None - or self._last_results_object.incumbent_objective is None + or self._last_results_object.best_feasible_objective is None ): raise RuntimeError( 'Solver does not currently have a valid solution. Please ' @@ -455,7 +451,7 @@ def get_duals(self, cons_to_load=None): if ( self._last_results_object is None or self._last_results_object.termination_condition - != TerminationCondition.convergenceCriteriaSatisfied + != TerminationCondition.optimal ): raise RuntimeError( 'Solver does not currently have valid duals. Please ' @@ -473,7 +469,7 @@ def get_reduced_costs( if ( self._last_results_object is None or self._last_results_object.termination_condition - != TerminationCondition.convergenceCriteriaSatisfied + != TerminationCondition.optimal ): raise RuntimeError( 'Solver does not currently have valid reduced costs. Please ' diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 6f02ac12eb1..f03bee6ecc5 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -1,34 +1,35 @@ -import logging -import math -import sys -import time -from typing import Optional, Sequence, Dict, List, Mapping - - from pyomo.common.tempfiles import TempfileManager +from pyomo.contrib.appsi.base import ( + PersistentSolver, + Results, + TerminationCondition, + MIPSolverConfig, + PersistentSolutionLoader, +) from pyomo.contrib.appsi.writers import LPWriter -from pyomo.common.log import LogStream +import logging +import math from pyomo.common.collections import ComponentMap +from typing import Optional, Sequence, NoReturn, List, Mapping, Dict from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.block import _BlockData from pyomo.core.base.param import _ParamData from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.common.timing import HierarchicalTimer +import sys +import time +from pyomo.common.log import LogStream from pyomo.common.config import ConfigValue, NonNegativeInt from pyomo.common.errors import PyomoException from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager -from pyomo.contrib.solver.base import PersistentSolverBase -from pyomo.contrib.solver.config import BranchAndBoundConfig -from pyomo.contrib.solver.results import TerminationCondition, Results -from pyomo.contrib.solver.solution import PersistentSolutionLoader logger = logging.getLogger(__name__) -class CplexConfig(BranchAndBoundConfig): +class CplexConfig(MIPSolverConfig): def __init__( self, description=None, @@ -37,7 +38,7 @@ def __init__( implicit_domain=None, visibility=0, ): - super().__init__( + super(CplexConfig, self).__init__( description=description, doc=doc, implicit=implicit, @@ -58,17 +59,17 @@ def __init__( class CplexResults(Results): def __init__(self, solver): - super().__init__() - self.timing_info.wall_time = None + super(CplexResults, self).__init__() + self.wallclock_time = None self.solution_loader = PersistentSolutionLoader(solver=solver) -class Cplex(PersistentSolverBase): +class Cplex(PersistentSolver): _available = None def __init__(self, only_child_vars=False): self._config = CplexConfig() - self._solver_options = {} + self._solver_options = dict() self._writer = LPWriter(only_child_vars=only_child_vars) self._filename = None self._last_results_object: Optional[CplexResults] = None @@ -244,7 +245,7 @@ def _apply_solver(self, timer: HierarchicalTimer): log_stream = LogStream( level=self.config.log_level, logger=self.config.solver_output_logger ) - if config.tee: + if config.stream_solver: def _process_stream(arg): sys.stdout.write(arg) @@ -263,8 +264,8 @@ def _process_stream(arg): if config.time_limit is not None: cplex_model.parameters.timelimit.set(config.time_limit) - if config.rel_gap is not None: - cplex_model.parameters.mip.tolerances.mipgap.set(config.rel_gap) + if config.mip_gap is not None: + cplex_model.parameters.mip.tolerances.mipgap.set(config.mip_gap) timer.start('cplex solve') t0 = time.time() @@ -279,46 +280,52 @@ def _postsolve(self, timer: HierarchicalTimer, solve_time): cpxprob = self._cplex_model results = CplexResults(solver=self) - results.timing_info.wall_time = solve_time + results.wallclock_time = solve_time status = cpxprob.solution.get_status() if status in [1, 101, 102]: - results.termination_condition = ( - TerminationCondition.convergenceCriteriaSatisfied - ) + results.termination_condition = TerminationCondition.optimal elif status in [2, 40, 118, 133, 134]: results.termination_condition = TerminationCondition.unbounded elif status in [4, 119, 134]: results.termination_condition = TerminationCondition.infeasibleOrUnbounded elif status in [3, 103]: - results.termination_condition = TerminationCondition.provenInfeasible + results.termination_condition = TerminationCondition.infeasible elif status in [10]: - results.termination_condition = TerminationCondition.iterationLimit + results.termination_condition = TerminationCondition.maxIterations elif status in [11, 25, 107, 131]: results.termination_condition = TerminationCondition.maxTimeLimit else: results.termination_condition = TerminationCondition.unknown if self._writer.get_active_objective() is None: - results.incumbent_objective = None - results.objective_bound = None + results.best_feasible_objective = None + results.best_objective_bound = None else: if cpxprob.solution.get_solution_type() != cpxprob.solution.type.none: if ( cpxprob.variables.get_num_binary() + cpxprob.variables.get_num_integer() ) == 0: - results.incumbent_objective = cpxprob.solution.get_objective_value() - results.objective_bound = cpxprob.solution.get_objective_value() + results.best_feasible_objective = ( + cpxprob.solution.get_objective_value() + ) + results.best_objective_bound = ( + cpxprob.solution.get_objective_value() + ) else: - results.incumbent_objective = cpxprob.solution.get_objective_value() - results.objective_bound = cpxprob.solution.MIP.get_best_objective() + results.best_feasible_objective = ( + cpxprob.solution.get_objective_value() + ) + results.best_objective_bound = ( + cpxprob.solution.MIP.get_best_objective() + ) else: - results.incumbent_objective = None + results.best_feasible_objective = None if cpxprob.objective.get_sense() == cpxprob.objective.sense.minimize: - results.objective_bound = -math.inf + results.best_objective_bound = -math.inf else: - results.objective_bound = math.inf + results.best_objective_bound = math.inf if config.load_solution: if cpxprob.solution.get_solution_type() == cpxprob.solution.type.none: @@ -326,13 +333,10 @@ def _postsolve(self, timer: HierarchicalTimer, solve_time): 'A feasible solution was not found, so no solution can be loades. ' 'Please set opt.config.load_solution=False and check ' 'results.termination_condition and ' - 'results.incumbent_objective before loading a solution.' + 'results.best_feasible_objective before loading a solution.' ) else: - if ( - results.termination_condition - != TerminationCondition.convergenceCriteriaSatisfied - ): + if results.termination_condition != TerminationCondition.optimal: logger.warning( 'Loading a feasible but suboptimal solution. ' 'Please set load_solution=False and check ' @@ -396,7 +400,7 @@ def get_duals( con_names = self._cplex_model.linear_constraints.get_names() dual_values = self._cplex_model.solution.get_dual_values() else: - con_names = [] + con_names = list() for con in cons_to_load: orig_name = symbol_map.byObject[id(con)] if con.equality: @@ -408,7 +412,7 @@ def get_duals( con_names.append(orig_name + '_ub') dual_values = self._cplex_model.solution.get_dual_values(con_names) - res = {} + res = dict() for name, val in zip(con_names, dual_values): orig_name = name[:-3] if orig_name == 'obj_const_con': diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index a947c8d7d7d..a173c69abc6 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -1,9 +1,7 @@ from collections.abc import Iterable import logging import math -import sys from typing import List, Dict, Optional - from pyomo.common.collections import ComponentSet, ComponentMap, OrderedSet from pyomo.common.log import LogStream from pyomo.common.dependencies import attempt_import @@ -14,20 +12,24 @@ from pyomo.common.config import ConfigValue, NonNegativeInt from pyomo.core.kernel.objective import minimize, maximize from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler -from pyomo.core.base.var import _GeneralVarData +from pyomo.core.base.var import Var, _GeneralVarData from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.sos import _SOSConstraintData from pyomo.core.base.param import _ParamData from pyomo.core.expr.numvalue import value, is_constant, is_fixed, native_numeric_types from pyomo.repn import generate_standard_repn from pyomo.core.expr.numeric_expr import NPV_MaxExpression, NPV_MinExpression +from pyomo.contrib.appsi.base import ( + PersistentSolver, + Results, + TerminationCondition, + MIPSolverConfig, + PersistentBase, + PersistentSolutionLoader, +) +from pyomo.contrib.appsi.cmodel import cmodel, cmodel_available from pyomo.core.staleflag import StaleFlagManager -from pyomo.contrib.solver.base import PersistentSolverBase -from pyomo.contrib.solver.config import BranchAndBoundConfig -from pyomo.contrib.solver.results import TerminationCondition, Results -from pyomo.contrib.solver.solution import PersistentSolutionLoader -from pyomo.contrib.solver.util import PersistentSolverUtils - +import sys logger = logging.getLogger(__name__) @@ -51,7 +53,7 @@ class DegreeError(PyomoException): pass -class GurobiConfig(BranchAndBoundConfig): +class GurobiConfig(MIPSolverConfig): def __init__( self, description=None, @@ -60,7 +62,7 @@ def __init__( implicit_domain=None, visibility=0, ): - super().__init__( + super(GurobiConfig, self).__init__( description=description, doc=doc, implicit=implicit, @@ -93,12 +95,12 @@ def get_primals(self, vars_to_load=None, solution_number=0): class GurobiResults(Results): def __init__(self, solver): - super().__init__() - self.timing_info.wall_time = None + super(GurobiResults, self).__init__() + self.wallclock_time = None self.solution_loader = GurobiSolutionLoader(solver=solver) -class _MutableLowerBound: +class _MutableLowerBound(object): def __init__(self, expr): self.var = None self.expr = expr @@ -107,7 +109,7 @@ def update(self): self.var.setAttr('lb', value(self.expr)) -class _MutableUpperBound: +class _MutableUpperBound(object): def __init__(self, expr): self.var = None self.expr = expr @@ -116,7 +118,7 @@ def update(self): self.var.setAttr('ub', value(self.expr)) -class _MutableLinearCoefficient: +class _MutableLinearCoefficient(object): def __init__(self): self.expr = None self.var = None @@ -127,7 +129,7 @@ def update(self): self.gurobi_model.chgCoeff(self.con, self.var, value(self.expr)) -class _MutableRangeConstant: +class _MutableRangeConstant(object): def __init__(self): self.lhs_expr = None self.rhs_expr = None @@ -143,7 +145,7 @@ def update(self): slack.ub = rhs_val - lhs_val -class _MutableConstant: +class _MutableConstant(object): def __init__(self): self.expr = None self.con = None @@ -152,7 +154,7 @@ def update(self): self.con.rhs = value(self.expr) -class _MutableQuadraticConstraint: +class _MutableQuadraticConstraint(object): def __init__( self, gurobi_model, gurobi_con, constant, linear_coefs, quadratic_coefs ): @@ -187,7 +189,7 @@ def get_updated_rhs(self): return value(self.constant.expr) -class _MutableObjective: +class _MutableObjective(object): def __init__(self, gurobi_model, constant, linear_coefs, quadratic_coefs): self.gurobi_model = gurobi_model self.constant = constant @@ -215,14 +217,14 @@ def get_updated_expression(self): return gurobi_expr -class _MutableQuadraticCoefficient: +class _MutableQuadraticCoefficient(object): def __init__(self): self.expr = None self.var1 = None self.var2 = None -class Gurobi(PersistentSolverUtils, PersistentSolverBase): +class Gurobi(PersistentBase, PersistentSolver): """ Interface to Gurobi """ @@ -231,21 +233,21 @@ class Gurobi(PersistentSolverUtils, PersistentSolverBase): _num_instances = 0 def __init__(self, only_child_vars=False): - super().__init__(only_child_vars=only_child_vars) + super(Gurobi, self).__init__(only_child_vars=only_child_vars) self._num_instances += 1 self._config = GurobiConfig() - self._solver_options = {} + self._solver_options = dict() self._solver_model = None self._symbol_map = SymbolMap() self._labeler = None - self._pyomo_var_to_solver_var_map = {} - self._pyomo_con_to_solver_con_map = {} - self._solver_con_to_pyomo_con_map = {} - self._pyomo_sos_to_solver_sos_map = {} + self._pyomo_var_to_solver_var_map = dict() + self._pyomo_con_to_solver_con_map = dict() + self._solver_con_to_pyomo_con_map = dict() + self._pyomo_sos_to_solver_sos_map = dict() self._range_constraints = OrderedSet() - self._mutable_helpers = {} - self._mutable_bounds = {} - self._mutable_quadratic_helpers = {} + self._mutable_helpers = dict() + self._mutable_bounds = dict() + self._mutable_quadratic_helpers = dict() self._mutable_objective = None self._needs_updated = True self._callback = None @@ -351,7 +353,7 @@ def _solve(self, timer: HierarchicalTimer): level=self.config.log_level, logger=self.config.solver_output_logger ) ] - if self.config.tee: + if self.config.stream_solver: ostreams.append(sys.stdout) with TeeStream(*ostreams) as t: @@ -364,8 +366,8 @@ def _solve(self, timer: HierarchicalTimer): if config.time_limit is not None: self._solver_model.setParam('TimeLimit', config.time_limit) - if config.rel_gap is not None: - self._solver_model.setParam('MIPGap', config.rel_gap) + if config.mip_gap is not None: + self._solver_model.setParam('MIPGap', config.mip_gap) for key, option in options.items(): self._solver_model.setParam(key, option) @@ -446,12 +448,12 @@ def _process_domain_and_bounds( return lb, ub, vtype def _add_variables(self, variables: List[_GeneralVarData]): - var_names = [] - vtypes = [] - lbs = [] - ubs = [] - mutable_lbs = {} - mutable_ubs = {} + var_names = list() + vtypes = list() + lbs = list() + ubs = list() + mutable_lbs = dict() + mutable_ubs = dict() for ndx, var in enumerate(variables): varname = self._symbol_map.getSymbol(var, self._labeler) lb, ub, vtype = self._process_domain_and_bounds( @@ -499,6 +501,8 @@ def set_instance(self, model): ) self._reinit() self._model = model + if self.use_extensions and cmodel_available: + self._expr_types = cmodel.PyomoExprTypes() if self.config.symbolic_solver_labels: self._labeler = TextLabeler() @@ -515,8 +519,8 @@ def set_instance(self, model): self.set_objective(None) def _get_expr_from_pyomo_expr(self, expr): - mutable_linear_coefficients = [] - mutable_quadratic_coefficients = [] + mutable_linear_coefficients = list() + mutable_quadratic_coefficients = list() repn = generate_standard_repn(expr, quadratic=True, compute_values=False) degree = repn.polynomial_degree() @@ -526,7 +530,7 @@ def _get_expr_from_pyomo_expr(self, expr): ) if len(repn.linear_vars) > 0: - linear_coef_vals = [] + linear_coef_vals = list() for ndx, coef in enumerate(repn.linear_coefs): if not is_constant(coef): mutable_linear_coefficient = _MutableLinearCoefficient() @@ -820,8 +824,8 @@ def _set_objective(self, obj): sense = gurobipy.GRB.MINIMIZE gurobi_expr = 0 repn_constant = 0 - mutable_linear_coefficients = [] - mutable_quadratic_coefficients = [] + mutable_linear_coefficients = list() + mutable_quadratic_coefficients = list() else: if obj.sense == minimize: sense = gurobipy.GRB.MINIMIZE @@ -865,16 +869,14 @@ def _postsolve(self, timer: HierarchicalTimer): status = gprob.Status results = GurobiResults(self) - results.timing_info.wall_time = gprob.Runtime + results.wallclock_time = gprob.Runtime if status == grb.LOADED: # problem is loaded, but no solution results.termination_condition = TerminationCondition.unknown elif status == grb.OPTIMAL: # optimal - results.termination_condition = ( - TerminationCondition.convergenceCriteriaSatisfied - ) + results.termination_condition = TerminationCondition.optimal elif status == grb.INFEASIBLE: - results.termination_condition = TerminationCondition.provenInfeasible + results.termination_condition = TerminationCondition.infeasible elif status == grb.INF_OR_UNBD: results.termination_condition = TerminationCondition.infeasibleOrUnbounded elif status == grb.UNBOUNDED: @@ -882,9 +884,9 @@ def _postsolve(self, timer: HierarchicalTimer): elif status == grb.CUTOFF: results.termination_condition = TerminationCondition.objectiveLimit elif status == grb.ITERATION_LIMIT: - results.termination_condition = TerminationCondition.iterationLimit + results.termination_condition = TerminationCondition.maxIterations elif status == grb.NODE_LIMIT: - results.termination_condition = TerminationCondition.iterationLimit + results.termination_condition = TerminationCondition.maxIterations elif status == grb.TIME_LIMIT: results.termination_condition = TerminationCondition.maxTimeLimit elif status == grb.SOLUTION_LIMIT: @@ -900,33 +902,30 @@ def _postsolve(self, timer: HierarchicalTimer): else: results.termination_condition = TerminationCondition.unknown - results.incumbent_objective = None - results.objective_bound = None + results.best_feasible_objective = None + results.best_objective_bound = None if self._objective is not None: try: - results.incumbent_objective = gprob.ObjVal + results.best_feasible_objective = gprob.ObjVal except (gurobipy.GurobiError, AttributeError): - results.incumbent_objective = None + results.best_feasible_objective = None try: - results.objective_bound = gprob.ObjBound + results.best_objective_bound = gprob.ObjBound except (gurobipy.GurobiError, AttributeError): if self._objective.sense == minimize: - results.objective_bound = -math.inf + results.best_objective_bound = -math.inf else: - results.objective_bound = math.inf + results.best_objective_bound = math.inf - if results.incumbent_objective is not None and not math.isfinite( - results.incumbent_objective + if results.best_feasible_objective is not None and not math.isfinite( + results.best_feasible_objective ): - results.incumbent_objective = None + results.best_feasible_objective = None timer.start('load solution') if config.load_solution: if gprob.SolCount > 0: - if ( - results.termination_condition - != TerminationCondition.convergenceCriteriaSatisfied - ): + if results.termination_condition != TerminationCondition.optimal: logger.warning( 'Loading a feasible but suboptimal solution. ' 'Please set load_solution=False and check ' @@ -939,7 +938,7 @@ def _postsolve(self, timer: HierarchicalTimer): 'A feasible solution was not found, so no solution can be loaded.' 'Please set opt.config.load_solution=False and check ' 'results.termination_condition and ' - 'results.incumbent_objective before loading a solution.' + 'results.best_feasible_objective before loading a solution.' ) timer.stop('load solution') @@ -1048,7 +1047,7 @@ def get_duals(self, cons_to_load=None): con_map = self._pyomo_con_to_solver_con_map reverse_con_map = self._solver_con_to_pyomo_con_map - dual = {} + dual = dict() if cons_to_load is None: linear_cons_to_load = self._solver_model.getConstrs() @@ -1091,7 +1090,7 @@ def get_slacks(self, cons_to_load=None): con_map = self._pyomo_con_to_solver_con_map reverse_con_map = self._solver_con_to_pyomo_con_map - slack = {} + slack = dict() gurobi_range_con_vars = OrderedSet(self._solver_model.getVars()) - OrderedSet( self._pyomo_var_to_solver_var_map.values() @@ -1141,7 +1140,7 @@ def get_slacks(self, cons_to_load=None): def update(self, timer: HierarchicalTimer = None): if self._needs_updated: self._update_gurobi_model() - super().update(timer=timer) + super(Gurobi, self).update(timer=timer) self._update_gurobi_model() def _update_gurobi_model(self): @@ -1197,8 +1196,8 @@ def set_linear_constraint_attr(self, con, attr, val): if attr in {'Sense', 'RHS', 'ConstrName'}: raise ValueError( 'Linear constraint attr {0} cannot be set with' - ' the set_linear_constraint_attr method. Please use' - ' the remove_constraint and add_constraint methods.'.format(attr) + + ' the set_linear_constraint_attr method. Please use' + + ' the remove_constraint and add_constraint methods.'.format(attr) ) self._pyomo_con_to_solver_con_map[con].setAttr(attr, val) self._needs_updated = True @@ -1226,8 +1225,8 @@ def set_var_attr(self, var, attr, val): if attr in {'LB', 'UB', 'VType', 'VarName'}: raise ValueError( 'Var attr {0} cannot be set with' - ' the set_var_attr method. Please use' - ' the update_var method.'.format(attr) + + ' the set_var_attr method. Please use' + + ' the update_var method.'.format(attr) ) if attr == 'Obj': raise ValueError( @@ -1385,7 +1384,7 @@ def set_callback(self, func=None): >>> _c = _add_cut(4) # this is an arbitrary choice >>> >>> opt = appsi.solvers.Gurobi() - >>> opt.config.tee = True + >>> opt.config.stream_solver = True >>> opt.set_instance(m) # doctest:+SKIP >>> opt.gurobi_options['PreCrush'] = 1 >>> opt.gurobi_options['LazyConstraints'] = 1 diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 1680831471c..3d498f9388e 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -1,7 +1,5 @@ import logging -import sys from typing import List, Dict, Optional - from pyomo.common.collections import ComponentMap from pyomo.common.dependencies import attempt_import from pyomo.common.errors import PyomoException @@ -18,13 +16,18 @@ from pyomo.core.expr.numvalue import value, is_constant from pyomo.repn import generate_standard_repn from pyomo.core.expr.numeric_expr import NPV_MaxExpression, NPV_MinExpression +from pyomo.contrib.appsi.base import ( + PersistentSolver, + Results, + TerminationCondition, + MIPSolverConfig, + PersistentBase, + PersistentSolutionLoader, +) +from pyomo.contrib.appsi.cmodel import cmodel, cmodel_available from pyomo.common.dependencies import numpy as np from pyomo.core.staleflag import StaleFlagManager -from pyomo.contrib.solver.base import PersistentSolverBase -from pyomo.contrib.solver.config import BranchAndBoundConfig -from pyomo.contrib.solver.results import TerminationCondition, Results -from pyomo.contrib.solver.solution import PersistentSolutionLoader -from pyomo.contrib.solver.util import PersistentSolverUtils +import sys logger = logging.getLogger(__name__) @@ -35,7 +38,7 @@ class DegreeError(PyomoException): pass -class HighsConfig(BranchAndBoundConfig): +class HighsConfig(MIPSolverConfig): def __init__( self, description=None, @@ -44,7 +47,7 @@ def __init__( implicit_domain=None, visibility=0, ): - super().__init__( + super(HighsConfig, self).__init__( description=description, doc=doc, implicit=implicit, @@ -64,11 +67,11 @@ def __init__( class HighsResults(Results): def __init__(self, solver): super().__init__() - self.timing_info.wall_time = None + self.wallclock_time = None self.solution_loader = PersistentSolutionLoader(solver=solver) -class _MutableVarBounds: +class _MutableVarBounds(object): def __init__(self, lower_expr, upper_expr, pyomo_var_id, var_map, highs): self.pyomo_var_id = pyomo_var_id self.lower_expr = lower_expr @@ -83,7 +86,7 @@ def update(self): self.highs.changeColBounds(col_ndx, lb, ub) -class _MutableLinearCoefficient: +class _MutableLinearCoefficient(object): def __init__(self, pyomo_con, pyomo_var_id, con_map, var_map, expr, highs): self.expr = expr self.highs = highs @@ -98,7 +101,7 @@ def update(self): self.highs.changeCoeff(row_ndx, col_ndx, value(self.expr)) -class _MutableObjectiveCoefficient: +class _MutableObjectiveCoefficient(object): def __init__(self, pyomo_var_id, var_map, expr, highs): self.expr = expr self.highs = highs @@ -110,7 +113,7 @@ def update(self): self.highs.changeColCost(col_ndx, value(self.expr)) -class _MutableObjectiveOffset: +class _MutableObjectiveOffset(object): def __init__(self, expr, highs): self.expr = expr self.highs = highs @@ -119,7 +122,7 @@ def update(self): self.highs.changeObjectiveOffset(value(self.expr)) -class _MutableConstraintBounds: +class _MutableConstraintBounds(object): def __init__(self, lower_expr, upper_expr, pyomo_con, con_map, highs): self.lower_expr = lower_expr self.upper_expr = upper_expr @@ -134,7 +137,7 @@ def update(self): self.highs.changeRowBounds(row_ndx, lb, ub) -class Highs(PersistentSolverUtils, PersistentSolverBase): +class Highs(PersistentBase, PersistentSolver): """ Interface to HiGHS """ @@ -144,14 +147,14 @@ class Highs(PersistentSolverUtils, PersistentSolverBase): def __init__(self, only_child_vars=False): super().__init__(only_child_vars=only_child_vars) self._config = HighsConfig() - self._solver_options = {} + self._solver_options = dict() self._solver_model = None - self._pyomo_var_to_solver_var_map = {} - self._pyomo_con_to_solver_con_map = {} - self._solver_con_to_pyomo_con_map = {} - self._mutable_helpers = {} - self._mutable_bounds = {} - self._objective_helpers = [] + self._pyomo_var_to_solver_var_map = dict() + self._pyomo_con_to_solver_con_map = dict() + self._solver_con_to_pyomo_con_map = dict() + self._mutable_helpers = dict() + self._mutable_bounds = dict() + self._objective_helpers = list() self._last_results_object: Optional[HighsResults] = None self._sol = None @@ -208,7 +211,7 @@ def _solve(self, timer: HierarchicalTimer): level=self.config.log_level, logger=self.config.solver_output_logger ) ] - if self.config.tee: + if self.config.stream_solver: ostreams.append(sys.stdout) with TeeStream(*ostreams) as t: @@ -219,8 +222,8 @@ def _solve(self, timer: HierarchicalTimer): if config.time_limit is not None: self._solver_model.setOptionValue('time_limit', config.time_limit) - if config.rel_gap is not None: - self._solver_model.setOptionValue('mip_rel_gap', config.rel_gap) + if config.mip_gap is not None: + self._solver_model.setOptionValue('mip_rel_gap', config.mip_gap) for key, option in options.items(): self._solver_model.setOptionValue(key, option) @@ -298,10 +301,10 @@ def _add_variables(self, variables: List[_GeneralVarData]): self._sol = None if self._last_results_object is not None: self._last_results_object.solution_loader.invalidate() - lbs = [] - ubs = [] - indices = [] - vtypes = [] + lbs = list() + ubs = list() + indices = list() + vtypes = list() current_num_vars = len(self._pyomo_var_to_solver_var_map) for v in variables: @@ -348,12 +351,14 @@ def set_instance(self, model): level=self.config.log_level, logger=self.config.solver_output_logger ) ] - if self.config.tee: + if self.config.stream_solver: ostreams.append(sys.stdout) with TeeStream(*ostreams) as t: with capture_output(output=t.STDOUT, capture_fd=True): self._reinit() self._model = model + if self.use_extensions and cmodel_available: + self._expr_types = cmodel.PyomoExprTypes() self._solver_model = highspy.Highs() self.add_block(model) @@ -365,11 +370,11 @@ def _add_constraints(self, cons: List[_GeneralConstraintData]): if self._last_results_object is not None: self._last_results_object.solution_loader.invalidate() current_num_cons = len(self._pyomo_con_to_solver_con_map) - lbs = [] - ubs = [] - starts = [] - var_indices = [] - coef_values = [] + lbs = list() + ubs = list() + starts = list() + var_indices = list() + coef_values = list() for con in cons: repn = generate_standard_repn( @@ -395,7 +400,7 @@ def _add_constraints(self, cons: List[_GeneralConstraintData]): highs=self._solver_model, ) if con not in self._mutable_helpers: - self._mutable_helpers[con] = [] + self._mutable_helpers[con] = list() self._mutable_helpers[con].append(mutable_linear_coefficient) if coef_val == 0: continue @@ -450,7 +455,7 @@ def _remove_constraints(self, cons: List[_GeneralConstraintData]): self._sol = None if self._last_results_object is not None: self._last_results_object.solution_loader.invalidate() - indices_to_remove = [] + indices_to_remove = list() for con in cons: con_ndx = self._pyomo_con_to_solver_con_map.pop(con) del self._solver_con_to_pyomo_con_map[con_ndx] @@ -460,7 +465,7 @@ def _remove_constraints(self, cons: List[_GeneralConstraintData]): len(indices_to_remove), np.array(indices_to_remove) ) con_ndx = 0 - new_con_map = {} + new_con_map = dict() for c in self._pyomo_con_to_solver_con_map.keys(): new_con_map[c] = con_ndx con_ndx += 1 @@ -481,7 +486,7 @@ def _remove_variables(self, variables: List[_GeneralVarData]): self._sol = None if self._last_results_object is not None: self._last_results_object.solution_loader.invalidate() - indices_to_remove = [] + indices_to_remove = list() for v in variables: v_id = id(v) v_ndx = self._pyomo_var_to_solver_var_map.pop(v_id) @@ -492,7 +497,7 @@ def _remove_variables(self, variables: List[_GeneralVarData]): len(indices_to_remove), np.array(indices_to_remove) ) v_ndx = 0 - new_var_map = {} + new_var_map = dict() for v_id in self._pyomo_var_to_solver_var_map.keys(): new_var_map[v_id] = v_ndx v_ndx += 1 @@ -506,10 +511,10 @@ def _update_variables(self, variables: List[_GeneralVarData]): self._sol = None if self._last_results_object is not None: self._last_results_object.solution_loader.invalidate() - indices = [] - lbs = [] - ubs = [] - vtypes = [] + indices = list() + lbs = list() + ubs = list() + vtypes = list() for v in variables: v_id = id(v) @@ -550,7 +555,7 @@ def _set_objective(self, obj): n = len(self._pyomo_var_to_solver_var_map) indices = np.arange(n) costs = np.zeros(n, dtype=np.double) - self._objective_helpers = [] + self._objective_helpers = list() if obj is None: sense = highspy.ObjSense.kMinimize self._solver_model.changeObjectiveOffset(0) @@ -602,7 +607,7 @@ def _postsolve(self, timer: HierarchicalTimer): status = highs.getModelStatus() results = HighsResults(self) - results.timing_info.wall_time = highs.getRunTime() + results.wallclock_time = highs.getRunTime() if status == highspy.HighsModelStatus.kNotset: results.termination_condition = TerminationCondition.unknown @@ -619,11 +624,9 @@ def _postsolve(self, timer: HierarchicalTimer): elif status == highspy.HighsModelStatus.kModelEmpty: results.termination_condition = TerminationCondition.unknown elif status == highspy.HighsModelStatus.kOptimal: - results.termination_condition = ( - TerminationCondition.convergenceCriteriaSatisfied - ) + results.termination_condition = TerminationCondition.optimal elif status == highspy.HighsModelStatus.kInfeasible: - results.termination_condition = TerminationCondition.provenInfeasible + results.termination_condition = TerminationCondition.infeasible elif status == highspy.HighsModelStatus.kUnboundedOrInfeasible: results.termination_condition = TerminationCondition.infeasibleOrUnbounded elif status == highspy.HighsModelStatus.kUnbounded: @@ -635,7 +638,7 @@ def _postsolve(self, timer: HierarchicalTimer): elif status == highspy.HighsModelStatus.kTimeLimit: results.termination_condition = TerminationCondition.maxTimeLimit elif status == highspy.HighsModelStatus.kIterationLimit: - results.termination_condition = TerminationCondition.iterationLimit + results.termination_condition = TerminationCondition.maxIterations elif status == highspy.HighsModelStatus.kUnknown: results.termination_condition = TerminationCondition.unknown else: @@ -644,14 +647,11 @@ def _postsolve(self, timer: HierarchicalTimer): timer.start('load solution') self._sol = highs.getSolution() has_feasible_solution = False - if ( - results.termination_condition - == TerminationCondition.convergenceCriteriaSatisfied - ): + if results.termination_condition == TerminationCondition.optimal: has_feasible_solution = True elif results.termination_condition in { TerminationCondition.objectiveLimit, - TerminationCondition.iterationLimit, + TerminationCondition.maxIterations, TerminationCondition.maxTimeLimit, }: if self._sol.value_valid: @@ -659,10 +659,7 @@ def _postsolve(self, timer: HierarchicalTimer): if config.load_solution: if has_feasible_solution: - if ( - results.termination_condition - != TerminationCondition.convergenceCriteriaSatisfied - ): + if results.termination_condition != TerminationCondition.optimal: logger.warning( 'Loading a feasible but suboptimal solution. ' 'Please set load_solution=False and check ' @@ -675,23 +672,23 @@ def _postsolve(self, timer: HierarchicalTimer): 'A feasible solution was not found, so no solution can be loaded.' 'Please set opt.config.load_solution=False and check ' 'results.termination_condition and ' - 'results.incumbent_objective before loading a solution.' + 'results.best_feasible_objective before loading a solution.' ) timer.stop('load solution') info = highs.getInfo() - results.objective_bound = None - results.incumbent_objective = None + results.best_objective_bound = None + results.best_feasible_objective = None if self._objective is not None: if has_feasible_solution: - results.incumbent_objective = info.objective_function_value + results.best_feasible_objective = info.objective_function_value if info.mip_node_count == -1: if has_feasible_solution: - results.objective_bound = info.objective_function_value + results.best_objective_bound = info.objective_function_value else: - results.objective_bound = None + results.best_objective_bound = None else: - results.objective_bound = info.mip_dual_bound + results.best_objective_bound = info.mip_dual_bound return results @@ -709,7 +706,7 @@ def get_primals(self, vars_to_load=None, solution_number=0): res = ComponentMap() if vars_to_load is None: - var_ids_to_load = [] + var_ids_to_load = list() for v, ref_info in self._referenced_variables.items(): using_cons, using_sos, using_obj = ref_info if using_cons or using_sos or (using_obj is not None): @@ -754,7 +751,7 @@ def get_duals(self, cons_to_load=None): 'check the termination condition.' ) - res = {} + res = dict() if cons_to_load is None: cons_to_load = list(self._pyomo_con_to_solver_con_map.keys()) @@ -773,7 +770,7 @@ def get_slacks(self, cons_to_load=None): 'check the termination condition.' ) - res = {} + res = dict() if cons_to_load is None: cons_to_load = list(self._pyomo_con_to_solver_con_map.keys()) diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index ec59b827192..d38a836a2ac 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -1,20 +1,22 @@ -import math -import os -import sys -from typing import Dict -import logging -import subprocess - - from pyomo.common.tempfiles import TempfileManager from pyomo.common.fileutils import Executable +from pyomo.contrib.appsi.base import ( + PersistentSolver, + Results, + TerminationCondition, + SolverConfig, + PersistentSolutionLoader, +) from pyomo.contrib.appsi.writers import NLWriter from pyomo.common.log import LogStream +import logging +import subprocess from pyomo.core.kernel.objective import minimize +import math from pyomo.common.collections import ComponentMap from pyomo.core.expr.numvalue import value from pyomo.core.expr.visitor import replace_expressions -from typing import Optional, Sequence, List, Mapping +from typing import Optional, Sequence, NoReturn, List, Mapping from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.block import _BlockData @@ -22,14 +24,13 @@ from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.common.timing import HierarchicalTimer from pyomo.common.tee import TeeStream +import sys +from typing import Dict from pyomo.common.config import ConfigValue, NonNegativeInt from pyomo.common.errors import PyomoException +import os from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager -from pyomo.contrib.solver.base import PersistentSolverBase -from pyomo.contrib.solver.config import SolverConfig -from pyomo.contrib.solver.results import TerminationCondition, Results -from pyomo.contrib.solver.solution import PersistentSolutionLoader logger = logging.getLogger(__name__) @@ -44,7 +45,7 @@ def __init__( implicit_domain=None, visibility=0, ): - super().__init__( + super(IpoptConfig, self).__init__( description=description, doc=doc, implicit=implicit, @@ -125,13 +126,13 @@ def __init__( } -class Ipopt(PersistentSolverBase): +class Ipopt(PersistentSolver): def __init__(self, only_child_vars=False): self._config = IpoptConfig() - self._solver_options = {} + self._solver_options = dict() self._writer = NLWriter(only_child_vars=only_child_vars) self._filename = None - self._dual_sol = {} + self._dual_sol = dict() self._primal_sol = ComponentMap() self._reduced_costs = ComponentMap() self._last_results_object: Optional[Results] = None @@ -296,20 +297,19 @@ def _parse_sol(self): solve_cons = self._writer.get_ordered_cons() results = Results() - with open(self._filename + '.sol', 'r') as f: - all_lines = list(f.readlines()) + f = open(self._filename + '.sol', 'r') + all_lines = list(f.readlines()) + f.close() termination_line = all_lines[1] if 'Optimal Solution Found' in termination_line: - results.termination_condition = ( - TerminationCondition.convergenceCriteriaSatisfied - ) + results.termination_condition = TerminationCondition.optimal elif 'Problem may be infeasible' in termination_line: - results.termination_condition = TerminationCondition.locallyInfeasible + results.termination_condition = TerminationCondition.infeasible elif 'problem might be unbounded' in termination_line: results.termination_condition = TerminationCondition.unbounded elif 'Maximum Number of Iterations Exceeded' in termination_line: - results.termination_condition = TerminationCondition.iterationLimit + results.termination_condition = TerminationCondition.maxIterations elif 'Maximum CPU Time Exceeded' in termination_line: results.termination_condition = TerminationCondition.maxTimeLimit else: @@ -347,7 +347,7 @@ def _parse_sol(self): + n_rc_lower ] - self._dual_sol = {} + self._dual_sol = dict() self._primal_sol = ComponentMap() self._reduced_costs = ComponentMap() @@ -384,24 +384,20 @@ def _parse_sol(self): self._reduced_costs[var] = 0 if ( - results.termination_condition - == TerminationCondition.convergenceCriteriaSatisfied + results.termination_condition == TerminationCondition.optimal and self.config.load_solution ): for v, val in self._primal_sol.items(): v.set_value(val, skip_validation=True) if self._writer.get_active_objective() is None: - results.incumbent_objective = None + results.best_feasible_objective = None else: - results.incumbent_objective = value( + results.best_feasible_objective = value( self._writer.get_active_objective().expr ) - elif ( - results.termination_condition - == TerminationCondition.convergenceCriteriaSatisfied - ): + elif results.termination_condition == TerminationCondition.optimal: if self._writer.get_active_objective() is None: - results.incumbent_objective = None + results.best_feasible_objective = None else: obj_expr_evaluated = replace_expressions( self._writer.get_active_objective().expr, @@ -411,13 +407,13 @@ def _parse_sol(self): descend_into_named_expressions=True, remove_named_expressions=True, ) - results.incumbent_objective = value(obj_expr_evaluated) + results.best_feasible_objective = value(obj_expr_evaluated) elif self.config.load_solution: raise RuntimeError( 'A feasible solution was not found, so no solution can be loaded.' 'Please set opt.config.load_solution=False and check ' 'results.termination_condition and ' - 'results.incumbent_objective before loading a solution.' + 'results.best_feasible_objective before loading a solution.' ) return results @@ -435,7 +431,7 @@ def _apply_solver(self, timer: HierarchicalTimer): level=self.config.log_level, logger=self.config.solver_output_logger ) ] - if self.config.tee: + if self.config.stream_solver: ostreams.append(sys.stdout) cmd = [ @@ -481,23 +477,23 @@ def _apply_solver(self, timer: HierarchicalTimer): 'A feasible solution was not found, so no solution can be loaded.' 'Please set opt.config.load_solution=False and check ' 'results.termination_condition and ' - 'results.incumbent_objective before loading a solution.' + 'results.best_feasible_objective before loading a solution.' ) results = Results() results.termination_condition = TerminationCondition.error - results.incumbent_objective = None + results.best_feasible_objective = None else: timer.start('parse solution') results = self._parse_sol() timer.stop('parse solution') if self._writer.get_active_objective() is None: - results.objective_bound = None + results.best_objective_bound = None else: if self._writer.get_active_objective().sense == minimize: - results.objective_bound = -math.inf + results.best_objective_bound = -math.inf else: - results.objective_bound = math.inf + results.best_objective_bound = math.inf results.solution_loader = PersistentSolutionLoader(solver=self) @@ -508,7 +504,7 @@ def get_primals( ) -> Mapping[_GeneralVarData, float]: if ( self._last_results_object is None - or self._last_results_object.incumbent_objective is None + or self._last_results_object.best_feasible_objective is None ): raise RuntimeError( 'Solver does not currently have a valid solution. Please ' @@ -530,7 +526,7 @@ def get_duals( if ( self._last_results_object is None or self._last_results_object.termination_condition - != TerminationCondition.convergenceCriteriaSatisfied + != TerminationCondition.optimal ): raise RuntimeError( 'Solver does not currently have valid duals. Please ' @@ -548,7 +544,7 @@ def get_reduced_costs( if ( self._last_results_object is None or self._last_results_object.termination_condition - != TerminationCondition.convergenceCriteriaSatisfied + != TerminationCondition.optimal ): raise RuntimeError( 'Solver does not currently have valid reduced costs. Please ' diff --git a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py index 4619a1c5452..b032f5c827e 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py @@ -1,8 +1,11 @@ -from pyomo.common import unittest +from pyomo.common.errors import PyomoException +import pyomo.common.unittest as unittest import pyomo.environ as pe from pyomo.contrib.appsi.solvers.gurobi import Gurobi -from pyomo.contrib.solver.results import TerminationCondition +from pyomo.contrib.appsi.base import TerminationCondition +from pyomo.core.expr.numeric_expr import LinearExpression from pyomo.core.expr.taylor_series import taylor_series_expansion +from pyomo.contrib.appsi.cmodel import cmodel_available opt = Gurobi() @@ -155,12 +158,10 @@ def test_lp(self): x, y = self.get_solution() opt = Gurobi() res = opt.solve(self.m) - self.assertAlmostEqual(x + y, res.incumbent_objective) - self.assertAlmostEqual(x + y, res.objective_bound) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertTrue(res.incumbent_objective is not None) + self.assertAlmostEqual(x + y, res.best_feasible_objective) + self.assertAlmostEqual(x + y, res.best_objective_bound) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertTrue(res.best_feasible_objective is not None) self.assertAlmostEqual(x, self.m.x.value) self.assertAlmostEqual(y, self.m.y.value) @@ -196,11 +197,11 @@ def test_nonconvex_qcp_objective_bound_1(self): opt.gurobi_options['BestBdStop'] = -8 opt.config.load_solution = False res = opt.solve(m) - self.assertEqual(res.incumbent_objective, None) - self.assertAlmostEqual(res.objective_bound, -8) + self.assertEqual(res.best_feasible_objective, None) + self.assertAlmostEqual(res.best_objective_bound, -8) def test_nonconvex_qcp_objective_bound_2(self): - # the goal of this test is to ensure we can objective_bound properly + # the goal of this test is to ensure we can best_objective_bound properly # for nonconvex but continuous problems when the solver terminates with a nonzero gap # # This is a fragile test because it could fail if Gurobi's algorithms change @@ -214,8 +215,8 @@ def test_nonconvex_qcp_objective_bound_2(self): opt.gurobi_options['nonconvex'] = 2 opt.gurobi_options['MIPGap'] = 0.5 res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, -4) - self.assertAlmostEqual(res.objective_bound, -6) + self.assertAlmostEqual(res.best_feasible_objective, -4) + self.assertAlmostEqual(res.best_objective_bound, -6) def test_range_constraints(self): m = pe.ConcreteModel() @@ -282,7 +283,7 @@ def test_quadratic_objective(self): res = opt.solve(m) self.assertAlmostEqual(m.x.value, -m.b.value / (2 * m.a.value)) self.assertAlmostEqual( - res.incumbent_objective, + res.best_feasible_objective, m.a.value * m.x.value**2 + m.b.value * m.x.value + m.c.value, ) @@ -292,7 +293,7 @@ def test_quadratic_objective(self): res = opt.solve(m) self.assertAlmostEqual(m.x.value, -m.b.value / (2 * m.a.value)) self.assertAlmostEqual( - res.incumbent_objective, + res.best_feasible_objective, m.a.value * m.x.value**2 + m.b.value * m.x.value + m.c.value, ) @@ -467,7 +468,7 @@ def test_zero_time_limit(self): # what we are trying to test. Unfortunately, I'm # not sure of a good way to guarantee that if num_solutions == 0: - self.assertIsNone(res.incumbent_objective) + self.assertIsNone(res.best_feasible_objective) class TestManualModel(unittest.TestCase): diff --git a/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py index cd65783c566..6451db18087 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py @@ -7,6 +7,7 @@ from pyomo.common.log import LoggingIntercept from pyomo.common.tee import capture_output from pyomo.contrib.appsi.solvers.highs import Highs +from pyomo.contrib.appsi.base import TerminationCondition opt = Highs() @@ -32,12 +33,12 @@ def test_mutable_params_with_remove_cons(self): opt = Highs() res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 1) + self.assertAlmostEqual(res.best_feasible_objective, 1) del m.c1 m.p2.value = 2 res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, -8) + self.assertAlmostEqual(res.best_feasible_objective, -8) def test_mutable_params_with_remove_vars(self): m = pe.ConcreteModel() @@ -59,14 +60,14 @@ def test_mutable_params_with_remove_vars(self): opt = Highs() res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 1) + self.assertAlmostEqual(res.best_feasible_objective, 1) del m.c1 del m.c2 m.p1.value = -9 m.p2.value = 9 res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, -9) + self.assertAlmostEqual(res.best_feasible_objective, -9) def test_capture_highs_output(self): # tests issue #3003 @@ -94,7 +95,7 @@ def test_capture_highs_output(self): model[-2:-1] = [ 'opt = Highs()', - 'opt.config.tee = True', + 'opt.config.stream_solver = True', 'result = opt.solve(m)', ] with LoggingIntercept() as LOG, capture_output(capture_fd=True) as OUT: diff --git a/pyomo/contrib/appsi/solvers/tests/test_ipopt_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_ipopt_persistent.py index 70e70fa65c5..6b86deaa535 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_ipopt_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_ipopt_persistent.py @@ -1,5 +1,5 @@ import pyomo.environ as pe -from pyomo.common import unittest +import pyomo.common.unittest as unittest from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.common.gsl import find_GSL @@ -11,7 +11,7 @@ def test_external_function(self): if not DLL: self.skipTest('Could not find the amplgls.dll library') - opt = pe.SolverFactory('ipopt_v2') + opt = pe.SolverFactory('appsi_ipopt') if not opt.available(exception_flag=False): raise unittest.SkipTest @@ -31,7 +31,7 @@ def test_external_function_in_objective(self): if not DLL: self.skipTest('Could not find the amplgls.dll library') - opt = pe.SolverFactory('ipopt_v2') + opt = pe.SolverFactory('appsi_ipopt') if not opt.available(exception_flag=False): raise unittest.SkipTest diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 6731eb645fa..33f6877aaf8 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -1,15 +1,15 @@ import pyomo.environ as pe from pyomo.common.dependencies import attempt_import -from pyomo.common import unittest +import pyomo.common.unittest as unittest parameterized, param_available = attempt_import('parameterized') parameterized = parameterized.parameterized -from pyomo.contrib.solver.base import PersistentSolverBase -from pyomo.contrib.solver.results import TerminationCondition, Results +from pyomo.contrib.appsi.base import TerminationCondition, Results, PersistentSolver from pyomo.contrib.appsi.cmodel import cmodel_available -from pyomo.contrib.appsi.solvers import Gurobi, Ipopt, Highs +from pyomo.contrib.appsi.solvers import Gurobi, Ipopt, Cplex, Cbc, Highs from typing import Type from pyomo.core.expr.numeric_expr import LinearExpression +import os numpy, numpy_available = attempt_import('numpy') import random @@ -19,11 +19,17 @@ if not param_available: raise unittest.SkipTest('Parameterized is not available.') -all_solvers = [('gurobi', Gurobi), ('ipopt', Ipopt), ('highs', Highs)] -mip_solvers = [('gurobi', Gurobi), ('highs', Highs)] +all_solvers = [ + ('gurobi', Gurobi), + ('ipopt', Ipopt), + ('cplex', Cplex), + ('cbc', Cbc), + ('highs', Highs), +] +mip_solvers = [('gurobi', Gurobi), ('cplex', Cplex), ('cbc', Cbc), ('highs', Highs)] nlp_solvers = [('ipopt', Ipopt)] -qcp_solvers = [('gurobi', Gurobi), ('ipopt', Ipopt)] -miqcqp_solvers = [('gurobi', Gurobi)] +qcp_solvers = [('gurobi', Gurobi), ('ipopt', Ipopt), ('cplex', Cplex)] +miqcqp_solvers = [('gurobi', Gurobi), ('cplex', Cplex)] only_child_vars_options = [True, False] @@ -62,7 +68,7 @@ def _load_tests(solver_list, only_child_vars_list): - res = [] + res = list() for solver_name, solver in solver_list: for child_var_option in only_child_vars_list: test_name = f"{solver_name}_only_child_vars_{child_var_option}" @@ -75,19 +81,17 @@ def _load_tests(solver_list, only_child_vars_list): class TestSolvers(unittest.TestCase): @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_remove_variable_and_objective( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): # this test is for issue #2888 - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() m.x = pe.Var(bounds=(2, None)) m.obj = pe.Objective(expr=m.x) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, 2) del m.x @@ -95,16 +99,14 @@ def test_remove_variable_and_objective( m.x = pe.Var(bounds=(2, None)) m.obj = pe.Objective(expr=m.x) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, 2) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_stale_vars( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -147,9 +149,9 @@ def test_stale_vars( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_range_constraint( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -157,26 +159,22 @@ def test_range_constraint( m.obj = pe.Objective(expr=m.x) m.c = pe.Constraint(expr=(-1, m.x, 1)) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, -1) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c], 1) m.obj.sense = pe.maximize res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, 1) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c], 1) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_reduced_costs( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -184,9 +182,7 @@ def test_reduced_costs( m.y = pe.Var(bounds=(-2, 2)) m.obj = pe.Objective(expr=3 * m.x + 4 * m.y) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, -1) self.assertAlmostEqual(m.y.value, -2) rc = opt.get_reduced_costs() @@ -195,35 +191,31 @@ def test_reduced_costs( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_reduced_costs2( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() m.x = pe.Var(bounds=(-1, 1)) m.obj = pe.Objective(expr=m.x) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, -1) rc = opt.get_reduced_costs() self.assertAlmostEqual(rc[m.x], 1) m.obj.sense = pe.maximize res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, 1) rc = opt.get_reduced_costs() self.assertAlmostEqual(rc[m.x], 1) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_param_changes( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -244,27 +236,24 @@ def test_param_changes( m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual( - res.termination_condition, - TerminationCondition.convergenceCriteriaSatisfied, - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) - self.assertAlmostEqual(res.incumbent_objective, m.y.value) - self.assertTrue(res.objective_bound <= m.y.value) + self.assertAlmostEqual(res.best_feasible_objective, m.y.value) + self.assertTrue(res.best_objective_bound <= m.y.value) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], (1 + a1 / (a2 - a1))) self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_immutable_param( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): """ This test is important because component_data_objects returns immutable params as floats. We want to make sure we process these correctly. """ - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -285,23 +274,20 @@ def test_immutable_param( m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual( - res.termination_condition, - TerminationCondition.convergenceCriteriaSatisfied, - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) - self.assertAlmostEqual(res.incumbent_objective, m.y.value) - self.assertTrue(res.objective_bound <= m.y.value) + self.assertAlmostEqual(res.best_feasible_objective, m.y.value) + self.assertTrue(res.best_objective_bound <= m.y.value) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], (1 + a1 / (a2 - a1))) self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_equality( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -322,23 +308,20 @@ def test_equality( m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual( - res.termination_condition, - TerminationCondition.convergenceCriteriaSatisfied, - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) - self.assertAlmostEqual(res.incumbent_objective, m.y.value) - self.assertTrue(res.objective_bound <= m.y.value) + self.assertAlmostEqual(res.best_feasible_objective, m.y.value) + self.assertTrue(res.best_objective_bound <= m.y.value) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], (1 + a1 / (a2 - a1))) self.assertAlmostEqual(duals[m.c2], -a1 / (a2 - a1)) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_linear_expression( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -365,19 +348,16 @@ def test_linear_expression( m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual( - res.termination_condition, - TerminationCondition.convergenceCriteriaSatisfied, - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) - self.assertAlmostEqual(res.incumbent_objective, m.y.value) - self.assertTrue(res.objective_bound <= m.y.value) + self.assertAlmostEqual(res.best_feasible_objective, m.y.value) + self.assertTrue(res.best_objective_bound <= m.y.value) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_no_objective( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -389,7 +369,7 @@ def test_no_objective( m.b2 = pe.Param(mutable=True) m.c1 = pe.Constraint(expr=m.y == m.a1 * m.x + m.b1) m.c2 = pe.Constraint(expr=m.y == m.a2 * m.x + m.b2) - opt.config.tee = True + opt.config.stream_solver = True params_to_test = [(1, -1, 2, 1), (1, -2, 2, 1), (1, -1, 3, 1)] for a1, a2, b1, b2 in params_to_test: @@ -398,23 +378,20 @@ def test_no_objective( m.b1.value = b1 m.b2.value = b2 res: Results = opt.solve(m) - self.assertEqual( - res.termination_condition, - TerminationCondition.convergenceCriteriaSatisfied, - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) - self.assertEqual(res.incumbent_objective, None) - self.assertEqual(res.objective_bound, None) + self.assertEqual(res.best_feasible_objective, None) + self.assertEqual(res.best_objective_bound, None) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], 0) self.assertAlmostEqual(duals[m.c2], 0) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_add_remove_cons( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -430,26 +407,22 @@ def test_add_remove_cons( m.c1 = pe.Constraint(expr=m.y >= a1 * m.x + b1) m.c2 = pe.Constraint(expr=m.y >= a2 * m.x + b2) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) - self.assertAlmostEqual(res.incumbent_objective, m.y.value) - self.assertTrue(res.objective_bound <= m.y.value) + self.assertAlmostEqual(res.best_feasible_objective, m.y.value) + self.assertTrue(res.best_objective_bound <= m.y.value) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], -(1 + a1 / (a2 - a1))) self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) m.c3 = pe.Constraint(expr=m.y >= a3 * m.x + b3) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, (b3 - b1) / (a1 - a3)) self.assertAlmostEqual(m.y.value, a1 * (b3 - b1) / (a1 - a3) + b1) - self.assertAlmostEqual(res.incumbent_objective, m.y.value) - self.assertTrue(res.objective_bound <= m.y.value) + self.assertAlmostEqual(res.best_feasible_objective, m.y.value) + self.assertTrue(res.best_objective_bound <= m.y.value) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], -(1 + a1 / (a3 - a1))) self.assertAlmostEqual(duals[m.c2], 0) @@ -457,22 +430,20 @@ def test_add_remove_cons( del m.c3 res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) - self.assertAlmostEqual(res.incumbent_objective, m.y.value) - self.assertTrue(res.objective_bound <= m.y.value) + self.assertAlmostEqual(res.best_feasible_objective, m.y.value) + self.assertTrue(res.best_objective_bound <= m.y.value) duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], -(1 + a1 / (a2 - a1))) self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_results_infeasible( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -485,25 +456,21 @@ def test_results_infeasible( res = opt.solve(m) opt.config.load_solution = False res = opt.solve(m) - self.assertNotEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) + self.assertNotEqual(res.termination_condition, TerminationCondition.optimal) if opt_class is Ipopt: acceptable_termination_conditions = { - TerminationCondition.provenInfeasible, - TerminationCondition.locallyInfeasible, + TerminationCondition.infeasible, TerminationCondition.unbounded, } else: acceptable_termination_conditions = { - TerminationCondition.provenInfeasible, - TerminationCondition.locallyInfeasible, + TerminationCondition.infeasible, TerminationCondition.infeasibleOrUnbounded, } self.assertIn(res.termination_condition, acceptable_termination_conditions) self.assertAlmostEqual(m.x.value, None) self.assertAlmostEqual(m.y.value, None) - self.assertTrue(res.incumbent_objective is None) + self.assertTrue(res.best_feasible_objective is None) with self.assertRaisesRegex( RuntimeError, '.*does not currently have a valid solution.*' @@ -519,10 +486,8 @@ def test_results_infeasible( res.solution_loader.get_reduced_costs() @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) - def test_duals( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars - ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + def test_duals(self, name: str, opt_class: Type[PersistentSolver], only_child_vars): + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -545,9 +510,9 @@ def test_duals( @parameterized.expand(input=_load_tests(qcp_solvers, only_child_vars_options)) def test_mutable_quadratic_coefficient( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -569,9 +534,9 @@ def test_mutable_quadratic_coefficient( @parameterized.expand(input=_load_tests(qcp_solvers, only_child_vars_options)) def test_mutable_quadratic_objective( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -596,10 +561,10 @@ def test_mutable_quadratic_objective( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_fixed_vars( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): for treat_fixed_vars_as_params in [True, False]: - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) opt.update_config.treat_fixed_vars_as_params = treat_fixed_vars_as_params if not opt.available(): raise unittest.SkipTest @@ -636,9 +601,9 @@ def test_fixed_vars( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_fixed_vars_2( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) opt.update_config.treat_fixed_vars_as_params = True if not opt.available(): raise unittest.SkipTest @@ -675,9 +640,9 @@ def test_fixed_vars_2( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_fixed_vars_3( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) opt.update_config.treat_fixed_vars_as_params = True if not opt.available(): raise unittest.SkipTest @@ -692,9 +657,9 @@ def test_fixed_vars_3( @parameterized.expand(input=_load_tests(nlp_solvers, only_child_vars_options)) def test_fixed_vars_4( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) opt.update_config.treat_fixed_vars_as_params = True if not opt.available(): raise unittest.SkipTest @@ -713,9 +678,9 @@ def test_fixed_vars_4( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_mutable_param_with_range( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest try: @@ -783,30 +748,27 @@ def test_mutable_param_with_range( m.c2.value = float(c2) m.obj.sense = sense res: Results = opt.solve(m) - self.assertEqual( - res.termination_condition, - TerminationCondition.convergenceCriteriaSatisfied, - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) if sense is pe.minimize: self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2), 6) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1, 6) - self.assertAlmostEqual(res.incumbent_objective, m.y.value, 6) - self.assertTrue(res.objective_bound <= m.y.value + 1e-12) + self.assertAlmostEqual(res.best_feasible_objective, m.y.value, 6) + self.assertTrue(res.best_objective_bound <= m.y.value + 1e-12) duals = opt.get_duals() self.assertAlmostEqual(duals[m.con1], (1 + a1 / (a2 - a1)), 6) self.assertAlmostEqual(duals[m.con2], -a1 / (a2 - a1), 6) else: self.assertAlmostEqual(m.x.value, (c2 - c1) / (a1 - a2), 6) self.assertAlmostEqual(m.y.value, a1 * (c2 - c1) / (a1 - a2) + c1, 6) - self.assertAlmostEqual(res.incumbent_objective, m.y.value, 6) - self.assertTrue(res.objective_bound >= m.y.value - 1e-12) + self.assertAlmostEqual(res.best_feasible_objective, m.y.value, 6) + self.assertTrue(res.best_objective_bound >= m.y.value - 1e-12) duals = opt.get_duals() self.assertAlmostEqual(duals[m.con1], (1 + a1 / (a2 - a1)), 6) self.assertAlmostEqual(duals[m.con2], -a1 / (a2 - a1), 6) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_add_and_remove_vars( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): opt = opt_class(only_child_vars=only_child_vars) if not opt.available(): @@ -823,9 +785,7 @@ def test_add_and_remove_vars( opt.update_config.check_for_new_or_removed_vars = False opt.config.load_solution = False res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) opt.load_vars() self.assertAlmostEqual(m.y.value, -1) m.x = pe.Var() @@ -839,9 +799,7 @@ def test_add_and_remove_vars( opt.add_variables([m.x]) opt.add_constraints([m.c1, m.c2]) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) opt.load_vars() self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) @@ -850,9 +808,7 @@ def test_add_and_remove_vars( opt.remove_variables([m.x]) m.x.value = None res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) opt.load_vars() self.assertEqual(m.x.value, None) self.assertAlmostEqual(m.y.value, -1) @@ -860,9 +816,7 @@ def test_add_and_remove_vars( opt.load_vars([m.x]) @parameterized.expand(input=_load_tests(nlp_solvers, only_child_vars_options)) - def test_exp( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars - ): + def test_exp(self, name: str, opt_class: Type[PersistentSolver], only_child_vars): opt = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest @@ -876,9 +830,7 @@ def test_exp( self.assertAlmostEqual(m.y.value, 0.6529186341994245) @parameterized.expand(input=_load_tests(nlp_solvers, only_child_vars_options)) - def test_log( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars - ): + def test_log(self, name: str, opt_class: Type[PersistentSolver], only_child_vars): opt = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest @@ -893,9 +845,9 @@ def test_log( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_with_numpy( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -917,17 +869,15 @@ def test_with_numpy( ) ) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_bounds_with_params( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -959,9 +909,9 @@ def test_bounds_with_params( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_solution_loader( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -1012,9 +962,9 @@ def test_solution_loader( @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_time_limit( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest from sys import platform @@ -1029,8 +979,8 @@ def test_time_limit( m.x = pe.Var(m.jobs, m.tasks, bounds=(0, 1)) random.seed(0) - coefs = [] - lin_vars = [] + coefs = list() + lin_vars = list() for j in m.jobs: for t in m.tasks: coefs.append(random.uniform(0, 10)) @@ -1062,13 +1012,21 @@ def test_time_limit( opt.config.time_limit = 0 opt.config.load_solution = False res = opt.solve(m) - self.assertEqual(res.termination_condition, TerminationCondition.maxTimeLimit) + if type(opt) is Cbc: # I can't figure out why CBC is reporting max iter... + self.assertIn( + res.termination_condition, + {TerminationCondition.maxIterations, TerminationCondition.maxTimeLimit}, + ) + else: + self.assertEqual( + res.termination_condition, TerminationCondition.maxTimeLimit + ) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_objective_changes( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -1078,13 +1036,13 @@ def test_objective_changes( m.c2 = pe.Constraint(expr=m.y >= -m.x + 1) m.obj = pe.Objective(expr=m.y) res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 1) + self.assertAlmostEqual(res.best_feasible_objective, 1) m.obj = pe.Objective(expr=2 * m.y) res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 2) + self.assertAlmostEqual(res.best_feasible_objective, 2) m.obj.expr = 3 * m.y res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 3) + self.assertAlmostEqual(res.best_feasible_objective, 3) m.obj.sense = pe.maximize opt.config.load_solution = False res = opt.solve(m) @@ -1100,88 +1058,88 @@ def test_objective_changes( m.obj = pe.Objective(expr=m.x * m.y) m.x.fix(2) res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 6, 6) + self.assertAlmostEqual(res.best_feasible_objective, 6, 6) m.x.fix(3) res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 12, 6) + self.assertAlmostEqual(res.best_feasible_objective, 12, 6) m.x.unfix() m.y.fix(2) m.x.setlb(-3) m.x.setub(5) res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, -2, 6) + self.assertAlmostEqual(res.best_feasible_objective, -2, 6) m.y.unfix() m.x.setlb(None) m.x.setub(None) m.e = pe.Expression(expr=2) m.obj = pe.Objective(expr=m.e * m.y) res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 2) + self.assertAlmostEqual(res.best_feasible_objective, 2) m.e.expr = 3 res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 3) + self.assertAlmostEqual(res.best_feasible_objective, 3) opt.update_config.check_for_new_objective = False m.e.expr = 4 res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 4) + self.assertAlmostEqual(res.best_feasible_objective, 4) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_domain( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() m.x = pe.Var(bounds=(1, None), domain=pe.NonNegativeReals) m.obj = pe.Objective(expr=m.x) res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 1) + self.assertAlmostEqual(res.best_feasible_objective, 1) m.x.setlb(-1) res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 0) + self.assertAlmostEqual(res.best_feasible_objective, 0) m.x.setlb(1) res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 1) + self.assertAlmostEqual(res.best_feasible_objective, 1) m.x.setlb(-1) m.x.domain = pe.Reals res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, -1) + self.assertAlmostEqual(res.best_feasible_objective, -1) m.x.domain = pe.NonNegativeReals res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 0) + self.assertAlmostEqual(res.best_feasible_objective, 0) @parameterized.expand(input=_load_tests(mip_solvers, only_child_vars_options)) def test_domain_with_integers( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() m.x = pe.Var(bounds=(-1, None), domain=pe.NonNegativeIntegers) m.obj = pe.Objective(expr=m.x) res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 0) + self.assertAlmostEqual(res.best_feasible_objective, 0) m.x.setlb(0.5) res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 1) + self.assertAlmostEqual(res.best_feasible_objective, 1) m.x.setlb(-5.5) m.x.domain = pe.Integers res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, -5) + self.assertAlmostEqual(res.best_feasible_objective, -5) m.x.domain = pe.Binary res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 0) + self.assertAlmostEqual(res.best_feasible_objective, 0) m.x.setlb(0.5) res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 1) + self.assertAlmostEqual(res.best_feasible_objective, 1) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_fixed_binaries( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -1191,25 +1149,25 @@ def test_fixed_binaries( m.c = pe.Constraint(expr=m.y >= m.x) m.x.fix(0) res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 0) + self.assertAlmostEqual(res.best_feasible_objective, 0) m.x.fix(1) res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 1) + self.assertAlmostEqual(res.best_feasible_objective, 1) - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) opt.update_config.treat_fixed_vars_as_params = False m.x.fix(0) res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 0) + self.assertAlmostEqual(res.best_feasible_objective, 0) m.x.fix(1) res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 1) + self.assertAlmostEqual(res.best_feasible_objective, 1) @parameterized.expand(input=_load_tests(mip_solvers, only_child_vars_options)) def test_with_gdp( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars + self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest @@ -1227,15 +1185,20 @@ def test_with_gdp( pe.TransformationFactory("gdp.bigm").apply_to(m) res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 1) + self.assertAlmostEqual(res.best_feasible_objective, 1) + self.assertAlmostEqual(m.x.value, 0) + self.assertAlmostEqual(m.y.value, 1) + + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + opt.use_extensions = True + res = opt.solve(m) + self.assertAlmostEqual(res.best_feasible_objective, 1) self.assertAlmostEqual(m.x.value, 0) self.assertAlmostEqual(m.y.value, 1) @parameterized.expand(input=all_solvers) - def test_variables_elsewhere( - self, name: str, opt_class: Type[PersistentSolverBase] - ): - opt: PersistentSolverBase = opt_class(only_child_vars=False) + def test_variables_elsewhere(self, name: str, opt_class: Type[PersistentSolver]): + opt: PersistentSolver = opt_class(only_child_vars=False) if not opt.available(): raise unittest.SkipTest @@ -1248,27 +1211,21 @@ def test_variables_elsewhere( m.b.c2 = pe.Constraint(expr=m.y >= -m.x) res = opt.solve(m.b) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertAlmostEqual(res.incumbent_objective, 1) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(res.best_feasible_objective, 1) self.assertAlmostEqual(m.x.value, -1) self.assertAlmostEqual(m.y.value, 1) m.x.setlb(0) res = opt.solve(m.b) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertAlmostEqual(res.incumbent_objective, 2) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(res.best_feasible_objective, 2) self.assertAlmostEqual(m.x.value, 0) self.assertAlmostEqual(m.y.value, 2) @parameterized.expand(input=all_solvers) - def test_variables_elsewhere2( - self, name: str, opt_class: Type[PersistentSolverBase] - ): - opt: PersistentSolverBase = opt_class(only_child_vars=False) + def test_variables_elsewhere2(self, name: str, opt_class: Type[PersistentSolver]): + opt: PersistentSolver = opt_class(only_child_vars=False) if not opt.available(): raise unittest.SkipTest @@ -1284,10 +1241,8 @@ def test_variables_elsewhere2( m.c4 = pe.Constraint(expr=m.y >= -m.z + 1) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertAlmostEqual(res.incumbent_objective, 1) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(res.best_feasible_objective, 1) sol = res.solution_loader.get_primals() self.assertIn(m.x, sol) self.assertIn(m.y, sol) @@ -1296,20 +1251,16 @@ def test_variables_elsewhere2( del m.c3 del m.c4 res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertAlmostEqual(res.incumbent_objective, 0) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(res.best_feasible_objective, 0) sol = res.solution_loader.get_primals() self.assertIn(m.x, sol) self.assertIn(m.y, sol) self.assertNotIn(m.z, sol) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) - def test_bug_1( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars - ): - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + def test_bug_1(self, name: str, opt_class: Type[PersistentSolver], only_child_vars): + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest @@ -1322,28 +1273,22 @@ def test_bug_1( m.c = pe.Constraint(expr=m.y >= m.p * m.x) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertAlmostEqual(res.incumbent_objective, 0) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(res.best_feasible_objective, 0) m.p.value = 1 res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertAlmostEqual(res.incumbent_objective, 3) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(res.best_feasible_objective, 3) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) - def test_bug_2( - self, name: str, opt_class: Type[PersistentSolverBase], only_child_vars - ): + def test_bug_2(self, name: str, opt_class: Type[PersistentSolver], only_child_vars): """ This test is for a bug where an objective containing a fixed variable does not get updated properly when the variable is unfixed. """ for fixed_var_option in [True, False]: - opt: PersistentSolverBase = opt_class(only_child_vars=only_child_vars) + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) if not opt.available(): raise unittest.SkipTest opt.update_config.treat_fixed_vars_as_params = fixed_var_option @@ -1356,19 +1301,19 @@ def test_bug_2( m.x.fix(1) res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, 2, 5) + self.assertAlmostEqual(res.best_feasible_objective, 2, 5) m.x.unfix() m.x.setlb(-9) m.x.setub(9) res = opt.solve(m) - self.assertAlmostEqual(res.incumbent_objective, -18, 5) + self.assertAlmostEqual(res.best_feasible_objective, -18, 5) @unittest.skipUnless(cmodel_available, 'appsi extensions are not available') class TestLegacySolverInterface(unittest.TestCase): @parameterized.expand(input=all_solvers) - def test_param_updates(self, name: str, opt_class: Type[PersistentSolverBase]): + def test_param_updates(self, name: str, opt_class: Type[PersistentSolver]): opt = pe.SolverFactory('appsi_' + name) if not opt.available(exception_flag=False): raise unittest.SkipTest @@ -1398,7 +1343,7 @@ def test_param_updates(self, name: str, opt_class: Type[PersistentSolverBase]): self.assertAlmostEqual(m.dual[m.c2], a1 / (a2 - a1)) @parameterized.expand(input=all_solvers) - def test_load_solutions(self, name: str, opt_class: Type[PersistentSolverBase]): + def test_load_solutions(self, name: str, opt_class: Type[PersistentSolver]): opt = pe.SolverFactory('appsi_' + name) if not opt.available(exception_flag=False): raise unittest.SkipTest diff --git a/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py index e09865294eb..d250923f104 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py @@ -1,6 +1,6 @@ import pyomo.environ as pe import pyomo.common.unittest as unittest -from pyomo.contrib.solver.results import TerminationCondition, SolutionStatus +from pyomo.contrib.appsi.base import TerminationCondition, Results, PersistentSolver from pyomo.contrib.appsi.solvers.wntr import Wntr, wntr_available import math @@ -18,18 +18,12 @@ def test_param_updates(self): opt = Wntr() opt.wntr_options.update(_default_wntr_options) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, 1) m.p.value = 2 res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, 2) def test_remove_add_constraint(self): @@ -42,10 +36,7 @@ def test_remove_add_constraint(self): opt.config.symbolic_solver_labels = True opt.wntr_options.update(_default_wntr_options) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, 0) self.assertAlmostEqual(m.y.value, 1) @@ -54,10 +45,7 @@ def test_remove_add_constraint(self): m.x.value = 0.5 m.y.value = 0.5 res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, 1) self.assertAlmostEqual(m.y.value, 0) @@ -70,30 +58,21 @@ def test_fixed_var(self): opt = Wntr() opt.wntr_options.update(_default_wntr_options) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, 0.5) self.assertAlmostEqual(m.y.value, 0.25) m.x.unfix() m.c2 = pe.Constraint(expr=m.y == pe.exp(m.x)) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, 0) self.assertAlmostEqual(m.y.value, 1) m.x.fix(0.5) del m.c2 res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, 0.5) self.assertAlmostEqual(m.y.value, 0.25) @@ -110,10 +89,7 @@ def test_remove_variables_params(self): opt = Wntr() opt.wntr_options.update(_default_wntr_options) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, 1) self.assertAlmostEqual(m.y.value, 1) self.assertAlmostEqual(m.z.value, 0) @@ -124,20 +100,14 @@ def test_remove_variables_params(self): m.z.value = 2 m.px.value = 2 res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, 2) self.assertAlmostEqual(m.z.value, 2) del m.z m.px.value = 3 res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, 3) def test_get_primals(self): @@ -150,10 +120,7 @@ def test_get_primals(self): opt.config.load_solution = False opt.wntr_options.update(_default_wntr_options) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, None) self.assertAlmostEqual(m.y.value, None) primals = opt.get_primals() @@ -167,73 +134,49 @@ def test_operators(self): opt = Wntr() opt.wntr_options.update(_default_wntr_options) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, 2) del m.c1 m.x.value = 0 m.c1 = pe.Constraint(expr=pe.sin(m.x) == math.sin(math.pi / 4)) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, math.pi / 4) del m.c1 m.c1 = pe.Constraint(expr=pe.cos(m.x) == 0) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, math.pi / 2) del m.c1 m.c1 = pe.Constraint(expr=pe.tan(m.x) == 1) m.x.value = 0 res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, math.pi / 4) del m.c1 m.c1 = pe.Constraint(expr=pe.asin(m.x) == math.asin(0.5)) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, 0.5) del m.c1 m.c1 = pe.Constraint(expr=pe.acos(m.x) == math.acos(0.6)) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, 0.6) del m.c1 m.c1 = pe.Constraint(expr=pe.atan(m.x) == math.atan(0.5)) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, 0.5) del m.c1 m.c1 = pe.Constraint(expr=pe.sqrt(m.x) == math.sqrt(0.6)) res = opt.solve(m) - self.assertEqual( - res.termination_condition, TerminationCondition.convergenceCriteriaSatisfied - ) - self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, 0.6) diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py index 04f54530c1b..0a358c6aedf 100644 --- a/pyomo/contrib/appsi/solvers/wntr.py +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -1,8 +1,11 @@ -from pyomo.contrib.solver.base import PersistentSolverBase -from pyomo.contrib.solver.util import PersistentSolverUtils -from pyomo.contrib.solver.config import SolverConfig -from pyomo.contrib.solver.results import Results, TerminationCondition, SolutionStatus -from pyomo.contrib.solver.solution import PersistentSolutionLoader +from pyomo.contrib.appsi.base import ( + PersistentBase, + PersistentSolver, + SolverConfig, + Results, + TerminationCondition, + PersistentSolutionLoader, +) from pyomo.core.expr.numeric_expr import ( ProductExpression, DivisionExpression, @@ -33,6 +36,7 @@ from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler from pyomo.common.dependencies import attempt_import from pyomo.core.staleflag import StaleFlagManager +from pyomo.contrib.appsi.cmodel import cmodel, cmodel_available wntr, wntr_available = attempt_import('wntr') import logging @@ -65,10 +69,11 @@ def __init__( class WntrResults(Results): def __init__(self, solver): super().__init__() + self.wallclock_time = None self.solution_loader = PersistentSolutionLoader(solver=solver) -class Wntr(PersistentSolverUtils, PersistentSolverBase): +class Wntr(PersistentBase, PersistentSolver): def __init__(self, only_child_vars=True): super().__init__(only_child_vars=only_child_vars) self._config = WntrConfig() @@ -121,7 +126,7 @@ def _solve(self, timer: HierarchicalTimer): options.update(self.wntr_options) opt = wntr.sim.solvers.NewtonSolver(options) - if self.config.tee: + if self.config.stream_solver: ostream = sys.stdout else: ostream = None @@ -138,14 +143,13 @@ def _solve(self, timer: HierarchicalTimer): tf = time.time() results = WntrResults(self) - results.timing_info.wall_time = tf - t0 + results.wallclock_time = tf - t0 if status == wntr.sim.solvers.SolverStatus.converged: - results.termination_condition = ( - TerminationCondition.convergenceCriteriaSatisfied - ) - results.solution_status = SolutionStatus.optimal + results.termination_condition = TerminationCondition.optimal else: results.termination_condition = TerminationCondition.error + results.best_feasible_objective = None + results.best_objective_bound = None if self.config.load_solution: if status == wntr.sim.solvers.SolverStatus.converged: @@ -157,7 +161,7 @@ def _solve(self, timer: HierarchicalTimer): 'A feasible solution was not found, so no solution can be loaded.' 'Please set opt.config.load_solution=False and check ' 'results.termination_condition and ' - 'results.incumbent_objective before loading a solution.' + 'results.best_feasible_objective before loading a solution.' ) return results @@ -208,6 +212,8 @@ def set_instance(self, model): ) self._reinit() self._model = model + if self.use_extensions and cmodel_available: + self._expr_types = cmodel.PyomoExprTypes() if self.config.symbolic_solver_labels: self._labeler = TextLabeler() diff --git a/pyomo/contrib/appsi/tests/test_base.py b/pyomo/contrib/appsi/tests/test_base.py new file mode 100644 index 00000000000..0d67ca4d01a --- /dev/null +++ b/pyomo/contrib/appsi/tests/test_base.py @@ -0,0 +1,91 @@ +from pyomo.common import unittest +from pyomo.contrib import appsi +import pyomo.environ as pe +from pyomo.core.base.var import ScalarVar + + +class TestResults(unittest.TestCase): + def test_uninitialized(self): + res = appsi.base.Results() + self.assertIsNone(res.best_feasible_objective) + self.assertIsNone(res.best_objective_bound) + self.assertEqual( + res.termination_condition, appsi.base.TerminationCondition.unknown + ) + + with self.assertRaisesRegex( + RuntimeError, '.*does not currently have a valid solution.*' + ): + res.solution_loader.load_vars() + with self.assertRaisesRegex( + RuntimeError, '.*does not currently have valid duals.*' + ): + res.solution_loader.get_duals() + with self.assertRaisesRegex( + RuntimeError, '.*does not currently have valid reduced costs.*' + ): + res.solution_loader.get_reduced_costs() + with self.assertRaisesRegex( + RuntimeError, '.*does not currently have valid slacks.*' + ): + res.solution_loader.get_slacks() + + def test_results(self): + m = pe.ConcreteModel() + m.x = ScalarVar() + m.y = ScalarVar() + m.c1 = pe.Constraint(expr=m.x == 1) + m.c2 = pe.Constraint(expr=m.y == 2) + + primals = dict() + primals[id(m.x)] = (m.x, 1) + primals[id(m.y)] = (m.y, 2) + duals = dict() + duals[m.c1] = 3 + duals[m.c2] = 4 + rc = dict() + rc[id(m.x)] = (m.x, 5) + rc[id(m.y)] = (m.y, 6) + slacks = dict() + slacks[m.c1] = 7 + slacks[m.c2] = 8 + + res = appsi.base.Results() + res.solution_loader = appsi.base.SolutionLoader( + primals=primals, duals=duals, slacks=slacks, reduced_costs=rc + ) + + res.solution_loader.load_vars() + self.assertAlmostEqual(m.x.value, 1) + self.assertAlmostEqual(m.y.value, 2) + + m.x.value = None + m.y.value = None + + res.solution_loader.load_vars([m.y]) + self.assertIsNone(m.x.value) + self.assertAlmostEqual(m.y.value, 2) + + duals2 = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.c1], duals2[m.c1]) + self.assertAlmostEqual(duals[m.c2], duals2[m.c2]) + + duals2 = res.solution_loader.get_duals([m.c2]) + self.assertNotIn(m.c1, duals2) + self.assertAlmostEqual(duals[m.c2], duals2[m.c2]) + + rc2 = res.solution_loader.get_reduced_costs() + self.assertAlmostEqual(rc[id(m.x)][1], rc2[m.x]) + self.assertAlmostEqual(rc[id(m.y)][1], rc2[m.y]) + + rc2 = res.solution_loader.get_reduced_costs([m.y]) + self.assertNotIn(m.x, rc2) + self.assertAlmostEqual(rc[id(m.y)][1], rc2[m.y]) + + slacks2 = res.solution_loader.get_slacks() + self.assertAlmostEqual(slacks[m.c1], slacks2[m.c1]) + self.assertAlmostEqual(slacks[m.c2], slacks2[m.c2]) + + slacks2 = res.solution_loader.get_slacks([m.c2]) + self.assertNotIn(m.c1, slacks2) + self.assertAlmostEqual(slacks[m.c2], slacks2[m.c2]) diff --git a/pyomo/contrib/appsi/tests/test_interval.py b/pyomo/contrib/appsi/tests/test_interval.py index 0924e3bbeed..7963cc31665 100644 --- a/pyomo/contrib/appsi/tests/test_interval.py +++ b/pyomo/contrib/appsi/tests/test_interval.py @@ -1,5 +1,5 @@ from pyomo.contrib.appsi.cmodel import cmodel, cmodel_available -from pyomo.common import unittest +import pyomo.common.unittest as unittest import math from pyomo.contrib.fbbt.tests.test_interval import IntervalTestBase @@ -7,7 +7,7 @@ @unittest.skipUnless(cmodel_available, 'appsi extensions are not available') class TestInterval(IntervalTestBase, unittest.TestCase): def setUp(self): - super().setUp() + super(TestInterval, self).setUp() self.add = cmodel.py_interval_add self.sub = cmodel.py_interval_sub self.mul = cmodel.py_interval_mul diff --git a/pyomo/contrib/appsi/utils/__init__.py b/pyomo/contrib/appsi/utils/__init__.py new file mode 100644 index 00000000000..f665736fd4a --- /dev/null +++ b/pyomo/contrib/appsi/utils/__init__.py @@ -0,0 +1,2 @@ +from .get_objective import get_objective +from .collect_vars_and_named_exprs import collect_vars_and_named_exprs diff --git a/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py b/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py new file mode 100644 index 00000000000..9027080f08c --- /dev/null +++ b/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py @@ -0,0 +1,50 @@ +from pyomo.core.expr.visitor import ExpressionValueVisitor, nonpyomo_leaf_types +import pyomo.core.expr as EXPR + + +class _VarAndNamedExprCollector(ExpressionValueVisitor): + def __init__(self): + self.named_expressions = dict() + self.variables = dict() + self.fixed_vars = dict() + self._external_functions = dict() + + def visit(self, node, values): + pass + + def visiting_potential_leaf(self, node): + if type(node) in nonpyomo_leaf_types: + return True, None + + if node.is_variable_type(): + self.variables[id(node)] = node + if node.is_fixed(): + self.fixed_vars[id(node)] = node + return True, None + + if node.is_named_expression_type(): + self.named_expressions[id(node)] = node + return False, None + + if type(node) is EXPR.ExternalFunctionExpression: + self._external_functions[id(node)] = node + return False, None + + if node.is_expression_type(): + return False, None + + return True, None + + +_visitor = _VarAndNamedExprCollector() + + +def collect_vars_and_named_exprs(expr): + _visitor.__init__() + _visitor.dfs_postorder_stack(expr) + return ( + list(_visitor.named_expressions.values()), + list(_visitor.variables.values()), + list(_visitor.fixed_vars.values()), + list(_visitor._external_functions.values()), + ) diff --git a/pyomo/contrib/appsi/utils/get_objective.py b/pyomo/contrib/appsi/utils/get_objective.py new file mode 100644 index 00000000000..30dd911f9c8 --- /dev/null +++ b/pyomo/contrib/appsi/utils/get_objective.py @@ -0,0 +1,12 @@ +from pyomo.core.base.objective import Objective + + +def get_objective(block): + obj = None + for o in block.component_data_objects( + Objective, descend_into=True, active=True, sort=True + ): + if obj is not None: + raise ValueError('Multiple active objectives found') + obj = o + return obj diff --git a/pyomo/contrib/appsi/utils/tests/__init__.py b/pyomo/contrib/appsi/utils/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pyomo/contrib/appsi/utils/tests/test_collect_vars_and_named_exprs.py b/pyomo/contrib/appsi/utils/tests/test_collect_vars_and_named_exprs.py new file mode 100644 index 00000000000..4c2a167a017 --- /dev/null +++ b/pyomo/contrib/appsi/utils/tests/test_collect_vars_and_named_exprs.py @@ -0,0 +1,56 @@ +from pyomo.common import unittest +import pyomo.environ as pe +from pyomo.contrib.appsi.utils import collect_vars_and_named_exprs +from pyomo.contrib.appsi.cmodel import cmodel, cmodel_available +from typing import Callable +from pyomo.common.gsl import find_GSL + + +class TestCollectVarsAndNamedExpressions(unittest.TestCase): + def basics_helper(self, collector: Callable, *args): + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.z = pe.Var() + m.E = pe.Expression(expr=2 * m.z + 1) + m.y.fix(3) + e = m.x * m.y + m.x * m.E + named_exprs, var_list, fixed_vars, external_funcs = collector(e, *args) + self.assertEqual([m.E], named_exprs) + self.assertEqual([m.x, m.y, m.z], var_list) + self.assertEqual([m.y], fixed_vars) + self.assertEqual([], external_funcs) + + def test_basics(self): + self.basics_helper(collect_vars_and_named_exprs) + + @unittest.skipUnless(cmodel_available, 'appsi extensions are not available') + def test_basics_cmodel(self): + self.basics_helper(cmodel.prep_for_repn, cmodel.PyomoExprTypes()) + + def external_func_helper(self, collector: Callable, *args): + DLL = find_GSL() + if not DLL: + self.skipTest('Could not find amplgsl.dll library') + + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.z = pe.Var() + m.hypot = pe.ExternalFunction(library=DLL, function='gsl_hypot') + func = m.hypot(m.x, m.x * m.y) + m.E = pe.Expression(expr=2 * func) + m.y.fix(3) + e = m.z + m.x * m.E + named_exprs, var_list, fixed_vars, external_funcs = collector(e, *args) + self.assertEqual([m.E], named_exprs) + self.assertEqual([m.z, m.x, m.y], var_list) + self.assertEqual([m.y], fixed_vars) + self.assertEqual([func], external_funcs) + + def test_external(self): + self.external_func_helper(collect_vars_and_named_exprs) + + @unittest.skipUnless(cmodel_available, 'appsi extensions are not available') + def test_external_cmodel(self): + self.basics_helper(cmodel.prep_for_repn, cmodel.PyomoExprTypes()) diff --git a/pyomo/contrib/appsi/writers/config.py b/pyomo/contrib/appsi/writers/config.py index 2a4e638f097..7a7faadaabe 100644 --- a/pyomo/contrib/appsi/writers/config.py +++ b/pyomo/contrib/appsi/writers/config.py @@ -1,3 +1,3 @@ -class WriterConfig: +class WriterConfig(object): def __init__(self): self.symbolic_solver_labels = False diff --git a/pyomo/contrib/appsi/writers/lp_writer.py b/pyomo/contrib/appsi/writers/lp_writer.py index 9d0b71fe794..8a76fa5f9eb 100644 --- a/pyomo/contrib/appsi/writers/lp_writer.py +++ b/pyomo/contrib/appsi/writers/lp_writer.py @@ -5,17 +5,19 @@ from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.core.base.sos import _SOSConstraintData from pyomo.core.base.block import _BlockData +from pyomo.repn.standard_repn import generate_standard_repn +from pyomo.core.expr.numvalue import value +from pyomo.contrib.appsi.base import PersistentBase from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler from pyomo.common.timing import HierarchicalTimer -from pyomo.core.kernel.objective import minimize -from pyomo.contrib.solver.util import PersistentSolverUtils +from pyomo.core.kernel.objective import minimize, maximize from .config import WriterConfig from ..cmodel import cmodel, cmodel_available -class LPWriter(PersistentSolverUtils): +class LPWriter(PersistentBase): def __init__(self, only_child_vars=False): - super().__init__(only_child_vars=only_child_vars) + super(LPWriter, self).__init__(only_child_vars=only_child_vars) self._config = WriterConfig() self._writer = None self._symbol_map = SymbolMap() @@ -23,11 +25,11 @@ def __init__(self, only_child_vars=False): self._con_labeler = None self._param_labeler = None self._obj_labeler = None - self._pyomo_var_to_solver_var_map = {} - self._pyomo_con_to_solver_con_map = {} - self._solver_var_to_pyomo_var_map = {} - self._solver_con_to_pyomo_con_map = {} - self._pyomo_param_to_solver_param_map = {} + self._pyomo_var_to_solver_var_map = dict() + self._pyomo_con_to_solver_con_map = dict() + self._solver_var_to_pyomo_var_map = dict() + self._solver_con_to_pyomo_con_map = dict() + self._pyomo_param_to_solver_param_map = dict() self._expr_types = None @property @@ -87,7 +89,7 @@ def _add_params(self, params: List[_ParamData]): self._pyomo_param_to_solver_param_map[id(p)] = cp def _add_constraints(self, cons: List[_GeneralConstraintData]): - cmodel.process_lp_constraints() + cmodel.process_lp_constraints(cons, self) def _add_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) != 0: diff --git a/pyomo/contrib/appsi/writers/nl_writer.py b/pyomo/contrib/appsi/writers/nl_writer.py index a9b44e63f36..9c739fd6ebb 100644 --- a/pyomo/contrib/appsi/writers/nl_writer.py +++ b/pyomo/contrib/appsi/writers/nl_writer.py @@ -1,6 +1,4 @@ -import os from typing import List - from pyomo.core.base.param import _ParamData from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.constraint import _GeneralConstraintData @@ -8,31 +6,32 @@ from pyomo.core.base.sos import _SOSConstraintData from pyomo.core.base.block import _BlockData from pyomo.repn.standard_repn import generate_standard_repn -from pyomo.core.base import SymbolMap, TextLabeler +from pyomo.core.expr.numvalue import value +from pyomo.contrib.appsi.base import PersistentBase +from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler from pyomo.common.timing import HierarchicalTimer from pyomo.core.kernel.objective import minimize -from pyomo.common.collections import OrderedSet -from pyomo.repn.plugins.ampl.ampl_ import set_pyomo_amplfunc_env -from pyomo.contrib.solver.util import PersistentSolverUtils - from .config import WriterConfig +from pyomo.common.collections import OrderedSet +import os from ..cmodel import cmodel, cmodel_available +from pyomo.repn.plugins.ampl.ampl_ import set_pyomo_amplfunc_env -class NLWriter(PersistentSolverUtils): +class NLWriter(PersistentBase): def __init__(self, only_child_vars=False): - super().__init__(only_child_vars=only_child_vars) + super(NLWriter, self).__init__(only_child_vars=only_child_vars) self._config = WriterConfig() self._writer = None self._symbol_map = SymbolMap() self._var_labeler = None self._con_labeler = None self._param_labeler = None - self._pyomo_var_to_solver_var_map = {} - self._pyomo_con_to_solver_con_map = {} - self._solver_var_to_pyomo_var_map = {} - self._solver_con_to_pyomo_con_map = {} - self._pyomo_param_to_solver_param_map = {} + self._pyomo_var_to_solver_var_map = dict() + self._pyomo_con_to_solver_con_map = dict() + self._solver_var_to_pyomo_var_map = dict() + self._solver_con_to_pyomo_con_map = dict() + self._pyomo_param_to_solver_param_map = dict() self._expr_types = None @property @@ -173,8 +172,8 @@ def update_params(self): def _set_objective(self, obj: _GeneralObjectiveData): if obj is None: const = cmodel.Constant(0) - lin_vars = [] - lin_coef = [] + lin_vars = list() + lin_coef = list() nonlin = cmodel.Constant(0) sense = 0 else: @@ -241,7 +240,7 @@ def write(self, model: _BlockData, filename: str, timer: HierarchicalTimer = Non timer.stop('write file') def update(self, timer: HierarchicalTimer = None): - super().update(timer=timer) + super(NLWriter, self).update(timer=timer) self._set_pyomo_amplfunc_env() def get_ordered_vars(self): diff --git a/pyomo/contrib/appsi/writers/tests/test_nl_writer.py b/pyomo/contrib/appsi/writers/tests/test_nl_writer.py index 297bc3d7617..3b61a5901c3 100644 --- a/pyomo/contrib/appsi/writers/tests/test_nl_writer.py +++ b/pyomo/contrib/appsi/writers/tests/test_nl_writer.py @@ -1,4 +1,4 @@ -from pyomo.common import unittest +import pyomo.common.unittest as unittest from pyomo.common.tempfiles import TempfileManager import pyomo.environ as pe from pyomo.contrib import appsi From c0d35302b00a5b41752fd641a2f9c098fb5b8b3e Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 16 Jan 2024 10:10:48 -0700 Subject: [PATCH 0796/1204] Explicitly register contrib.solver --- pyomo/environ/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/environ/__init__.py b/pyomo/environ/__init__.py index 51c68449247..5d488bda290 100644 --- a/pyomo/environ/__init__.py +++ b/pyomo/environ/__init__.py @@ -50,6 +50,7 @@ def _do_import(pkg_name): 'pyomo.contrib.multistart', 'pyomo.contrib.preprocessing', 'pyomo.contrib.pynumero', + 'pyomo.contrib.solver', 'pyomo.contrib.trustregion', ] From a1be778f394f2a92200e7ec5fb1ef8ea9c7b4ba1 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 16 Jan 2024 10:12:49 -0700 Subject: [PATCH 0797/1204] Fix linking in online docs --- doc/OnlineDocs/developer_reference/solvers.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index 75d95fc36db..10e7e829463 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -3,7 +3,7 @@ Solver Interfaces Pyomo offers interfaces into multiple solvers, both commercial and open source. -.. currentmodule:: pyomo.solver +.. currentmodule:: pyomo.contrib.solver Interface Implementation @@ -19,7 +19,7 @@ Every solver, at the end of a ``solve`` call, will return a ``Results`` object. This object is a :py:class:`pyomo.common.config.ConfigDict`, which can be manipulated similar to a standard ``dict`` in Python. -.. autoclass:: pyomo.solver.results.Results +.. autoclass:: pyomo.contrib.solver.results.Results :show-inheritance: :members: :undoc-members: @@ -35,7 +35,7 @@ returned solver messages or logs for more information. -.. autoclass:: pyomo.solver.results.TerminationCondition +.. autoclass:: pyomo.contrib.solver.results.TerminationCondition :show-inheritance: :noindex: @@ -48,7 +48,7 @@ intent of ``SolutionStatus`` is to notify the user of what the solver returned at a high level. The user is expected to inspect the ``Results`` object or any returned solver messages or logs for more information. -.. autoclass:: pyomo.solver.results.SolutionStatus +.. autoclass:: pyomo.contrib.solver.results.SolutionStatus :show-inheritance: :noindex: From d69959093467d97b1e289a3c9f72253b87d59254 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 16 Jan 2024 11:51:33 -0700 Subject: [PATCH 0798/1204] Add assertExpressionsEqual to base TestCase class --- pyomo/common/unittest.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pyomo/common/unittest.py b/pyomo/common/unittest.py index 6b416b82c2b..9bf68e53314 100644 --- a/pyomo/common/unittest.py +++ b/pyomo/common/unittest.py @@ -555,6 +555,20 @@ def assertRaisesRegex(self, expected_exception, expected_regex, *args, **kwargs) context = contextClass(expected_exception, self, expected_regex) return context.handle('assertRaisesRegex', args, kwargs) + def assertExpressionsEqual(self, a, b, include_named_exprs=True, places=None): + from pyomo.core.expr.compare import assertExpressionsEqual + + return assertExpressionsEqual(self, a, b, include_named_exprs, places) + + def assertExpressionsStructurallyEqual( + self, a, b, include_named_exprs=True, places=None + ): + from pyomo.core.expr.compare import assertExpressionsEqual + + return assertExpressionsStructurallyEqual( + self, a, b, include_named_exprs, places + ) + class BaselineTestDriver(object): """Generic driver for performing baseline tests in bulk From 7c39f8fbcbcf9d9bdf90e1449cf7606385958bc7 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 16 Jan 2024 11:52:05 -0700 Subject: [PATCH 0799/1204] Add to_expr() method to AMPLRepn class --- pyomo/repn/plugins/nl_writer.py | 20 +++++++++- pyomo/repn/tests/ampl/test_nlv2.py | 62 ++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index f79b5009122..8fb8f74285a 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -26,7 +26,7 @@ document_kwargs_from_configdict, ) from pyomo.common.deprecation import deprecation_warning -from pyomo.common.errors import DeveloperError, InfeasibleConstraintException +from pyomo.common.errors import DeveloperError, InfeasibleConstraintException, MouseTrap from pyomo.common.gc_manager import PauseGC from pyomo.common.numeric_types import ( native_complex_types, @@ -2132,6 +2132,24 @@ def append(self, other): elif _type is _CONSTANT: self.const += other[1] + def to_expr(self, var_map): + if self.nl is not None or self.nonlinear is not None: + # TODO: support converting general nonlinear expressiosn + # back to Pyomo expressions. This will require an AMPL + # parser. + raise MouseTrap("Cannot convert nonlinear AMPLRepn to Pyomo Expression") + if self.linear: + # Explicitly generate the LinearExpression. At time of + # writing, this is about 40% faster than standard operator + # overloading for O(1000) element sums + ans = LinearExpression( + [coef * var_map[vid] for vid, coef in self.linear.items()] + ) + ans += self.const + else: + ans = self.const + return ans * self.mult + def _create_strict_inequality_map(vars_): vars_['strict_inequality_map'] = { diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index fe5f422d323..42adfc9689d 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -24,6 +24,7 @@ from pyomo.repn.tests.nl_diff import nl_diff from pyomo.common.dependencies import numpy, numpy_available +from pyomo.common.errors import MouseTrap from pyomo.common.log import LoggingIntercept from pyomo.common.tee import capture_output from pyomo.common.tempfiles import TempfileManager @@ -872,6 +873,67 @@ def test_duplicate_shared_linear_expressions(self): self.assertEqual(repn2.linear, {id(m.x): 102, id(m.y): 103}) self.assertEqual(repn2.nonlinear, None) + def test_AMPLRepn_to_expr(self): + m = ConcreteModel() + m.p = Param([2, 3, 4], mutable=True, initialize=lambda m, i: i**2) + m.x = Var([2, 3, 4], initialize=lambda m, i: i) + + e = 10 + info = INFO() + with LoggingIntercept() as LOG: + repn = info.visitor.walk_expression((e, None, None, 1)) + self.assertEqual(LOG.getvalue(), "") + self.assertEqual(repn.nl, None) + self.assertEqual(repn.mult, 1) + self.assertEqual(repn.const, 10) + self.assertEqual(repn.linear, {}) + self.assertEqual(repn.nonlinear, None) + ee = repn.to_expr(info.var_map) + self.assertExpressionsEqual(ee, 10) + + e += sum(m.x[i] * m.p[i] for i in m.x) + info = INFO() + with LoggingIntercept() as LOG: + repn = info.visitor.walk_expression((e, None, None, 1)) + self.assertEqual(LOG.getvalue(), "") + self.assertEqual(repn.nl, None) + self.assertEqual(repn.mult, 1) + self.assertEqual(repn.const, 10) + self.assertEqual(repn.linear, {id(m.x[2]): 4, id(m.x[3]): 9, id(m.x[4]): 16}) + self.assertEqual(repn.nonlinear, None) + ee = repn.to_expr(info.var_map) + self.assertExpressionsEqual(ee, 4 * m.x[2] + 9 * m.x[3] + 16 * m.x[4] + 10) + self.assertEqual(ee(), 10 + 8 + 27 + 64) + + e = sum(m.x[i] * m.p[i] for i in m.x) + info = INFO() + with LoggingIntercept() as LOG: + repn = info.visitor.walk_expression((e, None, None, 1)) + self.assertEqual(LOG.getvalue(), "") + self.assertEqual(repn.nl, None) + self.assertEqual(repn.mult, 1) + self.assertEqual(repn.const, 0) + self.assertEqual(repn.linear, {id(m.x[2]): 4, id(m.x[3]): 9, id(m.x[4]): 16}) + self.assertEqual(repn.nonlinear, None) + ee = repn.to_expr(info.var_map) + self.assertExpressionsEqual(ee, 4 * m.x[2] + 9 * m.x[3] + 16 * m.x[4]) + self.assertEqual(ee(), 8 + 27 + 64) + + e += m.x[2] ** 2 + info = INFO() + with LoggingIntercept() as LOG: + repn = info.visitor.walk_expression((e, None, None, 1)) + self.assertEqual(LOG.getvalue(), "") + self.assertEqual(repn.nl, None) + self.assertEqual(repn.mult, 1) + self.assertEqual(repn.const, 0) + self.assertEqual(repn.linear, {id(m.x[2]): 4, id(m.x[3]): 9, id(m.x[4]): 16}) + self.assertEqual(repn.nonlinear, ('o5\n%s\nn2\n', [id(m.x[2])])) + with self.assertRaisesRegex( + MouseTrap, "Cannot convert nonlinear AMPLRepn to Pyomo Expression" + ): + ee = repn.to_expr(info.var_map) + class Test_NLWriter(unittest.TestCase): def test_external_function_str_args(self): From 1a8b9f7e6c830c9a861e2cc6c891729c4610202f Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 16 Jan 2024 11:52:35 -0700 Subject: [PATCH 0800/1204] Fix NLv2 to return eliminated_vars in line with documentation --- pyomo/repn/plugins/nl_writer.py | 3 +- pyomo/repn/tests/ampl/test_nlv2.py | 70 ++++++++++++++---------------- 2 files changed, 34 insertions(+), 39 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 8fb8f74285a..1081b69acff 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1536,7 +1536,8 @@ def write(self, model): # Generate the return information eliminated_vars = [ - (var_map[_id], expr_info) for _id, expr_info in eliminated_vars.items() + (var_map[_id], expr_info.to_expr(var_map)) + for _id, expr_info in eliminated_vars.items() ] eliminated_vars.reverse() if scale_model: diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 42adfc9689d..2e366039185 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1325,12 +1325,7 @@ def test_presolve_lower_triangular(self): self.assertEqual( nlinfo.eliminated_vars, - [ - (m.x[3], nl_writer.AMPLRepn(-4.0, {}, None)), - (m.x[1], nl_writer.AMPLRepn(4.0, {}, None)), - (m.x[2], nl_writer.AMPLRepn(3.0, {}, None)), - (m.x[0], nl_writer.AMPLRepn(5.0, {}, None)), - ], + [(m.x[3], -4.0), (m.x[1], 4.0), (m.x[2], 3.0), (m.x[0], 5.0)], ) self.assertEqual( *nl_diff( @@ -1376,12 +1371,7 @@ def test_presolve_lower_triangular_fixed(self): self.assertEqual( nlinfo.eliminated_vars, - [ - (m.x[3], nl_writer.AMPLRepn(-4.0, {}, None)), - (m.x[1], nl_writer.AMPLRepn(4.0, {}, None)), - (m.x[2], nl_writer.AMPLRepn(3.0, {}, None)), - (m.x[0], nl_writer.AMPLRepn(5.0, {}, None)), - ], + [(m.x[3], -4.0), (m.x[1], 4.0), (m.x[2], 3.0), (m.x[0], 5.0)], ) self.assertEqual( *nl_diff( @@ -1429,11 +1419,11 @@ def test_presolve_lower_triangular_implied(self): self.assertEqual( nlinfo.eliminated_vars, [ - (m.x[1], nl_writer.AMPLRepn(4.0, {}, None)), - (m.x[5], nl_writer.AMPLRepn(5.0, {}, None)), - (m.x[3], nl_writer.AMPLRepn(-4.0, {}, None)), - (m.x[2], nl_writer.AMPLRepn(3.0, {}, None)), - (m.x[0], nl_writer.AMPLRepn(5.0, {}, None)), + (m.x[1], 4.0), + (m.x[5], 5.0), + (m.x[3], -4.0), + (m.x[2], 3.0), + (m.x[0], 5.0), ], ) self.assertEqual( @@ -1477,15 +1467,18 @@ def test_presolve_almost_lower_triangular(self): nlinfo = nl_writer.NLWriter().write(m, OUT, linear_presolve=True) self.assertEqual(LOG.getvalue(), "") - self.assertEqual( - nlinfo.eliminated_vars, - [ - (m.x[4], nl_writer.AMPLRepn(-12, {id(m.x[1]): 3}, None)), - (m.x[3], nl_writer.AMPLRepn(-72, {id(m.x[1]): 17}, None)), - (m.x[2], nl_writer.AMPLRepn(-13, {id(m.x[1]): 4}, None)), - (m.x[0], nl_writer.AMPLRepn(29, {id(m.x[1]): -6}, None)), - ], - ) + self.assertIs(nlinfo.eliminated_vars[0][0], m.x[4]) + self.assertExpressionsEqual(nlinfo.eliminated_vars[0][1], 3.0 * m.x[1] - 12.0) + + self.assertIs(nlinfo.eliminated_vars[1][0], m.x[3]) + self.assertExpressionsEqual(nlinfo.eliminated_vars[1][1], 17.0 * m.x[1] - 72.0) + + self.assertIs(nlinfo.eliminated_vars[2][0], m.x[2]) + self.assertExpressionsEqual(nlinfo.eliminated_vars[2][1], 4.0 * m.x[1] - 13.0) + + self.assertIs(nlinfo.eliminated_vars[3][0], m.x[0]) + self.assertExpressionsEqual(nlinfo.eliminated_vars[3][1], -6.0 * m.x[1] + 29.0) + # Note: bounds on x[1] are: # min(22/3, 82/17, 23/4, -39/-6) == 4.823529411764706 # max(2/3, 62/17, 3/4, -19/-6) == 3.6470588235294117 @@ -1531,15 +1524,18 @@ def test_presolve_almost_lower_triangular_nonlinear(self): nlinfo = nl_writer.NLWriter().write(m, OUT, linear_presolve=True) self.assertEqual(LOG.getvalue(), "") - self.assertEqual( - nlinfo.eliminated_vars, - [ - (m.x[4], nl_writer.AMPLRepn(-12, {id(m.x[1]): 3}, None)), - (m.x[3], nl_writer.AMPLRepn(-72, {id(m.x[1]): 17}, None)), - (m.x[2], nl_writer.AMPLRepn(-13, {id(m.x[1]): 4}, None)), - (m.x[0], nl_writer.AMPLRepn(29, {id(m.x[1]): -6}, None)), - ], - ) + self.assertIs(nlinfo.eliminated_vars[0][0], m.x[4]) + self.assertExpressionsEqual(nlinfo.eliminated_vars[0][1], 3.0 * m.x[1] - 12.0) + + self.assertIs(nlinfo.eliminated_vars[1][0], m.x[3]) + self.assertExpressionsEqual(nlinfo.eliminated_vars[1][1], 17.0 * m.x[1] - 72.0) + + self.assertIs(nlinfo.eliminated_vars[2][0], m.x[2]) + self.assertExpressionsEqual(nlinfo.eliminated_vars[2][1], 4.0 * m.x[1] - 13.0) + + self.assertIs(nlinfo.eliminated_vars[3][0], m.x[0]) + self.assertExpressionsEqual(nlinfo.eliminated_vars[3][1], -6.0 * m.x[1] + 29.0) + # Note: bounds on x[1] are: # min(22/3, 82/17, 23/4, -39/-6) == 4.823529411764706 # max(2/3, 62/17, 3/4, -19/-6) == 3.6470588235294117 @@ -1637,9 +1633,7 @@ def test_presolve_named_expressions(self): ) self.assertEqual(LOG.getvalue(), "") - self.assertEqual( - nlinfo.eliminated_vars, [(m.x[1], nl_writer.AMPLRepn(7, {}, None))] - ) + self.assertEqual(nlinfo.eliminated_vars, [(m.x[1], 7)]) self.assertEqual( *nl_diff( From c77eb247b0910b45c986d27ee9194a3b5926193f Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 16 Jan 2024 14:18:20 -0700 Subject: [PATCH 0801/1204] rework ipopt solution loader --- pyomo/contrib/solver/ipopt.py | 64 +++++++++++++++++++----------- pyomo/contrib/solver/sol_reader.py | 50 +++++------------------ pyomo/contrib/solver/solution.py | 44 +++++++++++++++++++- 3 files changed, 92 insertions(+), 66 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 63dca0af0d9..4e2dcdf83ab 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -14,27 +14,28 @@ import datetime import io import sys -from typing import Mapping, Optional, Dict +from typing import Mapping, Optional, Sequence from pyomo.common import Executable from pyomo.common.config import ConfigValue, NonNegativeInt, NonNegativeFloat from pyomo.common.errors import PyomoException from pyomo.common.tempfiles import TempfileManager from pyomo.common.timing import HierarchicalTimer -from pyomo.core.base import Objective +from pyomo.core.base.var import _GeneralVarData from pyomo.core.staleflag import StaleFlagManager -from pyomo.repn.plugins.nl_writer import NLWriter, NLWriterInfo, AMPLRepn +from pyomo.repn.plugins.nl_writer import NLWriter, NLWriterInfo from pyomo.contrib.solver.base import SolverBase from pyomo.contrib.solver.config import SolverConfig from pyomo.contrib.solver.factory import SolverFactory from pyomo.contrib.solver.results import Results, TerminationCondition, SolutionStatus from .sol_reader import parse_sol_file -from pyomo.contrib.solver.solution import SolutionLoaderBase, SolutionLoader +from pyomo.contrib.solver.solution import SolSolutionLoader, SolutionLoader from pyomo.common.tee import TeeStream from pyomo.common.log import LogStream from pyomo.core.expr.visitor import replace_expressions from pyomo.core.expr.numvalue import value from pyomo.core.base.suffix import Suffix +from pyomo.common.collections import ComponentMap import logging @@ -110,8 +111,38 @@ def __init__( ) -class ipoptSolutionLoader(SolutionLoaderBase): - pass +class ipoptSolutionLoader(SolSolutionLoader): + def get_reduced_costs(self, vars_to_load: Sequence[_GeneralVarData] | None = None) -> Mapping[_GeneralVarData, float]: + sol_data = self._sol_data + nl_info = self._nl_info + zl_map = sol_data.var_suffixes['ipopt_zL_out'] + zu_map = sol_data.var_suffixes['ipopt_zU_out'] + rc = dict() + for v in nl_info.variables: + v_id = id(v) + rc[v_id] = (v, 0) + if v_id in zl_map: + zl = zl_map[v_id][1] + if abs(zl) > abs(rc[v_id][1]): + rc[v_id] = (v, zl) + if v_id in zu_map: + zu = zu_map[v_id][1] + if abs(zu) > abs(rc[v_id][1]): + rc[v_id] = (v, zu) + + if vars_to_load is None: + res = ComponentMap(rc.values()) + for v, _ in nl_info.eliminated_vars: + res[v] = 0 + else: + res = ComponentMap() + for v in vars_to_load: + if id(v) in rc: + res[v] = rc[id(v)][1] + else: + # eliminated vars + res[v] = 0 + return res ipopt_command_line_options = { @@ -452,24 +483,9 @@ def _parse_solution( if res.solution_status == SolutionStatus.noSolution: res.solution_loader = SolutionLoader(None, None, None, None) else: - rc = dict() - for v in nl_info.variables: - v_id = id(v) - rc[v_id] = (v, 0) - if v_id in sol_data.var_suffixes['ipopt_zL_out']: - zl = sol_data.var_suffixes['ipopt_zL_out'][v_id][1] - if abs(zl) > abs(rc[v_id][1]): - rc[v_id] = (v, zl) - if v_id in sol_data.var_suffixes['ipopt_zU_out']: - zu = sol_data.var_suffixes['ipopt_zU_out'][v_id][1] - if abs(zu) > abs(rc[v_id][1]): - rc[v_id] = (v, zu) - - res.solution_loader = SolutionLoader( - primals=sol_data.primals, - duals=sol_data.duals, - slacks=None, - reduced_costs=rc, + res.solution_loader = ipoptSolutionLoader( + sol_data=sol_data, + nl_info=nl_info, ) return res diff --git a/pyomo/contrib/solver/sol_reader.py b/pyomo/contrib/solver/sol_reader.py index 92761246241..28fe0100015 100644 --- a/pyomo/contrib/solver/sol_reader.py +++ b/pyomo/contrib/solver/sol_reader.py @@ -9,24 +9,13 @@ from pyomo.repn.plugins.nl_writer import AMPLRepn -def evaluate_ampl_repn(repn: AMPLRepn, sub_map): - assert not repn.nonlinear - assert repn.nl is None - val = repn.const - if repn.linear is not None: - for v_id, v_coef in repn.linear.items(): - val += v_coef * sub_map[v_id] - val *= repn.mult - return val - - class SolFileData: def __init__(self) -> None: - self.primals: Dict[int, Tuple[_GeneralVarData, float]] = dict() - self.duals: Dict[_ConstraintData, float] = dict() - self.var_suffixes: Dict[str, Dict[int, Tuple[_GeneralVarData, Any]]] = dict() - self.con_suffixes: Dict[str, Dict[_ConstraintData, Any]] = dict() - self.obj_suffixes: Dict[str, Dict[int, Tuple[_ObjectiveData, Any]]] = dict() + self.primals: List[float] = list() + self.duals: List[float] = list() + self.var_suffixes: Dict[str, Dict[int, Any]] = dict() + self.con_suffixes: Dict[str, Dict[Any]] = dict() + self.obj_suffixes: Dict[str, Dict[int, Any]] = dict() self.problem_suffixes: Dict[str, List[Any]] = dict() @@ -138,10 +127,8 @@ def parse_sol_file( result.extra_info.solver_message = exit_code_message if result.solution_status != SolutionStatus.noSolution: - for v, val in zip(nl_info.variables, variable_vals): - sol_data.primals[id(v)] = (v, val) - for c, val in zip(nl_info.constraints, duals): - sol_data.duals[c] = val + sol_data.primals = variable_vals + sol_data.duals = duals ### Read suffixes ### line = sol_file.readline() while line: @@ -180,18 +167,13 @@ def parse_sol_file( for cnt in range(nvalues): suf_line = sol_file.readline().split() var_ndx = int(suf_line[0]) - var = nl_info.variables[var_ndx] - sol_data.var_suffixes[suffix_name][id(var)] = ( - var, - convert_function(suf_line[1]), - ) + sol_data.var_suffixes[suffix_name][var_ndx] = convert_function(suf_line[1]) elif kind == 1: # Con sol_data.con_suffixes[suffix_name] = dict() for cnt in range(nvalues): suf_line = sol_file.readline().split() con_ndx = int(suf_line[0]) - con = nl_info.constraints[con_ndx] - sol_data.con_suffixes[suffix_name][con] = convert_function( + sol_data.con_suffixes[suffix_name][con_ndx] = convert_function( suf_line[1] ) elif kind == 2: # Obj @@ -199,11 +181,7 @@ def parse_sol_file( for cnt in range(nvalues): suf_line = sol_file.readline().split() obj_ndx = int(suf_line[0]) - obj = nl_info.objectives[obj_ndx] - sol_data.obj_suffixes[suffix_name][id(obj)] = ( - obj, - convert_function(suf_line[1]), - ) + sol_data.obj_suffixes[suffix_name][obj_ndx] = convert_function(suf_line[1]) elif kind == 3: # Prob sol_data.problem_suffixes[suffix_name] = list() for cnt in range(nvalues): @@ -213,12 +191,4 @@ def parse_sol_file( ) line = sol_file.readline() - if len(nl_info.eliminated_vars) > 0: - sub_map = {k: v[1] for k, v in sol_data.primals.items()} - for v, v_expr in nl_info.eliminated_vars: - val = evaluate_ampl_repn(v_expr, sub_map) - v_id = id(v) - sub_map[v_id] = val - sol_data.primals[v_id] = (v, val) - return result, sol_data diff --git a/pyomo/contrib/solver/solution.py b/pyomo/contrib/solver/solution.py index 4ec3f98cd08..7ea26c5f484 100644 --- a/pyomo/contrib/solver/solution.py +++ b/pyomo/contrib/solver/solution.py @@ -16,6 +16,10 @@ from pyomo.core.base.var import _GeneralVarData from pyomo.common.collections import ComponentMap from pyomo.core.staleflag import StaleFlagManager +from .sol_reader import SolFileData +from pyomo.repn.plugins.nl_writer import NLWriterInfo, AMPLRepn +from pyomo.core.expr.numvalue import value +from pyomo.core.expr.visitor import replace_expressions # CHANGES: # - `load` method: should just load the whole thing back into the model; load_solution = True @@ -49,8 +53,9 @@ def load_vars( Parameters ---------- vars_to_load: list - A list of the variables whose solution should be loaded. If vars_to_load is None, then the solution - to all primal variables will be loaded. + The minimum set of variables whose solution should be loaded. If vars_to_load is None, then the solution + to all primal variables will be loaded. Even if vars_to_load is specified, the values of other + variables may also be loaded depending on the interface. """ for v, val in self.get_primals(vars_to_load=vars_to_load).items(): v.set_value(val, skip_validation=True) @@ -195,6 +200,41 @@ def get_reduced_costs( return rc +class SolSolutionLoader(SolutionLoaderBase): + def __init__(self, sol_data: SolFileData, nl_info: NLWriterInfo) -> None: + self._sol_data = sol_data + self._nl_info = nl_info + + def load_vars( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> NoReturn: + for v, val in zip(self._nl_info.variables, self._sol_data.primals): + v.set_value(val, skip_validation=True) + + for v, v_expr in self._nl_info.eliminated_vars: + v.set_value(value(v_expr), skip_validation=True) + + StaleFlagManager.mark_all_as_stale(delayed=True) + + def get_primals(self, vars_to_load: Sequence[_GeneralVarData] | None = None) -> Mapping[_GeneralVarData, float]: + val_map = dict(zip([id(v) for v in self._nl_info.variables], self._sol_data.primals)) + + for v, v_expr in self._nl_info.eliminated_vars: + val = replace_expressions(v_expr, substitution_map=val_map) + v_id = id(v) + val_map[v_id] = val + + res = ComponentMap() + for v in vars_to_load: + res[v] = val_map[id(v)] + + return res + + def get_duals(self, cons_to_load: Sequence[_GeneralConstraintData] | None = None) -> Dict[_GeneralConstraintData, float]: + cons_to_load = set(cons_to_load) + return {c: val for c, val in zip(self._nl_info.constraints, self._sol_data.duals) if c in cons_to_load} + + class PersistentSolutionLoader(SolutionLoaderBase): def __init__(self, solver): self._solver = solver From a5e3873e6c37b54e115baed35a2999f6cbd99f98 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 16 Jan 2024 14:32:36 -0700 Subject: [PATCH 0802/1204] fix imports --- pyomo/contrib/solver/results.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyomo/contrib/solver/results.py b/pyomo/contrib/solver/results.py index e21adcc35cc..b4a30da0b35 100644 --- a/pyomo/contrib/solver/results.py +++ b/pyomo/contrib/solver/results.py @@ -28,7 +28,6 @@ TerminationCondition as LegacyTerminationCondition, SolverStatus as LegacySolverStatus, ) -from pyomo.contrib.solver.solution import SolutionLoaderBase class SolverResultsError(PyomoException): @@ -192,7 +191,7 @@ def __init__( visibility=visibility, ) - self.solution_loader: SolutionLoaderBase = self.declare( + self.solution_loader = self.declare( 'solution_loader', ConfigValue() ) self.termination_condition: TerminationCondition = self.declare( From f1bd6821001233423d7bb7ac123f5560c6340c7d Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 16 Jan 2024 14:45:41 -0700 Subject: [PATCH 0803/1204] override display() --- pyomo/contrib/solver/results.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyomo/contrib/solver/results.py b/pyomo/contrib/solver/results.py index b4a30da0b35..fa543d0aeaa 100644 --- a/pyomo/contrib/solver/results.py +++ b/pyomo/contrib/solver/results.py @@ -244,6 +244,9 @@ def __init__( ConfigValue(domain=str, default=None, visibility=ADVANCED_OPTION), ) + def display(self, content_filter=None, indent_spacing=2, ostream=None, visibility=0): + return super().display(content_filter, indent_spacing, ostream, visibility) + class ResultsReader: pass From e27281796968c96e05b3ad761e47196ff8d29738 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 16 Jan 2024 15:15:07 -0700 Subject: [PATCH 0804/1204] Reorganize ComponentSet.__eq__ to clarify logic --- pyomo/common/collections/component_set.py | 4 ++-- pyomo/core/tests/unit/kernel/test_component_set.py | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pyomo/common/collections/component_set.py b/pyomo/common/collections/component_set.py index ad13baced44..e205773220f 100644 --- a/pyomo/common/collections/component_set.py +++ b/pyomo/common/collections/component_set.py @@ -106,9 +106,9 @@ def discard(self, val): def __eq__(self, other): if self is other: return True - if not isinstance(other, collections_Set) or len(self) != len(other): + if not isinstance(other, collections_Set): return False - return all(id(key) in self._data for key in other) + return len(self) == len(other) and all(id(key) in self._data for key in other) def __ne__(self, other): return not (self == other) diff --git a/pyomo/core/tests/unit/kernel/test_component_set.py b/pyomo/core/tests/unit/kernel/test_component_set.py index 10a7b27e59e..30f2cf72716 100644 --- a/pyomo/core/tests/unit/kernel/test_component_set.py +++ b/pyomo/core/tests/unit/kernel/test_component_set.py @@ -264,6 +264,14 @@ def test_eq(self): self.assertTrue(cset1 != cset2) self.assertNotEqual(cset1, cset2) + cset2.add(variable()) + self.assertFalse(cset2 == cset1) + self.assertTrue(cset2 != cset1) + self.assertNotEqual(cset2, cset1) + self.assertFalse(cset1 == cset2) + self.assertTrue(cset1 != cset2) + self.assertNotEqual(cset1, cset2) + if __name__ == "__main__": unittest.main() From a44929ce6ff58adf9a638895261ba25b8a21e2b4 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 16 Jan 2024 15:19:45 -0700 Subject: [PATCH 0805/1204] better handling of solver output --- pyomo/contrib/solver/config.py | 8 ++++++++ pyomo/contrib/solver/ipopt.py | 11 +++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index 84b1c2d2c87..d053356a684 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -50,6 +50,14 @@ def __init__( description="If True, the solver log prints to stdout.", ), ) + self.log_solver_output: bool = self.declare( + 'log_solver_output', + ConfigValue( + domain=bool, + default=False, + description="If True, the solver output gets logged.", + ), + ) self.load_solution: bool = self.declare( 'load_solution', ConfigValue( diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 4e2dcdf83ab..1b091638343 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -69,15 +69,10 @@ def __init__( 'executable', ConfigValue(default=Executable('ipopt')) ) # TODO: Add in a deprecation here for keepfiles + # M.B.: Is the above TODO still relevant? self.temp_dir: str = self.declare( 'temp_dir', ConfigValue(domain=str, default=None) ) - self.solver_output_logger = self.declare( - 'solver_output_logger', ConfigValue(default=logger) - ) - self.log_level = self.declare( - 'log_level', ConfigValue(domain=NonNegativeInt, default=logging.INFO) - ) self.writer_config = self.declare( 'writer_config', ConfigValue(default=NLWriter.CONFIG()) ) @@ -347,10 +342,10 @@ def solve(self, model, **kwds): ostreams = [io.StringIO()] if config.tee: ostreams.append(sys.stdout) - else: + if config.log_solver_output: ostreams.append( LogStream( - level=config.log_level, logger=config.solver_output_logger + level=logging.INFO, logger=logger ) ) with TeeStream(*ostreams) as t: From a3fe00381b83c7cd98797da62c967651594efb90 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 16 Jan 2024 16:35:20 -0700 Subject: [PATCH 0806/1204] handle scaling when loading results --- pyomo/contrib/solver/ipopt.py | 15 ++++++++++----- pyomo/contrib/solver/solution.py | 33 +++++++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 1b091638343..47436d9d11f 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -108,20 +108,25 @@ def __init__( class ipoptSolutionLoader(SolSolutionLoader): def get_reduced_costs(self, vars_to_load: Sequence[_GeneralVarData] | None = None) -> Mapping[_GeneralVarData, float]: + if self._nl_info.scaling is None: + scale_list = [1] * len(self._nl_info.variables) + else: + scale_list = self._nl_info.scaling.variables sol_data = self._sol_data nl_info = self._nl_info zl_map = sol_data.var_suffixes['ipopt_zL_out'] zu_map = sol_data.var_suffixes['ipopt_zU_out'] rc = dict() - for v in nl_info.variables: + for ndx, v in enumerate(nl_info.variables): + scale = scale_list[ndx] v_id = id(v) rc[v_id] = (v, 0) - if v_id in zl_map: - zl = zl_map[v_id][1] + if ndx in zl_map: + zl = zl_map[ndx] * scale if abs(zl) > abs(rc[v_id][1]): rc[v_id] = (v, zl) - if v_id in zu_map: - zu = zu_map[v_id][1] + if ndx in zu_map: + zu = zu_map[ndx] * scale if abs(zu) > abs(rc[v_id][1]): rc[v_id] = (v, zu) diff --git a/pyomo/contrib/solver/solution.py b/pyomo/contrib/solver/solution.py index 7ea26c5f484..ae47491c310 100644 --- a/pyomo/contrib/solver/solution.py +++ b/pyomo/contrib/solver/solution.py @@ -208,8 +208,12 @@ def __init__(self, sol_data: SolFileData, nl_info: NLWriterInfo) -> None: def load_vars( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None ) -> NoReturn: - for v, val in zip(self._nl_info.variables, self._sol_data.primals): - v.set_value(val, skip_validation=True) + if self._nl_info.scaling is None: + scale_list = [1] * len(self._nl_info.variables) + else: + scale_list = self._nl_info.scaling.variables + for v, val, scale in zip(self._nl_info.variables, self._sol_data.primals, scale_list): + v.set_value(val/scale, skip_validation=True) for v, v_expr in self._nl_info.eliminated_vars: v.set_value(value(v_expr), skip_validation=True) @@ -217,7 +221,13 @@ def load_vars( StaleFlagManager.mark_all_as_stale(delayed=True) def get_primals(self, vars_to_load: Sequence[_GeneralVarData] | None = None) -> Mapping[_GeneralVarData, float]: - val_map = dict(zip([id(v) for v in self._nl_info.variables], self._sol_data.primals)) + if self._nl_info.scaling is None: + scale_list = [1] * len(self._nl_info.variables) + else: + scale_list = self._nl_info.scaling.variables + val_map = dict() + for v, val, scale in zip(self._nl_info.variables, self._sol_data.primals, scale_list): + val_map[id(v)] = val / scale for v, v_expr in self._nl_info.eliminated_vars: val = replace_expressions(v_expr, substitution_map=val_map) @@ -225,14 +235,27 @@ def get_primals(self, vars_to_load: Sequence[_GeneralVarData] | None = None) -> val_map[v_id] = val res = ComponentMap() + if vars_to_load is None: + vars_to_load = self._nl_info.variables + [v for v, _ in self._nl_info.eliminated_vars] for v in vars_to_load: res[v] = val_map[id(v)] return res def get_duals(self, cons_to_load: Sequence[_GeneralConstraintData] | None = None) -> Dict[_GeneralConstraintData, float]: - cons_to_load = set(cons_to_load) - return {c: val for c, val in zip(self._nl_info.constraints, self._sol_data.duals) if c in cons_to_load} + if self._nl_info.scaling is None: + scale_list = [1] * len(self._nl_info.constraints) + else: + scale_list = self._nl_info.scaling.constraints + if cons_to_load is None: + cons_to_load = set(self._nl_info.constraints) + else: + cons_to_load = set(cons_to_load) + res = dict() + for c, val, scale in zip(self._nl_info.constraints, self._sol_data.duals, scale_list): + if c in cons_to_load: + res[c] = val * scale + return res class PersistentSolutionLoader(SolutionLoaderBase): From d699864132114ebb60119ba696365ec1f07a4d27 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 16 Jan 2024 23:27:42 -0700 Subject: [PATCH 0807/1204] Fix import in assertExpressionsStructurallyEqual --- pyomo/common/unittest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/common/unittest.py b/pyomo/common/unittest.py index 9bf68e53314..1ed26f72320 100644 --- a/pyomo/common/unittest.py +++ b/pyomo/common/unittest.py @@ -563,7 +563,7 @@ def assertExpressionsEqual(self, a, b, include_named_exprs=True, places=None): def assertExpressionsStructurallyEqual( self, a, b, include_named_exprs=True, places=None ): - from pyomo.core.expr.compare import assertExpressionsEqual + from pyomo.core.expr.compare import assertExpressionsStructurallyEqual return assertExpressionsStructurallyEqual( self, a, b, include_named_exprs, places From 73820cc2c3b4c9fc0d591f34bee6a9af8fe62cfe Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 16 Jan 2024 23:28:07 -0700 Subject: [PATCH 0808/1204] Switch numeric expression tests to use assertExpression* from TestCase --- pyomo/core/tests/unit/test_numeric_expr.py | 235 +++++++++------------ 1 file changed, 94 insertions(+), 141 deletions(-) diff --git a/pyomo/core/tests/unit/test_numeric_expr.py b/pyomo/core/tests/unit/test_numeric_expr.py index 13af5adc9bb..82f1e5c11c4 100644 --- a/pyomo/core/tests/unit/test_numeric_expr.py +++ b/pyomo/core/tests/unit/test_numeric_expr.py @@ -104,10 +104,6 @@ MinExpression, _balanced_parens, ) -from pyomo.core.expr.compare import ( - assertExpressionsEqual, - assertExpressionsStructurallyEqual, -) from pyomo.core.expr.relational_expr import RelationalExpression, EqualityExpression from pyomo.core.expr.relational_expr import RelationalExpression, EqualityExpression from pyomo.common.errors import PyomoException @@ -642,8 +638,7 @@ def test_simpleSum(self): m.b = Var() e = m.a + m.b # - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [MonomialTermExpression((1, m.a)), MonomialTermExpression((1, m.b))] @@ -658,8 +653,7 @@ def test_simpleSum_API(self): m.b = Var() e = m.a + m.b e += 2 * m.a - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -675,12 +669,12 @@ def test_constSum(self): m = AbstractModel() m.a = Var() # - assertExpressionsEqual( - self, m.a + 5, LinearExpression([MonomialTermExpression((1, m.a)), 5]) + self.assertExpressionsEqual( + m.a + 5, LinearExpression([MonomialTermExpression((1, m.a)), 5]) ) - assertExpressionsEqual( - self, 5 + m.a, LinearExpression([5, MonomialTermExpression((1, m.a))]) + self.assertExpressionsEqual( + 5 + m.a, LinearExpression([5, MonomialTermExpression((1, m.a))]) ) def test_nestedSum(self): @@ -702,8 +696,7 @@ def test_nestedSum(self): # a b e1 = m.a + m.b e = e1 + 5 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [MonomialTermExpression((1, m.a)), MonomialTermExpression((1, m.b)), 5] @@ -717,8 +710,7 @@ def test_nestedSum(self): # a b e1 = m.a + m.b e = 5 + e1 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [MonomialTermExpression((1, m.a)), MonomialTermExpression((1, m.b)), 5] @@ -732,8 +724,7 @@ def test_nestedSum(self): # a b e1 = m.a + m.b e = e1 + m.c - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -751,8 +742,7 @@ def test_nestedSum(self): # a b e1 = m.a + m.b e = m.c + e1 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -772,8 +762,7 @@ def test_nestedSum(self): e2 = m.c + m.d e = e1 + e2 # - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -807,8 +796,7 @@ def test_nestedSum2(self): e1 = m.a + m.b e = 2 * e1 + m.c - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, SumExpression( [ @@ -840,8 +828,7 @@ def test_nestedSum2(self): e1 = m.a + m.b e = 3 * (2 * e1 + m.c) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, ProductExpression( ( @@ -903,8 +890,7 @@ def test_sumOf_nestedTrivialProduct(self): e1 = m.a * 5 e = e1 + m.b # - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [MonomialTermExpression((5, m.a)), MonomialTermExpression((1, m.b))] @@ -918,8 +904,7 @@ def test_sumOf_nestedTrivialProduct(self): # a 5 e = m.b + e1 # - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [MonomialTermExpression((1, m.b)), MonomialTermExpression((5, m.a))] @@ -934,8 +919,7 @@ def test_sumOf_nestedTrivialProduct(self): e2 = m.b + m.c e = e1 + e2 # - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -954,8 +938,7 @@ def test_sumOf_nestedTrivialProduct(self): e2 = m.b + m.c e = e2 + e1 # - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -978,8 +961,7 @@ def test_simpleDiff(self): # / \ # a b e = m.a - m.b - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [MonomialTermExpression((1, m.a)), MonomialTermExpression((-1, m.b))] @@ -996,15 +978,15 @@ def test_constDiff(self): # - # / \ # a 5 - assertExpressionsEqual( - self, m.a - 5, LinearExpression([MonomialTermExpression((1, m.a)), -5]) + self.assertExpressionsEqual( + m.a - 5, LinearExpression([MonomialTermExpression((1, m.a)), -5]) ) # - # / \ # 5 a - assertExpressionsEqual( - self, 5 - m.a, LinearExpression([5, MonomialTermExpression((-1, m.a))]) + self.assertExpressionsEqual( + 5 - m.a, LinearExpression([5, MonomialTermExpression((-1, m.a))]) ) def test_paramDiff(self): @@ -1019,8 +1001,7 @@ def test_paramDiff(self): # / \ # a p e = m.a - m.p - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [MonomialTermExpression((1, m.a)), NPV_NegationExpression((m.p,))] @@ -1031,8 +1012,8 @@ def test_paramDiff(self): # / \ # m.p a e = m.p - m.a - assertExpressionsEqual( - self, e, LinearExpression([m.p, MonomialTermExpression((-1, m.a))]) + self.assertExpressionsEqual( + e, LinearExpression([m.p, MonomialTermExpression((-1, m.a))]) ) def test_constparamDiff(self): @@ -1076,8 +1057,8 @@ def test_termDiff(self): e = 5 - 2 * m.a - assertExpressionsEqual( - self, e, LinearExpression([5, MonomialTermExpression((-2, m.a))]) + self.assertExpressionsEqual( + e, LinearExpression([5, MonomialTermExpression((-2, m.a))]) ) def test_nestedDiff(self): @@ -1097,8 +1078,7 @@ def test_nestedDiff(self): # a b e1 = m.a - m.b e = e1 - 5 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -1116,8 +1096,7 @@ def test_nestedDiff(self): # a b e1 = m.a - m.b e = 5 - e1 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, SumExpression( [ @@ -1143,8 +1122,7 @@ def test_nestedDiff(self): # a b e1 = m.a - m.b e = e1 - m.c - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -1162,8 +1140,7 @@ def test_nestedDiff(self): # a b e1 = m.a - m.b e = m.c - e1 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, SumExpression( [ @@ -1190,8 +1167,7 @@ def test_nestedDiff(self): e1 = m.a - m.b e2 = m.c - m.d e = e1 - e2 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, SumExpression( [ @@ -1233,8 +1209,8 @@ def test_negation_mutableparam(self): m = AbstractModel() m.p = Param(mutable=True, initialize=1.0) e = -m.p - assertExpressionsEqual(self, e, NPV_NegationExpression((m.p,))) - assertExpressionsEqual(self, -e, m.p) + self.assertExpressionsEqual(e, NPV_NegationExpression((m.p,))) + self.assertExpressionsEqual(-e, m.p) def test_negation_terms(self): # @@ -1244,15 +1220,15 @@ def test_negation_terms(self): m.v = Var() m.p = Param(mutable=True, initialize=1.0) e = -m.p * m.v - assertExpressionsEqual( - self, e, MonomialTermExpression((NPV_NegationExpression((m.p,)), m.v)) + self.assertExpressionsEqual( + e, MonomialTermExpression((NPV_NegationExpression((m.p,)), m.v)) ) - assertExpressionsEqual(self, -e, MonomialTermExpression((m.p, m.v))) + self.assertExpressionsEqual(-e, MonomialTermExpression((m.p, m.v))) # e = -5 * m.v - assertExpressionsEqual(self, e, MonomialTermExpression((-5, m.v))) - assertExpressionsEqual(self, -e, MonomialTermExpression((5, m.v))) + self.assertExpressionsEqual(e, MonomialTermExpression((-5, m.v))) + self.assertExpressionsEqual(-e, MonomialTermExpression((5, m.v))) def test_trivialDiff(self): # @@ -1389,8 +1365,7 @@ def test_sumOf_nestedTrivialProduct2(self): # a 5 e1 = m.a * m.p e = e1 - m.b - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [MonomialTermExpression((m.p, m.a)), MonomialTermExpression((-1, m.b))] @@ -1404,8 +1379,7 @@ def test_sumOf_nestedTrivialProduct2(self): # a 5 e1 = m.a * m.p e = m.b - e1 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -1423,8 +1397,7 @@ def test_sumOf_nestedTrivialProduct2(self): e1 = m.a * m.p e2 = m.b - m.c e = e1 - e2 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, SumExpression( [ @@ -1452,8 +1425,7 @@ def test_sumOf_nestedTrivialProduct2(self): e2 = m.b - m.c e = e2 - e1 self.maxDiff = None - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -1624,8 +1596,7 @@ def test_nestedProduct2(self): e3 = e1 + m.d e = e2 * e3 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, ProductExpression( ( @@ -1671,8 +1642,7 @@ def test_nestedProduct2(self): inner = LinearExpression( [MonomialTermExpression((1, m.a)), MonomialTermExpression((1, m.b))] ) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, ProductExpression( (ProductExpression((m.c, inner)), ProductExpression((inner, m.d))) @@ -1711,8 +1681,8 @@ def test_nestedProduct3(self): # a b e1 = m.a * m.b e = e1 * 5 - assertExpressionsEqual( - self, e, MonomialTermExpression((NPV_ProductExpression((m.a, 5)), m.b)) + self.assertExpressionsEqual( + e, MonomialTermExpression((NPV_ProductExpression((m.a, 5)), m.b)) ) # * @@ -1801,37 +1771,37 @@ def test_trivialProduct(self): m.q = Param(initialize=1) e = m.a * 0 - assertExpressionsEqual(self, e, MonomialTermExpression((0, m.a))) + self.assertExpressionsEqual(e, MonomialTermExpression((0, m.a))) e = 0 * m.a - assertExpressionsEqual(self, e, MonomialTermExpression((0, m.a))) + self.assertExpressionsEqual(e, MonomialTermExpression((0, m.a))) e = m.a * m.p - assertExpressionsEqual(self, e, MonomialTermExpression((0, m.a))) + self.assertExpressionsEqual(e, MonomialTermExpression((0, m.a))) e = m.p * m.a - assertExpressionsEqual(self, e, MonomialTermExpression((0, m.a))) + self.assertExpressionsEqual(e, MonomialTermExpression((0, m.a))) # # Check that multiplying by one gives the original expression # e = m.a * 1 - assertExpressionsEqual(self, e, m.a) + self.assertExpressionsEqual(e, m.a) e = 1 * m.a - assertExpressionsEqual(self, e, m.a) + self.assertExpressionsEqual(e, m.a) e = m.a * m.q - assertExpressionsEqual(self, e, m.a) + self.assertExpressionsEqual(e, m.a) e = m.q * m.a - assertExpressionsEqual(self, e, m.a) + self.assertExpressionsEqual(e, m.a) # # Check that numeric constants are simply muliplied out # e = NumericConstant(3) * NumericConstant(2) - assertExpressionsEqual(self, e, 6) + self.assertExpressionsEqual(e, 6) self.assertIs(type(e), int) self.assertEqual(e, 6) @@ -1996,19 +1966,19 @@ def test_trivialDivision(self): # Check that dividing zero by anything non-zero gives zero # e = 0 / m.a - assertExpressionsEqual(self, e, DivisionExpression((0, m.a))) + self.assertExpressionsEqual(e, DivisionExpression((0, m.a))) # # Check that dividing by one 1 gives the original expression # e = m.a / 1 - assertExpressionsEqual(self, e, m.a) + self.assertExpressionsEqual(e, m.a) # # Check the structure dividing 1 by an expression # e = 1 / m.a - assertExpressionsEqual(self, e, DivisionExpression((1, m.a))) + self.assertExpressionsEqual(e, DivisionExpression((1, m.a))) # # Check the structure dividing 1 by an expression @@ -3782,8 +3752,7 @@ def tearDown(self): def test_summation1(self): e = sum_product(self.m.a) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -3798,8 +3767,7 @@ def test_summation1(self): def test_summation2(self): e = sum_product(self.m.p, self.m.a) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -3814,8 +3782,7 @@ def test_summation2(self): def test_summation3(self): e = sum_product(self.m.q, self.m.a) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -3830,8 +3797,7 @@ def test_summation3(self): def test_summation4(self): e = sum_product(self.m.a, self.m.b) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, SumExpression( [ @@ -3846,8 +3812,7 @@ def test_summation4(self): def test_summation5(self): e = sum_product(self.m.b, denom=self.m.a) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, SumExpression( [ @@ -3862,8 +3827,7 @@ def test_summation5(self): def test_summation6(self): e = sum_product(self.m.a, denom=self.m.p) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -3888,8 +3852,7 @@ def test_summation6(self): def test_summation7(self): e = sum_product(self.m.p, self.m.q, index=self.m.I) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, NPV_SumExpression( [ @@ -3906,8 +3869,7 @@ def test_summation_compression(self): e1 = sum_product(self.m.a) e2 = sum_product(self.m.b) e = e1 + e2 - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -3948,8 +3910,7 @@ def test_deprecation(self): r"DEPRECATED: The quicksum\(linear=...\) argument is deprecated " r"and ignored.", ) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -3965,8 +3926,7 @@ def test_deprecation(self): def test_summation1(self): e = quicksum((self.m.a[i] for i in self.m.a)) self.assertEqual(e(), 25) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -3982,8 +3942,7 @@ def test_summation1(self): def test_summation2(self): e = quicksum(self.m.p[i] * self.m.a[i] for i in self.m.a) self.assertEqual(e(), 25) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -3999,8 +3958,7 @@ def test_summation2(self): def test_summation3(self): e = quicksum(self.m.q[i] * self.m.a[i] for i in self.m.a) self.assertEqual(e(), 75) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -4016,8 +3974,7 @@ def test_summation3(self): def test_summation4(self): e = quicksum(self.m.a[i] * self.m.b[i] for i in self.m.a) self.assertEqual(e(), 250) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, SumExpression( [ @@ -4033,8 +3990,7 @@ def test_summation4(self): def test_summation5(self): e = quicksum(self.m.b[i] / self.m.a[i] for i in self.m.a) self.assertEqual(e(), 10) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, SumExpression( [ @@ -4050,8 +4006,7 @@ def test_summation5(self): def test_summation6(self): e = quicksum(self.m.a[i] / self.m.p[i] for i in self.m.a) self.assertEqual(e(), 25) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, LinearExpression( [ @@ -4077,8 +4032,7 @@ def test_summation6(self): def test_summation7(self): e = quicksum((self.m.p[i] * self.m.q[i] for i in self.m.I), linear=False) self.assertEqual(e(), 15) - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, NPV_SumExpression( [ @@ -4453,7 +4407,7 @@ def test_Expr_if(self): # expr1 = Expr_if(IF=self.m.a + self.m.b < 20, THEN=self.m.a, ELSE=self.m.b) expr2 = expr1.clone() - assertExpressionsStructurallyEqual(self, expr1, expr2) + self.assertExpressionsStructurallyEqual(expr1, expr2) self.assertIsNot(expr1, expr2) self.assertIsNot(expr1.arg(0), expr2.arg(0)) @@ -5029,7 +4983,7 @@ def test_init(self): self.assertEqual(e.linear_vars, [m.x, m.y]) self.assertEqual(e.linear_coefs, [2, 3]) - assertExpressionsEqual(self, e, f) + self.assertExpressionsEqual(e, f) args = [10, MonomialTermExpression((4, m.y)), MonomialTermExpression((5, m.x))] with LoggingIntercept() as OUT: @@ -5116,7 +5070,7 @@ def test_sum_other(self): with linear_expression() as e: e = e - arg - assertExpressionsEqual(self, e, -arg) + self.assertExpressionsEqual(e, -arg) def test_mul_other(self): m = ConcreteModel() @@ -5255,13 +5209,12 @@ def test_pow_other(self): with linear_expression() as e: e += m.p e = 2**e - assertExpressionsEqual(self, e, NPV_PowExpression((2, m.p))) + self.assertExpressionsEqual(e, NPV_PowExpression((2, m.p))) with linear_expression() as e: e += m.v[0] + m.v[1] e = m.v[0] ** e - assertExpressionsEqual( - self, + self.assertExpressionsEqual( e, PowExpression( ( @@ -5545,7 +5498,7 @@ def test_simple(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def test_sum(self): M = ConcreteModel() @@ -5556,7 +5509,7 @@ def test_sum(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def Xtest_Sum(self): M = ConcreteModel() @@ -5566,7 +5519,7 @@ def Xtest_Sum(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def test_prod(self): M = ConcreteModel() @@ -5577,7 +5530,7 @@ def test_prod(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def test_negation(self): M = ConcreteModel() @@ -5586,7 +5539,7 @@ def test_negation(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def test_reciprocal(self): M = ConcreteModel() @@ -5597,7 +5550,7 @@ def test_reciprocal(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def test_multisum(self): M = ConcreteModel() @@ -5608,7 +5561,7 @@ def test_multisum(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def test_linear(self): M = ConcreteModel() @@ -5621,7 +5574,7 @@ def test_linear(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def test_linear_context(self): M = ConcreteModel() @@ -5634,7 +5587,7 @@ def test_linear_context(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def test_ExprIf(self): M = ConcreteModel() @@ -5643,7 +5596,7 @@ def test_ExprIf(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) def test_getitem(self): m = ConcreteModel() @@ -5656,7 +5609,7 @@ def test_getitem(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) self.assertEqual("x[{I} + P[{I} + 1]] + 3", str(e)) def test_abs(self): @@ -5666,7 +5619,7 @@ def test_abs(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) self.assertEqual(str(e), str(e_)) def test_sin(self): @@ -5676,7 +5629,7 @@ def test_sin(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) self.assertEqual(str(e), str(e_)) def test_external_fcn(self): @@ -5687,7 +5640,7 @@ def test_external_fcn(self): s = pickle.dumps(e) e_ = pickle.loads(s) self.assertIsNot(e, e_) - assertExpressionsStructurallyEqual(self, e, e_) + self.assertExpressionsStructurallyEqual(e, e_) # From da2e58f48c2043b01844fb2909793d2512107624 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 18 Jan 2024 07:53:42 -0700 Subject: [PATCH 0809/1204] Save state: fixing broken tests --- pyomo/contrib/solver/config.py | 3 +++ pyomo/contrib/solver/tests/unit/test_base.py | 16 +++++----------- pyomo/contrib/solver/tests/unit/test_config.py | 17 ++++++++++------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index 84b1c2d2c87..f5aa1e2c5c7 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -141,10 +141,13 @@ class AutoUpdateConfig(ConfigDict): check_for_new_or_removed_constraints: bool check_for_new_or_removed_vars: bool check_for_new_or_removed_params: bool + check_for_new_objective: bool update_constraints: bool update_vars: bool update_params: bool update_named_expressions: bool + update_objective: bool + treat_fixed_vars_as_params: bool """ def __init__( diff --git a/pyomo/contrib/solver/tests/unit/test_base.py b/pyomo/contrib/solver/tests/unit/test_base.py index 71690b7aa0e..e3a8999d8c5 100644 --- a/pyomo/contrib/solver/tests/unit/test_base.py +++ b/pyomo/contrib/solver/tests/unit/test_base.py @@ -19,7 +19,7 @@ def test_solver_base(self): self.instance = base.SolverBase() self.assertFalse(self.instance.is_persistent()) self.assertEqual(self.instance.version(), None) - self.assertEqual(self.instance.config, None) + self.assertEqual(self.instance.CONFIG, self.instance.config) self.assertEqual(self.instance.solve(None), None) self.assertEqual(self.instance.available(), None) @@ -39,18 +39,16 @@ def test_abstract_member_list(self): expected_list = [ 'remove_params', 'version', - 'config', 'update_variables', 'remove_variables', 'add_constraints', - 'get_primals', + '_get_primals', 'set_instance', 'set_objective', 'update_params', 'remove_block', 'add_block', 'available', - 'update_config', 'add_params', 'remove_constraints', 'add_variables', @@ -63,7 +61,6 @@ def test_abstract_member_list(self): def test_persistent_solver_base(self): self.instance = base.PersistentSolverBase() self.assertTrue(self.instance.is_persistent()) - self.assertEqual(self.instance.update_config, None) self.assertEqual(self.instance.set_instance(None), None) self.assertEqual(self.instance.add_variables(None), None) self.assertEqual(self.instance.add_params(None), None) @@ -78,13 +75,10 @@ def test_persistent_solver_base(self): self.assertEqual(self.instance.update_params(), None) with self.assertRaises(NotImplementedError): - self.instance.get_primals() + self.instance._get_primals() with self.assertRaises(NotImplementedError): - self.instance.get_duals() + self.instance._get_duals() with self.assertRaises(NotImplementedError): - self.instance.get_slacks() - - with self.assertRaises(NotImplementedError): - self.instance.get_reduced_costs() + self.instance._get_reduced_costs() diff --git a/pyomo/contrib/solver/tests/unit/test_config.py b/pyomo/contrib/solver/tests/unit/test_config.py index 1051825f4e5..3ad8319343b 100644 --- a/pyomo/contrib/solver/tests/unit/test_config.py +++ b/pyomo/contrib/solver/tests/unit/test_config.py @@ -16,12 +16,15 @@ class TestSolverConfig(unittest.TestCase): def test_interface_default_instantiation(self): config = SolverConfig() - self.assertEqual(config._description, None) + self.assertIsNone(config._description) self.assertEqual(config._visibility, 0) self.assertFalse(config.tee) self.assertTrue(config.load_solution) + self.assertTrue(config.raise_exception_on_nonoptimal_result) self.assertFalse(config.symbolic_solver_labels) - self.assertFalse(config.report_timing) + self.assertIsNone(config.timer) + self.assertIsNone(config.threads) + self.assertIsNone(config.time_limit) def test_interface_custom_instantiation(self): config = SolverConfig(description="A description") @@ -31,20 +34,19 @@ def test_interface_custom_instantiation(self): self.assertFalse(config.time_limit) config.time_limit = 1.0 self.assertEqual(config.time_limit, 1.0) + self.assertIsInstance(config.time_limit, float) class TestBranchAndBoundConfig(unittest.TestCase): def test_interface_default_instantiation(self): config = BranchAndBoundConfig() - self.assertEqual(config._description, None) + self.assertIsNone(config._description) self.assertEqual(config._visibility, 0) self.assertFalse(config.tee) self.assertTrue(config.load_solution) self.assertFalse(config.symbolic_solver_labels) - self.assertFalse(config.report_timing) - self.assertEqual(config.rel_gap, None) - self.assertEqual(config.abs_gap, None) - self.assertFalse(config.relax_integrality) + self.assertIsNone(config.rel_gap) + self.assertIsNone(config.abs_gap) def test_interface_custom_instantiation(self): config = BranchAndBoundConfig(description="A description") @@ -54,5 +56,6 @@ def test_interface_custom_instantiation(self): self.assertFalse(config.time_limit) config.time_limit = 1.0 self.assertEqual(config.time_limit, 1.0) + self.assertIsInstance(config.time_limit, float) config.rel_gap = 2.5 self.assertEqual(config.rel_gap, 2.5) From 7fa02de0cc8b87afa6e13b392eb2651af88ee9bb Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 18 Jan 2024 08:11:03 -0700 Subject: [PATCH 0810/1204] Remove slack referrences; fix broken unit tests --- pyomo/contrib/solver/base.py | 8 ------ pyomo/contrib/solver/ipopt.py | 4 +-- pyomo/contrib/solver/solution.py | 27 +------------------ .../solver/tests/solvers/test_ipopt.py | 5 ++-- .../contrib/solver/tests/unit/test_results.py | 20 +++----------- .../solver/tests/unit/test_solution.py | 2 -- 6 files changed, 9 insertions(+), 57 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 962d35582a1..0b33f8a5648 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -435,9 +435,6 @@ def solve( if hasattr(model, 'dual') and model.dual.import_enabled(): for c, val in results.solution_loader.get_duals().items(): model.dual[c] = val - if hasattr(model, 'slack') and model.slack.import_enabled(): - for c, val in results.solution_loader.get_slacks().items(): - model.slack[c] = val if hasattr(model, 'rc') and model.rc.import_enabled(): for v, val in results.solution_loader.get_reduced_costs().items(): model.rc[v] = val @@ -448,11 +445,6 @@ def solve( if hasattr(model, 'dual') and model.dual.import_enabled(): for c, val in results.solution_loader.get_duals().items(): legacy_soln.constraint[symbol_map.getSymbol(c)] = {'Dual': val} - if hasattr(model, 'slack') and model.slack.import_enabled(): - for c, val in results.solution_loader.get_slacks().items(): - symbol = symbol_map.getSymbol(c) - if symbol in legacy_soln.constraint: - legacy_soln.constraint[symbol]['Slack'] = val if hasattr(model, 'rc') and model.rc.import_enabled(): for v, val in results.solution_loader.get_reduced_costs().items(): legacy_soln.variable['Rc'] = val diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 47436d9d11f..6ab40fd3924 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -372,7 +372,7 @@ def solve(self, model, **kwds): if process.returncode != 0: results.termination_condition = TerminationCondition.error - results.solution_loader = SolutionLoader(None, None, None, None) + results.solution_loader = SolutionLoader(None, None, None) else: with open(basename + '.sol', 'r') as sol_file: timer.start('parse_sol') @@ -481,7 +481,7 @@ def _parse_solution( ) if res.solution_status == SolutionStatus.noSolution: - res.solution_loader = SolutionLoader(None, None, None, None) + res.solution_loader = SolutionLoader(None, None, None) else: res.solution_loader = ipoptSolutionLoader( sol_data=sol_data, diff --git a/pyomo/contrib/solver/solution.py b/pyomo/contrib/solver/solution.py index ae47491c310..18fd96759cf 100644 --- a/pyomo/contrib/solver/solution.py +++ b/pyomo/contrib/solver/solution.py @@ -17,31 +17,10 @@ from pyomo.common.collections import ComponentMap from pyomo.core.staleflag import StaleFlagManager from .sol_reader import SolFileData -from pyomo.repn.plugins.nl_writer import NLWriterInfo, AMPLRepn +from pyomo.repn.plugins.nl_writer import NLWriterInfo from pyomo.core.expr.numvalue import value from pyomo.core.expr.visitor import replace_expressions -# CHANGES: -# - `load` method: should just load the whole thing back into the model; load_solution = True -# - `load_variables` -# - `get_variables` -# - `get_constraints` -# - `get_objective` -# - `get_slacks` -# - `get_reduced_costs` - -# duals is how much better you could get if you weren't constrained. -# dual value of 0 means that the constraint isn't actively constraining anything. -# high dual value means that it is costing us a lot in the objective. -# can also be called "shadow price" - -# bounds on variables are implied constraints. -# getting a dual on the bound of a variable is the reduced cost. -# IPOPT calls these the bound multipliers (normally they are reduced costs, though). ZL, ZU - -# slacks are... something that I don't understand -# but they are necessary somewhere? I guess? - class SolutionLoaderBase(abc.ABC): def load_vars( @@ -129,7 +108,6 @@ def __init__( self, primals: Optional[MutableMapping], duals: Optional[MutableMapping], - slacks: Optional[MutableMapping], reduced_costs: Optional[MutableMapping], ): """ @@ -139,14 +117,11 @@ def __init__( maps id(Var) to (var, value) duals: dict maps Constraint to dual value - slacks: dict - maps Constraint to slack value reduced_costs: dict maps id(Var) to (var, reduced_cost) """ self._primals = primals self._duals = duals - self._slacks = slacks self._reduced_costs = reduced_costs def get_primals( diff --git a/pyomo/contrib/solver/tests/solvers/test_ipopt.py b/pyomo/contrib/solver/tests/solvers/test_ipopt.py index c1aecba05fc..9638d94bdda 100644 --- a/pyomo/contrib/solver/tests/solvers/test_ipopt.py +++ b/pyomo/contrib/solver/tests/solvers/test_ipopt.py @@ -45,13 +45,12 @@ def test_ipopt_config(self): config = ipoptConfig() self.assertTrue(config.load_solution) self.assertIsInstance(config.solver_options, ConfigDict) - print(type(config.executable)) self.assertIsInstance(config.executable, ExecutableData) # Test custom initialization - solver = SolverFactory('ipopt_v2', save_solver_io=True) - self.assertTrue(solver.config.save_solver_io) + solver = SolverFactory('ipopt_v2', executable='/path/to/exe') self.assertFalse(solver.config.tee) + self.assertTrue(solver.config.executable.startswith('/path')) # Change value on a solve call # model = self.create_model() diff --git a/pyomo/contrib/solver/tests/unit/test_results.py b/pyomo/contrib/solver/tests/unit/test_results.py index e7d02751f7d..927ab64ee12 100644 --- a/pyomo/contrib/solver/tests/unit/test_results.py +++ b/pyomo/contrib/solver/tests/unit/test_results.py @@ -82,6 +82,8 @@ def test_declared_items(self): 'solver_version', 'termination_condition', 'timing_info', + 'solver_log', + 'solver_configuration' } actual_declared = res._declared self.assertEqual(expected_declared, actual_declared) @@ -101,7 +103,7 @@ def test_uninitialized(self): self.assertIsInstance(res.extra_info, ConfigDict) self.assertIsNone(res.timing_info.start_timestamp) self.assertIsNone(res.timing_info.wall_time) - res.solution_loader = solution.SolutionLoader(None, None, None, None) + res.solution_loader = solution.SolutionLoader(None, None, None) with self.assertRaisesRegex( RuntimeError, '.*does not currently have a valid solution.*' @@ -115,10 +117,6 @@ def test_uninitialized(self): RuntimeError, '.*does not currently have valid reduced costs.*' ): res.solution_loader.get_reduced_costs() - with self.assertRaisesRegex( - RuntimeError, '.*does not currently have valid slacks.*' - ): - res.solution_loader.get_slacks() def test_results(self): m = pyo.ConcreteModel() @@ -136,13 +134,10 @@ def test_results(self): rc = {} rc[id(m.x)] = (m.x, 5) rc[id(m.y)] = (m.y, 6) - slacks = {} - slacks[m.c1] = 7 - slacks[m.c2] = 8 res = results.Results() res.solution_loader = solution.SolutionLoader( - primals=primals, duals=duals, slacks=slacks, reduced_costs=rc + primals=primals, duals=duals, reduced_costs=rc ) res.solution_loader.load_vars() @@ -172,10 +167,3 @@ def test_results(self): self.assertNotIn(m.x, rc2) self.assertAlmostEqual(rc[id(m.y)][1], rc2[m.y]) - slacks2 = res.solution_loader.get_slacks() - self.assertAlmostEqual(slacks[m.c1], slacks2[m.c1]) - self.assertAlmostEqual(slacks[m.c2], slacks2[m.c2]) - - slacks2 = res.solution_loader.get_slacks([m.c2]) - self.assertNotIn(m.c1, slacks2) - self.assertAlmostEqual(slacks[m.c2], slacks2[m.c2]) diff --git a/pyomo/contrib/solver/tests/unit/test_solution.py b/pyomo/contrib/solver/tests/unit/test_solution.py index dc53f1e4543..1ecba45b32a 100644 --- a/pyomo/contrib/solver/tests/unit/test_solution.py +++ b/pyomo/contrib/solver/tests/unit/test_solution.py @@ -27,7 +27,5 @@ def test_solution_loader_base(self): self.assertEqual(self.instance.get_primals(), None) with self.assertRaises(NotImplementedError): self.instance.get_duals() - with self.assertRaises(NotImplementedError): - self.instance.get_slacks() with self.assertRaises(NotImplementedError): self.instance.get_reduced_costs() From efb1eee6d471c88a18670ed91c7baea0edbdd491 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 18 Jan 2024 08:11:51 -0700 Subject: [PATCH 0811/1204] Apply black --- pyomo/contrib/solver/ipopt.py | 13 ++++---- pyomo/contrib/solver/results.py | 8 ++--- pyomo/contrib/solver/sol_reader.py | 8 +++-- pyomo/contrib/solver/solution.py | 30 +++++++++++++------ .../contrib/solver/tests/unit/test_results.py | 3 +- 5 files changed, 37 insertions(+), 25 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 6ab40fd3924..516c0fd7f4b 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -107,7 +107,9 @@ def __init__( class ipoptSolutionLoader(SolSolutionLoader): - def get_reduced_costs(self, vars_to_load: Sequence[_GeneralVarData] | None = None) -> Mapping[_GeneralVarData, float]: + def get_reduced_costs( + self, vars_to_load: Sequence[_GeneralVarData] | None = None + ) -> Mapping[_GeneralVarData, float]: if self._nl_info.scaling is None: scale_list = [1] * len(self._nl_info.variables) else: @@ -348,11 +350,7 @@ def solve(self, model, **kwds): if config.tee: ostreams.append(sys.stdout) if config.log_solver_output: - ostreams.append( - LogStream( - level=logging.INFO, logger=logger - ) - ) + ostreams.append(LogStream(level=logging.INFO, logger=logger)) with TeeStream(*ostreams) as t: timer.start('subprocess') process = subprocess.run( @@ -484,8 +482,7 @@ def _parse_solution( res.solution_loader = SolutionLoader(None, None, None) else: res.solution_loader = ipoptSolutionLoader( - sol_data=sol_data, - nl_info=nl_info, + sol_data=sol_data, nl_info=nl_info ) return res diff --git a/pyomo/contrib/solver/results.py b/pyomo/contrib/solver/results.py index fa543d0aeaa..1fa9d653d01 100644 --- a/pyomo/contrib/solver/results.py +++ b/pyomo/contrib/solver/results.py @@ -191,9 +191,7 @@ def __init__( visibility=visibility, ) - self.solution_loader = self.declare( - 'solution_loader', ConfigValue() - ) + self.solution_loader = self.declare('solution_loader', ConfigValue()) self.termination_condition: TerminationCondition = self.declare( 'termination_condition', ConfigValue( @@ -244,7 +242,9 @@ def __init__( ConfigValue(domain=str, default=None, visibility=ADVANCED_OPTION), ) - def display(self, content_filter=None, indent_spacing=2, ostream=None, visibility=0): + def display( + self, content_filter=None, indent_spacing=2, ostream=None, visibility=0 + ): return super().display(content_filter, indent_spacing, ostream, visibility) diff --git a/pyomo/contrib/solver/sol_reader.py b/pyomo/contrib/solver/sol_reader.py index 28fe0100015..a51f2cf9015 100644 --- a/pyomo/contrib/solver/sol_reader.py +++ b/pyomo/contrib/solver/sol_reader.py @@ -167,7 +167,9 @@ def parse_sol_file( for cnt in range(nvalues): suf_line = sol_file.readline().split() var_ndx = int(suf_line[0]) - sol_data.var_suffixes[suffix_name][var_ndx] = convert_function(suf_line[1]) + sol_data.var_suffixes[suffix_name][var_ndx] = convert_function( + suf_line[1] + ) elif kind == 1: # Con sol_data.con_suffixes[suffix_name] = dict() for cnt in range(nvalues): @@ -181,7 +183,9 @@ def parse_sol_file( for cnt in range(nvalues): suf_line = sol_file.readline().split() obj_ndx = int(suf_line[0]) - sol_data.obj_suffixes[suffix_name][obj_ndx] = convert_function(suf_line[1]) + sol_data.obj_suffixes[suffix_name][obj_ndx] = convert_function( + suf_line[1] + ) elif kind == 3: # Prob sol_data.problem_suffixes[suffix_name] = list() for cnt in range(nvalues): diff --git a/pyomo/contrib/solver/solution.py b/pyomo/contrib/solver/solution.py index 18fd96759cf..7dd882d9745 100644 --- a/pyomo/contrib/solver/solution.py +++ b/pyomo/contrib/solver/solution.py @@ -33,7 +33,7 @@ def load_vars( ---------- vars_to_load: list The minimum set of variables whose solution should be loaded. If vars_to_load is None, then the solution - to all primal variables will be loaded. Even if vars_to_load is specified, the values of other + to all primal variables will be loaded. Even if vars_to_load is specified, the values of other variables may also be loaded depending on the interface. """ for v, val in self.get_primals(vars_to_load=vars_to_load).items(): @@ -187,21 +187,27 @@ def load_vars( scale_list = [1] * len(self._nl_info.variables) else: scale_list = self._nl_info.scaling.variables - for v, val, scale in zip(self._nl_info.variables, self._sol_data.primals, scale_list): - v.set_value(val/scale, skip_validation=True) + for v, val, scale in zip( + self._nl_info.variables, self._sol_data.primals, scale_list + ): + v.set_value(val / scale, skip_validation=True) for v, v_expr in self._nl_info.eliminated_vars: v.set_value(value(v_expr), skip_validation=True) StaleFlagManager.mark_all_as_stale(delayed=True) - def get_primals(self, vars_to_load: Sequence[_GeneralVarData] | None = None) -> Mapping[_GeneralVarData, float]: + def get_primals( + self, vars_to_load: Sequence[_GeneralVarData] | None = None + ) -> Mapping[_GeneralVarData, float]: if self._nl_info.scaling is None: scale_list = [1] * len(self._nl_info.variables) else: scale_list = self._nl_info.scaling.variables val_map = dict() - for v, val, scale in zip(self._nl_info.variables, self._sol_data.primals, scale_list): + for v, val, scale in zip( + self._nl_info.variables, self._sol_data.primals, scale_list + ): val_map[id(v)] = val / scale for v, v_expr in self._nl_info.eliminated_vars: @@ -211,13 +217,17 @@ def get_primals(self, vars_to_load: Sequence[_GeneralVarData] | None = None) -> res = ComponentMap() if vars_to_load is None: - vars_to_load = self._nl_info.variables + [v for v, _ in self._nl_info.eliminated_vars] + vars_to_load = self._nl_info.variables + [ + v for v, _ in self._nl_info.eliminated_vars + ] for v in vars_to_load: res[v] = val_map[id(v)] return res - - def get_duals(self, cons_to_load: Sequence[_GeneralConstraintData] | None = None) -> Dict[_GeneralConstraintData, float]: + + def get_duals( + self, cons_to_load: Sequence[_GeneralConstraintData] | None = None + ) -> Dict[_GeneralConstraintData, float]: if self._nl_info.scaling is None: scale_list = [1] * len(self._nl_info.constraints) else: @@ -227,7 +237,9 @@ def get_duals(self, cons_to_load: Sequence[_GeneralConstraintData] | None = None else: cons_to_load = set(cons_to_load) res = dict() - for c, val, scale in zip(self._nl_info.constraints, self._sol_data.duals, scale_list): + for c, val, scale in zip( + self._nl_info.constraints, self._sol_data.duals, scale_list + ): if c in cons_to_load: res[c] = val * scale return res diff --git a/pyomo/contrib/solver/tests/unit/test_results.py b/pyomo/contrib/solver/tests/unit/test_results.py index 927ab64ee12..23c2c32f819 100644 --- a/pyomo/contrib/solver/tests/unit/test_results.py +++ b/pyomo/contrib/solver/tests/unit/test_results.py @@ -83,7 +83,7 @@ def test_declared_items(self): 'termination_condition', 'timing_info', 'solver_log', - 'solver_configuration' + 'solver_configuration', } actual_declared = res._declared self.assertEqual(expected_declared, actual_declared) @@ -166,4 +166,3 @@ def test_results(self): rc2 = res.solution_loader.get_reduced_costs([m.y]) self.assertNotIn(m.x, rc2) self.assertAlmostEqual(rc[id(m.y)][1], rc2[m.y]) - From 142dc30f8b0379be599defa8fc20d63ccf9f12c9 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 18 Jan 2024 08:44:22 -0700 Subject: [PATCH 0812/1204] Convert typing to spre-3.10 supported syntax --- pyomo/contrib/solver/solution.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/solver/solution.py b/pyomo/contrib/solver/solution.py index 7dd882d9745..33a3b1c939c 100644 --- a/pyomo/contrib/solver/solution.py +++ b/pyomo/contrib/solver/solution.py @@ -198,7 +198,7 @@ def load_vars( StaleFlagManager.mark_all_as_stale(delayed=True) def get_primals( - self, vars_to_load: Sequence[_GeneralVarData] | None = None + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None ) -> Mapping[_GeneralVarData, float]: if self._nl_info.scaling is None: scale_list = [1] * len(self._nl_info.variables) @@ -226,7 +226,7 @@ def get_primals( return res def get_duals( - self, cons_to_load: Sequence[_GeneralConstraintData] | None = None + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None ) -> Dict[_GeneralConstraintData, float]: if self._nl_info.scaling is None: scale_list = [1] * len(self._nl_info.constraints) From 7fd0c98ed8c1afdf2f5cbf4c24f42b7ef2aae7a8 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 18 Jan 2024 08:52:42 -0700 Subject: [PATCH 0813/1204] Add in DevError check for number of options --- pyomo/contrib/solver/sol_reader.py | 40 +++++++++++++++++++----------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/pyomo/contrib/solver/sol_reader.py b/pyomo/contrib/solver/sol_reader.py index a51f2cf9015..68654a4e9d7 100644 --- a/pyomo/contrib/solver/sol_reader.py +++ b/pyomo/contrib/solver/sol_reader.py @@ -1,12 +1,21 @@ +# ___________________________________________________________________________ +# +# 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 typing import Tuple, Dict, Any, List import io -from pyomo.core.base.var import _GeneralVarData -from pyomo.core.base.constraint import _ConstraintData -from pyomo.core.base.objective import _ObjectiveData +from pyomo.common.errors import DeveloperError from pyomo.repn.plugins.nl_writer import NLWriterInfo from .results import Results, SolverResultsError, SolutionStatus, TerminationCondition -from pyomo.repn.plugins.nl_writer import AMPLRepn class SolFileData: @@ -44,20 +53,21 @@ def parse_sol_file( if "Options" in line: line = sol_file.readline() number_of_options = int(line) - need_tolerance = False - if ( - number_of_options > 4 - ): # MRM: Entirely unclear why this is necessary, or if it even is - number_of_options -= 2 - need_tolerance = True + # We are adding in this DeveloperError to see if the alternative case + # is ever actually hit in the wild. In a previous iteration of the sol + # reader, there was logic to check for the number of options, but it + # was uncovered by tests and unclear if actually necessary. + if number_of_options > 4: + raise DeveloperError( + """ +The sol file reader has hit an unexpected error while parsing. The number of +options recorded is greater than 4. Please report this error to the Pyomo +developers. + """ + ) for i in range(number_of_options + 4): line = sol_file.readline() model_objects.append(int(line)) - if ( - need_tolerance - ): # MRM: Entirely unclear why this is necessary, or if it even is - line = sol_file.readline() - model_objects.append(float(line)) else: raise SolverResultsError("ERROR READING `sol` FILE. No 'Options' line found.") # Identify the total number of variables and constraints From f456f3736cb06b36919ec9d7834b318020cd18c8 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 18 Jan 2024 08:59:16 -0700 Subject: [PATCH 0814/1204] Missed pre-3.10 syntax error --- pyomo/contrib/solver/ipopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 516c0fd7f4b..1a153422eb1 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -108,7 +108,7 @@ def __init__( class ipoptSolutionLoader(SolSolutionLoader): def get_reduced_costs( - self, vars_to_load: Sequence[_GeneralVarData] | None = None + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None ) -> Mapping[_GeneralVarData, float]: if self._nl_info.scaling is None: scale_list = [1] * len(self._nl_info.variables) From 2e479276d92d55f1696018d69d5087ddc1845554 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 18 Jan 2024 16:48:54 -0700 Subject: [PATCH 0815/1204] Make SumExpression.args always immutable (always return a copy of the underlying list) --- pyomo/core/expr/numeric_expr.py | 131 ++++++++++++-------------------- 1 file changed, 48 insertions(+), 83 deletions(-) diff --git a/pyomo/core/expr/numeric_expr.py b/pyomo/core/expr/numeric_expr.py index 3eb7861e341..17f8a15d5cf 100644 --- a/pyomo/core/expr/numeric_expr.py +++ b/pyomo/core/expr/numeric_expr.py @@ -1162,13 +1162,30 @@ def nargs(self): @property def args(self): - if len(self._args_) != self._nargs: - self._args_ = self._args_[: self._nargs] - return self._args_ + # We unconditionally make a copy of the args to isolate the user + # from future possible updates to the underlying list + return self._args_[:self._nargs] def getname(self, *args, **kwds): return 'sum' + def _trunc_append(self, other): + _args = self._args_ + if len(_args) > self._nargs: + _args = _args[:self._nargs] + _args.append(other) + return self.__class__(_args) + + def _trunc_extend(self, other): + _args = self._args_ + if len(_args) > self._nargs: + _args = _args[:self._nargs] + if len(other._args_) == other._nargs: + _args.extend(other._args_) + else: + _args.extend(other._args_[:other._nargs]) + return self.__class__(_args) + def _apply_operation(self, result): return sum(result) @@ -1821,17 +1838,13 @@ def _add_native_monomial(a, b): def _add_native_linear(a, b): if not a: return b - args = b.args - args.append(a) - return b.__class__(args) + return b._trunc_append(a) def _add_native_sum(a, b): if not a: return b - args = b.args - args.append(a) - return b.__class__(args) + return b._trunc_append(a) def _add_native_other(a, b): @@ -1872,15 +1885,11 @@ def _add_npv_monomial(a, b): def _add_npv_linear(a, b): - args = b.args - args.append(a) - return b.__class__(args) + return b._trunc_append(a) def _add_npv_sum(a, b): - args = b.args - args.append(a) - return b.__class__(args) + return b._trunc_append(a) def _add_npv_other(a, b): @@ -1942,9 +1951,7 @@ def _add_param_linear(a, b): a = a.value if not a: return b - args = b.args - args.append(a) - return b.__class__(args) + return b._trunc_append(a) def _add_param_sum(a, b): @@ -1952,9 +1959,7 @@ def _add_param_sum(a, b): a = value(a) if not a: return b - args = b.args - args.append(a) - return b.__class__(args) + return b._trunc_append(a) def _add_param_other(a, b): @@ -1999,15 +2004,11 @@ def _add_var_monomial(a, b): def _add_var_linear(a, b): - args = b.args - args.append(MonomialTermExpression((1, a))) - return b.__class__(args) + return b._trunc_append(MonomialTermExpression((1, a))) def _add_var_sum(a, b): - args = b.args - args.append(a) - return b.__class__(args) + return b._trunc_append(a) def _add_var_other(a, b): @@ -2046,15 +2047,11 @@ def _add_monomial_monomial(a, b): def _add_monomial_linear(a, b): - args = b.args - args.append(a) - return b.__class__(args) + return b._trunc_append(a) def _add_monomial_sum(a, b): - args = b.args - args.append(a) - return b.__class__(args) + return b._trunc_append(a) def _add_monomial_other(a, b): @@ -2069,15 +2066,11 @@ def _add_monomial_other(a, b): def _add_linear_native(a, b): if not b: return a - args = a.args - args.append(b) - return a.__class__(args) + return a._trunc_append(b) def _add_linear_npv(a, b): - args = a.args - args.append(b) - return a.__class__(args) + return a._trunc_append(b) def _add_linear_param(a, b): @@ -2085,33 +2078,23 @@ def _add_linear_param(a, b): b = b.value if not b: return a - args = a.args - args.append(b) - return a.__class__(args) + return a._trunc_append(b) def _add_linear_var(a, b): - args = a.args - args.append(MonomialTermExpression((1, b))) - return a.__class__(args) + return a._trunc_append(MonomialTermExpression((1, b))) def _add_linear_monomial(a, b): - args = a.args - args.append(b) - return a.__class__(args) + return a._trunc_append(b) def _add_linear_linear(a, b): - args = a.args - args.extend(b.args) - return a.__class__(args) + return a._trunc_extend(b) def _add_linear_sum(a, b): - args = b.args - args.append(a) - return b.__class__(args) + return b._trunc_append(a) def _add_linear_other(a, b): @@ -2126,15 +2109,11 @@ def _add_linear_other(a, b): def _add_sum_native(a, b): if not b: return a - args = a.args - args.append(b) - return a.__class__(args) + return a._trunc_append(b) def _add_sum_npv(a, b): - args = a.args - args.append(b) - return a.__class__(args) + return a._trunc_append(b) def _add_sum_param(a, b): @@ -2142,39 +2121,27 @@ def _add_sum_param(a, b): b = b.value if not b: return a - args = a.args - args.append(b) - return a.__class__(args) + return a._trunc_append(b) def _add_sum_var(a, b): - args = a.args - args.append(b) - return a.__class__(args) + return a._trunc_append(b) def _add_sum_monomial(a, b): - args = a.args - args.append(b) - return a.__class__(args) + return a._trunc_append(b) def _add_sum_linear(a, b): - args = a.args - args.append(b) - return a.__class__(args) + return a._trunc_append(b) def _add_sum_sum(a, b): - args = a.args - args.extend(b.args) - return a.__class__(args) + return a._trunc_extend(b) def _add_sum_other(a, b): - args = a.args - args.append(b) - return a.__class__(args) + return a._trunc_append(b) # @@ -2213,9 +2180,7 @@ def _add_other_linear(a, b): def _add_other_sum(a, b): - args = b.args - args.append(a) - return b.__class__(args) + return b._trunc_append(a) def _add_other_other(a, b): @@ -2628,8 +2593,8 @@ def _neg_var(a): def _neg_monomial(a): - args = a.args - return MonomialTermExpression((-args[0], args[1])) + coef, var = a.args + return MonomialTermExpression((-coef, var)) def _neg_sum(a): From 00f24b8ef49f2d01a29afa657a10e74777871a5a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 18 Jan 2024 16:49:52 -0700 Subject: [PATCH 0816/1204] Update tests to reflect change in when underlying lists are duplicated --- pyomo/core/tests/unit/test_numeric_expr.py | 6 +++--- pyomo/core/tests/unit/test_numeric_expr_api.py | 9 ++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pyomo/core/tests/unit/test_numeric_expr.py b/pyomo/core/tests/unit/test_numeric_expr.py index 82f1e5c11c4..a4f3295441e 100644 --- a/pyomo/core/tests/unit/test_numeric_expr.py +++ b/pyomo/core/tests/unit/test_numeric_expr.py @@ -1618,9 +1618,9 @@ def test_nestedProduct2(self): ), ) # Verify shared args... - self.assertIsNot(e1._args_, e2._args_) - self.assertIs(e1._args_, e3._args_) - self.assertIs(e1._args_, e.arg(1)._args_) + self.assertIs(e1._args_, e2._args_) + self.assertIsNot(e1._args_, e3._args_) + self.assertIs(e1._args_, e.arg(0)._args_) self.assertIs(e.arg(0).arg(0), e.arg(1).arg(0)) self.assertIs(e.arg(0).arg(1), e.arg(1).arg(1)) diff --git a/pyomo/core/tests/unit/test_numeric_expr_api.py b/pyomo/core/tests/unit/test_numeric_expr_api.py index 0d85e959fa0..69cb43f3ad5 100644 --- a/pyomo/core/tests/unit/test_numeric_expr_api.py +++ b/pyomo/core/tests/unit/test_numeric_expr_api.py @@ -950,7 +950,14 @@ def test_sum(self): f = e.create_node_with_local_data(e.args) self.assertIsNot(f, e) self.assertIs(type(f), type(e)) - self.assertIs(f.args, e.args) + self.assertIsNot(f._args_, e._args_) + self.assertIsNot(f.args, e.args) + + f = e.create_node_with_local_data(e._args_) + self.assertIsNot(f, e) + self.assertIs(type(f), type(e)) + self.assertIs(f._args_, e._args_) + self.assertIsNot(f.args, e.args) f = e.create_node_with_local_data((m.x, 2, 3)) self.assertIsNot(f, e) From 7d64e1725b1af5173c9ff94cb885913ee1c834c2 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Mon, 22 Jan 2024 15:10:07 -0700 Subject: [PATCH 0817/1204] add get_config_from_kwds to use instead of hacking ConfigDict --- pyomo/contrib/incidence_analysis/config.py | 90 +++++++++++-------- pyomo/contrib/incidence_analysis/incidence.py | 18 ++-- pyomo/contrib/incidence_analysis/interface.py | 12 +-- 3 files changed, 70 insertions(+), 50 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/config.py b/pyomo/contrib/incidence_analysis/config.py index 036c563ae75..4ab086da508 100644 --- a/pyomo/contrib/incidence_analysis/config.py +++ b/pyomo/contrib/incidence_analysis/config.py @@ -69,45 +69,16 @@ class _ReconstructVisitor: pass -def _amplrepnvisitor_validator(visitor=_ReconstructVisitor): - # This checks for and returns a valid AMPLRepnVisitor, but I don't want - # to construct this if we're not using IncidenceMethod.ampl_repn. - # It is not necessarily the end of the world if we construct this, however, - # as the code should still work. - if visitor is _ReconstructVisitor: - subexpression_cache = {} - subexpression_order = [] - external_functions = {} - var_map = {} - used_named_expressions = set() - symbolic_solver_labels = False - # TODO: Explore potential performance benefit of exporting defined variables. - # This likely only shows up if we can preserve the subexpression cache across - # multiple constraint expressions. - export_defined_variables = False - sorter = FileDeterminism_to_SortComponents(FileDeterminism.ORDERED) - amplvisitor = AMPLRepnVisitor( - text_nl_template, - subexpression_cache, - subexpression_order, - external_functions, - var_map, - used_named_expressions, - symbolic_solver_labels, - export_defined_variables, - sorter, - ) - elif not isinstance(visitor, AMPLRepnVisitor): +def _amplrepnvisitor_validator(visitor): + if not isinstance(visitor, AMPLRepnVisitor): raise TypeError( "'visitor' config argument should be an instance of AMPLRepnVisitor" ) - else: - amplvisitor = visitor - return amplvisitor + return visitor _ampl_repn_visitor = ConfigValue( - default=_ReconstructVisitor, + default=None, domain=_amplrepnvisitor_validator, description="Visitor used to generate AMPLRepn of each constraint", ) @@ -141,14 +112,14 @@ def __call__( if ( new.method == IncidenceMethod.ampl_repn - and "ampl_repn_visitor" not in init_value + and "_ampl_repn_visitor" not in init_value ): - new.ampl_repn_visitor = _ReconstructVisitor + new._ampl_repn_visitor = _ReconstructVisitor return new -IncidenceConfig = _IncidenceConfigDict() +IncidenceConfig = ConfigDict() """Options for incidence graph generation - ``include_fixed`` -- Flag indicating whether fixed variables should be included @@ -157,8 +128,9 @@ def __call__( should be included. - ``method`` -- Method used to identify incident variables. Must be a value of the ``IncidenceMethod`` enum. -- ``ampl_repn_visitor`` -- Expression visitor used to generate ``AMPLRepn`` of each - constraint. Must be an instance of ``AMPLRepnVisitor``. +- ``_ampl_repn_visitor`` -- Expression visitor used to generate ``AMPLRepn`` of each + constraint. Must be an instance of ``AMPLRepnVisitor``. *This option is constructed + automatically when needed and should not be set by users!* """ @@ -172,4 +144,44 @@ def __call__( IncidenceConfig.declare("method", _method) -IncidenceConfig.declare("ampl_repn_visitor", _ampl_repn_visitor) +IncidenceConfig.declare("_ampl_repn_visitor", _ampl_repn_visitor) + + +def get_config_from_kwds(**kwds): + """Get an instance of IncidenceConfig from provided keyword arguments. + + If the ``method`` argument is ``IncidenceMethod.ampl_repn`` and no + ``AMPLRepnVisitor`` has been provided, a new ``AMPLRepnVisitor`` is + constructed. This function should generally be used by callers such + as ``IncidenceGraphInterface`` to ensure that a visitor is created then + re-used when calling ``get_incident_variables`` in a loop. + + """ + if ( + kwds.get("method", None) is IncidenceMethod.ampl_repn + and kwds.get("_ampl_repn_visitor", None) is None + ): + subexpression_cache = {} + subexpression_order = [] + external_functions = {} + var_map = {} + used_named_expressions = set() + symbolic_solver_labels = False + # TODO: Explore potential performance benefit of exporting defined variables. + # This likely only shows up if we can preserve the subexpression cache across + # multiple constraint expressions. + export_defined_variables = False + sorter = FileDeterminism_to_SortComponents(FileDeterminism.ORDERED) + amplvisitor = AMPLRepnVisitor( + text_nl_template, + subexpression_cache, + subexpression_order, + external_functions, + var_map, + used_named_expressions, + symbolic_solver_labels, + export_defined_variables, + sorter, + ) + kwds["_ampl_repn_visitor"] = amplvisitor + return IncidenceConfig(kwds) diff --git a/pyomo/contrib/incidence_analysis/incidence.py b/pyomo/contrib/incidence_analysis/incidence.py index 17307e89600..1fc3380fe6b 100644 --- a/pyomo/contrib/incidence_analysis/incidence.py +++ b/pyomo/contrib/incidence_analysis/incidence.py @@ -19,7 +19,9 @@ from pyomo.repn.plugins.nl_writer import AMPLRepnVisitor, AMPLRepn, text_nl_template from pyomo.repn.util import FileDeterminism, FileDeterminism_to_SortComponents from pyomo.util.subsystems import TemporarySubsystemManager -from pyomo.contrib.incidence_analysis.config import IncidenceMethod, IncidenceConfig +from pyomo.contrib.incidence_analysis.config import ( + IncidenceMethod, get_config_from_kwds +) # @@ -148,17 +150,24 @@ def get_incident_variables(expr, **kwds): ['x[1]', 'x[2]'] """ - config = IncidenceConfig(kwds) + config = get_config_from_kwds(**kwds) method = config.method include_fixed = config.include_fixed linear_only = config.linear_only - amplrepnvisitor = config.ampl_repn_visitor + amplrepnvisitor = config._ampl_repn_visitor + + # Check compatibility of arguments if linear_only and method is IncidenceMethod.identify_variables: raise RuntimeError( "linear_only=True is not supported when using identify_variables" ) if include_fixed and method is IncidenceMethod.ampl_repn: raise RuntimeError("include_fixed=True is not supported when using ampl_repn") + if method is IncidenceMethod.ampl_repn and amplrepnvisitor is None: + # Developer error, this should never happen! + raise RuntimeError("_ampl_repn_visitor must be provided when using ampl_repn") + + # Dispatch to correct method if method is IncidenceMethod.identify_variables: return _get_incident_via_identify_variables(expr, include_fixed) elif method is IncidenceMethod.standard_repn: @@ -174,6 +183,5 @@ def get_incident_variables(expr, **kwds): else: raise ValueError( f"Unrecognized value {method} for the method used to identify incident" - f" variables. Valid options are {IncidenceMethod.identify_variables}" - f" and {IncidenceMethod.standard_repn}." + f" variables. See the IncidenceMethod enum for valid methods." ) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index b8a6c1275f9..41f0ece3a75 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -29,7 +29,7 @@ plotly, ) from pyomo.common.deprecation import deprecated -from pyomo.contrib.incidence_analysis.config import IncidenceConfig, IncidenceMethod +from pyomo.contrib.incidence_analysis.config import get_config_from_kwds from pyomo.contrib.incidence_analysis.matching import maximum_matching from pyomo.contrib.incidence_analysis.connected import get_independent_submatrices from pyomo.contrib.incidence_analysis.triangularize import ( @@ -64,7 +64,7 @@ def _check_unindexed(complist): def get_incidence_graph(variables, constraints, **kwds): - config = IncidenceConfig(kwds) + config = get_config_from_kwds(**kwds) return get_bipartite_incidence_graph(variables, constraints, **config) @@ -95,7 +95,7 @@ def get_bipartite_incidence_graph(variables, constraints, **kwds): """ # Note that this ConfigDict contains the visitor that we will re-use # when constructing constraints. - config = IncidenceConfig(kwds) + config = get_config_from_kwds(**kwds) _check_unindexed(variables + constraints) N = len(variables) M = len(constraints) @@ -168,7 +168,7 @@ def extract_bipartite_subgraph(graph, nodes0, nodes1): def _generate_variables_in_constraints(constraints, **kwds): # Note: We construct a visitor here - config = IncidenceConfig(kwds) + config = get_config_from_kwds(**kwds) known_vars = ComponentSet() for con in constraints: for var in get_incident_variables(con.body, **config): @@ -196,7 +196,7 @@ def get_structural_incidence_matrix(variables, constraints, **kwds): Entries are 1.0. """ - config = IncidenceConfig(kwds) + config = get_config_from_kwds(**kwds) _check_unindexed(variables + constraints) N, M = len(variables), len(constraints) var_idx_map = ComponentMap((v, i) for i, v in enumerate(variables)) @@ -279,7 +279,7 @@ def __init__(self, model=None, active=True, include_inequality=True, **kwds): # to cache the incidence graph for fast analysis later on. # WARNING: This cache will become invalid if the user alters their # model. - self._config = IncidenceConfig(kwds) + self._config = get_config_from_kwds(**kwds) if model is None: self._incidence_graph = None self._variables = None From 57d3134725a82f150e4b63da2f32b93ade02d201 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Mon, 22 Jan 2024 15:11:22 -0700 Subject: [PATCH 0818/1204] remove now-unused ConfigDict hack --- pyomo/contrib/incidence_analysis/config.py | 39 ---------------------- 1 file changed, 39 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/config.py b/pyomo/contrib/incidence_analysis/config.py index 4ab086da508..d055be478fe 100644 --- a/pyomo/contrib/incidence_analysis/config.py +++ b/pyomo/contrib/incidence_analysis/config.py @@ -65,10 +65,6 @@ class IncidenceMethod(enum.Enum): ) -class _ReconstructVisitor: - pass - - def _amplrepnvisitor_validator(visitor): if not isinstance(visitor, AMPLRepnVisitor): raise TypeError( @@ -84,41 +80,6 @@ def _amplrepnvisitor_validator(visitor): ) -class _IncidenceConfigDict(ConfigDict): - def __call__( - self, - value=NOTSET, - default=NOTSET, - domain=NOTSET, - description=NOTSET, - doc=NOTSET, - visibility=NOTSET, - implicit=NOTSET, - implicit_domain=NOTSET, - preserve_implicit=False, - ): - init_value = value - new = super().__call__( - value=value, - default=default, - domain=domain, - description=description, - doc=doc, - visibility=visibility, - implicit=implicit, - implicit_domain=implicit_domain, - preserve_implicit=preserve_implicit, - ) - - if ( - new.method == IncidenceMethod.ampl_repn - and "_ampl_repn_visitor" not in init_value - ): - new._ampl_repn_visitor = _ReconstructVisitor - - return new - - IncidenceConfig = ConfigDict() """Options for incidence graph generation From f279fed36fe137a7d7bb33ebf48c4bd05d9f7619 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Mon, 22 Jan 2024 15:22:42 -0700 Subject: [PATCH 0819/1204] split imports onto separate lines --- pyomo/contrib/incidence_analysis/incidence.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/incidence_analysis/incidence.py b/pyomo/contrib/incidence_analysis/incidence.py index 1fc3380fe6b..636a400def4 100644 --- a/pyomo/contrib/incidence_analysis/incidence.py +++ b/pyomo/contrib/incidence_analysis/incidence.py @@ -20,7 +20,8 @@ from pyomo.repn.util import FileDeterminism, FileDeterminism_to_SortComponents from pyomo.util.subsystems import TemporarySubsystemManager from pyomo.contrib.incidence_analysis.config import ( - IncidenceMethod, get_config_from_kwds + IncidenceMethod, + get_config_from_kwds, ) From 049494c15f8db747824c280f1fe13bd641a672a5 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 22 Jan 2024 17:29:53 -0700 Subject: [PATCH 0820/1204] Add test for 3096 --- pyomo/core/tests/unit/test_derivs.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pyomo/core/tests/unit/test_derivs.py b/pyomo/core/tests/unit/test_derivs.py index 23a5a8bc7d1..7db284cb29a 100644 --- a/pyomo/core/tests/unit/test_derivs.py +++ b/pyomo/core/tests/unit/test_derivs.py @@ -322,6 +322,18 @@ def test_nested_named_expressions(self): self.assertAlmostEqual(derivs[m.y], pyo.value(symbolic[m.y]), tol + 3) self.assertAlmostEqual(derivs[m.y], approx_deriv(e, m.y), tol) + def test_linear_exprs_issue_3096(self): + m = pyo.ConcreteModel() + m.y1 = pyo.Var(initialize=10) + m.y2 = pyo.Var(initialize=100) + e = (m.y1 - 0.5) * (m.y1 - 0.5) + (m.y2 - 0.5) * (m.y2 - 0.5) + derivs = reverse_ad(e) + self.assertEqual(derivs[m.y1], 19) + self.assertEqual(derivs[m.y2], 199) + symbolic = reverse_sd(e) + self.assertExpressionsEqual(symbolic[m.y1], m.y1 - 0.5 + m.y1 - 0.5) + self.assertExpressionsEqual(symbolic[m.y2], m.y2 - 0.5 + m.y2 - 0.5) + class TestDifferentiate(unittest.TestCase): @unittest.skipUnless(sympy_available, "test requires sympy") From 3e2979558744e016b860d12d38e409cc2d87bf17 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 22 Jan 2024 17:41:32 -0700 Subject: [PATCH 0821/1204] NFC: Apply black --- pyomo/core/expr/numeric_expr.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/core/expr/numeric_expr.py b/pyomo/core/expr/numeric_expr.py index 17f8a15d5cf..a638903236f 100644 --- a/pyomo/core/expr/numeric_expr.py +++ b/pyomo/core/expr/numeric_expr.py @@ -1164,7 +1164,7 @@ def nargs(self): def args(self): # We unconditionally make a copy of the args to isolate the user # from future possible updates to the underlying list - return self._args_[:self._nargs] + return self._args_[: self._nargs] def getname(self, *args, **kwds): return 'sum' @@ -1172,18 +1172,18 @@ def getname(self, *args, **kwds): def _trunc_append(self, other): _args = self._args_ if len(_args) > self._nargs: - _args = _args[:self._nargs] + _args = _args[: self._nargs] _args.append(other) return self.__class__(_args) def _trunc_extend(self, other): _args = self._args_ if len(_args) > self._nargs: - _args = _args[:self._nargs] + _args = _args[: self._nargs] if len(other._args_) == other._nargs: _args.extend(other._args_) else: - _args.extend(other._args_[:other._nargs]) + _args.extend(other._args_[: other._nargs]) return self.__class__(_args) def _apply_operation(self, result): From daff9aa7308c09d0e6e48b6146ecb0c92802ac67 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 22 Jan 2024 21:11:01 -0700 Subject: [PATCH 0822/1204] Import pandas through pyomo.common.dependencies --- .../parmest/examples/reactor_design/bootstrap_example.py | 2 +- .../contrib/parmest/examples/reactor_design/datarec_example.py | 3 +-- .../parmest/examples/reactor_design/leaveNout_example.py | 3 +-- .../examples/reactor_design/likelihood_ratio_example.py | 3 +-- .../examples/reactor_design/multisensor_data_example.py | 2 +- .../examples/reactor_design/parameter_estimation_example.py | 2 +- .../contrib/parmest/examples/reactor_design/reactor_design.py | 2 +- .../parmest/examples/reactor_design/timeseries_data_example.py | 2 +- .../parmest/examples/rooney_biegler/bootstrap_example.py | 2 +- .../examples/rooney_biegler/likelihood_ratio_example.py | 3 +-- .../examples/rooney_biegler/parameter_estimation_example.py | 2 +- .../contrib/parmest/examples/rooney_biegler/rooney_biegler.py | 2 +- .../examples/rooney_biegler/rooney_biegler_with_constraint.py | 2 +- pyomo/contrib/parmest/examples/semibatch/parallel_example.py | 3 +-- .../pynumero/examples/callback/cyipopt_functor_callback.py | 2 +- .../examples/external_grey_box/param_est/generate_data.py | 2 +- .../examples/external_grey_box/param_est/perform_estimation.py | 2 +- pyomo/contrib/sensitivity_toolbox/examples/rooney_biegler.py | 2 +- 18 files changed, 18 insertions(+), 23 deletions(-) diff --git a/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py b/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py index e2d172f34f6..16ae9343dfd 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py @@ -9,7 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -import pandas as pd +from pyomo.common.dependencies import pandas as pd from os.path import join, abspath, dirname import pyomo.contrib.parmest.parmest as parmest from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( diff --git a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py index cfd3891c00e..507a3ee7582 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py @@ -9,8 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -import numpy as np -import pandas as pd +from pyomo.common.dependencies import numpy as np, pandas as pd import pyomo.contrib.parmest.parmest as parmest from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( reactor_design_model, diff --git a/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py b/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py index 6952a7fc733..cda50ef3efd 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py @@ -9,8 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -import numpy as np -import pandas as pd +from pyomo.common.dependencies import numpy as np, pandas as pd from os.path import join, abspath, dirname import pyomo.contrib.parmest.parmest as parmest from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( diff --git a/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py b/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py index a0fe6f22305..448354f600a 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py @@ -9,8 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -import numpy as np -import pandas as pd +from pyomo.common.dependencies import numpy as np, pandas as pd from itertools import product from os.path import join, abspath, dirname import pyomo.contrib.parmest.parmest as parmest diff --git a/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py b/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py index a92ac626fae..10d56c8e457 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py @@ -9,7 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -import pandas as pd +from pyomo.common.dependencies import pandas as pd from os.path import join, abspath, dirname import pyomo.contrib.parmest.parmest as parmest from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( diff --git a/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py b/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py index 581d3904c04..43af4fbcb94 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py @@ -9,7 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -import pandas as pd +from pyomo.common.dependencies import pandas as pd from os.path import join, abspath, dirname import pyomo.contrib.parmest.parmest as parmest from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( diff --git a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py index 16f65e236eb..e86446febd7 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py +++ b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py @@ -12,7 +12,7 @@ Continuously stirred tank reactor model, based on pyomo/examples/doc/pyomobook/nonlinear-ch/react_design/ReactorDesign.py """ -import pandas as pd +from pyomo.common.dependencies import pandas as pd from pyomo.environ import ( ConcreteModel, Param, diff --git a/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py b/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py index da2ab1874c9..ff6c167f68d 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py @@ -9,7 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -import pandas as pd +from pyomo.common.dependencies import pandas as pd from os.path import join, abspath, dirname import pyomo.contrib.parmest.parmest as parmest diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py b/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py index f686bbd933d..1c82adb909a 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py @@ -9,7 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -import pandas as pd +from pyomo.common.dependencies import pandas as pd import pyomo.contrib.parmest.parmest as parmest from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( rooney_biegler_model, diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.py b/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.py index 5e54a33abda..7cd77166a4b 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.py @@ -9,8 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -import numpy as np -import pandas as pd +from pyomo.common.dependencies import numpy as np, pandas as pd from itertools import product import pyomo.contrib.parmest.parmest as parmest from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.py b/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.py index 9af33217fe4..9aa59be6a17 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.py @@ -9,7 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -import pandas as pd +from pyomo.common.dependencies import pandas as pd import pyomo.contrib.parmest.parmest as parmest from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( rooney_biegler_model, diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py index 5a0e1238e85..7a48dcf190d 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py @@ -15,7 +15,7 @@ 47(8), 1794-1804. """ -import pandas as pd +from pyomo.common.dependencies import pandas as pd import pyomo.environ as pyo diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler_with_constraint.py b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler_with_constraint.py index 2582e3fe928..0ad65b1eb7a 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler_with_constraint.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler_with_constraint.py @@ -15,7 +15,7 @@ 47(8), 1794-1804. """ -import pandas as pd +from pyomo.common.dependencies import pandas as pd import pyomo.environ as pyo diff --git a/pyomo/contrib/parmest/examples/semibatch/parallel_example.py b/pyomo/contrib/parmest/examples/semibatch/parallel_example.py index ff1287811cf..ba69b9f2d06 100644 --- a/pyomo/contrib/parmest/examples/semibatch/parallel_example.py +++ b/pyomo/contrib/parmest/examples/semibatch/parallel_example.py @@ -14,8 +14,7 @@ parallel and save results to files for later analysis and graphics. Example command: mpiexec -n 4 python parallel_example.py """ -import numpy as np -import pandas as pd +from pyomo.common.dependencies import numpy as np, pandas as pd from itertools import product from os.path import join, abspath, dirname import pyomo.contrib.parmest.parmest as parmest diff --git a/pyomo/contrib/pynumero/examples/callback/cyipopt_functor_callback.py b/pyomo/contrib/pynumero/examples/callback/cyipopt_functor_callback.py index f977a2701a2..ca452f33c90 100644 --- a/pyomo/contrib/pynumero/examples/callback/cyipopt_functor_callback.py +++ b/pyomo/contrib/pynumero/examples/callback/cyipopt_functor_callback.py @@ -1,6 +1,6 @@ import pyomo.environ as pyo from pyomo.contrib.pynumero.examples.callback.reactor_design import model as m -import pandas as pd +from pyomo.common.dependencies import pandas as pd """ This example uses an iteration callback with a functor to store diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/generate_data.py b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/generate_data.py index 3588ba3853d..5bf0defbb8d 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/generate_data.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/generate_data.py @@ -1,7 +1,7 @@ import pyomo.environ as pyo import numpy.random as rnd import pyomo.contrib.pynumero.examples.external_grey_box.param_est.models as pm -import pandas as pd +from pyomo.common.dependencies import pandas as pd def generate_data(N, UA_mean, UA_std, seed=42): diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/perform_estimation.py b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/perform_estimation.py index 29ca7145475..f27192f9281 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/perform_estimation.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/perform_estimation.py @@ -1,7 +1,7 @@ import sys import pyomo.environ as pyo import numpy.random as rnd -import pandas as pd +from pyomo.common.dependencies import pandas as pd import pyomo.contrib.pynumero.examples.external_grey_box.param_est.models as po diff --git a/pyomo/contrib/sensitivity_toolbox/examples/rooney_biegler.py b/pyomo/contrib/sensitivity_toolbox/examples/rooney_biegler.py index 701d3f71bb4..f058e8189dc 100644 --- a/pyomo/contrib/sensitivity_toolbox/examples/rooney_biegler.py +++ b/pyomo/contrib/sensitivity_toolbox/examples/rooney_biegler.py @@ -15,7 +15,7 @@ model parameter uncertainty using nonlinear confidence regions. AIChE Journal, 47(8), 1794-1804. """ -import pandas as pd +from pyomo.common.dependencies import pandas as pd import pyomo.environ as pyo From 5e874ba2ed0e662c732ae6b83a3921b5753c7c70 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 23 Jan 2024 09:24:05 -0700 Subject: [PATCH 0823/1204] try using scip for pyros tests --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- pyomo/contrib/pyros/tests/test_grcs.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index a55a5db2433..2fc2232d278 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -618,7 +618,7 @@ jobs: - name: Run Pyomo tests if: matrix.mpi == 0 run: | - $PYTHON_EXE -m pytest -v \ + $PYTHON_EXE -m pytest -rs -v \ -W ignore::Warning ${{matrix.category}} \ pyomo `pwd`/pyomo-model-libraries \ `pwd`/examples `pwd`/doc --junitxml="TEST-pyomo.xml" diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index ac7691d32ae..4a99d25c452 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -648,7 +648,7 @@ jobs: - name: Run Pyomo tests if: matrix.mpi == 0 run: | - $PYTHON_EXE -m pytest -v \ + $PYTHON_EXE -m pytest -rs -v \ -W ignore::Warning ${{matrix.category}} \ pyomo `pwd`/pyomo-model-libraries \ `pwd`/examples `pwd`/doc --junitxml="TEST-pyomo.xml" diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 05cbdb849f4..11bdf46de75 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -4877,7 +4877,7 @@ def test_higher_order_decision_rules(self): ) @unittest.skipUnless( - baron_license_is_valid, "Global NLP solver is not available and licensed." + scip_available, "Global NLP solver is not available and licensed." ) def test_coefficient_matching_solve(self): # Write the deterministic Pyomo model @@ -4902,8 +4902,8 @@ def test_coefficient_matching_solve(self): pyros_solver = SolverFactory("pyros") # Define subsolvers utilized in the algorithm - local_subsolver = SolverFactory('baron') - global_subsolver = SolverFactory("baron") + local_subsolver = SolverFactory('scip') + global_subsolver = SolverFactory("scip") # Call the PyROS solver results = pyros_solver.solve( From f0f91525add5634c3dd35fbe80c34f2baaf63af1 Mon Sep 17 00:00:00 2001 From: Miranda Mundt <55767766+mrmundt@users.noreply.github.com> Date: Tue, 23 Jan 2024 10:56:42 -0700 Subject: [PATCH 0824/1204] Change other failed tests to scip --- pyomo/contrib/pyros/tests/test_grcs.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 11bdf46de75..16de6e19c04 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -4877,7 +4877,7 @@ def test_higher_order_decision_rules(self): ) @unittest.skipUnless( - scip_available, "Global NLP solver is not available and licensed." + scip_available, "Global NLP solver is not available." ) def test_coefficient_matching_solve(self): # Write the deterministic Pyomo model @@ -5010,8 +5010,8 @@ def test_coeff_matching_solver_insensitive(self): ) @unittest.skipUnless( - baron_license_is_valid and baron_version >= (23, 2, 27), - "BARON licensing and version requirements not met", + scip_available, + "NLP solver is not available.", ) def test_coefficient_matching_partitioning_insensitive(self): """ @@ -5023,7 +5023,7 @@ def test_coefficient_matching_partitioning_insensitive(self): m = self.create_mitsos_4_3() # instantiate BARON subsolver and PyROS solver - baron = SolverFactory("baron") + baron = SolverFactory("scip") pyros_solver = SolverFactory("pyros") # solve with PyROS @@ -5216,8 +5216,8 @@ def test_coefficient_matching_nonlinear_expr(self): @unittest.skipUnless( - baron_available and baron_license_is_valid, - "Global NLP solver is not available and licensed.", + scip_available, + "Global NLP solver is not available.", ) class testBypassingSeparation(unittest.TestCase): def test_bypass_global_separation(self): @@ -5241,7 +5241,7 @@ def test_bypass_global_separation(self): # Define subsolvers utilized in the algorithm local_subsolver = SolverFactory('ipopt') - global_subsolver = SolverFactory("baron") + global_subsolver = SolverFactory("scip") # Call the PyROS solver with LoggingIntercept(level=logging.WARNING) as LOG: @@ -5361,8 +5361,8 @@ def test_uninitialized_vars(self): @unittest.skipUnless( - baron_available and baron_license_is_valid, - "Global NLP solver is not available and licensed.", + scip_available, + "Global NLP solver is not available.", ) class testModelMultipleObjectives(unittest.TestCase): """ @@ -5398,7 +5398,7 @@ def test_multiple_objs(self): # Define subsolvers utilized in the algorithm local_subsolver = SolverFactory('ipopt') - global_subsolver = SolverFactory("baron") + global_subsolver = SolverFactory("scip") solve_kwargs = dict( model=m, From 25bd0c8c70edef26255f412385382402f97bd775 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 23 Jan 2024 11:12:10 -0700 Subject: [PATCH 0825/1204] Apply black --- pyomo/contrib/pyros/tests/test_grcs.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 16de6e19c04..1690ba72f6f 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -4876,9 +4876,7 @@ def test_higher_order_decision_rules(self): msg="Returned termination condition is not return robust_optimal.", ) - @unittest.skipUnless( - scip_available, "Global NLP solver is not available." - ) + @unittest.skipUnless(scip_available, "Global NLP solver is not available.") def test_coefficient_matching_solve(self): # Write the deterministic Pyomo model m = ConcreteModel() @@ -5009,10 +5007,7 @@ def test_coeff_matching_solver_insensitive(self): ), ) - @unittest.skipUnless( - scip_available, - "NLP solver is not available.", - ) + @unittest.skipUnless(scip_available, "NLP solver is not available.") def test_coefficient_matching_partitioning_insensitive(self): """ Check that result for instance with constraint subject to @@ -5215,10 +5210,7 @@ def test_coefficient_matching_nonlinear_expr(self): ) -@unittest.skipUnless( - scip_available, - "Global NLP solver is not available.", -) +@unittest.skipUnless(scip_available, "Global NLP solver is not available.") class testBypassingSeparation(unittest.TestCase): def test_bypass_global_separation(self): """Test bypassing of global separation solve calls.""" @@ -5360,10 +5352,7 @@ def test_uninitialized_vars(self): ) -@unittest.skipUnless( - scip_available, - "Global NLP solver is not available.", -) +@unittest.skipUnless(scip_available, "Global NLP solver is not available.") class testModelMultipleObjectives(unittest.TestCase): """ This class contains tests for models with multiple From 8287ff03c97f62016fbd083b32b1919a1a6a13f5 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 23 Jan 2024 12:47:40 -0700 Subject: [PATCH 0826/1204] Remove '-rs' flag because it's noisy --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 2fc2232d278..a55a5db2433 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -618,7 +618,7 @@ jobs: - name: Run Pyomo tests if: matrix.mpi == 0 run: | - $PYTHON_EXE -m pytest -rs -v \ + $PYTHON_EXE -m pytest -v \ -W ignore::Warning ${{matrix.category}} \ pyomo `pwd`/pyomo-model-libraries \ `pwd`/examples `pwd`/doc --junitxml="TEST-pyomo.xml" diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 4a99d25c452..ac7691d32ae 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -648,7 +648,7 @@ jobs: - name: Run Pyomo tests if: matrix.mpi == 0 run: | - $PYTHON_EXE -m pytest -rs -v \ + $PYTHON_EXE -m pytest -v \ -W ignore::Warning ${{matrix.category}} \ pyomo `pwd`/pyomo-model-libraries \ `pwd`/examples `pwd`/doc --junitxml="TEST-pyomo.xml" From bc875c2f3ca11a48d0ac0388d7a4bae625e66e02 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 23 Jan 2024 15:02:07 -0700 Subject: [PATCH 0827/1204] Adding missing import to gdpopt plugins --- pyomo/contrib/gdpopt/plugins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/gdpopt/plugins.py b/pyomo/contrib/gdpopt/plugins.py index 3ebad88a626..9d729c63d9c 100644 --- a/pyomo/contrib/gdpopt/plugins.py +++ b/pyomo/contrib/gdpopt/plugins.py @@ -16,3 +16,4 @@ def load(): import pyomo.contrib.gdpopt.branch_and_bound import pyomo.contrib.gdpopt.loa import pyomo.contrib.gdpopt.ric + import pyomo.contrib.gdpopt.enumerate From 1b9ac7ec32f1d5967c802d9e7568af0f3b77b593 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 24 Jan 2024 13:28:03 -0700 Subject: [PATCH 0828/1204] NFC: Fixing typo in docstring --- pyomo/core/base/suffix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index 30d79d57b8c..084b0893809 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -115,7 +115,7 @@ class SuffixDirection(enum.IntEnum): - LOCAL: Suffix is local to Pyomo and should not be sent to or read from the solver. - - EXPORT: Suffix should be sent tot he solver as supplemental model + - EXPORT: Suffix should be sent to the solver as supplemental model information. - IMPORT: Suffix values will be returned from the solver and should From b6395abfbc6db5198e04f15c361c745f7441a5af Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 26 Jan 2024 08:27:12 -0700 Subject: [PATCH 0829/1204] new ipopt interface: account for parameters in objective when load_solution=False --- pyomo/contrib/solver/ipopt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 1a153422eb1..48c314ffc79 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -420,7 +420,7 @@ def solve(self, model, **kwds): if config.load_solution: results.incumbent_objective = value(nl_info.objectives[0]) else: - results.incumbent_objective = replace_expressions( + results.incumbent_objective = value(replace_expressions( nl_info.objectives[0].expr, substitution_map={ id(v): val @@ -428,7 +428,7 @@ def solve(self, model, **kwds): }, descend_into_named_expressions=True, remove_named_expressions=True, - ) + )) results.solver_configuration = config results.solver_log = ostreams[0].getvalue() From f126bc4520cb6edd76219256e4df87e88a372e6d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 26 Jan 2024 08:52:39 -0700 Subject: [PATCH 0830/1204] Apply black --- pyomo/contrib/solver/ipopt.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 48c314ffc79..534a9173d07 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -420,15 +420,17 @@ def solve(self, model, **kwds): if config.load_solution: results.incumbent_objective = value(nl_info.objectives[0]) else: - results.incumbent_objective = value(replace_expressions( - nl_info.objectives[0].expr, - substitution_map={ - id(v): val - for v, val in results.solution_loader.get_primals().items() - }, - descend_into_named_expressions=True, - remove_named_expressions=True, - )) + results.incumbent_objective = value( + replace_expressions( + nl_info.objectives[0].expr, + substitution_map={ + id(v): val + for v, val in results.solution_loader.get_primals().items() + }, + descend_into_named_expressions=True, + remove_named_expressions=True, + ) + ) results.solver_configuration = config results.solver_log = ostreams[0].getvalue() From d024718991455519e09149ede53a38df1c067abe Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 26 Jan 2024 09:28:09 -0700 Subject: [PATCH 0831/1204] Black 24.1.0 release --- pyomo/common/config.py | 8 +- .../community_detection/community_graph.py | 12 +- .../contrib/community_detection/detection.py | 39 ++-- pyomo/contrib/cp/interval_var.py | 3 +- pyomo/contrib/fbbt/fbbt.py | 66 +++---- pyomo/contrib/gdp_bounds/info.py | 1 + .../gdp_bounds/tests/test_gdp_bounds.py | 1 + pyomo/contrib/gdpopt/branch_and_bound.py | 12 +- pyomo/contrib/gdpopt/gloa.py | 7 +- pyomo/contrib/gdpopt/loa.py | 7 +- pyomo/contrib/gdpopt/ric.py | 7 +- .../incidence_analysis/dulmage_mendelsohn.py | 2 +- pyomo/contrib/interior_point/interface.py | 7 +- pyomo/contrib/latex_printer/latex_printer.py | 12 +- pyomo/contrib/mcpp/pyomo_mcpp.py | 1 - pyomo/contrib/mindtpy/algorithm_base_class.py | 25 ++- pyomo/contrib/mindtpy/single_tree.py | 7 +- pyomo/contrib/mindtpy/tests/nonconvex3.py | 4 +- .../tests/test_mindtpy_solution_pool.py | 1 + pyomo/contrib/mindtpy/util.py | 8 +- pyomo/contrib/multistart/high_conf_stop.py | 1 + pyomo/contrib/multistart/reinit.py | 1 + pyomo/contrib/parmest/parmest.py | 27 ++- pyomo/contrib/parmest/tests/test_parmest.py | 4 +- .../preprocessing/plugins/int_to_binary.py | 1 + .../tests/test_bounds_to_vars_xfrm.py | 1 + .../tests/test_constraint_tightener.py | 1 + .../tests/test_detect_fixed_vars.py | 1 + .../tests/test_equality_propagate.py | 1 + .../preprocessing/tests/test_init_vars.py | 1 + .../preprocessing/tests/test_strip_bounds.py | 1 + .../tests/test_var_aggregator.py | 1 + .../tests/test_zero_sum_propagate.py | 1 + .../tests/test_zero_term_removal.py | 1 + .../tests/external_grey_box_models.py | 3 +- .../tests/test_external_pyomo_block.py | 8 +- .../tests/test_external_pyomo_model.py | 8 +- .../pynumero/sparse/mpi_block_vector.py | 6 +- pyomo/contrib/pyros/master_problem_methods.py | 10 +- .../contrib/pyros/pyros_algorithm_methods.py | 9 +- .../pyros/separation_problem_methods.py | 1 + pyomo/contrib/pyros/tests/test_grcs.py | 6 +- pyomo/contrib/pyros/uncertainty_sets.py | 1 - pyomo/contrib/pyros/util.py | 1 + pyomo/contrib/viewer/residual_table.py | 10 +- pyomo/core/base/block.py | 3 +- pyomo/core/base/boolean_var.py | 9 +- pyomo/core/base/componentuid.py | 8 +- pyomo/core/base/constraint.py | 3 +- pyomo/core/base/enums.py | 1 - pyomo/core/base/expression.py | 3 +- pyomo/core/base/external.py | 22 +-- pyomo/core/base/objective.py | 3 +- pyomo/core/base/param.py | 3 +- pyomo/core/base/piecewise.py | 14 +- pyomo/core/base/set.py | 20 +-- pyomo/core/base/suffix.py | 3 +- pyomo/core/base/var.py | 3 +- pyomo/core/expr/numeric_expr.py | 18 +- pyomo/core/expr/template_expr.py | 8 +- pyomo/core/expr/visitor.py | 1 - .../plugins/transform/expand_connectors.py | 22 ++- .../plugins/transform/logical_to_linear.py | 1 + .../plugins/transform/radix_linearization.py | 5 +- pyomo/core/tests/unit/kernel/test_block.py | 170 ++++++++++-------- pyomo/core/tests/unit/kernel/test_conic.py | 3 +- pyomo/core/tests/unit/test_units.py | 3 +- pyomo/dae/set_utils.py | 4 +- pyomo/dataportal/tests/test_dataportal.py | 20 +-- pyomo/duality/plugins.py | 13 +- pyomo/gdp/plugins/bigm.py | 7 +- pyomo/gdp/plugins/bound_pretransformation.py | 6 +- pyomo/gdp/plugins/cuttingplane.py | 10 +- pyomo/gdp/plugins/hull.py | 6 +- pyomo/gdp/plugins/multiple_bigm.py | 8 +- pyomo/gdp/tests/test_partition_disjuncts.py | 48 +++-- pyomo/neos/plugins/kestrel_plugin.py | 19 +- pyomo/opt/base/solvers.py | 16 +- pyomo/opt/plugins/sol.py | 6 +- pyomo/repn/plugins/ampl/ampl_.py | 6 +- pyomo/repn/plugins/baron_writer.py | 37 ++-- pyomo/repn/plugins/gams_writer.py | 8 +- pyomo/repn/plugins/nl_writer.py | 24 ++- pyomo/repn/tests/ampl/test_nlv2.py | 4 +- pyomo/solvers/plugins/solvers/GAMS.py | 12 +- pyomo/solvers/plugins/solvers/GUROBI.py | 12 +- .../solvers/plugins/solvers/direct_solver.py | 6 +- pyomo/solvers/plugins/solvers/mosek_direct.py | 26 +-- .../plugins/solvers/mosek_persistent.py | 20 +-- .../plugins/solvers/persistent_solver.py | 6 +- pyomo/solvers/tests/checks/test_GAMS.py | 24 +-- pyomo/solvers/tests/checks/test_cplex.py | 18 +- 92 files changed, 516 insertions(+), 512 deletions(-) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index 61e4f682a2a..e3466eb7686 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -1396,9 +1396,11 @@ def _item_body(self, indent, obj): None, [ 'dict' if isinstance(obj, ConfigDict) else obj.domain_name(), - 'optional' - if obj._default is None - else f'default={repr(obj._default)}', + ( + 'optional' + if obj._default is None + else f'default={repr(obj._default)}' + ), ], ) ) diff --git a/pyomo/contrib/community_detection/community_graph.py b/pyomo/contrib/community_detection/community_graph.py index d4aa7e0b973..f0a1f9149bd 100644 --- a/pyomo/contrib/community_detection/community_graph.py +++ b/pyomo/contrib/community_detection/community_graph.py @@ -116,9 +116,9 @@ def generate_model_graph( ] # Update constraint_variable_map - constraint_variable_map[ - numbered_constraint - ] = numbered_variables_in_constraint_equation + constraint_variable_map[numbered_constraint] = ( + numbered_variables_in_constraint_equation + ) # Create a list of all the edges that need to be created based on the variables in this constraint equation edges_between_nodes = [ @@ -145,9 +145,9 @@ def generate_model_graph( ] # Update constraint_variable_map - constraint_variable_map[ - numbered_objective - ] = numbered_variables_in_objective + constraint_variable_map[numbered_objective] = ( + numbered_variables_in_objective + ) # Create a list of all the edges that need to be created based on the variables in the objective function edges_between_nodes = [ diff --git a/pyomo/contrib/community_detection/detection.py b/pyomo/contrib/community_detection/detection.py index 5751f54e9c1..c5366394530 100644 --- a/pyomo/contrib/community_detection/detection.py +++ b/pyomo/contrib/community_detection/detection.py @@ -7,6 +7,7 @@ Original implementation developed by Rahul Joglekar in the Grossmann research group. """ + from logging import getLogger from pyomo.common.dependencies import attempt_import @@ -453,16 +454,14 @@ def visualize_model_graph( if type_of_graph != self.type_of_community_map: # Use the generate_model_graph function to create a NetworkX graph of the given model (along with # number_component_map and constraint_variable_map, which will be used to help with drawing the graph) - ( - model_graph, - number_component_map, - constraint_variable_map, - ) = generate_model_graph( - self.model, - type_of_graph=type_of_graph, - with_objective=self.with_objective, - weighted_graph=self.weighted_graph, - use_only_active_components=self.use_only_active_components, + (model_graph, number_component_map, constraint_variable_map) = ( + generate_model_graph( + self.model, + type_of_graph=type_of_graph, + with_objective=self.with_objective, + weighted_graph=self.weighted_graph, + use_only_active_components=self.use_only_active_components, + ) ) else: # This is the case where, as mentioned above, we can use the networkX graph that was made to create @@ -726,13 +725,10 @@ def generate_structured_model(self): variable_in_new_model = structured_model.find_component( new_variable ) - blocked_variable_map[ - variable_in_stored_constraint - ] = blocked_variable_map.get( - variable_in_stored_constraint, [] - ) + [ - variable_in_new_model - ] + blocked_variable_map[variable_in_stored_constraint] = ( + blocked_variable_map.get(variable_in_stored_constraint, []) + + [variable_in_new_model] + ) # Update replace_variables_in_expression_map accordingly replace_variables_in_expression_map[ @@ -802,11 +798,10 @@ def generate_structured_model(self): variable_in_new_model = structured_model.find_component( new_variable ) - blocked_variable_map[ - variable_in_objective - ] = blocked_variable_map.get(variable_in_objective, []) + [ - variable_in_new_model - ] + blocked_variable_map[variable_in_objective] = ( + blocked_variable_map.get(variable_in_objective, []) + + [variable_in_new_model] + ) # Update the dictionary that we will use to replace the variables replace_variables_in_expression_map[ diff --git a/pyomo/contrib/cp/interval_var.py b/pyomo/contrib/cp/interval_var.py index 911d9ba50ba..4e22c2b2d3d 100644 --- a/pyomo/contrib/cp/interval_var.py +++ b/pyomo/contrib/cp/interval_var.py @@ -161,8 +161,7 @@ def __init__( optional=False, name=None, doc=None - ): - ... + ): ... def __init__(self, *args, **kwargs): _start_arg = kwargs.pop('start', None) diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index db33c27dd96..bf42cbe7f33 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -919,38 +919,38 @@ def _prop_bnds_root_to_leaf_GeneralExpression(node, bnds_dict, feasibility_tol): _prop_bnds_root_to_leaf_map = dict() -_prop_bnds_root_to_leaf_map[ - numeric_expr.ProductExpression -] = _prop_bnds_root_to_leaf_ProductExpression -_prop_bnds_root_to_leaf_map[ - numeric_expr.DivisionExpression -] = _prop_bnds_root_to_leaf_DivisionExpression -_prop_bnds_root_to_leaf_map[ - numeric_expr.PowExpression -] = _prop_bnds_root_to_leaf_PowExpression -_prop_bnds_root_to_leaf_map[ - numeric_expr.SumExpression -] = _prop_bnds_root_to_leaf_SumExpression -_prop_bnds_root_to_leaf_map[ - numeric_expr.MonomialTermExpression -] = _prop_bnds_root_to_leaf_ProductExpression -_prop_bnds_root_to_leaf_map[ - numeric_expr.NegationExpression -] = _prop_bnds_root_to_leaf_NegationExpression -_prop_bnds_root_to_leaf_map[ - numeric_expr.UnaryFunctionExpression -] = _prop_bnds_root_to_leaf_UnaryFunctionExpression -_prop_bnds_root_to_leaf_map[ - numeric_expr.LinearExpression -] = _prop_bnds_root_to_leaf_SumExpression +_prop_bnds_root_to_leaf_map[numeric_expr.ProductExpression] = ( + _prop_bnds_root_to_leaf_ProductExpression +) +_prop_bnds_root_to_leaf_map[numeric_expr.DivisionExpression] = ( + _prop_bnds_root_to_leaf_DivisionExpression +) +_prop_bnds_root_to_leaf_map[numeric_expr.PowExpression] = ( + _prop_bnds_root_to_leaf_PowExpression +) +_prop_bnds_root_to_leaf_map[numeric_expr.SumExpression] = ( + _prop_bnds_root_to_leaf_SumExpression +) +_prop_bnds_root_to_leaf_map[numeric_expr.MonomialTermExpression] = ( + _prop_bnds_root_to_leaf_ProductExpression +) +_prop_bnds_root_to_leaf_map[numeric_expr.NegationExpression] = ( + _prop_bnds_root_to_leaf_NegationExpression +) +_prop_bnds_root_to_leaf_map[numeric_expr.UnaryFunctionExpression] = ( + _prop_bnds_root_to_leaf_UnaryFunctionExpression +) +_prop_bnds_root_to_leaf_map[numeric_expr.LinearExpression] = ( + _prop_bnds_root_to_leaf_SumExpression +) _prop_bnds_root_to_leaf_map[numeric_expr.AbsExpression] = _prop_bnds_root_to_leaf_abs -_prop_bnds_root_to_leaf_map[ - _GeneralExpressionData -] = _prop_bnds_root_to_leaf_GeneralExpression -_prop_bnds_root_to_leaf_map[ - ScalarExpression -] = _prop_bnds_root_to_leaf_GeneralExpression +_prop_bnds_root_to_leaf_map[_GeneralExpressionData] = ( + _prop_bnds_root_to_leaf_GeneralExpression +) +_prop_bnds_root_to_leaf_map[ScalarExpression] = ( + _prop_bnds_root_to_leaf_GeneralExpression +) def _check_and_reset_bounds(var, lb, ub): @@ -1033,9 +1033,9 @@ def _register_new_before_child_handler(visitor, child): _before_child_handlers = defaultdict(lambda: _register_new_before_child_handler) -_before_child_handlers[ - numeric_expr.ExternalFunctionExpression -] = _before_external_function +_before_child_handlers[numeric_expr.ExternalFunctionExpression] = ( + _before_external_function +) for _type in nonpyomo_leaf_types: _before_child_handlers[_type] = _before_constant diff --git a/pyomo/contrib/gdp_bounds/info.py b/pyomo/contrib/gdp_bounds/info.py index bad76e0f2f7..3ee87041d25 100644 --- a/pyomo/contrib/gdp_bounds/info.py +++ b/pyomo/contrib/gdp_bounds/info.py @@ -1,4 +1,5 @@ """Provides functions for retrieving disjunctive variable bound information stored on a model.""" + from pyomo.common.collections import ComponentMap from pyomo.core import value diff --git a/pyomo/contrib/gdp_bounds/tests/test_gdp_bounds.py b/pyomo/contrib/gdp_bounds/tests/test_gdp_bounds.py index a5f7780f043..e856ae247f3 100644 --- a/pyomo/contrib/gdp_bounds/tests/test_gdp_bounds.py +++ b/pyomo/contrib/gdp_bounds/tests/test_gdp_bounds.py @@ -1,4 +1,5 @@ """Tests explicit bound to variable bound transformation module.""" + import pyomo.common.unittest as unittest from pyomo.contrib.gdp_bounds.info import disjunctive_lb, disjunctive_ub from pyomo.environ import ( diff --git a/pyomo/contrib/gdpopt/branch_and_bound.py b/pyomo/contrib/gdpopt/branch_and_bound.py index f69a92efe16..26dc2b5f2eb 100644 --- a/pyomo/contrib/gdpopt/branch_and_bound.py +++ b/pyomo/contrib/gdpopt/branch_and_bound.py @@ -179,13 +179,13 @@ def _solve_gdp(self, model, config): # TODO might be worthwhile to log number of nonlinear # constraints in each disjunction for later branching # purposes - root_util_blk.disjunct_to_nonlinear_constraints[ - disjunct - ] = nonlinear_constraints_in_disjunct + root_util_blk.disjunct_to_nonlinear_constraints[disjunct] = ( + nonlinear_constraints_in_disjunct + ) - root_util_blk.disjunction_to_unfixed_disjuncts[ - disjunction - ] = unfixed_disjuncts + root_util_blk.disjunction_to_unfixed_disjuncts[disjunction] = ( + unfixed_disjuncts + ) pass # Add the BigM suffix if it does not already exist. Used later during diff --git a/pyomo/contrib/gdpopt/gloa.py b/pyomo/contrib/gdpopt/gloa.py index ba8ed2fe234..68bd692f967 100644 --- a/pyomo/contrib/gdpopt/gloa.py +++ b/pyomo/contrib/gdpopt/gloa.py @@ -89,10 +89,9 @@ def _solve_gdp(self, original_model, config): # constraints will be added by the transformation to a MIP, so these are # all we'll ever need. add_global_constraint_list(self.original_util_block) - ( - discrete_problem_util_block, - subproblem_util_block, - ) = _get_discrete_problem_and_subproblem(self, config) + (discrete_problem_util_block, subproblem_util_block) = ( + _get_discrete_problem_and_subproblem(self, config) + ) discrete = discrete_problem_util_block.parent_block() subproblem = subproblem_util_block.parent_block() discrete_obj = next( diff --git a/pyomo/contrib/gdpopt/loa.py b/pyomo/contrib/gdpopt/loa.py index 6a9889065bf..44c1f8609e8 100644 --- a/pyomo/contrib/gdpopt/loa.py +++ b/pyomo/contrib/gdpopt/loa.py @@ -99,10 +99,9 @@ def _solve_gdp(self, original_model, config): # We'll need these to get dual info after solving subproblems add_constraint_list(self.original_util_block) - ( - discrete_problem_util_block, - subproblem_util_block, - ) = _get_discrete_problem_and_subproblem(self, config) + (discrete_problem_util_block, subproblem_util_block) = ( + _get_discrete_problem_and_subproblem(self, config) + ) discrete = discrete_problem_util_block.parent_block() subproblem = subproblem_util_block.parent_block() diff --git a/pyomo/contrib/gdpopt/ric.py b/pyomo/contrib/gdpopt/ric.py index f3eb83b79a9..586a27362a1 100644 --- a/pyomo/contrib/gdpopt/ric.py +++ b/pyomo/contrib/gdpopt/ric.py @@ -62,10 +62,9 @@ def solve(self, model, **kwds): def _solve_gdp(self, original_model, config): logger = config.logger - ( - discrete_problem_util_block, - subproblem_util_block, - ) = _get_discrete_problem_and_subproblem(self, config) + (discrete_problem_util_block, subproblem_util_block) = ( + _get_discrete_problem_and_subproblem(self, config) + ) discrete_problem = discrete_problem_util_block.parent_block() subproblem = subproblem_util_block.parent_block() discrete_problem_obj = next( diff --git a/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py b/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py index 5a1b125c0ae..eb24b0559fc 100644 --- a/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py +++ b/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py @@ -160,7 +160,7 @@ def dulmage_mendelsohn(matrix_or_graph, top_nodes=None, matching=None): partition = ( row_partition, - tuple([n - M for n in subset] for subset in col_partition) + tuple([n - M for n in subset] for subset in col_partition), # Column nodes have values in [M, M+N-1]. Apply the offset # to get values corresponding to indices in user's matrix. ) diff --git a/pyomo/contrib/interior_point/interface.py b/pyomo/contrib/interior_point/interface.py index 38d91be5566..7d04f578238 100644 --- a/pyomo/contrib/interior_point/interface.py +++ b/pyomo/contrib/interior_point/interface.py @@ -258,10 +258,9 @@ def __init__(self, pyomo_model): # set the init_duals_primals_lb/ub from ipopt_zL_out, ipopt_zU_out if available # need to compress them as well and initialize the duals_primals_lb/ub - ( - self._init_duals_primals_lb, - self._init_duals_primals_ub, - ) = self._get_full_duals_primals_bounds() + (self._init_duals_primals_lb, self._init_duals_primals_ub) = ( + self._get_full_duals_primals_bounds() + ) self._init_duals_primals_lb[np.isneginf(self._nlp.primals_lb())] = 0 self._init_duals_primals_ub[np.isinf(self._nlp.primals_ub())] = 0 self._duals_primals_lb = self._init_duals_primals_lb.copy() diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index 63c8caddcd2..b84f9a420fc 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -1087,12 +1087,12 @@ def latex_printer( for ky, vl in setInfo.items(): ix = int(ky[3:]) - 1 setInfo[ky]['setObject'] = setMap_inverse[ky] # setList[ix] - setInfo[ky][ - 'setRegEx' - ] = r'__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__' % (ky) - setInfo[ky][ - 'sumSetRegEx' - ] = r'sum_{__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__}' % (ky) + setInfo[ky]['setRegEx'] = ( + r'__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__' % (ky) + ) + setInfo[ky]['sumSetRegEx'] = ( + r'sum_{__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__}' % (ky) + ) # setInfo[ky]['idxRegEx'] = r'__I_PLACEHOLDER_8675309_GROUP_[0-9*]_%s__'%(ky) if explicit_set_summation: diff --git a/pyomo/contrib/mcpp/pyomo_mcpp.py b/pyomo/contrib/mcpp/pyomo_mcpp.py index bfd4b80edc3..817b18bff7c 100644 --- a/pyomo/contrib/mcpp/pyomo_mcpp.py +++ b/pyomo/contrib/mcpp/pyomo_mcpp.py @@ -383,7 +383,6 @@ def finalizeResult(self, node_result): class McCormick(object): - """ This class takes the constructed expression from MCPP_Visitor and allows for MC methods to be performed on pyomo expressions. diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index c9169ab8c62..570e7c0a27d 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -2607,9 +2607,9 @@ def initialize_subsolvers(self): if config.mip_regularization_solver == 'gams': self.regularization_mip_opt.options['add_options'] = [] if config.regularization_mip_threads > 0: - self.regularization_mip_opt.options[ - 'threads' - ] = config.regularization_mip_threads + self.regularization_mip_opt.options['threads'] = ( + config.regularization_mip_threads + ) else: self.regularization_mip_opt.options['threads'] = config.threads @@ -2619,9 +2619,9 @@ def initialize_subsolvers(self): 'cplex_persistent', }: if config.solution_limit is not None: - self.regularization_mip_opt.options[ - 'mip_limits_solutions' - ] = config.solution_limit + self.regularization_mip_opt.options['mip_limits_solutions'] = ( + config.solution_limit + ) # We don't need to solve the regularization problem to optimality. # We will choose to perform aggressive node probing during presolve. self.regularization_mip_opt.options['mip_strategy_presolvenode'] = 3 @@ -2634,9 +2634,9 @@ def initialize_subsolvers(self): self.regularization_mip_opt.options['optimalitytarget'] = 3 elif config.mip_regularization_solver == 'gurobi': if config.solution_limit is not None: - self.regularization_mip_opt.options[ - 'SolutionLimit' - ] = config.solution_limit + self.regularization_mip_opt.options['SolutionLimit'] = ( + config.solution_limit + ) # Same reason as mip_strategy_presolvenode. self.regularization_mip_opt.options['Presolve'] = 2 @@ -2983,10 +2983,9 @@ def add_regularization(self): # The main problem might be unbounded, regularization is activated only when a valid bound is provided. if self.dual_bound != self.dual_bound_progress[0]: with time_code(self.timing, 'regularization main'): - ( - regularization_main_mip, - regularization_main_mip_results, - ) = self.solve_regularization_main() + (regularization_main_mip, regularization_main_mip_results) = ( + self.solve_regularization_main() + ) self.handle_regularization_main_tc( regularization_main_mip, regularization_main_mip_results ) diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index 9776920f434..5383624b6aa 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -629,10 +629,9 @@ def handle_lazy_subproblem_infeasible(self, fixed_nlp, mindtpy_solver, config, o dual_values = None config.logger.info('Solving feasibility problem') - ( - feas_subproblem, - feas_subproblem_results, - ) = mindtpy_solver.solve_feasibility_subproblem() + (feas_subproblem, feas_subproblem_results) = ( + mindtpy_solver.solve_feasibility_subproblem() + ) # In OA algorithm, OA cuts are generated based on the solution of the subproblem # We need to first copy the value of variables from the subproblem and then add cuts copy_var_list_values( diff --git a/pyomo/contrib/mindtpy/tests/nonconvex3.py b/pyomo/contrib/mindtpy/tests/nonconvex3.py index dbb88bb1fad..b08deb67b63 100644 --- a/pyomo/contrib/mindtpy/tests/nonconvex3.py +++ b/pyomo/contrib/mindtpy/tests/nonconvex3.py @@ -40,9 +40,7 @@ def __init__(self, *args, **kwargs): m.objective = Objective(expr=7 * m.x1 + 10 * m.x2, sense=minimize) - m.c1 = Constraint( - expr=(m.x1**1.2) * (m.x2**1.7) - 7 * m.x1 - 9 * m.x2 <= -24 - ) + m.c1 = Constraint(expr=(m.x1**1.2) * (m.x2**1.7) - 7 * m.x1 - 9 * m.x2 <= -24) m.c2 = Constraint(expr=-m.x1 - 2 * m.x2 <= 5) m.c3 = Constraint(expr=-3 * m.x1 + m.x2 <= 1) m.c4 = Constraint(expr=4 * m.x1 - 3 * m.x2 <= 11) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_solution_pool.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_solution_pool.py index e8ad85ad9bc..7a9898d3c7b 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_solution_pool.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_solution_pool.py @@ -1,4 +1,5 @@ """Tests for solution pool in the MindtPy solver.""" + from pyomo.core.expr.calculus.diff_with_sympy import differentiate_available import pyomo.common.unittest as unittest from pyomo.contrib.mindtpy.tests.eight_process_problem import EightProcessFlowsheet diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index e336715cc8f..cd2b31e5954 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -344,9 +344,11 @@ def generate_lag_objective_function( with time_code(timing, 'PyomoNLP'): nlp = pyomo_nlp.PyomoNLP(temp_model) lam = [ - -temp_model.dual[constr] - if abs(temp_model.dual[constr]) > config.zero_tolerance - else 0 + ( + -temp_model.dual[constr] + if abs(temp_model.dual[constr]) > config.zero_tolerance + else 0 + ) for constr in nlp.get_pyomo_constraints() ] nlp.set_duals(lam) diff --git a/pyomo/contrib/multistart/high_conf_stop.py b/pyomo/contrib/multistart/high_conf_stop.py index e18467c1741..f85daf633de 100644 --- a/pyomo/contrib/multistart/high_conf_stop.py +++ b/pyomo/contrib/multistart/high_conf_stop.py @@ -5,6 +5,7 @@ range, given some confidence. """ + from __future__ import division from collections import Counter diff --git a/pyomo/contrib/multistart/reinit.py b/pyomo/contrib/multistart/reinit.py index 214192df648..3904a7e343f 100644 --- a/pyomo/contrib/multistart/reinit.py +++ b/pyomo/contrib/multistart/reinit.py @@ -1,4 +1,5 @@ """Helper functions for variable reinitialization.""" + from __future__ import division import logging diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index cbdc9179f35..82bf893dd06 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -548,14 +548,13 @@ def _Q_opt( for ndname, Var, solval in ef_nonants(ef): ind_vars.append(Var) # calculate the reduced hessian - ( - solve_result, - inv_red_hes, - ) = inverse_reduced_hessian.inv_reduced_hessian_barrier( - self.ef_instance, - independent_variables=ind_vars, - solver_options=self.solver_options, - tee=self.tee, + (solve_result, inv_red_hes) = ( + inverse_reduced_hessian.inv_reduced_hessian_barrier( + self.ef_instance, + independent_variables=ind_vars, + solver_options=self.solver_options, + tee=self.tee, + ) ) if self.diagnostic_mode: @@ -745,14 +744,10 @@ def _Q_at_theta(self, thetavals, initialize_parmest_model=False): if self.diagnostic_mode: print(' Experiment = ', snum) print(' First solve with special diagnostics wrapper') - ( - status_obj, - solved, - iters, - time, - regu, - ) = utils.ipopt_solve_with_stats( - instance, optimizer, max_iter=500, max_cpu_time=120 + (status_obj, solved, iters, time, regu) = ( + utils.ipopt_solve_with_stats( + instance, optimizer, max_iter=500, max_cpu_time=120 + ) ) print( " status_obj, solved, iters, time, regularization_stat = ", diff --git a/pyomo/contrib/parmest/tests/test_parmest.py b/pyomo/contrib/parmest/tests/test_parmest.py index 2cc8ad36b0a..b5c1fe1bfac 100644 --- a/pyomo/contrib/parmest/tests/test_parmest.py +++ b/pyomo/contrib/parmest/tests/test_parmest.py @@ -411,9 +411,7 @@ def rooney_biegler_indexed_vars(data): model.theta = pyo.Var( model.var_names, initialize={"asymptote": 15, "rate_constant": 0.5} ) - model.theta[ - "asymptote" - ].fixed = ( + model.theta["asymptote"].fixed = ( True # parmest will unfix theta variables, even when they are indexed ) model.theta["rate_constant"].fixed = True diff --git a/pyomo/contrib/preprocessing/plugins/int_to_binary.py b/pyomo/contrib/preprocessing/plugins/int_to_binary.py index 8b264868ba5..55b9d26948f 100644 --- a/pyomo/contrib/preprocessing/plugins/int_to_binary.py +++ b/pyomo/contrib/preprocessing/plugins/int_to_binary.py @@ -1,4 +1,5 @@ """Transformation to reformulate integer variables into binary.""" + from __future__ import division from math import floor, log diff --git a/pyomo/contrib/preprocessing/tests/test_bounds_to_vars_xfrm.py b/pyomo/contrib/preprocessing/tests/test_bounds_to_vars_xfrm.py index 5770b23eb11..c2b8acd3e49 100644 --- a/pyomo/contrib/preprocessing/tests/test_bounds_to_vars_xfrm.py +++ b/pyomo/contrib/preprocessing/tests/test_bounds_to_vars_xfrm.py @@ -1,4 +1,5 @@ """Tests explicit bound to variable bound transformation module.""" + import pyomo.common.unittest as unittest from pyomo.environ import ( ConcreteModel, diff --git a/pyomo/contrib/preprocessing/tests/test_constraint_tightener.py b/pyomo/contrib/preprocessing/tests/test_constraint_tightener.py index aa7fa52d272..8f36bee15a1 100644 --- a/pyomo/contrib/preprocessing/tests/test_constraint_tightener.py +++ b/pyomo/contrib/preprocessing/tests/test_constraint_tightener.py @@ -1,4 +1,5 @@ """Tests the Bounds Tightening module.""" + import pyomo.common.unittest as unittest from pyomo.environ import ConcreteModel, Constraint, TransformationFactory, Var, value diff --git a/pyomo/contrib/preprocessing/tests/test_detect_fixed_vars.py b/pyomo/contrib/preprocessing/tests/test_detect_fixed_vars.py index d40206d621b..b3c72531f77 100644 --- a/pyomo/contrib/preprocessing/tests/test_detect_fixed_vars.py +++ b/pyomo/contrib/preprocessing/tests/test_detect_fixed_vars.py @@ -1,4 +1,5 @@ """Tests detection of fixed variables.""" + import pyomo.common.unittest as unittest from pyomo.environ import ConcreteModel, TransformationFactory, Var, value diff --git a/pyomo/contrib/preprocessing/tests/test_equality_propagate.py b/pyomo/contrib/preprocessing/tests/test_equality_propagate.py index 40e1d7eecb9..b77f5c5f3f5 100644 --- a/pyomo/contrib/preprocessing/tests/test_equality_propagate.py +++ b/pyomo/contrib/preprocessing/tests/test_equality_propagate.py @@ -1,4 +1,5 @@ """Tests the equality set propagation module.""" + import pyomo.common.unittest as unittest from pyomo.common.errors import InfeasibleConstraintException diff --git a/pyomo/contrib/preprocessing/tests/test_init_vars.py b/pyomo/contrib/preprocessing/tests/test_init_vars.py index f65773f7dbb..e52c9fd5cc8 100644 --- a/pyomo/contrib/preprocessing/tests/test_init_vars.py +++ b/pyomo/contrib/preprocessing/tests/test_init_vars.py @@ -1,4 +1,5 @@ """Tests initialization of uninitialized variables.""" + import pyomo.common.unittest as unittest from pyomo.environ import ConcreteModel, TransformationFactory, value, Var diff --git a/pyomo/contrib/preprocessing/tests/test_strip_bounds.py b/pyomo/contrib/preprocessing/tests/test_strip_bounds.py index deb1b6c8b37..a8526c613c4 100644 --- a/pyomo/contrib/preprocessing/tests/test_strip_bounds.py +++ b/pyomo/contrib/preprocessing/tests/test_strip_bounds.py @@ -1,4 +1,5 @@ """Tests stripping of variable bounds.""" + import pyomo.common.unittest as unittest from pyomo.environ import ( diff --git a/pyomo/contrib/preprocessing/tests/test_var_aggregator.py b/pyomo/contrib/preprocessing/tests/test_var_aggregator.py index d44f8abdeb2..1f2c06dd0d1 100644 --- a/pyomo/contrib/preprocessing/tests/test_var_aggregator.py +++ b/pyomo/contrib/preprocessing/tests/test_var_aggregator.py @@ -1,4 +1,5 @@ """Tests the variable aggregation module.""" + import pyomo.common.unittest as unittest from pyomo.common.collections import ComponentSet from pyomo.contrib.preprocessing.plugins.var_aggregator import ( diff --git a/pyomo/contrib/preprocessing/tests/test_zero_sum_propagate.py b/pyomo/contrib/preprocessing/tests/test_zero_sum_propagate.py index e5dc132628b..bec889c7635 100644 --- a/pyomo/contrib/preprocessing/tests/test_zero_sum_propagate.py +++ b/pyomo/contrib/preprocessing/tests/test_zero_sum_propagate.py @@ -1,4 +1,5 @@ """Tests the zero sum propagation module.""" + import pyomo.common.unittest as unittest from pyomo.environ import ( ConcreteModel, diff --git a/pyomo/contrib/preprocessing/tests/test_zero_term_removal.py b/pyomo/contrib/preprocessing/tests/test_zero_term_removal.py index 7ff40b6ae32..d1b74822747 100644 --- a/pyomo/contrib/preprocessing/tests/test_zero_term_removal.py +++ b/pyomo/contrib/preprocessing/tests/test_zero_term_removal.py @@ -1,4 +1,5 @@ """Tests detection of zero terms.""" + import pyomo.common.unittest as unittest from pyomo.environ import ConcreteModel, Constraint, TransformationFactory, Var import pyomo.core.expr as EXPR diff --git a/pyomo/contrib/pynumero/interfaces/tests/external_grey_box_models.py b/pyomo/contrib/pynumero/interfaces/tests/external_grey_box_models.py index 1f2a5169857..e65e9a7eb5c 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/external_grey_box_models.py +++ b/pyomo/contrib/pynumero/interfaces/tests/external_grey_box_models.py @@ -298,8 +298,7 @@ def evaluate_equality_constraints(self): P2 = self._input_values[3] Pout = self._input_values[4] return np.asarray( - [P2 - (Pin - 2 * c * F**2), Pout - (P2 - 2 * c * F**2)], - dtype=np.float64, + [P2 - (Pin - 2 * c * F**2), Pout - (P2 - 2 * c * F**2)], dtype=np.float64 ) def evaluate_jacobian_equality_constraints(self): diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_block.py b/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_block.py index 2d758e2e1a9..7e250b9194e 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_block.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_block.py @@ -68,12 +68,8 @@ def _make_external_model(): m.y_out = pyo.Var() m.c_out_1 = pyo.Constraint(expr=m.x_out - m.x == 0) m.c_out_2 = pyo.Constraint(expr=m.y_out - m.y == 0) - m.c_ex_1 = pyo.Constraint( - expr=m.x**3 - 2 * m.y == m.a**2 + m.b**3 - m.r**3 - 2 - ) - m.c_ex_2 = pyo.Constraint( - expr=m.x + m.y**3 == m.a**3 + 2 * m.b**2 + m.r**2 + 1 - ) + m.c_ex_1 = pyo.Constraint(expr=m.x**3 - 2 * m.y == m.a**2 + m.b**3 - m.r**3 - 2) + m.c_ex_2 = pyo.Constraint(expr=m.x + m.y**3 == m.a**3 + 2 * m.b**2 + m.r**2 + 1) return m diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_model.py b/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_model.py index f808decf26c..390d0b6fe63 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_model.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_model.py @@ -901,11 +901,9 @@ def test_full_space_lagrangian_hessians(self): # multipliers won't necessarily correspond). external_model.set_external_constraint_multipliers(lam) hlxx, hlxy, hlyy = external_model.get_full_space_lagrangian_hessians() - ( - pred_hlxx, - pred_hlxy, - pred_hlyy, - ) = model.calculate_full_space_lagrangian_hessians(lam, x) + (pred_hlxx, pred_hlxy, pred_hlyy) = ( + model.calculate_full_space_lagrangian_hessians(lam, x) + ) # TODO: Is comparing the array representation sufficient here? # Should I make sure I get the sparse representation I expect? diff --git a/pyomo/contrib/pynumero/sparse/mpi_block_vector.py b/pyomo/contrib/pynumero/sparse/mpi_block_vector.py index 5d89bbf5522..0f57f0eb41e 100644 --- a/pyomo/contrib/pynumero/sparse/mpi_block_vector.py +++ b/pyomo/contrib/pynumero/sparse/mpi_block_vector.py @@ -1112,9 +1112,9 @@ def make_local_copy(self): if ndx in block_indices: blk = self.get_block(ndx) if isinstance(blk, BlockVector): - local_data[ - offset : offset + self.get_block_size(ndx) - ] = blk.flatten() + local_data[offset : offset + self.get_block_size(ndx)] = ( + blk.flatten() + ) elif isinstance(blk, np.ndarray): local_data[offset : offset + self.get_block_size(ndx)] = blk else: diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index 5477fcc5048..e2ce74a493e 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -1,6 +1,7 @@ """ Functions for handling the construction and solving of the GRCS master problem via ROSolver """ + from pyomo.core.base import ( ConcreteModel, Block, @@ -758,12 +759,9 @@ def solver_call_master(model_data, config, solver, solve_data): solver_term_cond_dict[str(opt)] = str(results.solver.termination_condition) master_soln.termination_condition = results.solver.termination_condition master_soln.pyros_termination_condition = None - ( - try_backup, - _, - ) = ( - master_soln.master_subsolver_results - ) = process_termination_condition_master_problem(config=config, results=results) + (try_backup, _) = master_soln.master_subsolver_results = ( + process_termination_condition_master_problem(config=config, results=results) + ) master_soln.nominal_block = nlp_model.scenarios[0, 0] master_soln.results = results diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index 38f675e64a5..4ae033b9498 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -642,11 +642,10 @@ def ROSolver_iterative_solve(model_data, config): vals.append(dvar.value) dr_var_lists_original.append(vals) - ( - polishing_results, - polishing_successful, - ) = master_problem_methods.minimize_dr_vars( - model_data=master_data, config=config + (polishing_results, polishing_successful) = ( + master_problem_methods.minimize_dr_vars( + model_data=master_data, config=config + ) ) timing_data.total_dr_polish_time += get_time_from_solver(polishing_results) diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index 240291f5375..b9659f044f4 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -1,6 +1,7 @@ """ Functions for the construction and solving of the GRCS separation problem via ROsolver """ + from pyomo.core.base.constraint import Constraint, ConstraintList from pyomo.core.base.objective import Objective, maximize, value from pyomo.core.base import Var, Param diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 1690ba72f6f..8de1c2666b9 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -3766,9 +3766,9 @@ def test_solve_master(self): master_data.master_model.scenarios[0, 0].second_stage_objective = Expression( expr=master_data.master_model.scenarios[0, 0].x ) - master_data.master_model.scenarios[ - 0, 0 - ].util.dr_var_to_exponent_map = ComponentMap() + master_data.master_model.scenarios[0, 0].util.dr_var_to_exponent_map = ( + ComponentMap() + ) master_data.iteration = 0 master_data.timing = TimingData() diff --git a/pyomo/contrib/pyros/uncertainty_sets.py b/pyomo/contrib/pyros/uncertainty_sets.py index 54a268f204e..1b51e41fcaf 100644 --- a/pyomo/contrib/pyros/uncertainty_sets.py +++ b/pyomo/contrib/pyros/uncertainty_sets.py @@ -44,7 +44,6 @@ ``UncertaintySet`` object. """ - import abc import math import functools diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index 6aa6ed61936..e2986ae18c7 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -1,6 +1,7 @@ ''' Utility functions for the PyROS solver ''' + import copy from enum import Enum, auto from pyomo.common.collections import ComponentSet, ComponentMap diff --git a/pyomo/contrib/viewer/residual_table.py b/pyomo/contrib/viewer/residual_table.py index 46a86adbce6..73cf73847e5 100644 --- a/pyomo/contrib/viewer/residual_table.py +++ b/pyomo/contrib/viewer/residual_table.py @@ -102,10 +102,12 @@ def _inactive_to_back(c): self._items.sort( key=lambda o: ( o is None, - get_residual(self.ui_data, o) - if get_residual(self.ui_data, o) is not None - and not isinstance(get_residual(self.ui_data, o), str) - else _inactive_to_back(o), + ( + get_residual(self.ui_data, o) + if get_residual(self.ui_data, o) is not None + and not isinstance(get_residual(self.ui_data, o), str) + else _inactive_to_back(o) + ), ), reverse=True, ) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index d3950575435..89e872ebbe5 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -2056,8 +2056,7 @@ def __new__(cls, *args, **kwds): @overload def __init__( self, *indexes, rule=None, concrete=False, dense=True, name=None, doc=None - ): - ... + ): ... def __init__(self, *args, **kwargs): """Constructor""" diff --git a/pyomo/core/base/boolean_var.py b/pyomo/core/base/boolean_var.py index e2aebb4e466..1945045abdd 100644 --- a/pyomo/core/base/boolean_var.py +++ b/pyomo/core/base/boolean_var.py @@ -283,9 +283,11 @@ def associate_binary_var(self, binary_var): "with '%s') with '%s' is not allowed" % ( self.name, - self._associated_binary().name - if self._associated_binary is not None - else None, + ( + self._associated_binary().name + if self._associated_binary is not None + else None + ), binary_var.name if binary_var is not None else None, ) ) @@ -496,7 +498,6 @@ def _pprint(self): class ScalarBooleanVar(_GeneralBooleanVarData, BooleanVar): - """A single variable.""" def __init__(self, *args, **kwd): diff --git a/pyomo/core/base/componentuid.py b/pyomo/core/base/componentuid.py index 0ab57d1c253..89f7e5f8320 100644 --- a/pyomo/core/base/componentuid.py +++ b/pyomo/core/base/componentuid.py @@ -144,9 +144,11 @@ def __hash__(self): ( name, tuple( - (slice, x.start, x.stop, x.step) - if x.__class__ is slice - else x + ( + (slice, x.start, x.stop, x.step) + if x.__class__ is slice + else x + ) for x in idx ), ) diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index 53afa35c70c..e391b4a5605 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -746,8 +746,7 @@ def __new__(cls, *args, **kwds): return super(Constraint, cls).__new__(IndexedConstraint) @overload - def __init__(self, *indexes, expr=None, rule=None, name=None, doc=None): - ... + def __init__(self, *indexes, expr=None, rule=None, name=None, doc=None): ... def __init__(self, *args, **kwargs): _init = self._pop_from_kwargs('Constraint', kwargs, ('rule', 'expr'), None) diff --git a/pyomo/core/base/enums.py b/pyomo/core/base/enums.py index 972d6b09117..ddcc66fdc4e 100644 --- a/pyomo/core/base/enums.py +++ b/pyomo/core/base/enums.py @@ -33,7 +33,6 @@ class TraversalStrategy(enum.Enum, **strictEnum): class SortComponents(enum.Flag, **strictEnum): - """ This class is a convenient wrapper for specifying various sort ordering. We pass these objects to the "sort" argument to various diff --git a/pyomo/core/base/expression.py b/pyomo/core/base/expression.py index df9abf0a5a5..780bc17c8a3 100644 --- a/pyomo/core/base/expression.py +++ b/pyomo/core/base/expression.py @@ -293,8 +293,7 @@ def __new__(cls, *args, **kwds): @overload def __init__( self, *indexes, rule=None, expr=None, initialize=None, name=None, doc=None - ): - ... + ): ... def __init__(self, *args, **kwds): _init = self._pop_from_kwargs( diff --git a/pyomo/core/base/external.py b/pyomo/core/base/external.py index 8157ca4badb..93fb69e8cf7 100644 --- a/pyomo/core/base/external.py +++ b/pyomo/core/base/external.py @@ -81,12 +81,10 @@ def __new__(cls, *args, **kwargs): return super().__new__(AMPLExternalFunction) @overload - def __init__(self, function=None, gradient=None, hessian=None, *, fgh=None): - ... + def __init__(self, function=None, gradient=None, hessian=None, *, fgh=None): ... @overload - def __init__(self, *, library: str, function: str): - ... + def __init__(self, *, library: str, function: str): ... def __init__(self, *args, **kwargs): """Construct a reference to an external function. @@ -457,9 +455,11 @@ def _pprint(self): ('units', str(self._units)), ( 'arg_units', - [str(u) for u in self._arg_units] - if self._arg_units is not None - else None, + ( + [str(u) for u in self._arg_units] + if self._arg_units is not None + else None + ), ), ], (), @@ -609,9 +609,11 @@ def _pprint(self): ('units', str(self._units)), ( 'arg_units', - [str(u) for u in self._arg_units[:-1]] - if self._arg_units is not None - else None, + ( + [str(u) for u in self._arg_units[:-1]] + if self._arg_units is not None + else None + ), ), ], (), diff --git a/pyomo/core/base/objective.py b/pyomo/core/base/objective.py index 3c625d81c2d..7fb495f3e5b 100644 --- a/pyomo/core/base/objective.py +++ b/pyomo/core/base/objective.py @@ -266,8 +266,7 @@ def __new__(cls, *args, **kwds): @overload def __init__( self, *indexes, expr=None, rule=None, sense=minimize, name=None, doc=None - ): - ... + ): ... def __init__(self, *args, **kwargs): _sense = kwargs.pop('sense', minimize) diff --git a/pyomo/core/base/param.py b/pyomo/core/base/param.py index a6b893ec2c9..ea4290d880d 100644 --- a/pyomo/core/base/param.py +++ b/pyomo/core/base/param.py @@ -301,8 +301,7 @@ def __init__( units=None, name=None, doc=None, - ): - ... + ): ... def __init__(self, *args, **kwd): _init = self._pop_from_kwargs('Param', kwd, ('rule', 'initialize'), NOTSET) diff --git a/pyomo/core/base/piecewise.py b/pyomo/core/base/piecewise.py index 8ab6ce38ca5..0c949f87993 100644 --- a/pyomo/core/base/piecewise.py +++ b/pyomo/core/base/piecewise.py @@ -178,9 +178,11 @@ def _characterize_function(name, tol, f_rule, model, points, *index): # we have a step function step = True slopes = [ - (None) - if (points[i] == points[i - 1]) - else ((values[i] - values[i - 1]) / (points[i] - points[i - 1])) + ( + (None) + if (points[i] == points[i - 1]) + else ((values[i] - values[i - 1]) / (points[i] - points[i - 1])) + ) for i in range(1, len(points)) ] @@ -193,9 +195,9 @@ def _characterize_function(name, tol, f_rule, model, points, *index): # to send this warning through Pyomo if not all( itertools.starmap( - lambda x1, x2: (True) - if ((x1 is None) or (x2 is None)) - else (abs(x1 - x2) > tol), + lambda x1, x2: ( + (True) if ((x1 is None) or (x2 is None)) else (abs(x1 - x2) > tol) + ), zip(slopes, itertools.islice(slopes, 1, None)), ) ): diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index 6dfc3f07427..ba7fdd52446 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -2022,8 +2022,7 @@ def __init__( validate=None, name=None, doc=None, - ): - ... + ): ... def __init__(self, *args, **kwds): kwds.setdefault('ctype', Set) @@ -2238,9 +2237,11 @@ def _getitem_when_not_present(self, index): % ( self.name, ("[%s]" % (index,) if self.is_indexed() else ""), - _values - if _values.__class__ is type - else type(_values).__name__, + ( + _values + if _values.__class__ is type + else type(_values).__name__ + ), ) ) raise @@ -2860,8 +2861,7 @@ def __init__( validate=None, name=None, doc=None, - ): - ... + ): ... @overload def __init__( @@ -2877,8 +2877,7 @@ def __init__( validate=None, name=None, doc=None, - ): - ... + ): ... @overload def __init__( @@ -2891,8 +2890,7 @@ def __init__( validate=None, name=None, doc=None, - ): - ... + ): ... def __init__(self, *args, **kwds): # Finite was processed by __new__ diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index 46a87523001..19ffed0e6fd 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -181,8 +181,7 @@ def __init__( rule=None, name=None, doc=None - ): - ... + ): ... def __init__(self, **kwds): # Suffix type information diff --git a/pyomo/core/base/var.py b/pyomo/core/base/var.py index e7e9e4f8f2f..f54cea98a9e 100644 --- a/pyomo/core/base/var.py +++ b/pyomo/core/base/var.py @@ -688,8 +688,7 @@ def __init__( units=None, name=None, doc=None - ): - ... + ): ... def __init__(self, *args, **kwargs): # diff --git a/pyomo/core/expr/numeric_expr.py b/pyomo/core/expr/numeric_expr.py index a638903236f..d0609395f64 100644 --- a/pyomo/core/expr/numeric_expr.py +++ b/pyomo/core/expr/numeric_expr.py @@ -2355,9 +2355,9 @@ def _register_new_iadd_mutablenpvsum_handler(a, b): # Retrieve the appropriate handler, record it in the main # _iadd_mutablenpvsum_dispatcher dict (so this method is not called a second time for # these types) - _iadd_mutablenpvsum_dispatcher[ - b.__class__ - ] = handler = _iadd_mutablenpvsum_type_handler_mapping[types[0]] + _iadd_mutablenpvsum_dispatcher[b.__class__] = handler = ( + _iadd_mutablenpvsum_type_handler_mapping[types[0]] + ) # Call the appropriate handler return handler(a, b) @@ -2454,9 +2454,9 @@ def _register_new_iadd_mutablelinear_handler(a, b): # Retrieve the appropriate handler, record it in the main # _iadd_mutablelinear_dispatcher dict (so this method is not called a second time for # these types) - _iadd_mutablelinear_dispatcher[ - b.__class__ - ] = handler = _iadd_mutablelinear_type_handler_mapping[types[0]] + _iadd_mutablelinear_dispatcher[b.__class__] = handler = ( + _iadd_mutablelinear_type_handler_mapping[types[0]] + ) # Call the appropriate handler return handler(a, b) @@ -2555,9 +2555,9 @@ def _register_new_iadd_mutablesum_handler(a, b): # Retrieve the appropriate handler, record it in the main # _iadd_mutablesum_dispatcher dict (so this method is not called a # second time for these types) - _iadd_mutablesum_dispatcher[ - b.__class__ - ] = handler = _iadd_mutablesum_type_handler_mapping[types[0]] + _iadd_mutablesum_dispatcher[b.__class__] = handler = ( + _iadd_mutablesum_type_handler_mapping[types[0]] + ) # Call the appropriate handler return handler(a, b) diff --git a/pyomo/core/expr/template_expr.py b/pyomo/core/expr/template_expr.py index 21e26038771..fd6294f2289 100644 --- a/pyomo/core/expr/template_expr.py +++ b/pyomo/core/expr/template_expr.py @@ -120,9 +120,11 @@ def _resolve_template(self, args): def _apply_operation(self, result): args = tuple( - arg - if arg.__class__ in native_types or not arg.is_numeric_type() - else value(arg) + ( + arg + if arg.__class__ in native_types or not arg.is_numeric_type() + else value(arg) + ) for arg in result[1:] ) return result[0].__getitem__(tuple(result[1:])) diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index ca3a1c9e745..1d02146b1e5 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -681,7 +681,6 @@ def _nonrecursive_walker_loop(self, ptr): class SimpleExpressionVisitor(object): - """ Note: This class is a customization of the PyUtilib :class:`SimpleVisitor diff --git a/pyomo/core/plugins/transform/expand_connectors.py b/pyomo/core/plugins/transform/expand_connectors.py index bf1b517c1b0..8fe14318669 100644 --- a/pyomo/core/plugins/transform/expand_connectors.py +++ b/pyomo/core/plugins/transform/expand_connectors.py @@ -180,9 +180,11 @@ def _validate_and_expand_connector_set(self, connectors): # -3 if v is None else -2 if k in c.aggregators - else -1 - if not hasattr(v, 'is_indexed') or not v.is_indexed() - else len(v) + else ( + -1 + if not hasattr(v, 'is_indexed') or not v.is_indexed() + else len(v) + ) ) ref[k] = (v, _len, c) @@ -220,11 +222,15 @@ def _validate_and_expand_connector_set(self, connectors): _len = ( -3 if _v is None - else -2 - if k in c.aggregators - else -1 - if not hasattr(_v, 'is_indexed') or not _v.is_indexed() - else len(_v) + else ( + -2 + if k in c.aggregators + else ( + -1 + if not hasattr(_v, 'is_indexed') or not _v.is_indexed() + else len(_v) + ) + ) ) if (_len >= 0) ^ (v[1] >= 0): raise ValueError( diff --git a/pyomo/core/plugins/transform/logical_to_linear.py b/pyomo/core/plugins/transform/logical_to_linear.py index e6554e0ed38..f4107b8a32c 100644 --- a/pyomo/core/plugins/transform/logical_to_linear.py +++ b/pyomo/core/plugins/transform/logical_to_linear.py @@ -1,5 +1,6 @@ """Transformation from BooleanVar and LogicalConstraint to Binary and Constraints.""" + from pyomo.common.collections import ComponentMap from pyomo.common.errors import MouseTrap, DeveloperError from pyomo.common.modeling import unique_component_name diff --git a/pyomo/core/plugins/transform/radix_linearization.py b/pyomo/core/plugins/transform/radix_linearization.py index 0d77a342147..b7ff3375a76 100644 --- a/pyomo/core/plugins/transform/radix_linearization.py +++ b/pyomo/core/plugins/transform/radix_linearization.py @@ -237,10 +237,7 @@ def _discretize_bilinear(self, b, v, v_idx, u, u_idx): K = max(b.DISCRETIZATION) _dw = Var( - bounds=( - min(0, _lb * 2**-K, _ub * 2**-K), - max(0, _lb * 2**-K, _ub * 2**-K), - ) + bounds=(min(0, _lb * 2**-K, _ub * 2**-K), max(0, _lb * 2**-K, _ub * 2**-K)) ) b.add_component("dw%s_v%s" % (u_idx, v_idx), _dw) diff --git a/pyomo/core/tests/unit/kernel/test_block.py b/pyomo/core/tests/unit/kernel/test_block.py index 5d1ecc33f06..a22ed4fb4b5 100644 --- a/pyomo/core/tests/unit/kernel/test_block.py +++ b/pyomo/core/tests/unit/kernel/test_block.py @@ -1646,13 +1646,15 @@ def test_components_no_descend_active_True(self): ctype=IBlock, active=True, descend_into=False ) ), - sorted( - str(_b) - for _b in self._components_no_descend[obj][IBlock] - if _b.active - ) - if getattr(obj, 'active', True) - else [], + ( + sorted( + str(_b) + for _b in self._components_no_descend[obj][IBlock] + if _b.active + ) + if getattr(obj, 'active', True) + else [] + ), ) self.assertEqual( set( @@ -1661,13 +1663,15 @@ def test_components_no_descend_active_True(self): ctype=IBlock, active=True, descend_into=False ) ), - set( - id(_b) - for _b in self._components_no_descend[obj][IBlock] - if _b.active - ) - if getattr(obj, 'active', True) - else set(), + ( + set( + id(_b) + for _b in self._components_no_descend[obj][IBlock] + if _b.active + ) + if getattr(obj, 'active', True) + else set() + ), ) # test ctype=IVariable self.assertEqual( @@ -1677,9 +1681,13 @@ def test_components_no_descend_active_True(self): ctype=IVariable, active=True, descend_into=False ) ), - sorted(str(_v) for _v in self._components_no_descend[obj][IVariable]) - if getattr(obj, 'active', True) - else [], + ( + sorted( + str(_v) for _v in self._components_no_descend[obj][IVariable] + ) + if getattr(obj, 'active', True) + else [] + ), ) self.assertEqual( set( @@ -1688,34 +1696,40 @@ def test_components_no_descend_active_True(self): ctype=IVariable, active=True, descend_into=False ) ), - set(id(_v) for _v in self._components_no_descend[obj][IVariable]) - if getattr(obj, 'active', True) - else set(), + ( + set(id(_v) for _v in self._components_no_descend[obj][IVariable]) + if getattr(obj, 'active', True) + else set() + ), ) # test no ctype self.assertEqual( sorted( str(_c) for _c in obj.components(active=True, descend_into=False) ), - sorted( - str(_c) - for ctype in self._components_no_descend[obj] - for _c in self._components_no_descend[obj][ctype] - if getattr(_c, "active", True) - ) - if getattr(obj, 'active', True) - else [], + ( + sorted( + str(_c) + for ctype in self._components_no_descend[obj] + for _c in self._components_no_descend[obj][ctype] + if getattr(_c, "active", True) + ) + if getattr(obj, 'active', True) + else [] + ), ) self.assertEqual( set(id(_c) for _c in obj.components(active=True, descend_into=False)), - set( - id(_c) - for ctype in self._components_no_descend[obj] - for _c in self._components_no_descend[obj][ctype] - if getattr(_c, "active", True) - ) - if getattr(obj, 'active', True) - else set(), + ( + set( + id(_c) + for ctype in self._components_no_descend[obj] + for _c in self._components_no_descend[obj][ctype] + if getattr(_c, "active", True) + ) + if getattr(obj, 'active', True) + else set() + ), ) def test_components_active_None(self): @@ -1794,9 +1808,11 @@ def test_components_active_True(self): ctype=IBlock, active=True, descend_into=True ) ), - sorted(str(_b) for _b in self._components[obj][IBlock] if _b.active) - if getattr(obj, 'active', True) - else [], + ( + sorted(str(_b) for _b in self._components[obj][IBlock] if _b.active) + if getattr(obj, 'active', True) + else [] + ), ) self.assertEqual( set( @@ -1805,9 +1821,11 @@ def test_components_active_True(self): ctype=IBlock, active=True, descend_into=True ) ), - set(id(_b) for _b in self._components[obj][IBlock] if _b.active) - if getattr(obj, 'active', True) - else set(), + ( + set(id(_b) for _b in self._components[obj][IBlock] if _b.active) + if getattr(obj, 'active', True) + else set() + ), ) # test ctype=IVariable self.assertEqual( @@ -1817,13 +1835,15 @@ def test_components_active_True(self): ctype=IVariable, active=True, descend_into=True ) ), - sorted( - str(_v) - for _v in self._components[obj][IVariable] - if _active_path_to_object_exists(obj, _v) - ) - if getattr(obj, 'active', True) - else [], + ( + sorted( + str(_v) + for _v in self._components[obj][IVariable] + if _active_path_to_object_exists(obj, _v) + ) + if getattr(obj, 'active', True) + else [] + ), ) self.assertEqual( set( @@ -1832,38 +1852,44 @@ def test_components_active_True(self): ctype=IVariable, active=True, descend_into=True ) ), - set( - id(_v) - for _v in self._components[obj][IVariable] - if _active_path_to_object_exists(obj, _v) - ) - if getattr(obj, 'active', True) - else set(), + ( + set( + id(_v) + for _v in self._components[obj][IVariable] + if _active_path_to_object_exists(obj, _v) + ) + if getattr(obj, 'active', True) + else set() + ), ) # test no ctype self.assertEqual( sorted( str(_c) for _c in obj.components(active=True, descend_into=True) ), - sorted( - str(_c) - for ctype in self._components[obj] - for _c in self._components[obj][ctype] - if _active_path_to_object_exists(obj, _c) - ) - if getattr(obj, 'active', True) - else [], + ( + sorted( + str(_c) + for ctype in self._components[obj] + for _c in self._components[obj][ctype] + if _active_path_to_object_exists(obj, _c) + ) + if getattr(obj, 'active', True) + else [] + ), ) self.assertEqual( set(id(_c) for _c in obj.components(active=True, descend_into=True)), - set( - id(_c) - for ctype in self._components[obj] - for _c in self._components[obj][ctype] - if _active_path_to_object_exists(obj, _c) - ) - if getattr(obj, 'active', True) - else set(), + ( + set( + id(_c) + for ctype in self._components[obj] + for _c in self._components[obj][ctype] + if _active_path_to_object_exists(obj, _c) + ) + if getattr(obj, 'active', True) + else set() + ), ) diff --git a/pyomo/core/tests/unit/kernel/test_conic.py b/pyomo/core/tests/unit/kernel/test_conic.py index e7416210b8a..352976a2410 100644 --- a/pyomo/core/tests/unit/kernel/test_conic.py +++ b/pyomo/core/tests/unit/kernel/test_conic.py @@ -700,8 +700,7 @@ def test_expression(self): c.x[0].value = 1.2 c.x[1].value = -5.3 val = round( - (1.2**2 + (-5.3) ** 2) ** 0.5 - - ((2.7 / 0.4) ** 0.4) * ((3.7 / 0.6) ** 0.6), + (1.2**2 + (-5.3) ** 2) ** 0.5 - ((2.7 / 0.4) ** 0.4) * ((3.7 / 0.6) ** 0.6), 9, ) self.assertEqual(round(c(), 9), val) diff --git a/pyomo/core/tests/unit/test_units.py b/pyomo/core/tests/unit/test_units.py index 8ec83fe1a73..809db733cde 100644 --- a/pyomo/core/tests/unit/test_units.py +++ b/pyomo/core/tests/unit/test_units.py @@ -920,8 +920,7 @@ def test_module_example(self): model = ConcreteModel() model.acc = Var() model.obj = Objective( - expr=(model.acc * units.m / units.s**2 - 9.81 * units.m / units.s**2) - ** 2 + expr=(model.acc * units.m / units.s**2 - 9.81 * units.m / units.s**2) ** 2 ) self.assertEqual('m**2/s**4', str(units.get_units(model.obj.expr))) diff --git a/pyomo/dae/set_utils.py b/pyomo/dae/set_utils.py index 96a7489261e..981954189b3 100644 --- a/pyomo/dae/set_utils.py +++ b/pyomo/dae/set_utils.py @@ -164,8 +164,8 @@ def get_indices_of_projection(index_set, *sets): info['set_except'] = [None] # index_getter returns an index corresponding to the values passed to # it, re-ordered according to order of indexing sets in component. - info['index_getter'] = ( - lambda incomplete_index, *newvals: newvals[0] + info['index_getter'] = lambda incomplete_index, *newvals: ( + newvals[0] if len(newvals) <= 1 else tuple([newvals[location[i]] for i in location]) ) diff --git a/pyomo/dataportal/tests/test_dataportal.py b/pyomo/dataportal/tests/test_dataportal.py index db9423abff6..3171a118118 100644 --- a/pyomo/dataportal/tests/test_dataportal.py +++ b/pyomo/dataportal/tests/test_dataportal.py @@ -772,22 +772,22 @@ def test_data_namespace(self): self.assertEqual( sorted( md.values(), - key=lambda x: tuple(sorted(x) + [0]) - if type(x) is list - else tuple(sorted(x.values())) - if not type(x) is int - else (x,), + key=lambda x: ( + tuple(sorted(x) + [0]) + if type(x) is list + else tuple(sorted(x.values())) if not type(x) is int else (x,) + ), ), [-4, -3, -2, -1, [1, 3, 5], {1: 10, 3: 30, 5: 50}], ) self.assertEqual( sorted( md.values('ns1'), - key=lambda x: tuple(sorted(x) + [0]) - if type(x) is list - else tuple(sorted(x.values())) - if not type(x) is int - else (x,), + key=lambda x: ( + tuple(sorted(x) + [0]) + if type(x) is list + else tuple(sorted(x.values())) if not type(x) is int else (x,) + ), ), [1, [7, 9, 11], {7: 70, 9: 90, 11: 110}], ) diff --git a/pyomo/duality/plugins.py b/pyomo/duality/plugins.py index 9a8e10b4cfc..c8c84153975 100644 --- a/pyomo/duality/plugins.py +++ b/pyomo/duality/plugins.py @@ -87,16 +87,9 @@ def _dualize(self, block, unfixed=[]): # # Collect linear terms from the block # - ( - A, - b_coef, - c_rhs, - c_sense, - d_sense, - vnames, - cnames, - v_domain, - ) = collect_linear_terms(block, unfixed) + (A, b_coef, c_rhs, c_sense, d_sense, vnames, cnames, v_domain) = ( + collect_linear_terms(block, unfixed) + ) ##print(A) ##print(vnames) ##print(cnames) diff --git a/pyomo/gdp/plugins/bigm.py b/pyomo/gdp/plugins/bigm.py index b960b5087ea..e554d5593ab 100644 --- a/pyomo/gdp/plugins/bigm.py +++ b/pyomo/gdp/plugins/bigm.py @@ -409,10 +409,9 @@ def _update_M_from_suffixes(self, constraint, suffix_list, lower, upper): ) def get_m_value_src(self, constraint): transBlock = _get_constraint_transBlock(constraint) - ( - (lower_val, lower_source, lower_key), - (upper_val, upper_source, upper_key), - ) = transBlock.bigm_src[constraint] + ((lower_val, lower_source, lower_key), (upper_val, upper_source, upper_key)) = ( + transBlock.bigm_src[constraint] + ) if ( constraint.lower is not None diff --git a/pyomo/gdp/plugins/bound_pretransformation.py b/pyomo/gdp/plugins/bound_pretransformation.py index 0d6b14a4b80..56a39115f34 100644 --- a/pyomo/gdp/plugins/bound_pretransformation.py +++ b/pyomo/gdp/plugins/bound_pretransformation.py @@ -244,9 +244,9 @@ def _create_transformation_constraints( disjunction, transformation_blocks ) if self.transformation_name not in disjunction._transformation_map: - disjunction._transformation_map[ - self.transformation_name - ] = ComponentMap() + disjunction._transformation_map[self.transformation_name] = ( + ComponentMap() + ) trans_map = disjunction._transformation_map[self.transformation_name] for disj in disjunction.disjuncts: diff --git a/pyomo/gdp/plugins/cuttingplane.py b/pyomo/gdp/plugins/cuttingplane.py index 49d984a0712..fcb0e8886f1 100644 --- a/pyomo/gdp/plugins/cuttingplane.py +++ b/pyomo/gdp/plugins/cuttingplane.py @@ -808,13 +808,9 @@ def _apply_to(self, instance, bigM=None, **kwds): else: self.verbose = False - ( - instance_rBigM, - cuts_obj, - instance_rHull, - var_info, - transBlockName, - ) = self._setup_subproblems(instance, bigM, self._config.tighten_relaxation) + (instance_rBigM, cuts_obj, instance_rHull, var_info, transBlockName) = ( + self._setup_subproblems(instance, bigM, self._config.tighten_relaxation) + ) self._generate_cuttingplanes( instance_rBigM, cuts_obj, instance_rHull, var_info, transBlockName diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index b8e2b3e3699..a600ef76bc7 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -470,9 +470,9 @@ def _transform_disjunctionData( and disj not in disjunctsVarAppearsIn[var] ): relaxationBlock = disj._transformation_block().parent_block() - relaxationBlock._bigMConstraintMap[ - disaggregated_var - ] = Reference(disaggregated_var_bounds[idx, :]) + relaxationBlock._bigMConstraintMap[disaggregated_var] = ( + Reference(disaggregated_var_bounds[idx, :]) + ) relaxationBlock._disaggregatedVarMap['srcVar'][ disaggregated_var ] = var diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index 18f159c7ca2..85fb1e4aa6b 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -310,10 +310,10 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, root_disjunct) Ms = arg_Ms if not self._config.only_mbigm_bound_constraints: - Ms = ( - transBlock.calculated_missing_m_values - ) = self._calculate_missing_M_values( - active_disjuncts, arg_Ms, transBlock, transformed_constraints + Ms = transBlock.calculated_missing_m_values = ( + self._calculate_missing_M_values( + active_disjuncts, arg_Ms, transBlock, transformed_constraints + ) ) # Now we can deactivate the constraints we deferred, so that we don't diff --git a/pyomo/gdp/tests/test_partition_disjuncts.py b/pyomo/gdp/tests/test_partition_disjuncts.py index 56faaa9b8f5..b050bc5e653 100644 --- a/pyomo/gdp/tests/test_partition_disjuncts.py +++ b/pyomo/gdp/tests/test_partition_disjuncts.py @@ -227,14 +227,18 @@ def check_transformation_block( aux22ub, partitions, ): - ( - b, - disj1, - disj2, - aux_vars1, - aux_vars2, - ) = self.check_transformation_block_structure( - m, aux11lb, aux11ub, aux12lb, aux12ub, aux21lb, aux21ub, aux22lb, aux22ub + (b, disj1, disj2, aux_vars1, aux_vars2) = ( + self.check_transformation_block_structure( + m, + aux11lb, + aux11ub, + aux12lb, + aux12ub, + aux21lb, + aux21ub, + aux22lb, + aux22ub, + ) ) self.check_disjunct_constraints(disj1, disj2, aux_vars1, aux_vars2) @@ -351,14 +355,12 @@ def check_transformation_block_nested_disjunction( else: block_prefix = disjunction_block + "." disjunction_parent = m.component(disjunction_block) - ( - inner_b, - inner_disj1, - inner_disj2, - ) = self.check_transformation_block_disjuncts_and_constraints( - disj2, - disjunction_parent.disj2.disjunction, - "%sdisj2.disjunction" % block_prefix, + (inner_b, inner_disj1, inner_disj2) = ( + self.check_transformation_block_disjuncts_and_constraints( + disj2, + disjunction_parent.disj2.disjunction, + "%sdisj2.disjunction" % block_prefix, + ) ) # Has it's own indicator var, the aux vars, and the Reference to the @@ -753,13 +755,9 @@ def test_assume_fixed_vars_permanent(self): # This actually changes the structure of the model because fixed vars # move to the constants. I think this is fair, and we should allow it # because it will allow for a tighter relaxation. - ( - b, - disj1, - disj2, - aux_vars1, - aux_vars2, - ) = self.check_transformation_block_structure(m, 0, 36, 0, 72, -9, 16, -18, 32) + (b, disj1, disj2, aux_vars1, aux_vars2) = ( + self.check_transformation_block_structure(m, 0, 36, 0, 72, -9, 16, -18, 32) + ) # check disjunct constraints self.check_disjunct_constraints(disj1, disj2, aux_vars1, aux_vars2) @@ -1702,9 +1700,7 @@ def test_transformation_block_fbbt_bounds(self): compute_bounds_method=compute_fbbt_bounds, ) - self.check_transformation_block( - m, 0, (2 * 6**4) ** 0.25, 0, (2 * 5**4) ** 0.25 - ) + self.check_transformation_block(m, 0, (2 * 6**4) ** 0.25, 0, (2 * 5**4) ** 0.25) def test_invalid_partition_error(self): m = models.makeNonQuadraticNonlinearGDP() diff --git a/pyomo/neos/plugins/kestrel_plugin.py b/pyomo/neos/plugins/kestrel_plugin.py index 72d73d15ace..49fb3809622 100644 --- a/pyomo/neos/plugins/kestrel_plugin.py +++ b/pyomo/neos/plugins/kestrel_plugin.py @@ -193,13 +193,9 @@ def _perform_wait_any(self): del self._ah[jobNumber] ah.status = ActionStatus.done - ( - opt, - smap_id, - load_solutions, - select_index, - default_variable_value, - ) = self._opt_data[jobNumber] + (opt, smap_id, load_solutions, select_index, default_variable_value) = ( + self._opt_data[jobNumber] + ) del self._opt_data[jobNumber] args = self._args[jobNumber] @@ -262,11 +258,10 @@ def _perform_wait_any(self): # minutes. If NEOS doesn't produce intermediate results # by then we will need to catch (and eat) the exception try: - ( - message_fragment, - new_offset, - ) = self.kestrel.neos.getIntermediateResults( - jobNumber, self._ah[jobNumber].password, current_offset + (message_fragment, new_offset) = ( + self.kestrel.neos.getIntermediateResults( + jobNumber, self._ah[jobNumber].password, current_offset + ) ) logger.info(message_fragment) self._neos_log[jobNumber] = ( diff --git a/pyomo/opt/base/solvers.py b/pyomo/opt/base/solvers.py index 0de60902af2..b11e6393b02 100644 --- a/pyomo/opt/base/solvers.py +++ b/pyomo/opt/base/solvers.py @@ -641,9 +641,9 @@ def solve(self, *args, **kwds): result.solution(0).symbol_map = getattr( _model, "._symbol_maps" )[result._smap_id] - result.solution( - 0 - ).default_variable_value = self._default_variable_value + result.solution(0).default_variable_value = ( + self._default_variable_value + ) if self._load_solutions: _model.load_solution(result.solution(0)) else: @@ -699,12 +699,10 @@ def _presolve(self, *args, **kwds): if self._problem_format: write_start_time = time.time() - ( - self._problem_files, - self._problem_format, - self._smap_id, - ) = self._convert_problem( - args, self._problem_format, self._valid_problem_formats, **kwds + (self._problem_files, self._problem_format, self._smap_id) = ( + self._convert_problem( + args, self._problem_format, self._valid_problem_formats, **kwds + ) ) total_time = time.time() - write_start_time if self._report_timing: diff --git a/pyomo/opt/plugins/sol.py b/pyomo/opt/plugins/sol.py index 6e1ca666633..297b1c87d06 100644 --- a/pyomo/opt/plugins/sol.py +++ b/pyomo/opt/plugins/sol.py @@ -241,9 +241,9 @@ def _load(self, fin, res, soln, suffixes): translated_suffix_name = ( suffix_name[0].upper() + suffix_name[1:] ) - soln_constraint[key][ - translated_suffix_name - ] = convert_function(suf_line[1]) + soln_constraint[key][translated_suffix_name] = ( + convert_function(suf_line[1]) + ) elif kind == 2: # Obj for cnt in range(nvalues): suf_line = fin.readline().split() diff --git a/pyomo/repn/plugins/ampl/ampl_.py b/pyomo/repn/plugins/ampl/ampl_.py index a2bd55cb73a..d1a11bf2f38 100644 --- a/pyomo/repn/plugins/ampl/ampl_.py +++ b/pyomo/repn/plugins/ampl/ampl_.py @@ -1964,9 +1964,9 @@ def _print_model_NL( for obj_ID, (obj, wrapped_repn) in Objectives_dict.items(): grad_entries = {} for idx, obj_var in enumerate(wrapped_repn.linear_vars): - grad_entries[ - self_ampl_var_id[obj_var] - ] = wrapped_repn.repn.linear_coefs[idx] + grad_entries[self_ampl_var_id[obj_var]] = ( + wrapped_repn.repn.linear_coefs[idx] + ) for obj_var in wrapped_repn.nonlinear_vars: if obj_var not in wrapped_repn.linear_vars: grad_entries[self_ampl_var_id[obj_var]] = 0 diff --git a/pyomo/repn/plugins/baron_writer.py b/pyomo/repn/plugins/baron_writer.py index 4242ae7431c..0d684fcd1d2 100644 --- a/pyomo/repn/plugins/baron_writer.py +++ b/pyomo/repn/plugins/baron_writer.py @@ -176,12 +176,14 @@ def _monomial_to_string(self, node): def _linear_to_string(self, node): values = [ - self._monomial_to_string(arg) - if ( - arg.__class__ is EXPR.MonomialTermExpression - and not arg.arg(1).is_fixed() + ( + self._monomial_to_string(arg) + if ( + arg.__class__ is EXPR.MonomialTermExpression + and not arg.arg(1).is_fixed() + ) + else ftoa(value(arg)) ) - else ftoa(value(arg)) for arg in node.args ] return node._to_string(values, False, self.smap) @@ -644,19 +646,18 @@ def _write_bar_file(self, model, output_file, solver_capability, io_options): # variables. # equation_section_stream = StringIO() - ( - referenced_variable_ids, - branching_priorities_suffixes, - ) = self._write_equations_section( - model, - equation_section_stream, - all_blocks_list, - active_components_data_var, - symbol_map, - c_labeler, - output_fixed_variable_bounds, - skip_trivial_constraints, - sorter, + (referenced_variable_ids, branching_priorities_suffixes) = ( + self._write_equations_section( + model, + equation_section_stream, + all_blocks_list, + active_components_data_var, + symbol_map, + c_labeler, + output_fixed_variable_bounds, + skip_trivial_constraints, + sorter, + ) ) # diff --git a/pyomo/repn/plugins/gams_writer.py b/pyomo/repn/plugins/gams_writer.py index de0e4684fc4..719839fc8dd 100644 --- a/pyomo/repn/plugins/gams_writer.py +++ b/pyomo/repn/plugins/gams_writer.py @@ -180,9 +180,11 @@ def _monomial_to_string(self, node): def _linear_to_string(self, node): values = [ - self._monomial_to_string(arg) - if arg.__class__ is EXPR.MonomialTermExpression - else ftoa(arg, True) + ( + self._monomial_to_string(arg) + if arg.__class__ is EXPR.MonomialTermExpression + else ftoa(arg, True) + ) for arg in node.args ] return node._to_string(values, False, self.smap) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 1081b69acff..2d5eae151b0 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1451,9 +1451,11 @@ def write(self, model): ostream.write( 'r%s\n' % ( - "\t#%d ranges (rhs's)" % len(constraints) - if symbolic_solver_labels - else '', + ( + "\t#%d ranges (rhs's)" % len(constraints) + if symbolic_solver_labels + else '' + ), ) ) ostream.write("\n".join(r_lines)) @@ -1466,9 +1468,11 @@ def write(self, model): ostream.write( 'b%s\n' % ( - "\t#%d bounds (on variables)" % len(variables) - if symbolic_solver_labels - else '', + ( + "\t#%d bounds (on variables)" % len(variables) + if symbolic_solver_labels + else '' + ), ) ) for var_idx, _id in enumerate(variables): @@ -1492,9 +1496,11 @@ def write(self, model): 'k%d%s\n' % ( len(variables) - 1, - "\t#intermediate Jacobian column lengths" - if symbolic_solver_labels - else '', + ( + "\t#intermediate Jacobian column lengths" + if symbolic_solver_labels + else '' + ), ) ) ktot = 0 diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 2e366039185..6422a2b0020 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1123,9 +1123,7 @@ def test_linear_constraint_npv_const(self): m.x = Var([1, 2]) m.p = Param(initialize=5, mutable=True) m.o = Objective(expr=1) - m.c = Constraint( - expr=LinearExpression([m.p**2, 5 * m.x[1], 10 * m.x[2]]) <= 0 - ) + m.c = Constraint(expr=LinearExpression([m.p**2, 5 * m.x[1], 10 * m.x[2]]) <= 0) OUT = io.StringIO() nl_writer.NLWriter().write(m, OUT) diff --git a/pyomo/solvers/plugins/solvers/GAMS.py b/pyomo/solvers/plugins/solvers/GAMS.py index 16a126b9af4..d0365d49078 100644 --- a/pyomo/solvers/plugins/solvers/GAMS.py +++ b/pyomo/solvers/plugins/solvers/GAMS.py @@ -605,9 +605,9 @@ def solve(self, *args, **kwds): results.solution(0).symbol_map = getattr(model, "._symbol_maps")[ results._smap_id ] - results.solution( - 0 - ).default_variable_value = self._default_variable_value + results.solution(0).default_variable_value = ( + self._default_variable_value + ) if load_solutions: model.load_solution(results.solution(0)) else: @@ -1187,9 +1187,9 @@ def solve(self, *args, **kwds): results.solution(0).symbol_map = getattr(model, "._symbol_maps")[ results._smap_id ] - results.solution( - 0 - ).default_variable_value = self._default_variable_value + results.solution(0).default_variable_value = ( + self._default_variable_value + ) if load_solutions: model.load_solution(results.solution(0)) else: diff --git a/pyomo/solvers/plugins/solvers/GUROBI.py b/pyomo/solvers/plugins/solvers/GUROBI.py index 3c7c227b9e7..e0eddf008af 100644 --- a/pyomo/solvers/plugins/solvers/GUROBI.py +++ b/pyomo/solvers/plugins/solvers/GUROBI.py @@ -480,9 +480,9 @@ def process_soln_file(self, results): name = tokens[1] if name != "c_e_ONE_VAR_CONSTANT": if name.startswith('c_'): - soln_constraints.setdefault(tokens[1], {})[ - "Dual" - ] = float(tokens[2]) + soln_constraints.setdefault(tokens[1], {})["Dual"] = ( + float(tokens[2]) + ) elif name.startswith('r_l_'): range_duals.setdefault(name[4:], [0, 0])[0] = float( tokens[2] @@ -495,9 +495,9 @@ def process_soln_file(self, results): name = tokens[1] if name != "c_e_ONE_VAR_CONSTANT": if name.startswith('c_'): - soln_constraints.setdefault(tokens[1], {})[ - "Slack" - ] = float(tokens[2]) + soln_constraints.setdefault(tokens[1], {})["Slack"] = ( + float(tokens[2]) + ) elif name.startswith('r_l_'): range_slacks.setdefault(name[4:], [0, 0])[0] = float( tokens[2] diff --git a/pyomo/solvers/plugins/solvers/direct_solver.py b/pyomo/solvers/plugins/solvers/direct_solver.py index a99eec79fd9..4f90a753fe6 100644 --- a/pyomo/solvers/plugins/solvers/direct_solver.py +++ b/pyomo/solvers/plugins/solvers/direct_solver.py @@ -178,9 +178,9 @@ def solve(self, *args, **kwds): result.solution(0).symbol_map = getattr( _model, "._symbol_maps" )[result._smap_id] - result.solution( - 0 - ).default_variable_value = self._default_variable_value + result.solution(0).default_variable_value = ( + self._default_variable_value + ) if self._load_solutions: _model.load_solution(result.solution(0)) else: diff --git a/pyomo/solvers/plugins/solvers/mosek_direct.py b/pyomo/solvers/plugins/solvers/mosek_direct.py index 6a21e0fcb9b..4c0718bfe74 100644 --- a/pyomo/solvers/plugins/solvers/mosek_direct.py +++ b/pyomo/solvers/plugins/solvers/mosek_direct.py @@ -305,10 +305,12 @@ def _get_expr_from_pyomo_repn(self, repn, max_degree=2): referenced_vars.update(q_vars) qsubi, qsubj = zip( *[ - (i, j) - if self._pyomo_var_to_solver_var_map[i] - >= self._pyomo_var_to_solver_var_map[j] - else (j, i) + ( + (i, j) + if self._pyomo_var_to_solver_var_map[i] + >= self._pyomo_var_to_solver_var_map[j] + else (j, i) + ) for i, j in repn.quadratic_vars ] ) @@ -465,15 +467,19 @@ def _add_constraints(self, con_seq): q_is, q_js, q_vals = zip(*qexp) l_ids, l_coefs, constants = zip(*arow) lbs = tuple( - -inf - if value(lq_all[i].lower) is None - else value(lq_all[i].lower) - constants[i] + ( + -inf + if value(lq_all[i].lower) is None + else value(lq_all[i].lower) - constants[i] + ) for i in range(num_lq) ) ubs = tuple( - inf - if value(lq_all[i].upper) is None - else value(lq_all[i].upper) - constants[i] + ( + inf + if value(lq_all[i].upper) is None + else value(lq_all[i].upper) - constants[i] + ) for i in range(num_lq) ) fxs = tuple(c.equality for c in lq_all) diff --git a/pyomo/solvers/plugins/solvers/mosek_persistent.py b/pyomo/solvers/plugins/solvers/mosek_persistent.py index 4e2aa97b379..6eaad564781 100644 --- a/pyomo/solvers/plugins/solvers/mosek_persistent.py +++ b/pyomo/solvers/plugins/solvers/mosek_persistent.py @@ -213,19 +213,19 @@ def update_vars(self, *solver_vars): var_ids.append(self._pyomo_var_to_solver_var_map[v]) vtypes = tuple(map(self._mosek_vartype_from_var, solver_vars)) lbs = tuple( - value(v) - if v.fixed - else -float('inf') - if value(v.lb) is None - else value(v.lb) + ( + value(v) + if v.fixed + else -float('inf') if value(v.lb) is None else value(v.lb) + ) for v in solver_vars ) ubs = tuple( - value(v) - if v.fixed - else float('inf') - if value(v.ub) is None - else value(v.ub) + ( + value(v) + if v.fixed + else float('inf') if value(v.ub) is None else value(v.ub) + ) for v in solver_vars ) fxs = tuple(v.is_fixed() for v in solver_vars) diff --git a/pyomo/solvers/plugins/solvers/persistent_solver.py b/pyomo/solvers/plugins/solvers/persistent_solver.py index 34df4e4b454..141621d0a31 100644 --- a/pyomo/solvers/plugins/solvers/persistent_solver.py +++ b/pyomo/solvers/plugins/solvers/persistent_solver.py @@ -547,9 +547,9 @@ def solve(self, *args, **kwds): result.solution(0).symbol_map = getattr( _model, "._symbol_maps" )[result._smap_id] - result.solution( - 0 - ).default_variable_value = self._default_variable_value + result.solution(0).default_variable_value = ( + self._default_variable_value + ) if self._load_solutions: _model.load_solution(result.solution(0)) else: diff --git a/pyomo/solvers/tests/checks/test_GAMS.py b/pyomo/solvers/tests/checks/test_GAMS.py index c4913672694..7aa952a6c69 100644 --- a/pyomo/solvers/tests/checks/test_GAMS.py +++ b/pyomo/solvers/tests/checks/test_GAMS.py @@ -212,12 +212,12 @@ def test_fixed_var_sign_gms(self): def test_long_var_py(self): with SolverFactory("gams", solver_io="python") as opt: m = ConcreteModel() - x = ( - m.a23456789012345678901234567890123456789012345678901234567890123 - ) = Var() - y = ( - m.b234567890123456789012345678901234567890123456789012345678901234 - ) = Var() + x = m.a23456789012345678901234567890123456789012345678901234567890123 = ( + Var() + ) + y = m.b234567890123456789012345678901234567890123456789012345678901234 = ( + Var() + ) z = ( m.c23456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 ) = Var() @@ -236,12 +236,12 @@ def test_long_var_py(self): def test_long_var_gms(self): with SolverFactory("gams", solver_io="gms") as opt: m = ConcreteModel() - x = ( - m.a23456789012345678901234567890123456789012345678901234567890123 - ) = Var() - y = ( - m.b234567890123456789012345678901234567890123456789012345678901234 - ) = Var() + x = m.a23456789012345678901234567890123456789012345678901234567890123 = ( + Var() + ) + y = m.b234567890123456789012345678901234567890123456789012345678901234 = ( + Var() + ) z = ( m.c23456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 ) = Var() diff --git a/pyomo/solvers/tests/checks/test_cplex.py b/pyomo/solvers/tests/checks/test_cplex.py index 4f1d7aca99b..44b82d2ad77 100644 --- a/pyomo/solvers/tests/checks/test_cplex.py +++ b/pyomo/solvers/tests/checks/test_cplex.py @@ -129,16 +129,14 @@ def get_mock_model(self): def get_mock_cplex_shell(self, mock_model): solver = MockCPLEX() - ( - solver._problem_files, - solver._problem_format, - solver._smap_id, - ) = convert_problem( - (mock_model,), - ProblemFormat.cpxlp, - [ProblemFormat.cpxlp], - has_capability=lambda x: True, - symbolic_solver_labels=True, + (solver._problem_files, solver._problem_format, solver._smap_id) = ( + convert_problem( + (mock_model,), + ProblemFormat.cpxlp, + [ProblemFormat.cpxlp], + has_capability=lambda x: True, + symbolic_solver_labels=True, + ) ) return solver From 916881c12f0145de0df2e613195eb14918e2a478 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 26 Jan 2024 09:29:43 -0700 Subject: [PATCH 0832/1204] Update ignore-revs --- .git-blame-ignore-revs | 1 + 1 file changed, 1 insertion(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 6d3e6401c5b..8863634b6a2 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -43,4 +43,5 @@ ed13c8c65d6c3f56973887744be1c62a5d4756de 0d93f98aa608f892df404ad8015885d26e09bb55 63a3c602a00a2b747fc308c0571bbe33e55a3731 363a16a609f519b3edfdfcf40c66d6de7ac135af +d024718991455519e09149ede53a38df1c067abe From 017e21ee50d98d8b2f2083e6880f030025ed5378 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 26 Jan 2024 09:36:28 -0700 Subject: [PATCH 0833/1204] Apply new black to docs/examples --- doc/OnlineDocs/src/scripting/spy4Constraints.py | 1 + doc/OnlineDocs/src/scripting/spy4Expressions.py | 1 + doc/OnlineDocs/src/scripting/spy4PyomoCommand.py | 1 + doc/OnlineDocs/src/scripting/spy4Variables.py | 1 + examples/gdp/constrained_layout/cons_layout_model.py | 1 + examples/gdp/eight_process/eight_proc_logical.py | 1 + examples/gdp/eight_process/eight_proc_model.py | 1 + examples/gdp/eight_process/eight_proc_verbose_model.py | 1 + examples/gdp/nine_process/small_process.py | 1 + examples/gdp/small_lit/basic_step.py | 1 + examples/gdp/small_lit/ex_633_trespalacios.py | 1 + examples/gdp/small_lit/nonconvex_HEN.py | 1 - examples/gdp/strip_packing/strip_packing_concrete.py | 1 + examples/gdp/two_rxn_lee/two_rxn_model.py | 1 + examples/kernel/mosek/power1.py | 4 +--- examples/pyomobook/nonlinear-ch/react_design/ReactorDesign.py | 4 +--- 16 files changed, 15 insertions(+), 7 deletions(-) diff --git a/doc/OnlineDocs/src/scripting/spy4Constraints.py b/doc/OnlineDocs/src/scripting/spy4Constraints.py index ac42b4d38b3..f0033bbc33e 100644 --- a/doc/OnlineDocs/src/scripting/spy4Constraints.py +++ b/doc/OnlineDocs/src/scripting/spy4Constraints.py @@ -2,6 +2,7 @@ David L. Woodruff and Mingye Yang, Spring 2018 Code snippets for Constraints.rst in testable form """ + from pyomo.environ import * model = ConcreteModel() diff --git a/doc/OnlineDocs/src/scripting/spy4Expressions.py b/doc/OnlineDocs/src/scripting/spy4Expressions.py index d4a5cad321a..0e8a50c78b3 100644 --- a/doc/OnlineDocs/src/scripting/spy4Expressions.py +++ b/doc/OnlineDocs/src/scripting/spy4Expressions.py @@ -2,6 +2,7 @@ David L. Woodruff and Mingye Yang, Spring 2018 Code snippets for Expressions.rst in testable form """ + from pyomo.environ import * model = ConcreteModel() diff --git a/doc/OnlineDocs/src/scripting/spy4PyomoCommand.py b/doc/OnlineDocs/src/scripting/spy4PyomoCommand.py index c03ee1e5039..f655b812076 100644 --- a/doc/OnlineDocs/src/scripting/spy4PyomoCommand.py +++ b/doc/OnlineDocs/src/scripting/spy4PyomoCommand.py @@ -2,6 +2,7 @@ David L. Woodruff and Mingye Yang, Spring 2018 Code snippets for PyomoCommand.rst in testable form """ + from pyomo.environ import * model = ConcreteModel() diff --git a/doc/OnlineDocs/src/scripting/spy4Variables.py b/doc/OnlineDocs/src/scripting/spy4Variables.py index 802226247c5..c4e2ff612f1 100644 --- a/doc/OnlineDocs/src/scripting/spy4Variables.py +++ b/doc/OnlineDocs/src/scripting/spy4Variables.py @@ -2,6 +2,7 @@ David L. Woodruff and Mingye Yang, Spring 2018 Code snippets for Variables.rst in testable form """ + from pyomo.environ import * model = ConcreteModel() diff --git a/examples/gdp/constrained_layout/cons_layout_model.py b/examples/gdp/constrained_layout/cons_layout_model.py index 10595db4c22..c10c6f6be81 100644 --- a/examples/gdp/constrained_layout/cons_layout_model.py +++ b/examples/gdp/constrained_layout/cons_layout_model.py @@ -9,6 +9,7 @@ with each other. """ + from __future__ import division from pyomo.environ import ConcreteModel, Objective, Param, RangeSet, Set, Var, value diff --git a/examples/gdp/eight_process/eight_proc_logical.py b/examples/gdp/eight_process/eight_proc_logical.py index 7e183dfc397..aaa71f0b2c9 100644 --- a/examples/gdp/eight_process/eight_proc_logical.py +++ b/examples/gdp/eight_process/eight_proc_logical.py @@ -22,6 +22,7 @@ http://dx.doi.org/10.1016/0098-1354(95)00219-7 """ + from __future__ import division from pyomo.core.expr.logical_expr import land, lor diff --git a/examples/gdp/eight_process/eight_proc_model.py b/examples/gdp/eight_process/eight_proc_model.py index d4bd4dbd102..4ab4eb780db 100644 --- a/examples/gdp/eight_process/eight_proc_model.py +++ b/examples/gdp/eight_process/eight_proc_model.py @@ -22,6 +22,7 @@ http://dx.doi.org/10.1016/0098-1354(95)00219-7 """ + from __future__ import division from pyomo.environ import ( diff --git a/examples/gdp/eight_process/eight_proc_verbose_model.py b/examples/gdp/eight_process/eight_proc_verbose_model.py index 78da347e564..4c4886afe10 100644 --- a/examples/gdp/eight_process/eight_proc_verbose_model.py +++ b/examples/gdp/eight_process/eight_proc_verbose_model.py @@ -4,6 +4,7 @@ eight_proc_model.py. """ + from __future__ import division from pyomo.environ import ( diff --git a/examples/gdp/nine_process/small_process.py b/examples/gdp/nine_process/small_process.py index 7f96f32c65c..2758069f316 100644 --- a/examples/gdp/nine_process/small_process.py +++ b/examples/gdp/nine_process/small_process.py @@ -1,6 +1,7 @@ """Small process synthesis-inspired toy GDP example. """ + from pyomo.core import ConcreteModel, RangeSet, Var, Constraint, Objective from pyomo.core.expr.current import exp, log, sqrt from pyomo.gdp import Disjunction diff --git a/examples/gdp/small_lit/basic_step.py b/examples/gdp/small_lit/basic_step.py index 16d134500e7..48ef52d9ba0 100644 --- a/examples/gdp/small_lit/basic_step.py +++ b/examples/gdp/small_lit/basic_step.py @@ -9,6 +9,7 @@ Pyomo model implementation by @RomeoV """ + from pyomo.environ import * from pyomo.gdp import * from pyomo.gdp.basic_step import apply_basic_step diff --git a/examples/gdp/small_lit/ex_633_trespalacios.py b/examples/gdp/small_lit/ex_633_trespalacios.py index b281e009d1f..ce9ae55a85c 100644 --- a/examples/gdp/small_lit/ex_633_trespalacios.py +++ b/examples/gdp/small_lit/ex_633_trespalacios.py @@ -14,6 +14,7 @@ Pyomo model implementation by @bernalde and @qtothec. """ + from __future__ import division from pyomo.environ import * diff --git a/examples/gdp/small_lit/nonconvex_HEN.py b/examples/gdp/small_lit/nonconvex_HEN.py index 61c24c3187a..99e2c4f15e2 100644 --- a/examples/gdp/small_lit/nonconvex_HEN.py +++ b/examples/gdp/small_lit/nonconvex_HEN.py @@ -7,7 +7,6 @@ Pyomo model implementation by @RomeoV """ - from pyomo.environ import ( ConcreteModel, Constraint, diff --git a/examples/gdp/strip_packing/strip_packing_concrete.py b/examples/gdp/strip_packing/strip_packing_concrete.py index 4fa6172a8d1..9e11b702366 100644 --- a/examples/gdp/strip_packing/strip_packing_concrete.py +++ b/examples/gdp/strip_packing/strip_packing_concrete.py @@ -9,6 +9,7 @@ cutting fabric. """ + from __future__ import division from pyomo.environ import ConcreteModel, NonNegativeReals, Objective, Param, Set, Var diff --git a/examples/gdp/two_rxn_lee/two_rxn_model.py b/examples/gdp/two_rxn_lee/two_rxn_model.py index 9057ef8c006..7e43dc4e744 100644 --- a/examples/gdp/two_rxn_lee/two_rxn_model.py +++ b/examples/gdp/two_rxn_lee/two_rxn_model.py @@ -1,4 +1,5 @@ """Two reactor model from literature. See README.md.""" + from __future__ import division from pyomo.core import ConcreteModel, Constraint, Objective, Param, Var, maximize diff --git a/examples/kernel/mosek/power1.py b/examples/kernel/mosek/power1.py index 7274b587dae..d7a12c1ce54 100644 --- a/examples/kernel/mosek/power1.py +++ b/examples/kernel/mosek/power1.py @@ -12,9 +12,7 @@ def solve_nonlinear(): m.c = pmo.constraint(body=m.x + m.y + 0.5 * m.z, rhs=2) - m.o = pmo.objective( - (m.x**0.2) * (m.y**0.8) + (m.z**0.4) - m.x, sense=pmo.maximize - ) + m.o = pmo.objective((m.x**0.2) * (m.y**0.8) + (m.z**0.4) - m.x, sense=pmo.maximize) m.x.value, m.y.value, m.z.value = (1, 1, 1) ipopt = pmo.SolverFactory("ipopt") diff --git a/examples/pyomobook/nonlinear-ch/react_design/ReactorDesign.py b/examples/pyomobook/nonlinear-ch/react_design/ReactorDesign.py index 814b4a5938e..90822c153a5 100644 --- a/examples/pyomobook/nonlinear-ch/react_design/ReactorDesign.py +++ b/examples/pyomobook/nonlinear-ch/react_design/ReactorDesign.py @@ -33,9 +33,7 @@ def create_model(k1, k2, k3, caf): model.cc_bal = pyo.Constraint(expr=(0 == -model.sv * model.cc + k2 * model.cb)) - model.cd_bal = pyo.Constraint( - expr=(0 == -model.sv * model.cd + k3 * model.ca**2.0) - ) + model.cd_bal = pyo.Constraint(expr=(0 == -model.sv * model.cd + k3 * model.ca**2.0)) return model From aa401ac19a9d47c3762895e305bf31bb3e297070 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 26 Jan 2024 09:37:10 -0700 Subject: [PATCH 0834/1204] Ignore black rev --- .git-blame-ignore-revs | 1 + 1 file changed, 1 insertion(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 8863634b6a2..36b9898397f 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -44,4 +44,5 @@ ed13c8c65d6c3f56973887744be1c62a5d4756de 63a3c602a00a2b747fc308c0571bbe33e55a3731 363a16a609f519b3edfdfcf40c66d6de7ac135af d024718991455519e09149ede53a38df1c067abe +017e21ee50d98d8b2f2083e6880f030025ed5378 From 6ec5561c71120677dbc90b69aed8c88875a25bfa Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 26 Jan 2024 10:18:40 -0700 Subject: [PATCH 0835/1204] Remove __future__ imports for Python 2->3 --- doc/OnlineDocs/src/scripting/abstract2.py | 2 +- doc/OnlineDocs/src/scripting/concrete1.py | 1 - doc/OnlineDocs/src/scripting/driveabs2.py | 2 +- doc/OnlineDocs/src/scripting/driveconc1.py | 2 +- examples/dae/stochpdegas_automatic.py | 2 +- .../gdp/constrained_layout/cons_layout_model.py | 2 -- examples/gdp/eight_process/eight_proc_logical.py | 2 -- examples/gdp/eight_process/eight_proc_model.py | 2 -- .../eight_process/eight_proc_verbose_model.py | 2 -- examples/gdp/small_lit/ex_633_trespalacios.py | 2 -- .../gdp/strip_packing/strip_packing_8rect.py | 2 -- .../gdp/strip_packing/strip_packing_concrete.py | 2 -- examples/gdp/two_rxn_lee/two_rxn_model.py | 2 -- .../performance/dae/stochpdegas1_automatic.py | 2 +- .../pyomo-components-ch/var_declaration.py | 2 +- .../community_detection/tests/test_detection.py | 1 - pyomo/contrib/mcpp/pyomo_mcpp.py | 2 +- pyomo/contrib/mcpp/test_mcpp.py | 2 +- pyomo/contrib/multistart/high_conf_stop.py | 2 -- pyomo/contrib/multistart/multi.py | 2 -- pyomo/contrib/multistart/reinit.py | 2 -- .../preprocessing/plugins/bounds_to_vars.py | 1 - .../preprocessing/plugins/induced_linearity.py | 1 - pyomo/contrib/preprocessing/plugins/init_vars.py | 2 +- .../preprocessing/plugins/int_to_binary.py | 2 -- .../preprocessing/plugins/remove_zero_terms.py | 2 +- .../preprocessing/plugins/var_aggregator.py | 1 - .../react_example/maximize_cb_outputs.py | 2 +- .../react_example/reactor_model_outputs.py | 1 - .../react_example/reactor_model_residuals.py | 1 - .../pynumero/sparse/tests/test_block_vector.py | 2 +- .../examples/HIV_Transmission.py | 2 +- .../sensitivity_toolbox/examples/parameter.py | 2 +- pyomo/core/base/piecewise.py | 14 +------------- pyomo/core/expr/logical_expr.py | 1 - pyomo/core/expr/numeric_expr.py | 8 +------- pyomo/core/expr/numvalue.py | 16 ---------------- pyomo/core/expr/visitor.py | 1 - .../tests/unit/test_logical_expr_expanded.py | 2 +- pyomo/dae/tests/test_colloc.py | 2 +- pyomo/dae/tests/test_finite_diff.py | 2 +- pyomo/dae/tests/test_simulator.py | 1 - pyomo/gdp/plugins/cuttingplane.py | 2 +- pyomo/gdp/plugins/partition_disjuncts.py | 2 +- pyomo/repn/standard_aux.py | 1 - pyomo/repn/standard_repn.py | 1 - 46 files changed, 21 insertions(+), 91 deletions(-) diff --git a/doc/OnlineDocs/src/scripting/abstract2.py b/doc/OnlineDocs/src/scripting/abstract2.py index 7eb444914db..1e14d1d1898 100644 --- a/doc/OnlineDocs/src/scripting/abstract2.py +++ b/doc/OnlineDocs/src/scripting/abstract2.py @@ -1,6 +1,6 @@ # abstract2.py -from __future__ import division + from pyomo.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/scripting/concrete1.py b/doc/OnlineDocs/src/scripting/concrete1.py index 1c1f1517e17..2cd1a1f722c 100644 --- a/doc/OnlineDocs/src/scripting/concrete1.py +++ b/doc/OnlineDocs/src/scripting/concrete1.py @@ -1,4 +1,3 @@ -from __future__ import division from pyomo.environ import * model = ConcreteModel() diff --git a/doc/OnlineDocs/src/scripting/driveabs2.py b/doc/OnlineDocs/src/scripting/driveabs2.py index 67ab7468864..45862195a57 100644 --- a/doc/OnlineDocs/src/scripting/driveabs2.py +++ b/doc/OnlineDocs/src/scripting/driveabs2.py @@ -1,5 +1,5 @@ # driveabs2.py -from __future__ import division + import pyomo.environ as pyo from pyomo.opt import SolverFactory diff --git a/doc/OnlineDocs/src/scripting/driveconc1.py b/doc/OnlineDocs/src/scripting/driveconc1.py index ca5d6fc1593..95b0f42806d 100644 --- a/doc/OnlineDocs/src/scripting/driveconc1.py +++ b/doc/OnlineDocs/src/scripting/driveconc1.py @@ -1,5 +1,5 @@ # driveconc1.py -from __future__ import division + import pyomo.environ as pyo from pyomo.opt import SolverFactory diff --git a/examples/dae/stochpdegas_automatic.py b/examples/dae/stochpdegas_automatic.py index 3cd5c34f011..fdde099a396 100644 --- a/examples/dae/stochpdegas_automatic.py +++ b/examples/dae/stochpdegas_automatic.py @@ -1,7 +1,7 @@ # stochastic pde model for natural gas network # victor m. zavala / 2013 -# from __future__ import division +# from pyomo.environ import * from pyomo.dae import * diff --git a/examples/gdp/constrained_layout/cons_layout_model.py b/examples/gdp/constrained_layout/cons_layout_model.py index c10c6f6be81..245aa2df58e 100644 --- a/examples/gdp/constrained_layout/cons_layout_model.py +++ b/examples/gdp/constrained_layout/cons_layout_model.py @@ -10,8 +10,6 @@ """ -from __future__ import division - from pyomo.environ import ConcreteModel, Objective, Param, RangeSet, Set, Var, value # Constrained layout model examples. These are from Nicolas Sawaya (2006). diff --git a/examples/gdp/eight_process/eight_proc_logical.py b/examples/gdp/eight_process/eight_proc_logical.py index aaa71f0b2c9..60f7acee876 100644 --- a/examples/gdp/eight_process/eight_proc_logical.py +++ b/examples/gdp/eight_process/eight_proc_logical.py @@ -23,8 +23,6 @@ """ -from __future__ import division - from pyomo.core.expr.logical_expr import land, lor from pyomo.core.plugins.transform.logical_to_linear import ( update_boolean_vars_from_binary, diff --git a/examples/gdp/eight_process/eight_proc_model.py b/examples/gdp/eight_process/eight_proc_model.py index 4ab4eb780db..840b6911d83 100644 --- a/examples/gdp/eight_process/eight_proc_model.py +++ b/examples/gdp/eight_process/eight_proc_model.py @@ -23,8 +23,6 @@ """ -from __future__ import division - from pyomo.environ import ( ConcreteModel, Constraint, diff --git a/examples/gdp/eight_process/eight_proc_verbose_model.py b/examples/gdp/eight_process/eight_proc_verbose_model.py index 4c4886afe10..cae584d4127 100644 --- a/examples/gdp/eight_process/eight_proc_verbose_model.py +++ b/examples/gdp/eight_process/eight_proc_verbose_model.py @@ -5,8 +5,6 @@ """ -from __future__ import division - from pyomo.environ import ( ConcreteModel, Constraint, diff --git a/examples/gdp/small_lit/ex_633_trespalacios.py b/examples/gdp/small_lit/ex_633_trespalacios.py index ce9ae55a85c..61b7294e3ba 100644 --- a/examples/gdp/small_lit/ex_633_trespalacios.py +++ b/examples/gdp/small_lit/ex_633_trespalacios.py @@ -15,8 +15,6 @@ """ -from __future__ import division - from pyomo.environ import * from pyomo.gdp import * diff --git a/examples/gdp/strip_packing/strip_packing_8rect.py b/examples/gdp/strip_packing/strip_packing_8rect.py index eba3c82dc05..e1350dbc39e 100644 --- a/examples/gdp/strip_packing/strip_packing_8rect.py +++ b/examples/gdp/strip_packing/strip_packing_8rect.py @@ -11,8 +11,6 @@ """ -from __future__ import division - from pyomo.environ import ( ConcreteModel, NonNegativeReals, diff --git a/examples/gdp/strip_packing/strip_packing_concrete.py b/examples/gdp/strip_packing/strip_packing_concrete.py index 9e11b702366..1313d75561c 100644 --- a/examples/gdp/strip_packing/strip_packing_concrete.py +++ b/examples/gdp/strip_packing/strip_packing_concrete.py @@ -10,8 +10,6 @@ """ -from __future__ import division - from pyomo.environ import ConcreteModel, NonNegativeReals, Objective, Param, Set, Var diff --git a/examples/gdp/two_rxn_lee/two_rxn_model.py b/examples/gdp/two_rxn_lee/two_rxn_model.py index 7e43dc4e744..2e5f1734130 100644 --- a/examples/gdp/two_rxn_lee/two_rxn_model.py +++ b/examples/gdp/two_rxn_lee/two_rxn_model.py @@ -1,7 +1,5 @@ """Two reactor model from literature. See README.md.""" -from __future__ import division - from pyomo.core import ConcreteModel, Constraint, Objective, Param, Var, maximize # from pyomo.environ import * # NOQA diff --git a/examples/performance/dae/stochpdegas1_automatic.py b/examples/performance/dae/stochpdegas1_automatic.py index cd0153eee61..905ec9a5330 100644 --- a/examples/performance/dae/stochpdegas1_automatic.py +++ b/examples/performance/dae/stochpdegas1_automatic.py @@ -1,7 +1,7 @@ # stochastic pde model for natural gas network # victor m. zavala / 2013 -# from __future__ import division +# from pyomo.environ import * from pyomo.dae import * diff --git a/examples/pyomobook/pyomo-components-ch/var_declaration.py b/examples/pyomobook/pyomo-components-ch/var_declaration.py index 538cbea1842..a122decd2ae 100644 --- a/examples/pyomobook/pyomo-components-ch/var_declaration.py +++ b/examples/pyomobook/pyomo-components-ch/var_declaration.py @@ -1,4 +1,4 @@ -from __future__ import print_function + import pyomo.environ as pyo model = pyo.ConcreteModel() diff --git a/pyomo/contrib/community_detection/tests/test_detection.py b/pyomo/contrib/community_detection/tests/test_detection.py index 724388f9ab6..acfd441005f 100644 --- a/pyomo/contrib/community_detection/tests/test_detection.py +++ b/pyomo/contrib/community_detection/tests/test_detection.py @@ -12,7 +12,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from __future__ import division import logging diff --git a/pyomo/contrib/mcpp/pyomo_mcpp.py b/pyomo/contrib/mcpp/pyomo_mcpp.py index 817b18bff7c..25a4237ff16 100644 --- a/pyomo/contrib/mcpp/pyomo_mcpp.py +++ b/pyomo/contrib/mcpp/pyomo_mcpp.py @@ -11,7 +11,7 @@ # Note: the self.mcpp.* functions are all C-style functions implemented # in the compiled MC++ wrapper library # Note: argument to pow must be an integer -from __future__ import division + import ctypes import logging diff --git a/pyomo/contrib/mcpp/test_mcpp.py b/pyomo/contrib/mcpp/test_mcpp.py index 9d8c670d470..23b963e11bf 100644 --- a/pyomo/contrib/mcpp/test_mcpp.py +++ b/pyomo/contrib/mcpp/test_mcpp.py @@ -8,7 +8,7 @@ # rights in this software. # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from __future__ import division + import logging from math import pi diff --git a/pyomo/contrib/multistart/high_conf_stop.py b/pyomo/contrib/multistart/high_conf_stop.py index f85daf633de..96b350557ae 100644 --- a/pyomo/contrib/multistart/high_conf_stop.py +++ b/pyomo/contrib/multistart/high_conf_stop.py @@ -6,8 +6,6 @@ """ -from __future__ import division - from collections import Counter from math import log, sqrt diff --git a/pyomo/contrib/multistart/multi.py b/pyomo/contrib/multistart/multi.py index a0e424d2c95..867d47d4951 100644 --- a/pyomo/contrib/multistart/multi.py +++ b/pyomo/contrib/multistart/multi.py @@ -10,8 +10,6 @@ # ___________________________________________________________________________ -from __future__ import division - import logging from pyomo.common.config import ( diff --git a/pyomo/contrib/multistart/reinit.py b/pyomo/contrib/multistart/reinit.py index 3904a7e343f..de10fe3ba8b 100644 --- a/pyomo/contrib/multistart/reinit.py +++ b/pyomo/contrib/multistart/reinit.py @@ -1,7 +1,5 @@ """Helper functions for variable reinitialization.""" -from __future__ import division - import logging import random diff --git a/pyomo/contrib/preprocessing/plugins/bounds_to_vars.py b/pyomo/contrib/preprocessing/plugins/bounds_to_vars.py index ece2376774c..33eaa731816 100644 --- a/pyomo/contrib/preprocessing/plugins/bounds_to_vars.py +++ b/pyomo/contrib/preprocessing/plugins/bounds_to_vars.py @@ -11,7 +11,6 @@ """Transformation to convert explicit bounds to variable bounds.""" -from __future__ import division from math import fabs import math diff --git a/pyomo/contrib/preprocessing/plugins/induced_linearity.py b/pyomo/contrib/preprocessing/plugins/induced_linearity.py index 88c062fdee2..6378c94e44e 100644 --- a/pyomo/contrib/preprocessing/plugins/induced_linearity.py +++ b/pyomo/contrib/preprocessing/plugins/induced_linearity.py @@ -17,7 +17,6 @@ """ -from __future__ import division import logging import textwrap diff --git a/pyomo/contrib/preprocessing/plugins/init_vars.py b/pyomo/contrib/preprocessing/plugins/init_vars.py index 2b37e13e4cd..7469722cf23 100644 --- a/pyomo/contrib/preprocessing/plugins/init_vars.py +++ b/pyomo/contrib/preprocessing/plugins/init_vars.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ """Automatically initialize variables.""" -from __future__ import division + from pyomo.core.base.var import Var from pyomo.core.base.transformation import TransformationFactory diff --git a/pyomo/contrib/preprocessing/plugins/int_to_binary.py b/pyomo/contrib/preprocessing/plugins/int_to_binary.py index 55b9d26948f..6ed6c3a9cfa 100644 --- a/pyomo/contrib/preprocessing/plugins/int_to_binary.py +++ b/pyomo/contrib/preprocessing/plugins/int_to_binary.py @@ -1,7 +1,5 @@ """Transformation to reformulate integer variables into binary.""" -from __future__ import division - from math import floor, log import logging diff --git a/pyomo/contrib/preprocessing/plugins/remove_zero_terms.py b/pyomo/contrib/preprocessing/plugins/remove_zero_terms.py index 7cce719f98d..256c94d4b7a 100644 --- a/pyomo/contrib/preprocessing/plugins/remove_zero_terms.py +++ b/pyomo/contrib/preprocessing/plugins/remove_zero_terms.py @@ -11,7 +11,7 @@ # -*- coding: UTF-8 -*- """Transformation to remove zero terms from constraints.""" -from __future__ import division + from pyomo.core import quicksum from pyomo.core.base.constraint import Constraint diff --git a/pyomo/contrib/preprocessing/plugins/var_aggregator.py b/pyomo/contrib/preprocessing/plugins/var_aggregator.py index 0a429cb5a67..651c0ecf7e0 100644 --- a/pyomo/contrib/preprocessing/plugins/var_aggregator.py +++ b/pyomo/contrib/preprocessing/plugins/var_aggregator.py @@ -11,7 +11,6 @@ """Transformation to aggregate equal variables.""" -from __future__ import division from pyomo.common.collections import ComponentMap, ComponentSet from pyomo.core.base import Block, Constraint, VarList, Objective, TransformationFactory diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/react_example/maximize_cb_outputs.py b/pyomo/contrib/pynumero/examples/external_grey_box/react_example/maximize_cb_outputs.py index eff4f34cabc..9f683b146fe 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/react_example/maximize_cb_outputs.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/react_example/maximize_cb_outputs.py @@ -9,7 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from __future__ import division + import pyomo.environ as pyo from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock from pyomo.contrib.pynumero.examples.external_grey_box.react_example.reactor_model_outputs import ( diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_outputs.py b/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_outputs.py index 7570a20b066..6e6c997880b 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_outputs.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_outputs.py @@ -21,7 +21,6 @@ box model interface. """ -from __future__ import division import numpy as np from scipy.optimize import fsolve diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_residuals.py b/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_residuals.py index 6a6ae9bb652..69a79425750 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_residuals.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_residuals.py @@ -19,7 +19,6 @@ box model interface. """ -from __future__ import division import pyomo.environ as pyo import numpy as np diff --git a/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py b/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py index 2d1bc7b640d..780a8bc2609 100644 --- a/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py +++ b/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py @@ -9,7 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from __future__ import division + import pyomo.common.unittest as unittest from pyomo.contrib.pynumero.dependencies import ( diff --git a/pyomo/contrib/sensitivity_toolbox/examples/HIV_Transmission.py b/pyomo/contrib/sensitivity_toolbox/examples/HIV_Transmission.py index 146baedd8aa..2c8996c95ca 100755 --- a/pyomo/contrib/sensitivity_toolbox/examples/HIV_Transmission.py +++ b/pyomo/contrib/sensitivity_toolbox/examples/HIV_Transmission.py @@ -14,7 +14,7 @@ # and D.K. Owen 1998, Interfaces # -from __future__ import division + from pyomo.environ import ( ConcreteModel, Param, diff --git a/pyomo/contrib/sensitivity_toolbox/examples/parameter.py b/pyomo/contrib/sensitivity_toolbox/examples/parameter.py index 93c6124701b..3ed1628f2c2 100644 --- a/pyomo/contrib/sensitivity_toolbox/examples/parameter.py +++ b/pyomo/contrib/sensitivity_toolbox/examples/parameter.py @@ -13,7 +13,7 @@ # # Original implementation by Hans Pirany is in pyomo/examples/pyomo/suffixes # -from __future__ import print_function + from pyomo.environ import ( ConcreteModel, Param, diff --git a/pyomo/core/base/piecewise.py b/pyomo/core/base/piecewise.py index 0c949f87993..ef2fb9eefae 100644 --- a/pyomo/core/base/piecewise.py +++ b/pyomo/core/base/piecewise.py @@ -32,10 +32,6 @@ *) piecewise for functions of the form y = f(x1,x2,...) """ -# ****** NOTE: Nothing in this file relies on integer division ******* -# I predict this will save numerous headaches as -# well as gratuitous calls to float() in this code -from __future__ import division __all__ = ['Piecewise'] @@ -151,8 +147,6 @@ def _characterize_function(name, tol, f_rule, model, points, *index): # expression generation errors in the checks below points = [value(_p) for _p in points] - # we use future division to protect against the case where - # the user supplies integer type points for return values if isinstance(f_rule, types.FunctionType): values = [f_rule(model, *flatten_tuple((index, x))) for x in points] elif f_rule.__class__ is dict: @@ -272,7 +266,6 @@ def __call__(self, x): yU = self._range_pts[i + 1] if xL == xU: # a step function return yU - # using future division return yL + ((yU - yL) / (xU - xL)) * (x - xL) raise ValueError( "The point %s is outside the list of domain " @@ -299,7 +292,6 @@ def construct(self, pblock, x_var, y_var): # create a single linear constraint LHS = y_var F_AT_XO = y_pts[0] - # using future division dF_AT_XO = (y_pts[1] - y_pts[0]) / (x_pts[1] - x_pts[0]) X_MINUS_XO = x_var - x_pts[0] if bound_type == Bound.Upper: @@ -739,7 +731,7 @@ def construct(self, pblock, x_var, y_var): # create indexers polytopes = range(1, len_x_pts) - # create constants (using future division) + # create constants SLOPE = { p: (y_pts[p] - y_pts[p - 1]) / (x_pts[p] - x_pts[p - 1]) for p in polytopes } @@ -908,7 +900,6 @@ def con1_rule(model, i): rhs *= 0.0 else: rhs *= OPT_M['UB'][i] * (1 - bigm_y[i]) - # using future division return ( y_var - y_pts[i - 1] @@ -922,7 +913,6 @@ def con1_rule(model, i): rhs *= 0.0 else: rhs *= OPT_M['LB'][i] * (1 - bigm_y[i]) - # using future division return ( y_var - y_pts[i - 1] @@ -944,7 +934,6 @@ def conAFF_rule(model, i): rhs *= 0.0 else: rhs *= OPT_M['LB'][i] * (1 - bigm_y[i]) - # using future division return ( y_var - y_pts[i - 1] @@ -974,7 +963,6 @@ def conAFF_rule(model, i): pblock.bigm_domain_constraint_upper = Constraint(expr=x_var <= x_pts[-1]) def _M_func(self, a, Fa, b, Fb, c, Fc): - # using future division return Fa - Fb - ((a - b) * ((Fc - Fb) / (c - b))) def _find_M(self, x_pts, y_pts, bound_type): diff --git a/pyomo/core/expr/logical_expr.py b/pyomo/core/expr/logical_expr.py index e5a2f411a6e..f2d3e110166 100644 --- a/pyomo/core/expr/logical_expr.py +++ b/pyomo/core/expr/logical_expr.py @@ -10,7 +10,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from __future__ import division import types from itertools import islice diff --git a/pyomo/core/expr/numeric_expr.py b/pyomo/core/expr/numeric_expr.py index d0609395f64..0a300474790 100644 --- a/pyomo/core/expr/numeric_expr.py +++ b/pyomo/core/expr/numeric_expr.py @@ -17,14 +17,8 @@ logger = logging.getLogger('pyomo.core') -from math import isclose - from pyomo.common.dependencies import attempt_import -from pyomo.common.deprecation import ( - deprecated, - deprecation_warning, - relocated_module_attribute, -) +from pyomo.common.deprecation import deprecated, relocated_module_attribute from pyomo.common.errors import PyomoException, DeveloperError from pyomo.common.formatting import tostr from pyomo.common.numeric_types import ( diff --git a/pyomo/core/expr/numvalue.py b/pyomo/core/expr/numvalue.py index 305391daffa..d54b9621e70 100644 --- a/pyomo/core/expr/numvalue.py +++ b/pyomo/core/expr/numvalue.py @@ -264,15 +264,6 @@ def polynomial_degree(obj): # constants get repeated many times. KnownConstants lets us re-use / # share constants we have seen before. # -# Note: -# For now, all constants are coerced to floats. This avoids integer -# division in Python 2.x. (At least some of the time.) -# -# When we eliminate support for Python 2.x, we will not need this -# coercion. The main difference in the following code is that we will -# need to index KnownConstants by both the class type and value, since -# INT, FLOAT and LONG values sometimes hash the same. -# _KnownConstants = {} @@ -299,13 +290,6 @@ def as_numeric(obj): if val is not None: return val # - # Coerce the value to a float, if possible - # - try: - obj = float(obj) - except: - pass - # # Create the numeric constant. This really # should be the only place in the code # where these objects are constructed. diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index 1d02146b1e5..f1cd3b7bde6 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -9,7 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from __future__ import division import inspect import logging diff --git a/pyomo/core/tests/unit/test_logical_expr_expanded.py b/pyomo/core/tests/unit/test_logical_expr_expanded.py index f5b86d59cbd..95ae0494a48 100644 --- a/pyomo/core/tests/unit/test_logical_expr_expanded.py +++ b/pyomo/core/tests/unit/test_logical_expr_expanded.py @@ -13,7 +13,7 @@ """ Testing for the logical expression system """ -from __future__ import division + import operator from itertools import product diff --git a/pyomo/dae/tests/test_colloc.py b/pyomo/dae/tests/test_colloc.py index dda928110ae..0786903f12e 100644 --- a/pyomo/dae/tests/test_colloc.py +++ b/pyomo/dae/tests/test_colloc.py @@ -9,7 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from __future__ import print_function + import pyomo.common.unittest as unittest from pyomo.environ import Var, Set, ConcreteModel, TransformationFactory, pyomo diff --git a/pyomo/dae/tests/test_finite_diff.py b/pyomo/dae/tests/test_finite_diff.py index 9ae7ecdea91..adca8bf6a15 100644 --- a/pyomo/dae/tests/test_finite_diff.py +++ b/pyomo/dae/tests/test_finite_diff.py @@ -9,7 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from __future__ import print_function + import pyomo.common.unittest as unittest from pyomo.environ import Var, Set, ConcreteModel, TransformationFactory diff --git a/pyomo/dae/tests/test_simulator.py b/pyomo/dae/tests/test_simulator.py index b3003bb5a0d..e79bc7b23b6 100644 --- a/pyomo/dae/tests/test_simulator.py +++ b/pyomo/dae/tests/test_simulator.py @@ -9,7 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from __future__ import print_function import json import pyomo.common.unittest as unittest diff --git a/pyomo/gdp/plugins/cuttingplane.py b/pyomo/gdp/plugins/cuttingplane.py index fcb0e8886f1..7a6a927a316 100644 --- a/pyomo/gdp/plugins/cuttingplane.py +++ b/pyomo/gdp/plugins/cuttingplane.py @@ -15,7 +15,7 @@ Implements a general cutting plane-based reformulation for linear and convex GDPs. """ -from __future__ import division + from pyomo.common.config import ( ConfigBlock, diff --git a/pyomo/gdp/plugins/partition_disjuncts.py b/pyomo/gdp/plugins/partition_disjuncts.py index 57cfe1852c3..fbe25ed3ae1 100644 --- a/pyomo/gdp/plugins/partition_disjuncts.py +++ b/pyomo/gdp/plugins/partition_disjuncts.py @@ -15,7 +15,7 @@ J. Kronqvist, R. Misener, and C. Tsay, "Between Steps: Intermediate Relaxations between big-M and Convex Hull Reformulations," 2021. """ -from __future__ import division + from pyomo.common.config import ( ConfigBlock, diff --git a/pyomo/repn/standard_aux.py b/pyomo/repn/standard_aux.py index 7995949fc05..8704253eca3 100644 --- a/pyomo/repn/standard_aux.py +++ b/pyomo/repn/standard_aux.py @@ -9,7 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from __future__ import division __all__ = ['compute_standard_repn'] diff --git a/pyomo/repn/standard_repn.py b/pyomo/repn/standard_repn.py index 95fa824b14a..53618d3eb50 100644 --- a/pyomo/repn/standard_repn.py +++ b/pyomo/repn/standard_repn.py @@ -9,7 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from __future__ import division __all__ = ['StandardRepn', 'generate_standard_repn'] From da8e3b6e92dc78ab7687bcedb6e20efe0794435d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 26 Jan 2024 10:21:22 -0700 Subject: [PATCH 0836/1204] Black whoops --- examples/pyomobook/pyomo-components-ch/var_declaration.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/pyomobook/pyomo-components-ch/var_declaration.py b/examples/pyomobook/pyomo-components-ch/var_declaration.py index a122decd2ae..60d3b00756a 100644 --- a/examples/pyomobook/pyomo-components-ch/var_declaration.py +++ b/examples/pyomobook/pyomo-components-ch/var_declaration.py @@ -1,4 +1,3 @@ - import pyomo.environ as pyo model = pyo.ConcreteModel() From 2e98405297b8eec41be40c53b71cfa54f1fe549d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 26 Jan 2024 10:38:33 -0700 Subject: [PATCH 0837/1204] Add back value coercion --- pyomo/core/expr/numvalue.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pyomo/core/expr/numvalue.py b/pyomo/core/expr/numvalue.py index d54b9621e70..1ef0f7f3044 100644 --- a/pyomo/core/expr/numvalue.py +++ b/pyomo/core/expr/numvalue.py @@ -290,6 +290,13 @@ def as_numeric(obj): if val is not None: return val # + # Coerce the value to a float, if possible + # + try: + obj = float(obj) + except: + pass + # # Create the numeric constant. This really # should be the only place in the code # where these objects are constructed. From 3260e1394906bdc23c7e08aba25c747d967e7f12 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 26 Jan 2024 13:02:09 -0700 Subject: [PATCH 0838/1204] Readding note - but should be addressed soon because it's not accurate --- pyomo/core/expr/numvalue.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyomo/core/expr/numvalue.py b/pyomo/core/expr/numvalue.py index 1ef0f7f3044..6c605b080a3 100644 --- a/pyomo/core/expr/numvalue.py +++ b/pyomo/core/expr/numvalue.py @@ -259,6 +259,14 @@ def polynomial_degree(obj): ) +# Note: +# For now, all constants are coerced to floats. This avoids integer +# division in Python 2.x. (At least some of the time.) +# +# When we eliminate support for Python 2.x, we will not need this +# coercion. The main difference in the following code is that we will +# need to index KnownConstants by both the class type and value, since +# INT, FLOAT and LONG values sometimes hash the same. # # It is very common to have only a few constants in a model, but those # constants get repeated many times. KnownConstants lets us re-use / From 0ce77fb64b870a7637c144646f49450296a63cfe Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Fri, 26 Jan 2024 15:12:50 -0700 Subject: [PATCH 0839/1204] Fixing kernel online doc test and black formatting --- doc/OnlineDocs/src/kernel/examples.txt | 2 +- pyomo/core/base/suffix.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/OnlineDocs/src/kernel/examples.txt b/doc/OnlineDocs/src/kernel/examples.txt index 306eff0e929..e85c64efd86 100644 --- a/doc/OnlineDocs/src/kernel/examples.txt +++ b/doc/OnlineDocs/src/kernel/examples.txt @@ -154,7 +154,7 @@ 5 Declarations: SOS2_y_index SOS2_y SOS2_constraint_index SOS2_constraint SOS2_sosconstraint 1 Suffix Declarations - dual : Direction=Suffix.IMPORT, Datatype=Suffix.FLOAT + dual : Direction=IMPORT, Datatype=FLOAT Key : Value 27 Declarations: b s q p pd v vd vl_index vl c cd_index cd cl_index cl e ed o od ol_index ol sos1 sos2 sd_index sd dual f pw diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index d8a5feb6009..160ae20f116 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -189,7 +189,7 @@ def __init__( initialize=None, rule=None, name=None, - doc=None + doc=None, ): ... def __init__(self, **kwargs): From b689a97dcec94fe040c4c7c786ce9f35ba9bee0f Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 30 Jan 2024 09:39:16 -0700 Subject: [PATCH 0840/1204] Apply new black --- pyomo/contrib/solver/ipopt.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 534a9173d07..c6c7a6ee17a 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -94,15 +94,15 @@ def __init__( implicit_domain=implicit_domain, visibility=visibility, ) - self.timing_info.no_function_solve_time: Optional[ - float - ] = self.timing_info.declare( - 'no_function_solve_time', ConfigValue(domain=NonNegativeFloat) + self.timing_info.no_function_solve_time: Optional[float] = ( + self.timing_info.declare( + 'no_function_solve_time', ConfigValue(domain=NonNegativeFloat) + ) ) - self.timing_info.function_solve_time: Optional[ - float - ] = self.timing_info.declare( - 'function_solve_time', ConfigValue(domain=NonNegativeFloat) + self.timing_info.function_solve_time: Optional[float] = ( + self.timing_info.declare( + 'function_solve_time', ConfigValue(domain=NonNegativeFloat) + ) ) From 56a36e97ffeb540964014718219051ea306cc9f8 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 30 Jan 2024 11:55:12 -0700 Subject: [PATCH 0841/1204] Flesh out unit tests for base classes; change to load_solutions for backwards compatibility --- pyomo/contrib/solver/base.py | 34 ++++--- pyomo/contrib/solver/config.py | 4 +- pyomo/contrib/solver/factory.py | 4 +- pyomo/contrib/solver/ipopt.py | 8 +- .../solver/tests/solvers/test_ipopt.py | 2 +- pyomo/contrib/solver/tests/unit/test_base.py | 90 ++++++++++++++++++- .../contrib/solver/tests/unit/test_config.py | 4 +- 7 files changed, 114 insertions(+), 32 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 0b33f8a5648..e0eb58924c1 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -14,8 +14,6 @@ from typing import Sequence, Dict, Optional, Mapping, NoReturn, List, Tuple import os -from .config import SolverConfig - from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.param import _ParamData @@ -29,6 +27,7 @@ from pyomo.core.base import SymbolMap from pyomo.core.base.label import NumericLabeler from pyomo.core.staleflag import StaleFlagManager +from pyomo.contrib.solver.config import SolverConfig from pyomo.contrib.solver.util import get_objective from pyomo.contrib.solver.results import ( Results, @@ -215,7 +214,7 @@ def _get_primals( A map of variables to primals. """ raise NotImplementedError( - '{0} does not support the get_primals method'.format(type(self)) + f'{type(self)} does not support the get_primals method' ) def _get_duals( @@ -235,9 +234,7 @@ def _get_duals( duals: dict Maps constraints to dual values """ - raise NotImplementedError( - '{0} does not support the get_duals method'.format(type(self)) - ) + raise NotImplementedError(f'{type(self)} does not support the get_duals method') def _get_reduced_costs( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None @@ -255,7 +252,7 @@ def _get_reduced_costs( Maps variable to reduced cost """ raise NotImplementedError( - '{0} does not support the get_reduced_costs method'.format(type(self)) + f'{type(self)} does not support the get_reduced_costs method' ) @abc.abstractmethod @@ -331,15 +328,20 @@ def update_params(self): """ -class LegacySolverInterface: +class LegacySolverWrapper: """ Class to map the new solver interface features into the legacy solver interface. Necessary for backwards compatibility. """ - def set_config(self, config): - # TODO: Make a mapping from new config -> old config - pass + # + # Support "with" statements + # + def __enter__(self): + return self + + def __exit__(self, t, v, traceback): + """Exit statement - enables `with` statements.""" def solve( self, @@ -369,7 +371,7 @@ def solve( original_config = self.config self.config = self.config() self.config.tee = tee - self.config.load_solution = load_solutions + self.config.load_solutions = load_solutions self.config.symbolic_solver_labels = symbolic_solver_labels self.config.time_limit = timelimit self.config.report_timing = report_timing @@ -489,7 +491,7 @@ def options(self): """ Read the options for the dictated solver. - NOTE: Only the set of solvers for which the LegacySolverInterface is compatible + NOTE: Only the set of solvers for which the LegacySolverWrapper is compatible are accounted for within this property. Not all solvers are currently covered by this backwards compatibility class. @@ -511,9 +513,3 @@ def options(self, val): found = True if not found: raise NotImplementedError('Could not find the correct options') - - def __enter__(self): - return self - - def __exit__(self, t, v, traceback): - pass diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index 6068269dcae..4c81d31a820 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -58,8 +58,8 @@ def __init__( description="If True, the solver output gets logged.", ), ) - self.load_solution: bool = self.declare( - 'load_solution', + self.load_solutions: bool = self.declare( + 'load_solutions', ConfigValue( domain=bool, default=True, diff --git a/pyomo/contrib/solver/factory.py b/pyomo/contrib/solver/factory.py index fa3e2611667..e499605afd4 100644 --- a/pyomo/contrib/solver/factory.py +++ b/pyomo/contrib/solver/factory.py @@ -12,7 +12,7 @@ from pyomo.opt.base import SolverFactory as LegacySolverFactory from pyomo.common.factory import Factory -from pyomo.contrib.solver.base import LegacySolverInterface +from pyomo.contrib.solver.base import LegacySolverWrapper class SolverFactoryClass(Factory): @@ -21,7 +21,7 @@ def decorator(cls): self._cls[name] = cls self._doc[name] = doc - class LegacySolver(LegacySolverInterface, cls): + class LegacySolver(LegacySolverWrapper, cls): pass LegacySolverFactory.register(name, doc)(LegacySolver) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index c6c7a6ee17a..7c2a3f471e3 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -390,15 +390,15 @@ def solve(self, model, **kwds): results.solver_name = 'ipopt' results.solver_version = self.version() if ( - config.load_solution + config.load_solutions and results.solution_status == SolutionStatus.noSolution ): raise RuntimeError( 'A feasible solution was not found, so no solution can be loaded.' - 'Please set config.load_solution=False to bypass this error.' + 'Please set config.load_solutions=False to bypass this error.' ) - if config.load_solution: + if config.load_solutions: results.solution_loader.load_vars() if ( hasattr(model, 'dual') @@ -417,7 +417,7 @@ def solve(self, model, **kwds): results.solution_status in {SolutionStatus.feasible, SolutionStatus.optimal} and len(nl_info.objectives) > 0 ): - if config.load_solution: + if config.load_solutions: results.incumbent_objective = value(nl_info.objectives[0]) else: results.incumbent_objective = value( diff --git a/pyomo/contrib/solver/tests/solvers/test_ipopt.py b/pyomo/contrib/solver/tests/solvers/test_ipopt.py index 9638d94bdda..627d502629c 100644 --- a/pyomo/contrib/solver/tests/solvers/test_ipopt.py +++ b/pyomo/contrib/solver/tests/solvers/test_ipopt.py @@ -43,7 +43,7 @@ def rosenbrock(m): def test_ipopt_config(self): # Test default initialization config = ipoptConfig() - self.assertTrue(config.load_solution) + self.assertTrue(config.load_solutions) self.assertIsInstance(config.solver_options, ConfigDict) self.assertIsInstance(config.executable, ExecutableData) diff --git a/pyomo/contrib/solver/tests/unit/test_base.py b/pyomo/contrib/solver/tests/unit/test_base.py index e3a8999d8c5..dd94ef18fc3 100644 --- a/pyomo/contrib/solver/tests/unit/test_base.py +++ b/pyomo/contrib/solver/tests/unit/test_base.py @@ -14,8 +14,27 @@ class TestSolverBase(unittest.TestCase): + def test_abstract_member_list(self): + expected_list = ['solve', 'available', 'version'] + member_list = list(base.SolverBase.__abstractmethods__) + self.assertEqual(sorted(expected_list), sorted(member_list)) + + def test_class_method_list(self): + expected_list = [ + 'Availability', + 'CONFIG', + 'available', + 'is_persistent', + 'solve', + 'version', + ] + method_list = [ + method for method in dir(base.SolverBase) if method.startswith('_') is False + ] + self.assertEqual(sorted(expected_list), sorted(method_list)) + @unittest.mock.patch.multiple(base.SolverBase, __abstractmethods__=set()) - def test_solver_base(self): + def test_init(self): self.instance = base.SolverBase() self.assertFalse(self.instance.is_persistent()) self.assertEqual(self.instance.version(), None) @@ -23,6 +42,20 @@ def test_solver_base(self): self.assertEqual(self.instance.solve(None), None) self.assertEqual(self.instance.available(), None) + @unittest.mock.patch.multiple(base.SolverBase, __abstractmethods__=set()) + def test_context_manager(self): + with base.SolverBase() as self.instance: + self.assertFalse(self.instance.is_persistent()) + self.assertEqual(self.instance.version(), None) + self.assertEqual(self.instance.CONFIG, self.instance.config) + self.assertEqual(self.instance.solve(None), None) + self.assertEqual(self.instance.available(), None) + + @unittest.mock.patch.multiple(base.SolverBase, __abstractmethods__=set()) + def test_config_kwds(self): + self.instance = base.SolverBase(tee=True) + self.assertTrue(self.instance.config.tee) + @unittest.mock.patch.multiple(base.SolverBase, __abstractmethods__=set()) def test_solver_availability(self): self.instance = base.SolverBase() @@ -57,8 +90,41 @@ def test_abstract_member_list(self): member_list = list(base.PersistentSolverBase.__abstractmethods__) self.assertEqual(sorted(expected_list), sorted(member_list)) + def test_class_method_list(self): + expected_list = [ + 'Availability', + 'CONFIG', + '_abc_impl', + '_get_duals', + '_get_primals', + '_get_reduced_costs', + '_load_vars', + 'add_block', + 'add_constraints', + 'add_params', + 'add_variables', + 'available', + 'is_persistent', + 'remove_block', + 'remove_constraints', + 'remove_params', + 'remove_variables', + 'set_instance', + 'set_objective', + 'solve', + 'update_params', + 'update_variables', + 'version', + ] + method_list = [ + method + for method in dir(base.PersistentSolverBase) + if method.startswith('__') is False + ] + self.assertEqual(sorted(expected_list), sorted(method_list)) + @unittest.mock.patch.multiple(base.PersistentSolverBase, __abstractmethods__=set()) - def test_persistent_solver_base(self): + def test_init(self): self.instance = base.PersistentSolverBase() self.assertTrue(self.instance.is_persistent()) self.assertEqual(self.instance.set_instance(None), None) @@ -82,3 +148,23 @@ def test_persistent_solver_base(self): with self.assertRaises(NotImplementedError): self.instance._get_reduced_costs() + + @unittest.mock.patch.multiple(base.PersistentSolverBase, __abstractmethods__=set()) + def test_context_manager(self): + with base.PersistentSolverBase() as self.instance: + self.assertTrue(self.instance.is_persistent()) + self.assertEqual(self.instance.set_instance(None), None) + self.assertEqual(self.instance.add_variables(None), None) + self.assertEqual(self.instance.add_params(None), None) + self.assertEqual(self.instance.add_constraints(None), None) + self.assertEqual(self.instance.add_block(None), None) + self.assertEqual(self.instance.remove_variables(None), None) + self.assertEqual(self.instance.remove_params(None), None) + self.assertEqual(self.instance.remove_constraints(None), None) + self.assertEqual(self.instance.remove_block(None), None) + self.assertEqual(self.instance.set_objective(None), None) + self.assertEqual(self.instance.update_variables(None), None) + self.assertEqual(self.instance.update_params(), None) + +class TestLegacySolverWrapper(unittest.TestCase): + pass diff --git a/pyomo/contrib/solver/tests/unit/test_config.py b/pyomo/contrib/solver/tests/unit/test_config.py index 3ad8319343b..4a7cc250623 100644 --- a/pyomo/contrib/solver/tests/unit/test_config.py +++ b/pyomo/contrib/solver/tests/unit/test_config.py @@ -19,7 +19,7 @@ def test_interface_default_instantiation(self): self.assertIsNone(config._description) self.assertEqual(config._visibility, 0) self.assertFalse(config.tee) - self.assertTrue(config.load_solution) + self.assertTrue(config.load_solutions) self.assertTrue(config.raise_exception_on_nonoptimal_result) self.assertFalse(config.symbolic_solver_labels) self.assertIsNone(config.timer) @@ -43,7 +43,7 @@ def test_interface_default_instantiation(self): self.assertIsNone(config._description) self.assertEqual(config._visibility, 0) self.assertFalse(config.tee) - self.assertTrue(config.load_solution) + self.assertTrue(config.load_solutions) self.assertFalse(config.symbolic_solver_labels) self.assertIsNone(config.rel_gap) self.assertIsNone(config.abs_gap) From 9d7e5c0b15e4758e3fe318995a990b04205cd226 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 30 Jan 2024 12:29:52 -0700 Subject: [PATCH 0842/1204] Save state: making Legacy wrapper more testable --- pyomo/contrib/solver/base.py | 103 +++++++++++++------ pyomo/contrib/solver/tests/unit/test_base.py | 1 + 2 files changed, 73 insertions(+), 31 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index e0eb58924c1..98fa60b722f 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -343,32 +343,21 @@ def __enter__(self): def __exit__(self, t, v, traceback): """Exit statement - enables `with` statements.""" - def solve( + def _map_config( self, - model: _BlockData, - tee: bool = False, - load_solutions: bool = True, - logfile: Optional[str] = None, - solnfile: Optional[str] = None, - timelimit: Optional[float] = None, - report_timing: bool = False, - solver_io: Optional[str] = None, - suffixes: Optional[Sequence] = None, - options: Optional[Dict] = None, - keepfiles: bool = False, - symbolic_solver_labels: bool = False, - raise_exception_on_nonoptimal_result: bool = False, + tee, + load_solutions, + symbolic_solver_labels, + timelimit, + report_timing, + raise_exception_on_nonoptimal_result, + solver_io, + suffixes, + logfile, + keepfiles, + solnfile, ): - """ - Solve method: maps new solve method style to backwards compatible version. - - Returns - ------- - legacy_results - Legacy results object - - """ - original_config = self.config + """Map between legacy and new interface configuration options""" self.config = self.config() self.config.tee = tee self.config.load_solutions = load_solutions @@ -392,12 +381,9 @@ def solve( if 'filename' in self.config: filename = os.path.splitext(solnfile)[0] self.config.filename = filename - original_options = self.options - if options is not None: - self.options = options - - results: Results = super().solve(model) + def _map_results(self, model, results): + """Map between legacy and new Results objects""" legacy_results = LegacySolverResults() legacy_soln = LegacySolution() legacy_results.solver.status = legacy_solver_status_map[ @@ -408,7 +394,6 @@ def solve( ] legacy_soln.status = legacy_solution_status_map[results.solution_status] legacy_results.solver.termination_message = str(results.termination_condition) - obj = get_objective(model) if len(list(obj)) > 0: legacy_results.problem.sense = obj.sense @@ -426,12 +411,16 @@ def solve( legacy_soln.gap = abs(results.incumbent_objective - results.objective_bound) else: legacy_soln.gap = None + return legacy_results, legacy_soln + def _solution_handler( + self, load_solutions, model, results, legacy_results, legacy_soln + ): + """Method to handle the preferred action for the solution""" symbol_map = SymbolMap() symbol_map.default_labeler = NumericLabeler('x') model.solutions.add_symbol_map(symbol_map) legacy_results._smap_id = id(symbol_map) - delete_legacy_soln = True if load_solutions: if hasattr(model, 'dual') and model.dual.import_enabled(): @@ -454,6 +443,58 @@ def solve( legacy_results.solution.insert(legacy_soln) if delete_legacy_soln: legacy_results.solution.delete(0) + return legacy_results + + def solve( + self, + model: _BlockData, + tee: bool = False, + load_solutions: bool = True, + logfile: Optional[str] = None, + solnfile: Optional[str] = None, + timelimit: Optional[float] = None, + report_timing: bool = False, + solver_io: Optional[str] = None, + suffixes: Optional[Sequence] = None, + options: Optional[Dict] = None, + keepfiles: bool = False, + symbolic_solver_labels: bool = False, + raise_exception_on_nonoptimal_result: bool = False, + ): + """ + Solve method: maps new solve method style to backwards compatible version. + + Returns + ------- + legacy_results + Legacy results object + + """ + original_config = self.config + self._map_config( + tee, + load_solutions, + symbolic_solver_labels, + timelimit, + report_timing, + raise_exception_on_nonoptimal_result, + solver_io, + suffixes, + logfile, + keepfiles, + solnfile, + ) + + original_options = self.options + if options is not None: + self.options = options + + results: Results = super().solve(model) + legacy_results, legacy_soln = self._map_results(model, results) + + legacy_results = self._solution_handler( + load_solutions, model, results, legacy_results, legacy_soln + ) self.config = original_config self.options = original_options diff --git a/pyomo/contrib/solver/tests/unit/test_base.py b/pyomo/contrib/solver/tests/unit/test_base.py index dd94ef18fc3..2d158025903 100644 --- a/pyomo/contrib/solver/tests/unit/test_base.py +++ b/pyomo/contrib/solver/tests/unit/test_base.py @@ -166,5 +166,6 @@ def test_context_manager(self): self.assertEqual(self.instance.update_variables(None), None) self.assertEqual(self.instance.update_params(), None) + class TestLegacySolverWrapper(unittest.TestCase): pass From 909962426476eb9ee27b08deb4efb67d9bf2508a Mon Sep 17 00:00:00 2001 From: Sakshi <73687517+Sakshi21299@users.noreply.github.com> Date: Tue, 30 Jan 2024 14:32:37 -0500 Subject: [PATCH 0843/1204] add a method to add an edge in the incidence graph interface --- pyomo/contrib/incidence_analysis/interface.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index e922551c6a4..177ca97a6b6 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -932,3 +932,58 @@ def plot(self, variables=None, constraints=None, title=None, show=True): fig.update_layout(title=dict(text=title)) if show: fig.show() + + def add_edge_to_graph(self, node0, node1): + """Adds an edge between node0 and node1 in the incidence graph + + Parameters + --------- + nodes0: VarData/ConstraintData + A node in the graph from the first bipartite set + (``bipartite=0``) + node1: VarData/ConstraintData + A node in the graph from the second bipartite set + (``bipartite=1``) + """ + if self._incidence_graph is None: + raise RuntimeError( + "Attempting to add edge in an incidence graph from cached " + "incidence graph,\nbut no incidence graph has been cached." + ) + + if node0 not in ComponentSet(self._variables) and node0 not in ComponentSet(self._constraints): + raise RuntimeError( + "%s is not a node in the incidence graph" % node0 + ) + + if node1 not in ComponentSet(self._variables) and node1 not in ComponentSet(self._constraints): + raise RuntimeError( + "%s is not a node in the incidence graph" % node1 + ) + + if node0 in ComponentSet(self._variables): + node0_idx = self._var_index_map[node0] + len(self._con_index_map) + if node1 in ComponentSet(self._variables): + raise RuntimeError( + "%s & %s are both variables. Cannot add an edge between two" + "variables.\nThe resulting graph won't be bipartite" + % (node0, node1) + ) + node1_idx = self._con_index_map[node1] + + if node0 in ComponentSet(self._constraints): + node0_idx = self._con_index_map[node0] + if node1 in ComponentSet(self._constraints): + raise RuntimeError( + "%s & %s are both constraints. Cannot add an edge between two" + "constraints.\nThe resulting graph won't be bipartite" + % (node0, node1) + ) + node1_idx = self._var_index_map[node1] + len(self._con_index_map) + + self._incidence_graph.add_edge(node0_idx, node1_idx) + + + + + From fe35b2727db40a2a51a3688168b20ccc1022753f Mon Sep 17 00:00:00 2001 From: Sakshi <73687517+Sakshi21299@users.noreply.github.com> Date: Tue, 30 Jan 2024 14:33:22 -0500 Subject: [PATCH 0844/1204] add tests for the add edge method --- .../tests/test_interface.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/pyomo/contrib/incidence_analysis/tests/test_interface.py b/pyomo/contrib/incidence_analysis/tests/test_interface.py index 490ea94f63c..63bc74ee6dc 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_interface.py +++ b/pyomo/contrib/incidence_analysis/tests/test_interface.py @@ -1790,7 +1790,58 @@ def test_linear_only(self): self.assertEqual(len(matching), 2) self.assertIs(matching[m.eq2], m.x[2]) self.assertIs(matching[m.eq3], m.x[3]) + + def test_add_edge(self): + m = pyo.ConcreteModel() + m.x = pyo.Var([1, 2, 3, 4]) + m.eq1 = pyo.Constraint(expr=m.x[1] ** 2 + m.x[2] ** 2 + m.x[3] ** 2 == 1) + m.eq2 = pyo.Constraint(expr=m.x[2] + pyo.sqrt(m.x[1]) + pyo.exp(m.x[3]) == 1) + m.eq3 = pyo.Constraint(expr=m.x[3] + m.x[2] + m.x[4] == 1) + m.eq4 = pyo.Constraint(expr=m.x[1] + m.x[2]**2 == 5) + + # nodes: component + # 0 : eq1 + # 1 : eq2 + # 2 : eq3 + # 3 : eq4 + # 4 : x[1] + # 5 : x[2] + # 6 : x[3] + # 7 : x[4] + + igraph = IncidenceGraphInterface(m, linear_only=False) + n_edges_original = igraph.n_edges + + #Test if there already exists an edge between two nodes, nothing is added + igraph.add_edge_to_graph(m.eq3, m.x[4]) + n_edges_new = igraph.n_edges + self.assertEqual(n_edges_original, n_edges_new) + + igraph.add_edge_to_graph(m.x[1], m.eq3) + n_edges_new = igraph.n_edges + self.assertEqual(set(igraph._incidence_graph[2]), {6, 5, 7, 4}) + self.assertEqual(n_edges_original +1, n_edges_new) + + igraph.add_edge_to_graph(m.eq4, m.x[4]) + n_edges_new = igraph.n_edges + self.assertEqual(set(igraph._incidence_graph[3]), {4, 5, 7}) + self.assertEqual(n_edges_original + 2, n_edges_new) + + def test_add_edge_linear_igraph(self): + m = pyo.ConcreteModel() + m.x = pyo.Var([1, 2, 3, 4]) + m.eq1 = pyo.Constraint(expr=m.x[1] + m.x[3] == 1) + m.eq2 = pyo.Constraint(expr=m.x[2] + pyo.sqrt(m.x[1]) + pyo.exp(m.x[3]) == 1) + m.eq3 = pyo.Constraint(expr=m.x[4]**2 + m.x[1] ** 3 + m.x[2] == 1) + + #Make sure error is raised when a variable is not in the igraph + igraph = IncidenceGraphInterface(m, linear_only=True) + n_edges_original = igraph.n_edges + msg = "is not a node in the incidence graph" + with self.assertRaisesRegex(RuntimeError, msg): + igraph.add_edge_to_graph(m.x[4], m.eq2) + @unittest.skipUnless(networkx_available, "networkx is not available.") class TestIndexedBlock(unittest.TestCase): From 0f36c3f53f2256b4a632d781e108817f6adaf80d Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Tue, 30 Jan 2024 16:01:51 -0700 Subject: [PATCH 0845/1204] workaround an improvement in mumps memory prediction algorithms --- .../interior_point/linalg/tests/test_realloc.py | 7 +++++++ pyomo/contrib/interior_point/tests/test_realloc.py | 12 ++++++++++-- pyomo/contrib/pynumero/linalg/mumps_interface.py | 5 ++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/interior_point/linalg/tests/test_realloc.py b/pyomo/contrib/interior_point/linalg/tests/test_realloc.py index 0b2e449e349..bfe089dc602 100644 --- a/pyomo/contrib/interior_point/linalg/tests/test_realloc.py +++ b/pyomo/contrib/interior_point/linalg/tests/test_realloc.py @@ -44,6 +44,13 @@ def test_reallocate_memory_mumps(self): predicted = linear_solver.get_infog(16) + # We predict that factorization will take 2 MB + self.assertEqual(predicted, 2) + + # Explicitly set maximum memory to less than the predicted + # requirement. + linear_solver.set_icntl(23, 1) + res = linear_solver.do_numeric_factorization(matrix, raise_on_error=False) self.assertEqual(res.status, LinearSolverStatus.not_enough_memory) diff --git a/pyomo/contrib/interior_point/tests/test_realloc.py b/pyomo/contrib/interior_point/tests/test_realloc.py index 9789c7d3ac0..d5c7df62441 100644 --- a/pyomo/contrib/interior_point/tests/test_realloc.py +++ b/pyomo/contrib/interior_point/tests/test_realloc.py @@ -68,11 +68,19 @@ def test_mumps(self): predicted = linear_solver.get_infog(16) self._test_ip_with_reallocation(linear_solver, interface) + # In Mumps 5.6.2 (and likely previous versions), ICNTL(23)=0 + # corresponds to "use default increase factor over prediction". actual = linear_solver.get_icntl(23) + percent_increase = linear_solver.get_icntl(14) + increase_factor = (1.0 + percent_increase/100.0) - self.assertTrue(predicted == 12 or predicted == 11) + if actual == 0: + actual = increase_factor * predicted + + # As of Mumps 5.6.2, predicted == 9, which is lower than the + # default actual of 10.8 + #self.assertTrue(predicted == 12 or predicted == 11) self.assertTrue(actual > predicted) - # self.assertEqual(actual, 14) # NOTE: This test will break if Mumps (or your Mumps version) # gets more conservative at estimating memory requirement, # or if the numeric factorization gets more efficient. diff --git a/pyomo/contrib/pynumero/linalg/mumps_interface.py b/pyomo/contrib/pynumero/linalg/mumps_interface.py index 95aca114f2f..baab5562716 100644 --- a/pyomo/contrib/pynumero/linalg/mumps_interface.py +++ b/pyomo/contrib/pynumero/linalg/mumps_interface.py @@ -175,7 +175,10 @@ def do_numeric_factorization( res.status = LinearSolverStatus.successful elif stat in {-6, -10}: res.status = LinearSolverStatus.singular - elif stat in {-8, -9}: + elif stat in {-8, -9, -19}: + # -8: Integer workspace too small for factorization + # -9: Real workspace too small for factorization + # -19: Maximum size of working memory is too small res.status = LinearSolverStatus.not_enough_memory elif stat < 0: res.status = LinearSolverStatus.error From 2ae37c4ba16670c5d8f4c14bba37f4bf0986c778 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Tue, 30 Jan 2024 16:43:45 -0700 Subject: [PATCH 0846/1204] apply black --- pyomo/contrib/interior_point/interface.py | 7 ++++--- pyomo/contrib/interior_point/tests/test_realloc.py | 6 ++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/interior_point/interface.py b/pyomo/contrib/interior_point/interface.py index 7d04f578238..38d91be5566 100644 --- a/pyomo/contrib/interior_point/interface.py +++ b/pyomo/contrib/interior_point/interface.py @@ -258,9 +258,10 @@ def __init__(self, pyomo_model): # set the init_duals_primals_lb/ub from ipopt_zL_out, ipopt_zU_out if available # need to compress them as well and initialize the duals_primals_lb/ub - (self._init_duals_primals_lb, self._init_duals_primals_ub) = ( - self._get_full_duals_primals_bounds() - ) + ( + self._init_duals_primals_lb, + self._init_duals_primals_ub, + ) = self._get_full_duals_primals_bounds() self._init_duals_primals_lb[np.isneginf(self._nlp.primals_lb())] = 0 self._init_duals_primals_ub[np.isinf(self._nlp.primals_ub())] = 0 self._duals_primals_lb = self._init_duals_primals_lb.copy() diff --git a/pyomo/contrib/interior_point/tests/test_realloc.py b/pyomo/contrib/interior_point/tests/test_realloc.py index d5c7df62441..b3758c946d4 100644 --- a/pyomo/contrib/interior_point/tests/test_realloc.py +++ b/pyomo/contrib/interior_point/tests/test_realloc.py @@ -67,19 +67,21 @@ def test_mumps(self): res = linear_solver.do_symbolic_factorization(kkt) predicted = linear_solver.get_infog(16) + linear_solver.set_icntl(23, 8) + self._test_ip_with_reallocation(linear_solver, interface) # In Mumps 5.6.2 (and likely previous versions), ICNTL(23)=0 # corresponds to "use default increase factor over prediction". actual = linear_solver.get_icntl(23) percent_increase = linear_solver.get_icntl(14) - increase_factor = (1.0 + percent_increase/100.0) + increase_factor = 1.0 + percent_increase / 100.0 if actual == 0: actual = increase_factor * predicted # As of Mumps 5.6.2, predicted == 9, which is lower than the # default actual of 10.8 - #self.assertTrue(predicted == 12 or predicted == 11) + # self.assertTrue(predicted == 12 or predicted == 11) self.assertTrue(actual > predicted) # NOTE: This test will break if Mumps (or your Mumps version) # gets more conservative at estimating memory requirement, From d90761fa17ee1ef472ecf3e116eaa2ca937a74de Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Tue, 30 Jan 2024 17:29:37 -0700 Subject: [PATCH 0847/1204] Fix black formatting --- pyomo/contrib/interior_point/interface.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/interior_point/interface.py b/pyomo/contrib/interior_point/interface.py index 38d91be5566..7d04f578238 100644 --- a/pyomo/contrib/interior_point/interface.py +++ b/pyomo/contrib/interior_point/interface.py @@ -258,10 +258,9 @@ def __init__(self, pyomo_model): # set the init_duals_primals_lb/ub from ipopt_zL_out, ipopt_zU_out if available # need to compress them as well and initialize the duals_primals_lb/ub - ( - self._init_duals_primals_lb, - self._init_duals_primals_ub, - ) = self._get_full_duals_primals_bounds() + (self._init_duals_primals_lb, self._init_duals_primals_ub) = ( + self._get_full_duals_primals_bounds() + ) self._init_duals_primals_lb[np.isneginf(self._nlp.primals_lb())] = 0 self._init_duals_primals_ub[np.isinf(self._nlp.primals_ub())] = 0 self._duals_primals_lb = self._init_duals_primals_lb.copy() From 0298371af4d6330be0635371ed2df486331d79a9 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 31 Jan 2024 11:11:19 -0700 Subject: [PATCH 0848/1204] Remove _decl_order from CondifDict --- pyomo/common/config.py | 42 +++++++++++++++--------------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index e3466eb7686..c218eb3f11e 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -31,6 +31,8 @@ import textwrap import types +from operator import attrgetter + from pyomo.common.collections import Sequence, Mapping from pyomo.common.deprecation import ( deprecated, @@ -1688,11 +1690,9 @@ def __call__( ans.reset() else: # Copy over any Dict definitions - for k in self._decl_order: + for k, v in self._data.items(): if preserve_implicit or k in self._declared: - v = self._data[k] ans._data[k] = _tmp = v(preserve_implicit=preserve_implicit) - ans._decl_order.append(k) if k in self._declared: ans._declared.add(k) _tmp._parent = ans @@ -2384,7 +2384,6 @@ class ConfigDict(ConfigBase, Mapping): content_filters = {None, 'all', 'userdata'} __slots__ = ( - '_decl_order', '_declared', '_implicit_declaration', '_implicit_domain', @@ -2399,7 +2398,6 @@ def __init__( implicit_domain=None, visibility=0, ): - self._decl_order = [] self._declared = set() self._implicit_declaration = implicit if ( @@ -2478,7 +2476,6 @@ def __delitem__(self, key): _key = str(key).replace(' ', '_') del self._data[_key] # Clean up the other data structures - self._decl_order.remove(_key) self._declared.discard(_key) def __contains__(self, key): @@ -2486,10 +2483,10 @@ def __contains__(self, key): return _key in self._data def __len__(self): - return self._decl_order.__len__() + return len(self._data) def __iter__(self): - return (self._data[key]._name for key in self._decl_order) + return map(attrgetter('_name'), self._data.values()) def __getattr__(self, name): # Note: __getattr__ is only called after all "usual" attribute @@ -2526,13 +2523,12 @@ def keys(self): def values(self): self._userAccessed = True - for key in self._decl_order: - yield self[key] + return map(self.__getitem__, self._data) def items(self): self._userAccessed = True - for key in self._decl_order: - yield (self._data[key]._name, self[key]) + for key, val in self._data.items(): + yield (val._name, self[key]) @deprecated('The iterkeys method is deprecated. Use dict.keys().', version='6.0') def iterkeys(self): @@ -2561,7 +2557,6 @@ def _add(self, name, config): % (name, self.name(True)) ) self._data[_name] = config - self._decl_order.append(_name) config._parent = self config._name = name return config @@ -2614,8 +2609,7 @@ def value(self, accessValue=True): if accessValue: self._userAccessed = True return { - cfg._name: cfg.value(accessValue) - for cfg in map(self._data.__getitem__, self._decl_order) + cfg._name: cfg.value(accessValue) for cfg in self._data.values() } def set_value(self, value, skip_implicit=False): @@ -2636,7 +2630,7 @@ def set_value(self, value, skip_implicit=False): _key = str(key).replace(' ', '_') if _key in self._data: # str(key) may not be key... store the mapping so that - # when we later iterate over the _decl_order, we can map + # when we later iterate over the _data, we can map # the local keys back to the incoming value keys. _decl_map[_key] = key else: @@ -2659,7 +2653,7 @@ def set_value(self, value, skip_implicit=False): # We want to set the values in declaration order (so that # things are deterministic and in case a validation depends # on the order) - for key in self._decl_order: + for key in self._data: if key in _decl_map: self[key] = value[_decl_map[key]] # implicit data is declared at the end (in sorted order) @@ -2675,16 +2669,11 @@ def set_value(self, value, skip_implicit=False): def reset(self): # Reset the values in the order they were declared. This # allows reset functions to have a deterministic ordering. - def _keep(self, key): - keep = key in self._declared - if keep: - self._data[key].reset() + for key, val in list(self._data.items()): + if key in self._declared: + val.reset() else: del self._data[key] - return keep - - # this is an in-place slice of a list... - self._decl_order[:] = [x for x in self._decl_order if _keep(self, x)] self._userAccessed = False self._userSet = False @@ -2695,8 +2684,7 @@ def _data_collector(self, level, prefix, visibility=None, docMode=False): yield (level, prefix, None, self) if level is not None: level += 1 - for key in self._decl_order: - cfg = self._data[key] + for cfg in self._data.values(): yield from cfg._data_collector(level, cfg._name + ': ', visibility, docMode) From 011a3d2d4bec220cbfda6cc2ce772b416fceac2c Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 31 Jan 2024 11:11:24 -0700 Subject: [PATCH 0849/1204] Test ConfigDict with declarations in __init__ --- pyomo/common/tests/test_config.py | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/pyomo/common/tests/test_config.py b/pyomo/common/tests/test_config.py index 9bafd852eb9..d7dcfca7a12 100644 --- a/pyomo/common/tests/test_config.py +++ b/pyomo/common/tests/test_config.py @@ -3027,6 +3027,46 @@ def fcn(self): self.assertEqual(add_docstring_list("", ExampleClass.CONFIG), ref) self.assertIn('add_docstring_list is deprecated', LOG.getvalue()) + def test_declaration_in_init(self): + class CustomConfig(ConfigDict): + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + super().__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, + ) + + self.declare('time_limit', ConfigValue(domain=NonNegativeFloat)) + self.declare('stream_solver', ConfigValue(domain=bool)) + + cfg = CustomConfig() + OUT = StringIO() + cfg.display(ostream=OUT) + self.assertEqual( + "time_limit: None\nstream_solver: None\n", + OUT.getvalue() + ) + + # Test that creating a copy of a ConfigDict with declared fields + # in the __init__ does not result in duplicate outputs in the + # display (reported in PR #3113) + cfg2 = cfg({'time_limit': 10, 'stream_solver': 0}) + OUT = StringIO() + cfg2.display(ostream=OUT) + self.assertEqual( + "time_limit: 10.0\nstream_solver: false\n", + OUT.getvalue() + ) + if __name__ == "__main__": unittest.main() From 3878f669e46ffa4a786e8c88fbde422824840f71 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 31 Jan 2024 11:59:08 -0700 Subject: [PATCH 0850/1204] NFC: apply black --- pyomo/common/config.py | 10 ++-------- pyomo/common/tests/test_config.py | 22 ++++++++-------------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index c218eb3f11e..15f15872fc6 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -2383,11 +2383,7 @@ class ConfigDict(ConfigBase, Mapping): content_filters = {None, 'all', 'userdata'} - __slots__ = ( - '_declared', - '_implicit_declaration', - '_implicit_domain', - ) + __slots__ = ('_declared', '_implicit_declaration', '_implicit_domain') _all_slots = set(__slots__ + ConfigBase.__slots__) def __init__( @@ -2608,9 +2604,7 @@ def add(self, name, config): def value(self, accessValue=True): if accessValue: self._userAccessed = True - return { - cfg._name: cfg.value(accessValue) for cfg in self._data.values() - } + return {cfg._name: cfg.value(accessValue) for cfg in self._data.values()} def set_value(self, value, skip_implicit=False): if value is None: diff --git a/pyomo/common/tests/test_config.py b/pyomo/common/tests/test_config.py index d7dcfca7a12..93d770037fb 100644 --- a/pyomo/common/tests/test_config.py +++ b/pyomo/common/tests/test_config.py @@ -3030,12 +3030,12 @@ def fcn(self): def test_declaration_in_init(self): class CustomConfig(ConfigDict): def __init__( - self, - description=None, - doc=None, - implicit=False, - implicit_domain=None, - visibility=0, + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, ): super().__init__( description=description, @@ -3051,10 +3051,7 @@ def __init__( cfg = CustomConfig() OUT = StringIO() cfg.display(ostream=OUT) - self.assertEqual( - "time_limit: None\nstream_solver: None\n", - OUT.getvalue() - ) + self.assertEqual("time_limit: None\nstream_solver: None\n", OUT.getvalue()) # Test that creating a copy of a ConfigDict with declared fields # in the __init__ does not result in duplicate outputs in the @@ -3062,10 +3059,7 @@ def __init__( cfg2 = cfg({'time_limit': 10, 'stream_solver': 0}) OUT = StringIO() cfg2.display(ostream=OUT) - self.assertEqual( - "time_limit: 10.0\nstream_solver: false\n", - OUT.getvalue() - ) + self.assertEqual("time_limit: 10.0\nstream_solver: false\n", OUT.getvalue()) if __name__ == "__main__": From bac8bda15dea59856c2f03e4ffa33fb52afab4b9 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 31 Jan 2024 12:06:33 -0700 Subject: [PATCH 0851/1204] Correcting precedence and some other mistakes John caught --- pyomo/core/expr/logical_expr.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/pyomo/core/expr/logical_expr.py b/pyomo/core/expr/logical_expr.py index 31082293a71..aabef99597d 100644 --- a/pyomo/core/expr/logical_expr.py +++ b/pyomo/core/expr/logical_expr.py @@ -539,7 +539,7 @@ class AllDifferentExpression(NaryBooleanExpression): __slots__ = () - PRECEDENCE = 9 # TODO: maybe? + PRECEDENCE = None def getname(self, *arg, **kwd): return 'all_different' @@ -548,9 +548,13 @@ def _to_string(self, values, verbose, smap): return "all_different(%s)" % (", ".join(values)) def _apply_operation(self, result): - for val1, val2 in combinations(result, 2): - if val1 == val2: + last = None + # we know these are integer-valued, so we can just sort them an make + # sure that no adjacent pairs have the same value. + for val in sorted(result): + if last == val: return False + last = val return True @@ -561,13 +565,7 @@ class CountIfExpression(NumericExpression): """ __slots__ = () - PRECEDENCE = 10 # TODO: maybe? - - def __init__(self, args): - # require a list, a la SumExpression - if args.__class__ is not list: - args = list(args) - self._args_ = args + PRECEDENCE = None # NumericExpression assumes binary operator, so we have to override. def nargs(self): @@ -580,7 +578,7 @@ def _to_string(self, values, verbose, smap): return "count_if(%s)" % (", ".join(values)) def _apply_operation(self, result): - return sum(value(r) for r in result) + return sum(r for r in result) special_boolean_atom_types = {ExactlyExpression, AtMostExpression, AtLeastExpression} From 272ea35a03d305ccd4bffd6b5aa428a95b51d2c6 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 31 Jan 2024 12:56:11 -0700 Subject: [PATCH 0852/1204] Checking argument types for logical expressions --- pyomo/core/expr/logical_expr.py | 59 ++++++++++++++++--- .../tests/unit/test_logical_expr_expanded.py | 10 +++- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/pyomo/core/expr/logical_expr.py b/pyomo/core/expr/logical_expr.py index aabef99597d..d345e0f64ae 100644 --- a/pyomo/core/expr/logical_expr.py +++ b/pyomo/core/expr/logical_expr.py @@ -183,12 +183,57 @@ def _flattened(args): yield arg +def _flattened_boolean_args(args): + """Flatten any potentially indexed arguments and check that they are + Boolean-valued.""" + for arg in args: + if arg.__class__ in native_types: + myiter = (arg,) + elif isinstance(arg, (types.GeneratorType, list)): + myiter = arg + elif arg.is_indexed(): + myiter = arg.values() + else: + myiter = (arg,) + for _argdata in myiter: + if _argdata.__class__ in native_logical_types: + yield _argdata + elif hasattr(_argdata, 'is_logical_type') and _argdata.is_logical_type(): + yield _argdata + else: + raise ValueError( + "Non-Boolean-valued argument '%s' encountered when constructing " + "expression of Boolean arguments" % arg) + + +def _flattened_numeric_args(args): + """Flatten any potentially indexed arguments and check that they are + numeric.""" + for arg in args: + if arg.__class__ in native_types: + myiter = (arg,) + elif isinstance(arg, (types.GeneratorType, list)): + myiter = arg + elif arg.is_indexed(): + myiter = arg.values() + else: + myiter = (arg,) + for _argdata in myiter: + if _argdata.__class__ in native_numeric_types: + yield _argdata + elif hasattr(_argdata, 'is_numeric_type') and _argdata.is_numeric_type(): + yield _argdata + else: + raise ValueError( + "Non-numeric argument '%s' encountered when constructing " + "expression with numeric arguments" % arg) + def land(*args): """ Construct an AndExpression between passed arguments. """ result = AndExpression([]) - for argdata in _flattened(args): + for argdata in _flattened_boolean_args(args): result = result.add(argdata) return result @@ -198,7 +243,7 @@ def lor(*args): Construct an OrExpression between passed arguments. """ result = OrExpression([]) - for argdata in _flattened(args): + for argdata in _flattened_boolean_args(args): result = result.add(argdata) return result @@ -211,7 +256,7 @@ def exactly(n, *args): Usage: exactly(2, m.Y1, m.Y2, m.Y3, ...) """ - result = ExactlyExpression([n] + list(_flattened(args))) + result = ExactlyExpression([n] + list(_flattened_boolean_args(args))) return result @@ -223,7 +268,7 @@ def atmost(n, *args): Usage: atmost(2, m.Y1, m.Y2, m.Y3, ...) """ - result = AtMostExpression([n] + list(_flattened(args))) + result = AtMostExpression([n] + list(_flattened_boolean_args(args))) return result @@ -235,7 +280,7 @@ def atleast(n, *args): Usage: atleast(2, m.Y1, m.Y2, m.Y3, ...) """ - result = AtLeastExpression([n] + list(_flattened(args))) + result = AtLeastExpression([n] + list(_flattened_boolean_args(args))) return result @@ -246,7 +291,7 @@ def all_different(*args): Usage: all_different(m.X1, m.X2, ...) """ - return AllDifferentExpression(list(_flattened(args))) + return AllDifferentExpression(list(_flattened_numeric_args(args))) def count_if(*args): @@ -256,7 +301,7 @@ def count_if(*args): Usage: count_if(m.Y1, m.Y2, ...) """ - return CountIfExpression(list(_flattened(args))) + return CountIfExpression(list(_flattened_boolean_args(args))) class UnaryBooleanExpression(BooleanExpression): diff --git a/pyomo/core/tests/unit/test_logical_expr_expanded.py b/pyomo/core/tests/unit/test_logical_expr_expanded.py index ca2b64957ef..0e5bb4da445 100644 --- a/pyomo/core/tests/unit/test_logical_expr_expanded.py +++ b/pyomo/core/tests/unit/test_logical_expr_expanded.py @@ -280,6 +280,8 @@ def test_to_string(self): m.Y2 = BooleanVar() m.Y3 = BooleanVar() m.Y4 = BooleanVar() + m.int1 = Var(domain=Integers) + m.int2 = Var(domain=Integers) self.assertEqual(str(land(m.Y1, m.Y2, m.Y3)), "Y1 ∧ Y2 ∧ Y3") self.assertEqual(str(lor(m.Y1, m.Y2, m.Y3)), "Y1 ∨ Y2 ∨ Y3") @@ -289,7 +291,8 @@ def test_to_string(self): self.assertEqual(str(atleast(1, m.Y1, m.Y2)), "atleast(1: [Y1, Y2])") self.assertEqual(str(atmost(1, m.Y1, m.Y2)), "atmost(1: [Y1, Y2])") self.assertEqual(str(exactly(1, m.Y1, m.Y2)), "exactly(1: [Y1, Y2])") - self.assertEqual(str(all_different(m.Y1, m.Y2)), "all_different(Y1, Y2)") + self.assertEqual(str(all_different(m.int1, m.int2)), + "all_different(int1, int2)") self.assertEqual(str(count_if(m.Y1, m.Y2)), "count_if(Y1, Y2)") # Precedence checks @@ -308,12 +311,15 @@ def test_node_types(self): m.Y1 = BooleanVar() m.Y2 = BooleanVar() m.Y3 = BooleanVar() + m.int1 = Var(domain=Integers) + m.int2 = Var(domain=Integers) + m.int3 = Var(domain=Integers) self.assertFalse(m.Y1.is_expression_type()) self.assertTrue(lnot(m.Y1).is_expression_type()) self.assertTrue(equivalent(m.Y1, m.Y2).is_expression_type()) self.assertTrue(atmost(1, [m.Y1, m.Y2, m.Y3]).is_expression_type()) - self.assertTrue(all_different(m.Y1, m.Y2, m.Y3).is_expression_type()) + self.assertTrue(all_different(m.int1, m.int2, m.int3).is_expression_type()) self.assertTrue(count_if(m.Y1, m.Y2, m.Y3).is_expression_type()) def test_numeric_invalid(self): From e0edbe792caa3d7041abf496793b2c89fcd13e51 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 31 Jan 2024 12:57:24 -0700 Subject: [PATCH 0853/1204] Black disagrees --- pyomo/core/expr/logical_expr.py | 11 +++++++---- pyomo/core/tests/unit/test_logical_expr_expanded.py | 5 +++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pyomo/core/expr/logical_expr.py b/pyomo/core/expr/logical_expr.py index d345e0f64ae..17f4a4dd564 100644 --- a/pyomo/core/expr/logical_expr.py +++ b/pyomo/core/expr/logical_expr.py @@ -184,7 +184,7 @@ def _flattened(args): def _flattened_boolean_args(args): - """Flatten any potentially indexed arguments and check that they are + """Flatten any potentially indexed arguments and check that they are Boolean-valued.""" for arg in args: if arg.__class__ in native_types: @@ -203,11 +203,12 @@ def _flattened_boolean_args(args): else: raise ValueError( "Non-Boolean-valued argument '%s' encountered when constructing " - "expression of Boolean arguments" % arg) + "expression of Boolean arguments" % arg + ) def _flattened_numeric_args(args): - """Flatten any potentially indexed arguments and check that they are + """Flatten any potentially indexed arguments and check that they are numeric.""" for arg in args: if arg.__class__ in native_types: @@ -226,7 +227,9 @@ def _flattened_numeric_args(args): else: raise ValueError( "Non-numeric argument '%s' encountered when constructing " - "expression with numeric arguments" % arg) + "expression with numeric arguments" % arg + ) + def land(*args): """ diff --git a/pyomo/core/tests/unit/test_logical_expr_expanded.py b/pyomo/core/tests/unit/test_logical_expr_expanded.py index 0e5bb4da445..0360e9b4783 100644 --- a/pyomo/core/tests/unit/test_logical_expr_expanded.py +++ b/pyomo/core/tests/unit/test_logical_expr_expanded.py @@ -291,8 +291,9 @@ def test_to_string(self): self.assertEqual(str(atleast(1, m.Y1, m.Y2)), "atleast(1: [Y1, Y2])") self.assertEqual(str(atmost(1, m.Y1, m.Y2)), "atmost(1: [Y1, Y2])") self.assertEqual(str(exactly(1, m.Y1, m.Y2)), "exactly(1: [Y1, Y2])") - self.assertEqual(str(all_different(m.int1, m.int2)), - "all_different(int1, int2)") + self.assertEqual( + str(all_different(m.int1, m.int2)), "all_different(int1, int2)" + ) self.assertEqual(str(count_if(m.Y1, m.Y2)), "count_if(Y1, Y2)") # Precedence checks From 07a6bcbbdb6eeeaf97cf3c585e0935477cbea33b Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 31 Jan 2024 15:10:17 -0700 Subject: [PATCH 0854/1204] Make tests robust under pypy --- pyomo/common/tests/test_config.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pyomo/common/tests/test_config.py b/pyomo/common/tests/test_config.py index 93d770037fb..1b732d86c0a 100644 --- a/pyomo/common/tests/test_config.py +++ b/pyomo/common/tests/test_config.py @@ -3051,7 +3051,11 @@ def __init__( cfg = CustomConfig() OUT = StringIO() cfg.display(ostream=OUT) - self.assertEqual("time_limit: None\nstream_solver: None\n", OUT.getvalue()) + # Note: pypy outputs "None" as "null" + self.assertEqual( + "time_limit: None\nstream_solver: None\n", + OUT.getvalue().replace('null', 'None'), + ) # Test that creating a copy of a ConfigDict with declared fields # in the __init__ does not result in duplicate outputs in the @@ -3059,7 +3063,10 @@ def __init__( cfg2 = cfg({'time_limit': 10, 'stream_solver': 0}) OUT = StringIO() cfg2.display(ostream=OUT) - self.assertEqual("time_limit: 10.0\nstream_solver: false\n", OUT.getvalue()) + self.assertEqual( + "time_limit: 10.0\nstream_solver: false\n", + OUT.getvalue().replace('null', 'None'), + ) if __name__ == "__main__": From cdc21268a71ec906e84dd3c8731d4c296abd6a01 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 31 Jan 2024 15:12:21 -0700 Subject: [PATCH 0855/1204] We do need to evaluate the args when we apply count_if because they can be relational expressions --- pyomo/core/expr/logical_expr.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyomo/core/expr/logical_expr.py b/pyomo/core/expr/logical_expr.py index 17f4a4dd564..875f5107f3a 100644 --- a/pyomo/core/expr/logical_expr.py +++ b/pyomo/core/expr/logical_expr.py @@ -200,6 +200,8 @@ def _flattened_boolean_args(args): yield _argdata elif hasattr(_argdata, 'is_logical_type') and _argdata.is_logical_type(): yield _argdata + elif isinstance(_argdata, BooleanValue): + yield _argdata else: raise ValueError( "Non-Boolean-valued argument '%s' encountered when constructing " @@ -626,7 +628,7 @@ def _to_string(self, values, verbose, smap): return "count_if(%s)" % (", ".join(values)) def _apply_operation(self, result): - return sum(r for r in result) + return sum(value(r) for r in result) special_boolean_atom_types = {ExactlyExpression, AtMostExpression, AtLeastExpression} From 8d2116265326a834680b9cc0bb896747d2749f78 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 31 Jan 2024 15:13:12 -0700 Subject: [PATCH 0856/1204] Removing another test with Boolean args to all diff --- pyomo/contrib/cp/tests/test_docplex_walker.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/pyomo/contrib/cp/tests/test_docplex_walker.py b/pyomo/contrib/cp/tests/test_docplex_walker.py index 0f1c73cd3b1..b897053c93a 100644 --- a/pyomo/contrib/cp/tests/test_docplex_walker.py +++ b/pyomo/contrib/cp/tests/test_docplex_walker.py @@ -424,22 +424,6 @@ def test_all_diff_expression(self): self.assertTrue(expr[1].equals(cp.all_diff(a[i] for i in m.I))) - def test_Boolean_args_in_all_diff_expression(self): - m = self.get_model() - m.a.domain = Integers - m.a.bounds = (11, 20) - m.c = LogicalConstraint(expr=all_different(m.a[1] == 13, m.b)) - - visitor = self.get_visitor() - expr = visitor.walk_expression((m.c.body, m.c, 0)) - - self.assertIn(id(m.a[1]), visitor.var_map) - a0 = visitor.var_map[id(m.a[1])] - self.assertIn(id(m.b), visitor.var_map) - b = visitor.var_map[id(m.b)] - - self.assertTrue(expr[1].equals(cp.all_diff(a0 == 13, b))) - def test_count_if_expression(self): m = self.get_model() m.a.domain = Integers From 1b73570e6cceab7127d0dca416dfad2774fedca6 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 31 Jan 2024 18:39:13 -0500 Subject: [PATCH 0857/1204] correct typos --- pyomo/contrib/mindtpy/algorithm_base_class.py | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index ad462221ec5..b6a223ba24b 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -108,7 +108,7 @@ def __init__(self, **kwds): self.curr_int_sol = [] self.should_terminate = False self.integer_list = [] - # Dictionary {integer solution (list): [cuts begin index, cuts end index] (list)} + # Dictionary {integer solution (tuple): [cuts begin index, cuts end index] (list)} self.integer_solution_to_cuts_index = dict() # Set up iteration counters @@ -2679,9 +2679,9 @@ def initialize_subsolvers(self): if config.mip_regularization_solver == 'gams': self.regularization_mip_opt.options['add_options'] = [] if config.regularization_mip_threads > 0: - self.regularization_mip_opt.options['threads'] = ( - config.regularization_mip_threads - ) + self.regularization_mip_opt.options[ + 'threads' + ] = config.regularization_mip_threads else: self.regularization_mip_opt.options['threads'] = config.threads @@ -2691,9 +2691,9 @@ def initialize_subsolvers(self): 'cplex_persistent', }: if config.solution_limit is not None: - self.regularization_mip_opt.options['mip_limits_solutions'] = ( - config.solution_limit - ) + self.regularization_mip_opt.options[ + 'mip_limits_solutions' + ] = config.solution_limit # We don't need to solve the regularization problem to optimality. # We will choose to perform aggressive node probing during presolve. self.regularization_mip_opt.options['mip_strategy_presolvenode'] = 3 @@ -2706,9 +2706,9 @@ def initialize_subsolvers(self): self.regularization_mip_opt.options['optimalitytarget'] = 3 elif config.mip_regularization_solver == 'gurobi': if config.solution_limit is not None: - self.regularization_mip_opt.options['SolutionLimit'] = ( - config.solution_limit - ) + self.regularization_mip_opt.options[ + 'SolutionLimit' + ] = config.solution_limit # Same reason as mip_strategy_presolvenode. self.regularization_mip_opt.options['Presolve'] = 2 @@ -3055,9 +3055,10 @@ def add_regularization(self): # The main problem might be unbounded, regularization is activated only when a valid bound is provided. if self.dual_bound != self.dual_bound_progress[0]: with time_code(self.timing, 'regularization main'): - (regularization_main_mip, regularization_main_mip_results) = ( - self.solve_regularization_main() - ) + ( + regularization_main_mip, + regularization_main_mip_results, + ) = self.solve_regularization_main() self.handle_regularization_main_tc( regularization_main_mip, regularization_main_mip_results ) From 4ec0e8c69ac7d1992f6879ba9b0e3e52352a9fea Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 31 Jan 2024 18:40:21 -0500 Subject: [PATCH 0858/1204] remove unused log --- pyomo/contrib/mindtpy/algorithm_base_class.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index b6a223ba24b..a4f3075a1e9 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -1290,7 +1290,6 @@ def handle_subproblem_infeasible(self, fixed_nlp, cb_opt=None): # elif var.has_lb() and abs(value(var) - var.lb) < config.absolute_bound_tolerance: # fixed_nlp.ipopt_zU_out[var] = -1 - # config.logger.info('Solving feasibility problem') feas_subproblem, feas_subproblem_results = self.solve_feasibility_subproblem() # TODO: do we really need this? if self.should_terminate: From 462457b38b501922f10fcdf26928a78124b31b9c Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 31 Jan 2024 16:57:30 -0700 Subject: [PATCH 0859/1204] Fix backwards compatibility --- pyomo/contrib/solver/base.py | 39 +++++------------------------------ pyomo/contrib/solver/ipopt.py | 4 ++-- pyomo/contrib/solver/util.py | 2 +- 3 files changed, 8 insertions(+), 37 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 98fa60b722f..8aca11d2f0a 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -349,6 +349,8 @@ def _map_config( load_solutions, symbolic_solver_labels, timelimit, + # Report timing is no longer a valid option. We now always return a + # timer object that can be inspected. report_timing, raise_exception_on_nonoptimal_result, solver_io, @@ -356,6 +358,7 @@ def _map_config( logfile, keepfiles, solnfile, + options, ): """Map between legacy and new interface configuration options""" self.config = self.config() @@ -363,7 +366,7 @@ def _map_config( self.config.load_solutions = load_solutions self.config.symbolic_solver_labels = symbolic_solver_labels self.config.time_limit = timelimit - self.config.report_timing = report_timing + self.config.solver_options.set_value(options) # This is a new flag in the interface. To preserve backwards compatibility, # its default is set to "False" self.config.raise_exception_on_nonoptimal_result = ( @@ -483,12 +486,9 @@ def solve( logfile, keepfiles, solnfile, + options, ) - original_options = self.options - if options is not None: - self.options = options - results: Results = super().solve(model) legacy_results, legacy_soln = self._map_results(model, results) @@ -497,7 +497,6 @@ def solve( ) self.config = original_config - self.options = original_options return legacy_results @@ -526,31 +525,3 @@ def license_is_valid(self) -> bool: """ return bool(self.available()) - - @property - def options(self): - """ - Read the options for the dictated solver. - - NOTE: Only the set of solvers for which the LegacySolverWrapper is compatible - are accounted for within this property. - Not all solvers are currently covered by this backwards compatibility - class. - """ - for solver_name in ['gurobi', 'ipopt', 'cplex', 'cbc', 'highs']: - if hasattr(self, 'solver_options'): - return getattr(self, 'solver_options') - raise NotImplementedError('Could not find the correct options') - - @options.setter - def options(self, val): - """ - Set the options for the dictated solver. - """ - found = False - for solver_name in ['gurobi', 'ipopt', 'cplex', 'cbc', 'highs']: - if hasattr(self, 'solver_options'): - setattr(self, 'solver_options', val) - found = True - if not found: - raise NotImplementedError('Could not find the correct options') diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 7c2a3f471e3..49cb0430e32 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -17,7 +17,7 @@ from typing import Mapping, Optional, Sequence from pyomo.common import Executable -from pyomo.common.config import ConfigValue, NonNegativeInt, NonNegativeFloat +from pyomo.common.config import ConfigValue, NonNegativeFloat from pyomo.common.errors import PyomoException from pyomo.common.tempfiles import TempfileManager from pyomo.common.timing import HierarchicalTimer @@ -285,7 +285,7 @@ def solve(self, model, **kwds): f'Solver {self.__class__} is not available ({avail}).' ) # Update configuration options, based on keywords passed to solve - config: ipoptConfig = self.config(value=kwds) + config: ipoptConfig = self.config(value=kwds, preserve_implicit=True) if config.threads: logger.log( logging.WARNING, diff --git a/pyomo/contrib/solver/util.py b/pyomo/contrib/solver/util.py index 727d9c354e2..f8641b06c50 100644 --- a/pyomo/contrib/solver/util.py +++ b/pyomo/contrib/solver/util.py @@ -41,7 +41,7 @@ def check_optimal_termination(results): # Look at the original version of this function to make that happen. """ This function returns True if the termination condition for the solver - is 'optimal', 'locallyOptimal', or 'globallyOptimal', and the status is 'ok' + is 'optimal'. Parameters ---------- From acb10de438ccc8c7acb5b8ba7d42af7eab3694e9 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 31 Jan 2024 19:32:34 -0500 Subject: [PATCH 0860/1204] add one condition for fix dual bound --- pyomo/contrib/mindtpy/algorithm_base_class.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index a4f3075a1e9..ca428563a68 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -1561,7 +1561,7 @@ def fix_dual_bound(self, last_iter_cuts): self.handle_nlp_subproblem_tc(fixed_nlp, fixed_nlp_result) MindtPy = self.mip.MindtPy_utils - # deactivate the integer cuts generated after the best solution was found. + # Deactivate the integer cuts generated after the best solution was found. self.deactivate_no_good_cuts_when_fixing_bound(MindtPy.cuts.no_good_cuts) if ( config.add_regularization is not None @@ -3013,10 +3013,12 @@ def MindtPy_iteration_loop(self): # if add_no_good_cuts is True, the bound obtained in the last iteration is no reliable. # we correct it after the iteration. + # There is no need to fix the dual bound if no feasible solution has been found. if ( (config.add_no_good_cuts or config.use_tabu_list) and not self.should_terminate and config.add_regularization is None + and self.best_solution_found is not None ): self.fix_dual_bound(self.last_iter_cuts) config.logger.info( From a0ad77e40b74dbaf3bfcf445eabe13d99b9d84af Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 31 Jan 2024 17:37:28 -0700 Subject: [PATCH 0861/1204] Add timing information to legacy results wrapper --- pyomo/contrib/solver/base.py | 4 ++++ pyomo/contrib/solver/ipopt.py | 1 + 2 files changed, 5 insertions(+) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 8aca11d2f0a..1948bf8bf1b 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -444,6 +444,10 @@ def _solution_handler( legacy_soln.variable['Rc'] = val legacy_results.solution.insert(legacy_soln) + # Timing info was not originally on the legacy results, but we want + # to make it accessible to folks who are utilizing the backwards + # compatible version. + legacy_results.timing_info = results.timing_info if delete_legacy_soln: legacy_results.solution.delete(0) return legacy_results diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 49cb0430e32..7f62d67d38e 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -441,6 +441,7 @@ def solve(self, model, **kwds): results.timing_info.wall_time = ( end_timestamp - start_timestamp ).total_seconds() + results.timing_info.timer = timer return results def _parse_ipopt_output(self, stream: io.StringIO): From de73340cf82b50d932ebe25a165c9bc3d7c63230 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 31 Jan 2024 19:41:24 -0500 Subject: [PATCH 0862/1204] remove fix_dual_bound for ECP method --- pyomo/contrib/mindtpy/extended_cutting_plane.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pyomo/contrib/mindtpy/extended_cutting_plane.py b/pyomo/contrib/mindtpy/extended_cutting_plane.py index ac13e352e35..0a98f88ed3f 100644 --- a/pyomo/contrib/mindtpy/extended_cutting_plane.py +++ b/pyomo/contrib/mindtpy/extended_cutting_plane.py @@ -66,12 +66,6 @@ def MindtPy_iteration_loop(self): add_ecp_cuts(self.mip, self.jacobians, self.config, self.timing) - # if add_no_good_cuts is True, the bound obtained in the last iteration is no reliable. - # we correct it after the iteration. - if ( - self.config.add_no_good_cuts or self.config.use_tabu_list - ) and not self.should_terminate: - self.fix_dual_bound(self.last_iter_cuts) self.config.logger.info( ' ===============================================================================================' ) From 248ffd523a2b7d74464b0a94b48ad311a3279848 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 31 Jan 2024 17:51:47 -0700 Subject: [PATCH 0863/1204] Add more base unit tets --- pyomo/contrib/solver/tests/unit/test_base.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/solver/tests/unit/test_base.py b/pyomo/contrib/solver/tests/unit/test_base.py index 2d158025903..5531e0530fc 100644 --- a/pyomo/contrib/solver/tests/unit/test_base.py +++ b/pyomo/contrib/solver/tests/unit/test_base.py @@ -168,4 +168,20 @@ def test_context_manager(self): class TestLegacySolverWrapper(unittest.TestCase): - pass + def test_class_method_list(self): + expected_list = [ + 'available', + 'license_is_valid', + 'solve' + ] + method_list = [ + method for method in dir(base.LegacySolverWrapper) if method.startswith('_') is False + ] + self.assertEqual(sorted(expected_list), sorted(method_list)) + + def test_context_manager(self): + with base.LegacySolverWrapper() as instance: + with self.assertRaises(AttributeError) as context: + instance.available() + + From 92d9477985e92bcccd2785c4862eb1491dca3488 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 31 Jan 2024 19:52:56 -0500 Subject: [PATCH 0864/1204] black format --- pyomo/contrib/mindtpy/single_tree.py | 7 ++++--- pyomo/contrib/mindtpy/tests/nonconvex3.py | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index c1e52ed72d3..228810a8f90 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -588,9 +588,10 @@ def handle_lazy_subproblem_infeasible(self, fixed_nlp, mindtpy_solver, config, o dual_values = None config.logger.info('Solving feasibility problem') - (feas_subproblem, feas_subproblem_results) = ( - mindtpy_solver.solve_feasibility_subproblem() - ) + ( + feas_subproblem, + feas_subproblem_results, + ) = mindtpy_solver.solve_feasibility_subproblem() # In OA algorithm, OA cuts are generated based on the solution of the subproblem # We need to first copy the value of variables from the subproblem and then add cuts copy_var_list_values( diff --git a/pyomo/contrib/mindtpy/tests/nonconvex3.py b/pyomo/contrib/mindtpy/tests/nonconvex3.py index b08deb67b63..dbb88bb1fad 100644 --- a/pyomo/contrib/mindtpy/tests/nonconvex3.py +++ b/pyomo/contrib/mindtpy/tests/nonconvex3.py @@ -40,7 +40,9 @@ def __init__(self, *args, **kwargs): m.objective = Objective(expr=7 * m.x1 + 10 * m.x2, sense=minimize) - m.c1 = Constraint(expr=(m.x1**1.2) * (m.x2**1.7) - 7 * m.x1 - 9 * m.x2 <= -24) + m.c1 = Constraint( + expr=(m.x1**1.2) * (m.x2**1.7) - 7 * m.x1 - 9 * m.x2 <= -24 + ) m.c2 = Constraint(expr=-m.x1 - 2 * m.x2 <= 5) m.c3 = Constraint(expr=-3 * m.x1 + m.x2 <= 1) m.c4 = Constraint(expr=4 * m.x1 - 3 * m.x2 <= 11) From a34bbff873a957d38c751a16ce71c5986b001444 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 31 Jan 2024 17:53:07 -0700 Subject: [PATCH 0865/1204] Create contributors data gathering script --- scripts/admin/contributors.py | 191 ++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 scripts/admin/contributors.py diff --git a/scripts/admin/contributors.py b/scripts/admin/contributors.py new file mode 100644 index 00000000000..2a23c86ed88 --- /dev/null +++ b/scripts/admin/contributors.py @@ -0,0 +1,191 @@ +""" +This script is intended to query the GitHub REST API and get contributor +information for a given time period. +""" + +import sys +import pprint + +from datetime import datetime +from os import environ +from time import perf_counter +from github import Github, Auth + + +def collect_contributors(repository, start_date, end_date): + """ + Return contributor information for a repository in a given timeframe + + Parameters + ---------- + repository : String + The org/repo combination for target repository (GitHub). E.g., + IDAES/idaes-pse + start_date : String + Start date in YYYY-MM-DD. + end_date : String + End date in YYYY-MM-DD. + + Returns + ------- + contributor_information : Dict + A dictionary with contributor information including Authors, Reviewers, + Committers, and Pull Requests. + + """ + # Create data structure + contributor_information = {} + contributor_information['Pull Requests'] = {} + contributor_information['Authors'] = {} + contributor_information['Reviewers'] = {} + contributor_information['Commits'] = {} + # Collect the authorization token from the user's environment + token = environ.get('GH_TOKEN') + auth_token = Auth.Token(token) + # Create a connection to GitHub + gh = Github(auth=auth_token) + # Create a repository object for the requested repository + repo = gh.get_repo(repository) + commits = repo.get_commits(since=start_date, until=end_date) + # Search the commits between the two dates for those that match the string; + # this is the default pull request merge message. If a team uses a custom + # message, this will not work. + merged_prs = [ + int( + commit.commit.message.replace('Merge pull request #', '').split(' from ')[0] + ) + for commit in commits + if commit.commit.message.startswith("Merge pull request") + ] + # Count the number of commits from each person within the two dates + for commit in commits: + try: + if commit.author.login in contributor_information['Commits'].keys(): + contributor_information['Commits'][commit.author.login] += 1 + else: + contributor_information['Commits'][commit.author.login] = 1 + except AttributeError: + # Sometimes GitHub returns an author who doesn't have a handle, + # which seems impossible but happens. In that case, we just record + # their "human-readable" name + if commit.commit.author.name in contributor_information['Commits'].keys(): + contributor_information['Commits'][commit.commit.author.name] += 1 + else: + contributor_information['Commits'][commit.commit.author.name] = 1 + + author_tags = set() + reviewer_tags = set() + for num in merged_prs: + try: + # sometimes the commit messages can lie and give a PR number + # for a different repository fork/branch. + # We try to query it, and if it doesn't work, whatever, move on. + pr = repo.get_pull(num) + except: + continue + # Sometimes the user does not have a handle recorded by GitHub. + # In this case, we replace it with "NOTFOUND" so the person running + # the code knows to go inspect it manually. + author_tag = pr.user.login + if author_tag is None: + author_tag = "NOTFOUND" + # Count the number of PRs authored by each person + if author_tag in author_tags: + contributor_information['Authors'][author_tag] += 1 + else: + contributor_information['Authors'][author_tag] = 1 + author_tags.add(author_tag) + + # Now we inspect all of the reviews to see who engaged in reviewing + # this specific PR + reviews = pr.get_reviews() + review_tags = set(review.user.login for review in reviews) + # Count how many PRs this person has reviewed + for tag in review_tags: + if tag in reviewer_tags: + contributor_information['Reviewers'][tag] += 1 + else: + contributor_information['Reviewers'][tag] = 1 + reviewer_tags.update(review_tags) + contributor_information['Pull Requests'][num] = { + 'author': author_tag, + 'reviewers': review_tags, + } + # This portion replaces tags with human-readable names, if they are present, + # so as to remove the step of "Who does that handle belong to?" + all_tags = author_tags.union(reviewer_tags) + tag_name_map = {} + for tag in all_tags: + if tag in tag_name_map.keys(): + continue + name = gh.search_users(tag + ' in:login')[0].name + # If they don't have a name listed, just keep the tag + if name is not None: + tag_name_map[tag] = name + for key in tag_name_map.keys(): + if key in contributor_information['Authors'].keys(): + contributor_information['Authors'][tag_name_map[key]] = ( + contributor_information['Authors'].pop(key) + ) + if key in contributor_information['Reviewers'].keys(): + contributor_information['Reviewers'][tag_name_map[key]] = ( + contributor_information['Reviewers'].pop(key) + ) + return contributor_information + + +if __name__ == '__main__': + if len(sys.argv) != 4: + print(f"Usage: {sys.argv[0]} ") + print( + " : the GitHub organization/repository combo (e.g., Pyomo/pyomo)" + ) + print( + " : date from which to start exploring contributors in YYYY-MM-DD" + ) + print( + " : date at which to stop exploring contributors in YYYY-MM-DD" + ) + print("") + print( + "ALSO REQUIRED: Please generate a GitHub token (with repo permissions) and export to the environment variable GH_TOKEN." + ) + print(" Visit GitHub's official documentation for more details.") + sys.exit(1) + repository = sys.argv[1] + try: + start = sys.argv[2].split('-') + year = int(start[0]) + try: + month = int(start[1]) + except SyntaxError: + month = int(start[1][1]) + try: + day = int(start[2]) + except SyntaxError: + day = int(start[2][1]) + start_date = datetime(year, month, day) + except: + print("Ensure that the start date is in YYYY-MM-DD format.") + sys.exit(1) + try: + end = sys.argv[3].split('-') + year = int(end[0]) + try: + month = int(end[1]) + except SyntaxError: + month = int(end[1][1]) + try: + day = int(end[2]) + except SyntaxError: + day = int(end[2][1]) + end_date = datetime(year, month, day) + except: + print("Ensure that the end date is in YYYY-MM-DD format.") + sys.exit(1) + tic = perf_counter() + contrib_info = collect_contributors(repository, start_date, end_date) + toc = perf_counter() + print(f"\nCOLLECTION COMPLETE. Time to completion: {toc - tic:0.4f} seconds") + print(f"\nContributors between {sys.argv[2]} and {sys.argv[3]}:") + pprint.pprint(contrib_info) From 42688d12649e915d1213739e9a93c6e9fce60062 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 1 Feb 2024 09:23:58 -0700 Subject: [PATCH 0866/1204] Actions Version Update: Address node.js deprecations --- .github/workflows/release_wheel_creation.yml | 2 +- .github/workflows/test_branches.yml | 20 ++++++++++---------- .github/workflows/test_pr_and_main.yml | 20 ++++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index 72a3ce1110b..3c837cb62b2 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -91,7 +91,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index a55a5db2433..1a1a0e5bd57 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -35,7 +35,7 @@ jobs: - name: Checkout Pyomo source uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' - name: Black Formatting Check @@ -134,7 +134,7 @@ jobs: | tr '\n' ' ' | sed 's/ \+/ /g' >> $GITHUB_ENV #- name: Pip package cache - # uses: actions/cache@v3 + # uses: actions/cache@v4 # if: matrix.PYENV == 'pip' # id: pip-cache # with: @@ -142,7 +142,7 @@ jobs: # key: pip-${{env.CACHE_VER}}.0-${{runner.os}}-${{matrix.python}} #- name: OS package cache - # uses: actions/cache@v3 + # uses: actions/cache@v4 # if: matrix.TARGET != 'osx' # id: os-cache # with: @@ -150,7 +150,7 @@ jobs: # key: pkg-${{env.CACHE_VER}}.0-${{runner.os}} - name: TPL package download cache - uses: actions/cache@v3 + uses: actions/cache@v4 if: ${{ ! matrix.slim }} id: download-cache with: @@ -202,13 +202,13 @@ jobs: - name: Set up Python ${{ matrix.python }} if: matrix.PYENV == 'pip' - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Set up Miniconda Python ${{ matrix.python }} if: matrix.PYENV == 'conda' - uses: conda-incubator/setup-miniconda@v2 + uses: conda-incubator/setup-miniconda@v3 with: auto-update-conda: false python-version: ${{ matrix.python }} @@ -668,7 +668,7 @@ jobs: uses: actions/checkout@v4 - name: Set up Python 3.8 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.8 @@ -724,19 +724,19 @@ jobs: # We need the source for .codecov.yml and running "coverage xml" #- name: Pip package cache - # uses: actions/cache@v3 + # uses: actions/cache@v4 # id: pip-cache # with: # path: cache/pip # key: pip-${{env.CACHE_VER}}.0-${{runner.os}}-3.8 - name: Download build artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: path: artifacts - name: Set up Python 3.8 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.8 diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index ac7691d32ae..f9a8cef99b2 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -38,7 +38,7 @@ jobs: - name: Checkout Pyomo source uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' - name: Black Formatting Check @@ -164,7 +164,7 @@ jobs: | tr '\n' ' ' | sed 's/ \+/ /g' >> $GITHUB_ENV #- name: Pip package cache - # uses: actions/cache@v3 + # uses: actions/cache@v4 # if: matrix.PYENV == 'pip' # id: pip-cache # with: @@ -172,7 +172,7 @@ jobs: # key: pip-${{env.CACHE_VER}}.0-${{runner.os}}-${{matrix.python}} #- name: OS package cache - # uses: actions/cache@v3 + # uses: actions/cache@v4 # if: matrix.TARGET != 'osx' # id: os-cache # with: @@ -180,7 +180,7 @@ jobs: # key: pkg-${{env.CACHE_VER}}.0-${{runner.os}} - name: TPL package download cache - uses: actions/cache@v3 + uses: actions/cache@v4 if: ${{ ! matrix.slim }} id: download-cache with: @@ -232,13 +232,13 @@ jobs: - name: Set up Python ${{ matrix.python }} if: matrix.PYENV == 'pip' - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Set up Miniconda Python ${{ matrix.python }} if: matrix.PYENV == 'conda' - uses: conda-incubator/setup-miniconda@v2 + uses: conda-incubator/setup-miniconda@v3 with: auto-update-conda: false python-version: ${{ matrix.python }} @@ -699,7 +699,7 @@ jobs: uses: actions/checkout@v4 - name: Set up Python 3.8 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.8 @@ -755,19 +755,19 @@ jobs: # We need the source for .codecov.yml and running "coverage xml" #- name: Pip package cache - # uses: actions/cache@v3 + # uses: actions/cache@v4 # id: pip-cache # with: # path: cache/pip # key: pip-${{env.CACHE_VER}}.0-${{runner.os}}-3.8 - name: Download build artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: path: artifacts - name: Set up Python 3.8 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.8 From 8d051b09ebaf84ec946b2d2e4f64a865bedde66e Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 1 Feb 2024 13:06:34 -0500 Subject: [PATCH 0867/1204] black format --- pyomo/contrib/mindtpy/algorithm_base_class.py | 25 +++++++++---------- pyomo/contrib/mindtpy/single_tree.py | 7 +++--- pyomo/contrib/mindtpy/tests/nonconvex3.py | 4 +-- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index ca428563a68..3d5a7ebad03 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -2678,9 +2678,9 @@ def initialize_subsolvers(self): if config.mip_regularization_solver == 'gams': self.regularization_mip_opt.options['add_options'] = [] if config.regularization_mip_threads > 0: - self.regularization_mip_opt.options[ - 'threads' - ] = config.regularization_mip_threads + self.regularization_mip_opt.options['threads'] = ( + config.regularization_mip_threads + ) else: self.regularization_mip_opt.options['threads'] = config.threads @@ -2690,9 +2690,9 @@ def initialize_subsolvers(self): 'cplex_persistent', }: if config.solution_limit is not None: - self.regularization_mip_opt.options[ - 'mip_limits_solutions' - ] = config.solution_limit + self.regularization_mip_opt.options['mip_limits_solutions'] = ( + config.solution_limit + ) # We don't need to solve the regularization problem to optimality. # We will choose to perform aggressive node probing during presolve. self.regularization_mip_opt.options['mip_strategy_presolvenode'] = 3 @@ -2705,9 +2705,9 @@ def initialize_subsolvers(self): self.regularization_mip_opt.options['optimalitytarget'] = 3 elif config.mip_regularization_solver == 'gurobi': if config.solution_limit is not None: - self.regularization_mip_opt.options[ - 'SolutionLimit' - ] = config.solution_limit + self.regularization_mip_opt.options['SolutionLimit'] = ( + config.solution_limit + ) # Same reason as mip_strategy_presolvenode. self.regularization_mip_opt.options['Presolve'] = 2 @@ -3056,10 +3056,9 @@ def add_regularization(self): # The main problem might be unbounded, regularization is activated only when a valid bound is provided. if self.dual_bound != self.dual_bound_progress[0]: with time_code(self.timing, 'regularization main'): - ( - regularization_main_mip, - regularization_main_mip_results, - ) = self.solve_regularization_main() + (regularization_main_mip, regularization_main_mip_results) = ( + self.solve_regularization_main() + ) self.handle_regularization_main_tc( regularization_main_mip, regularization_main_mip_results ) diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index 228810a8f90..c1e52ed72d3 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -588,10 +588,9 @@ def handle_lazy_subproblem_infeasible(self, fixed_nlp, mindtpy_solver, config, o dual_values = None config.logger.info('Solving feasibility problem') - ( - feas_subproblem, - feas_subproblem_results, - ) = mindtpy_solver.solve_feasibility_subproblem() + (feas_subproblem, feas_subproblem_results) = ( + mindtpy_solver.solve_feasibility_subproblem() + ) # In OA algorithm, OA cuts are generated based on the solution of the subproblem # We need to first copy the value of variables from the subproblem and then add cuts copy_var_list_values( diff --git a/pyomo/contrib/mindtpy/tests/nonconvex3.py b/pyomo/contrib/mindtpy/tests/nonconvex3.py index dbb88bb1fad..b08deb67b63 100644 --- a/pyomo/contrib/mindtpy/tests/nonconvex3.py +++ b/pyomo/contrib/mindtpy/tests/nonconvex3.py @@ -40,9 +40,7 @@ def __init__(self, *args, **kwargs): m.objective = Objective(expr=7 * m.x1 + 10 * m.x2, sense=minimize) - m.c1 = Constraint( - expr=(m.x1**1.2) * (m.x2**1.7) - 7 * m.x1 - 9 * m.x2 <= -24 - ) + m.c1 = Constraint(expr=(m.x1**1.2) * (m.x2**1.7) - 7 * m.x1 - 9 * m.x2 <= -24) m.c2 = Constraint(expr=-m.x1 - 2 * m.x2 <= 5) m.c3 = Constraint(expr=-3 * m.x1 + m.x2 <= 1) m.c4 = Constraint(expr=4 * m.x1 - 3 * m.x2 <= 11) From d61a5cb82e64052f77ef046f609c82625caf89f0 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 1 Feb 2024 11:28:10 -0700 Subject: [PATCH 0868/1204] Add README and copyright; update commit search for squashed commit regex --- scripts/admin/README.md | 28 ++++++++++++++++++++ scripts/admin/contributors.py | 50 ++++++++++++++++++++++++++++++++--- 2 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 scripts/admin/README.md diff --git a/scripts/admin/README.md b/scripts/admin/README.md new file mode 100644 index 00000000000..50ad2020b94 --- /dev/null +++ b/scripts/admin/README.md @@ -0,0 +1,28 @@ +# Contributors Script + +The `contributors.py` script is intended to be used to determine contributors +to a public GitHub repository within a given time frame. + +## Requirements + +1. Python 3.7+ +1. [PyGithub](https://pypi.org/project/PyGithub/) +1. A [GitHub Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) with `repo` access, exported to the environment variable `GH_TOKEN` + +## Usage + +``` +Usage: contributors.py + : the GitHub organization/repository combo (e.g., Pyomo/pyomo) + : date from which to start exploring contributors in YYYY-MM-DD + : date at which to stop exploring contributors in YYYY-MM-DD + +ALSO REQUIRED: Please generate a GitHub token (with repo permissions) and export to the environment variable GH_TOKEN. + Visit GitHub's official documentation for more details. +``` + +## Results + +A list of contributors will print to the terminal upon completion. More detailed +information, including authors, committers, reviewers, and pull requests, can +be found in the `contributors-start_date-end_date.json` generated file. diff --git a/scripts/admin/contributors.py b/scripts/admin/contributors.py index 2a23c86ed88..3b416b632cd 100644 --- a/scripts/admin/contributors.py +++ b/scripts/admin/contributors.py @@ -1,10 +1,22 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """ This script is intended to query the GitHub REST API and get contributor information for a given time period. """ import sys -import pprint +import re +import json from datetime import datetime from os import environ @@ -57,6 +69,14 @@ def collect_contributors(repository, start_date, end_date): for commit in commits if commit.commit.message.startswith("Merge pull request") ] + if not merged_prs: + regex_pattern = '\(#.*\)' + for commit in commits: + results = re.search(regex_pattern, commit.commit.message) + try: + merged_prs.append(int(results.group().replace('(#', '').split(')')[0])) + except AttributeError: + continue # Count the number of commits from each person within the two dates for commit in commits: try: @@ -115,6 +135,7 @@ def collect_contributors(repository, start_date, end_date): # so as to remove the step of "Who does that handle belong to?" all_tags = author_tags.union(reviewer_tags) tag_name_map = {} + only_tag_available = [] for tag in all_tags: if tag in tag_name_map.keys(): continue @@ -122,6 +143,8 @@ def collect_contributors(repository, start_date, end_date): # If they don't have a name listed, just keep the tag if name is not None: tag_name_map[tag] = name + else: + only_tag_available.append(tag) for key in tag_name_map.keys(): if key in contributor_information['Authors'].keys(): contributor_information['Authors'][tag_name_map[key]] = ( @@ -131,7 +154,16 @@ def collect_contributors(repository, start_date, end_date): contributor_information['Reviewers'][tag_name_map[key]] = ( contributor_information['Reviewers'].pop(key) ) - return contributor_information + return contributor_information, tag_name_map, only_tag_available + + +def set_default(obj): + """ + Converts sets to list for JSON dump + """ + if isinstance(obj, set): + return list(obj) + raise TypeError if __name__ == '__main__': @@ -183,9 +215,19 @@ def collect_contributors(repository, start_date, end_date): except: print("Ensure that the end date is in YYYY-MM-DD format.") sys.exit(1) + print('BEGIN DATA COLLECTION... (this can take some time)') tic = perf_counter() - contrib_info = collect_contributors(repository, start_date, end_date) + contrib_info, author_name_map, tags_only = collect_contributors( + repository, start_date, end_date + ) toc = perf_counter() print(f"\nCOLLECTION COMPLETE. Time to completion: {toc - tic:0.4f} seconds") print(f"\nContributors between {sys.argv[2]} and {sys.argv[3]}:") - pprint.pprint(contrib_info) + for item in author_name_map.values(): + print(item) + print("\nOnly GitHub handles are available for the following contributors:") + for tag in tags_only: + print(tag) + json_filename = f"contributors-{sys.argv[2]}-{sys.argv[3]}.json" + with open(json_filename, 'w') as file: + json.dump(contrib_info, file, default=set_default) From 71a0a09921709a06dc3da394a2c697616abda2bf Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 1 Feb 2024 11:29:59 -0700 Subject: [PATCH 0869/1204] Update upload-artifact version --- .github/workflows/release_wheel_creation.yml | 6 +++--- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index 3c837cb62b2..ef44806d6d4 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -41,7 +41,7 @@ jobs: CIBW_BUILD_VERBOSITY: 1 CIBW_BEFORE_BUILD: pip install cython pybind11 CIBW_CONFIG_SETTINGS: '--global-option="--with-cython --with-distributable-extensions"' - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: native_wheels path: dist/*.whl @@ -72,7 +72,7 @@ jobs: CIBW_BUILD_VERBOSITY: 1 CIBW_BEFORE_BUILD: pip install cython pybind11 CIBW_CONFIG_SETTINGS: '--global-option="--with-cython --with-distributable-extensions"' - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: alt_wheels path: dist/*.whl @@ -102,7 +102,7 @@ jobs: run: | python setup.py --without-cython sdist --format=gztar - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: generictarball path: dist diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 1a1a0e5bd57..a6857fb996e 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -648,7 +648,7 @@ jobs: coverage xml -i - name: Record build artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{github.job}}_${{env.GHA_JOBGROUP}}-${{env.GHA_JOBNAME}} path: | diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index f9a8cef99b2..949d3099d74 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -678,7 +678,7 @@ jobs: coverage xml -i - name: Record build artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{github.job}}_${{env.GHA_JOBGROUP}}-${{env.GHA_JOBNAME}} path: | From 6a4b14b0cd934e88a5d413ffd3efa3ac9cf52371 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 1 Feb 2024 11:34:20 -0700 Subject: [PATCH 0870/1204] Add one more helpful print message --- scripts/admin/contributors.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/admin/contributors.py b/scripts/admin/contributors.py index 3b416b632cd..3ea20f61bb3 100644 --- a/scripts/admin/contributors.py +++ b/scripts/admin/contributors.py @@ -231,3 +231,4 @@ def set_default(obj): json_filename = f"contributors-{sys.argv[2]}-{sys.argv[3]}.json" with open(json_filename, 'w') as file: json.dump(contrib_info, file, default=set_default) + print(f"\nDetailed information can be found in {json_filename}.") From e7b9cf495eed29da24ab7eec3c265567261a3422 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 1 Feb 2024 12:14:34 -0700 Subject: [PATCH 0871/1204] Add reponame to filename; check if file exists; update comments --- scripts/admin/contributors.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/scripts/admin/contributors.py b/scripts/admin/contributors.py index 3ea20f61bb3..fe5d483f16d 100644 --- a/scripts/admin/contributors.py +++ b/scripts/admin/contributors.py @@ -17,6 +17,7 @@ import sys import re import json +import os from datetime import datetime from os import environ @@ -43,6 +44,12 @@ def collect_contributors(repository, start_date, end_date): contributor_information : Dict A dictionary with contributor information including Authors, Reviewers, Committers, and Pull Requests. + tag_name_map : Dict + A dictionary that maps GitHub handles to GitHub display names (if they + exist). + only_tag_available : List + A list of the handles for contributors who do not have GitHub display names + available. """ # Create data structure @@ -60,8 +67,8 @@ def collect_contributors(repository, start_date, end_date): repo = gh.get_repo(repository) commits = repo.get_commits(since=start_date, until=end_date) # Search the commits between the two dates for those that match the string; - # this is the default pull request merge message. If a team uses a custom - # message, this will not work. + # this is the default pull request merge message. This works assuming that + # a repo does not squash commits merged_prs = [ int( commit.commit.message.replace('Merge pull request #', '').split(' from ')[0] @@ -69,6 +76,8 @@ def collect_contributors(repository, start_date, end_date): for commit in commits if commit.commit.message.startswith("Merge pull request") ] + # If the search above returned nothing, it's likely that the repo squashes + # commits when merging PRs. This is a different regex for that case. if not merged_prs: regex_pattern = '\(#.*\)' for commit in commits: @@ -185,6 +194,7 @@ def set_default(obj): print(" Visit GitHub's official documentation for more details.") sys.exit(1) repository = sys.argv[1] + repository_name = sys.argv[1].split('/')[1] try: start = sys.argv[2].split('-') year = int(start[0]) @@ -215,6 +225,9 @@ def set_default(obj): except: print("Ensure that the end date is in YYYY-MM-DD format.") sys.exit(1) + json_filename = f"contributors-{repository_name}-{sys.argv[2]}-{sys.argv[3]}.json" + if os.path.isfile(json_filename): + raise FileExistsError(f'ERROR: The file {json_filename} already exists!') print('BEGIN DATA COLLECTION... (this can take some time)') tic = perf_counter() contrib_info, author_name_map, tags_only = collect_contributors( @@ -228,7 +241,6 @@ def set_default(obj): print("\nOnly GitHub handles are available for the following contributors:") for tag in tags_only: print(tag) - json_filename = f"contributors-{sys.argv[2]}-{sys.argv[3]}.json" with open(json_filename, 'w') as file: json.dump(contrib_info, file, default=set_default) print(f"\nDetailed information can be found in {json_filename}.") From 1d243baef3301fb7c552d701adaa85b8b8bfaf4d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 1 Feb 2024 12:23:40 -0700 Subject: [PATCH 0872/1204] Update codecov-action version --- .github/workflows/test_branches.yml | 4 ++-- .github/workflows/test_pr_and_main.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index a6857fb996e..e5513d25975 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -831,7 +831,7 @@ jobs: - name: Upload codecov reports if: github.repository_owner == 'Pyomo' || github.ref != 'refs/heads/main' - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: files: coverage.xml token: ${{ secrets.PYOMO_CODECOV_TOKEN }} @@ -843,7 +843,7 @@ jobs: if: | hashFiles('coverage-other.xml') != '' && (github.repository_owner == 'Pyomo' || github.ref != 'refs/heads/main') - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: files: coverage-other.xml token: ${{ secrets.PYOMO_CODECOV_TOKEN }} diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 949d3099d74..c5028606c17 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -862,7 +862,7 @@ jobs: - name: Upload codecov reports if: github.repository_owner == 'Pyomo' || github.ref != 'refs/heads/main' - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: files: coverage.xml token: ${{ secrets.PYOMO_CODECOV_TOKEN }} @@ -874,7 +874,7 @@ jobs: if: | hashFiles('coverage-other.xml') != '' && (github.repository_owner == 'Pyomo' || github.ref != 'refs/heads/main') - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: files: coverage-other.xml token: ${{ secrets.PYOMO_CODECOV_TOKEN }} From ff0d0351b687d95678181f07f401f5deb8ee6f17 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 1 Feb 2024 15:15:03 -0700 Subject: [PATCH 0873/1204] Fix RangeSet.__len__ when defined by floats --- pyomo/core/base/set.py | 2 +- pyomo/core/tests/unit/test_set.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index ba7fdd52446..d820ae8d933 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -2668,7 +2668,7 @@ def __len__(self): if r.start == r.end: return 1 else: - return (r.end - r.start) // r.step + 1 + return int((r.end - r.start) // r.step) + 1 else: return sum(1 for _ in self) diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index a1072e7156c..2154c02e659 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -1238,6 +1238,9 @@ def __len__(self): # Test types that cannot be case to set self.assertNotEqual(SetOf({3}), 3) + # Test floats + self.assertEqual(RangeSet(0.0, 2.0), RangeSet(0.0, 2.0)) + def test_inequality(self): self.assertTrue(SetOf([1, 2, 3]) <= SetOf({1, 2, 3})) self.assertFalse(SetOf([1, 2, 3]) < SetOf({1, 2, 3})) From 714edcd069677c9d1ac09a67f9e10e4dc2871ff4 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 1 Feb 2024 15:22:06 -0700 Subject: [PATCH 0874/1204] Expand the test to mixed-type RangeSets --- pyomo/core/tests/unit/test_set.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index 2154c02e659..72231bb08d7 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -1240,6 +1240,7 @@ def __len__(self): # Test floats self.assertEqual(RangeSet(0.0, 2.0), RangeSet(0.0, 2.0)) + self.assertEqual(RangeSet(0.0, 2.0), RangeSet(0, 2)) def test_inequality(self): self.assertTrue(SetOf([1, 2, 3]) <= SetOf({1, 2, 3})) From a2a5513a4d517e088b3970f83fb27faef0fbe7c7 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 1 Feb 2024 16:00:08 -0700 Subject: [PATCH 0875/1204] Add LegacySolverWrapper tests --- pyomo/contrib/solver/base.py | 14 ++- pyomo/contrib/solver/config.py | 11 ++- pyomo/contrib/solver/ipopt.py | 11 +-- pyomo/contrib/solver/tests/unit/test_base.py | 98 ++++++++++++++++++-- 4 files changed, 116 insertions(+), 18 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 1948bf8bf1b..42524296d74 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -21,6 +21,7 @@ from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.common.timing import HierarchicalTimer from pyomo.common.errors import ApplicationError +from pyomo.common.deprecation import deprecation_warning from pyomo.opt.results.results_ import SolverResults as LegacySolverResults from pyomo.opt.results.solution import Solution as LegacySolution from pyomo.core.kernel.objective import minimize @@ -378,8 +379,17 @@ def _map_config( raise NotImplementedError('Still working on this') if logfile is not None: raise NotImplementedError('Still working on this') - if 'keepfiles' in self.config: - self.config.keepfiles = keepfiles + if keepfiles or 'keepfiles' in self.config: + cwd = os.getcwd() + deprecation_warning( + "`keepfiles` has been deprecated in the new solver interface. " + "Use `working_dir` instead to designate a directory in which " + f"files should be generated and saved. Setting `working_dir` to `{cwd}`.", + version='6.7.1.dev0', + ) + self.config.working_dir = cwd + # I believe this currently does nothing; however, it is unclear what + # our desired behavior is for this. if solnfile is not None: if 'filename' in self.config: filename = os.path.splitext(solnfile)[0] diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index 4c81d31a820..d5921c526b0 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -58,6 +58,14 @@ def __init__( description="If True, the solver output gets logged.", ), ) + self.working_dir: str = self.declare( + 'working_dir', + ConfigValue( + domain=str, + default=None, + description="The directory in which generated files should be saved. This replaced the `keepfiles` option.", + ), + ) self.load_solutions: bool = self.declare( 'load_solutions', ConfigValue( @@ -79,7 +87,8 @@ def __init__( ConfigValue( domain=bool, default=False, - description="If True, the names given to the solver will reflect the names of the Pyomo components. Cannot be changed after set_instance is called.", + description="If True, the names given to the solver will reflect the names of the Pyomo components." + "Cannot be changed after set_instance is called.", ), ) self.timer: HierarchicalTimer = self.declare( diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 7f62d67d38e..4c4b932381d 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -68,11 +68,6 @@ def __init__( self.executable = self.declare( 'executable', ConfigValue(default=Executable('ipopt')) ) - # TODO: Add in a deprecation here for keepfiles - # M.B.: Is the above TODO still relevant? - self.temp_dir: str = self.declare( - 'temp_dir', ConfigValue(domain=str, default=None) - ) self.writer_config = self.declare( 'writer_config', ConfigValue(default=NLWriter.CONFIG()) ) @@ -262,7 +257,7 @@ def _create_command_line(self, basename: str, config: ipoptConfig, opt_file: boo cmd.append('option_file_name=' + basename + '.opt') if 'option_file_name' in config.solver_options: raise ValueError( - 'Pyomo generates the ipopt options file as part of the solve method. ' + 'Pyomo generates the ipopt options file as part of the `solve` method. ' 'Add all options to ipopt.config.solver_options instead.' ) if ( @@ -298,10 +293,10 @@ def solve(self, model, **kwds): StaleFlagManager.mark_all_as_stale() results = ipoptResults() with TempfileManager.new_context() as tempfile: - if config.temp_dir is None: + if config.working_dir is None: dname = tempfile.mkdtemp() else: - dname = config.temp_dir + dname = config.working_dir if not os.path.exists(dname): os.mkdir(dname) basename = os.path.join(dname, model.name) diff --git a/pyomo/contrib/solver/tests/unit/test_base.py b/pyomo/contrib/solver/tests/unit/test_base.py index 5531e0530fc..00e38d9ac59 100644 --- a/pyomo/contrib/solver/tests/unit/test_base.py +++ b/pyomo/contrib/solver/tests/unit/test_base.py @@ -9,7 +9,10 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +import os + from pyomo.common import unittest +from pyomo.common.config import ConfigDict from pyomo.contrib.solver import base @@ -169,13 +172,11 @@ def test_context_manager(self): class TestLegacySolverWrapper(unittest.TestCase): def test_class_method_list(self): - expected_list = [ - 'available', - 'license_is_valid', - 'solve' - ] + expected_list = ['available', 'license_is_valid', 'solve'] method_list = [ - method for method in dir(base.LegacySolverWrapper) if method.startswith('_') is False + method + for method in dir(base.LegacySolverWrapper) + if method.startswith('_') is False ] self.assertEqual(sorted(expected_list), sorted(method_list)) @@ -184,4 +185,87 @@ def test_context_manager(self): with self.assertRaises(AttributeError) as context: instance.available() - + def test_map_config(self): + # Create a fake/empty config structure that can be added to an empty + # instance of LegacySolverWrapper + self.config = ConfigDict(implicit=True) + self.config.declare( + 'solver_options', + ConfigDict(implicit=True, description="Options to pass to the solver."), + ) + instance = base.LegacySolverWrapper() + instance.config = self.config + instance._map_config( + True, False, False, 20, True, False, None, None, None, False, None, None + ) + self.assertTrue(instance.config.tee) + self.assertFalse(instance.config.load_solutions) + self.assertEqual(instance.config.time_limit, 20) + # Report timing shouldn't be created because it no longer exists + with self.assertRaises(AttributeError) as context: + print(instance.config.report_timing) + # Keepfiles should not be created because we did not declare keepfiles on + # the original config + with self.assertRaises(AttributeError) as context: + print(instance.config.keepfiles) + # We haven't implemented solver_io, suffixes, or logfile + with self.assertRaises(NotImplementedError) as context: + instance._map_config( + False, + False, + False, + 20, + False, + False, + None, + None, + '/path/to/bogus/file', + False, + None, + None, + ) + with self.assertRaises(NotImplementedError) as context: + instance._map_config( + False, + False, + False, + 20, + False, + False, + None, + '/path/to/bogus/file', + None, + False, + None, + None, + ) + with self.assertRaises(NotImplementedError) as context: + instance._map_config( + False, + False, + False, + 20, + False, + False, + '/path/to/bogus/file', + None, + None, + False, + None, + None, + ) + # If they ask for keepfiles, we redirect them to working_dir + instance._map_config( + False, False, False, 20, False, False, None, None, None, True, None, None + ) + self.assertEqual(instance.config.working_dir, os.getcwd()) + with self.assertRaises(AttributeError) as context: + print(instance.config.keepfiles) + + def test_map_results(self): + # Unclear how to test this + pass + + def test_solution_handler(self): + # Unclear how to test this + pass From 93a04098e02a7c6c705d13a353565fa9ebe77db8 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 1 Feb 2024 16:06:46 -0700 Subject: [PATCH 0876/1204] Starting to draft correct mapping for disaggregated vars--this is totally broken --- pyomo/gdp/plugins/hull.py | 45 +++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 3d2be2f9e15..c7a005bb4ea 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -599,6 +599,9 @@ def _transform_disjunct(self, obj, transBlock, vars_to_disaggregate, local_vars, bigmConstraint = Constraint(transBlock.lbub) relaxationBlock.add_component(conName, bigmConstraint) + parent_block = var.parent_block() + disaggregated_var_map = self._get_disaggregated_var_map(parent_block) + print("Adding bounds constraints for local var '%s'" % var) # TODO: This gets mapped in a place where we can't find it if we ask # for it from the local var itself. @@ -610,7 +613,7 @@ def _transform_disjunct(self, obj, transBlock, vars_to_disaggregate, local_vars, 'lb', 'ub', obj.indicator_var.get_associated_binary(), - transBlock, + disaggregated_var_map, ) var_substitute_map = dict( @@ -647,10 +650,8 @@ def _declare_disaggregated_var_bounds( lb_idx, ub_idx, var_free_indicator, - transBlock=None, + disaggregated_var_map, ): - # If transBlock is None then this is a disaggregated variable for - # multiple Disjuncts and we will handle the mappings separately. lb = original_var.lb ub = original_var.ub if lb is None or ub is None: @@ -669,13 +670,18 @@ def _declare_disaggregated_var_bounds( bigmConstraint.add(ub_idx, disaggregatedVar <= ub * var_free_indicator) # store the mappings from variables to their disaggregated selves on - # the transformation block. - if transBlock is not None: - transBlock._disaggregatedVarMap['disaggregatedVar'][disjunct][ - original_var - ] = disaggregatedVar - transBlock._disaggregatedVarMap['srcVar'][disaggregatedVar] = original_var - transBlock._bigMConstraintMap[disaggregatedVar] = bigmConstraint + # the transformation block + disaggregated_var_map['disaggregatedVar'][disjunct][ + original_var] = disaggregatedVar + disaggregated_var_map['srcVar'][disaggregatedVar] = original_var + bigMConstraintMap[disaggregatedVar] = bigmConstraint + + # if transBlock is not None: + # transBlock._disaggregatedVarMap['disaggregatedVar'][disjunct][ + # original_var + # ] = disaggregatedVar + # transBlock._disaggregatedVarMap['srcVar'][disaggregatedVar] = original_var + # transBlock._bigMConstraintMap[disaggregatedVar] = bigmConstraint def _get_local_var_list(self, parent_disjunct): # Add or retrieve Suffix from parent_disjunct so that, if this is @@ -916,7 +922,7 @@ def get_src_var(self, disaggregated_var): Parameters ---------- - disaggregated_var: a Var which was created by the hull + disaggregated_var: a Var that was created by the hull transformation as a disaggregated variable (and so appears on a transformation block of some Disjunct) @@ -925,17 +931,14 @@ def get_src_var(self, disaggregated_var): "'%s' does not appear to be a " "disaggregated variable" % disaggregated_var.name ) - # There are two possibilities: It is declared on a Disjunct - # transformation Block, or it is declared on the parent of a Disjunct - # transformation block (if it is a single variable for multiple - # Disjuncts the original doesn't appear in) + # We always put a dictionary called '_disaggregatedVarMap' on the parent + # block of the variable. If it's not there, then this probably isn't a + # disaggregated Var (or if it is it's a developer error). Similarly, if + # the var isn't in the dictionary, if we're doing what we should, then + # it's not a disaggregated var. transBlock = disaggregated_var.parent_block() if not hasattr(transBlock, '_disaggregatedVarMap'): - try: - transBlock = transBlock.parent_block().parent_block() - except: - logger.error(msg) - raise + raise GDP_Error(msg) try: return transBlock._disaggregatedVarMap['srcVar'][disaggregated_var] except: From 2c8a4d818c70c7088a4f45d6b5fa23b07f028a75 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 1 Feb 2024 16:48:59 -0700 Subject: [PATCH 0877/1204] Backwards compatibility; add tests --- pyomo/contrib/solver/tests/unit/test_util.py | 43 ++++++++++++++++- pyomo/contrib/solver/util.py | 50 +++++++++++++++----- 2 files changed, 80 insertions(+), 13 deletions(-) diff --git a/pyomo/contrib/solver/tests/unit/test_util.py b/pyomo/contrib/solver/tests/unit/test_util.py index 9bf92af72cf..8a8a0221362 100644 --- a/pyomo/contrib/solver/tests/unit/test_util.py +++ b/pyomo/contrib/solver/tests/unit/test_util.py @@ -11,9 +11,18 @@ from pyomo.common import unittest import pyomo.environ as pyo -from pyomo.contrib.solver.util import collect_vars_and_named_exprs, get_objective +from pyomo.contrib.solver.util import ( + collect_vars_and_named_exprs, + get_objective, + check_optimal_termination, + assert_optimal_termination, + SolverStatus, + LegacyTerminationCondition, +) +from pyomo.contrib.solver.results import Results, SolutionStatus, TerminationCondition from typing import Callable from pyomo.common.gsl import find_GSL +from pyomo.opt.results import SolverResults class TestGenericUtils(unittest.TestCase): @@ -73,3 +82,35 @@ def test_get_objective_raise(self): model.OBJ2 = pyo.Objective(expr=model.x[1] - 4 * model.x[2]) with self.assertRaises(ValueError): get_objective(model) + + def test_check_optimal_termination_new_interface(self): + results = Results() + results.solution_status = SolutionStatus.optimal + results.termination_condition = ( + TerminationCondition.convergenceCriteriaSatisfied + ) + # Both items satisfied + self.assertTrue(check_optimal_termination(results)) + # Termination condition not satisfied + results.termination_condition = TerminationCondition.iterationLimit + self.assertFalse(check_optimal_termination(results)) + # Both not satisfied + results.solution_status = SolutionStatus.noSolution + self.assertFalse(check_optimal_termination(results)) + + def test_check_optimal_termination_condition_legacy_interface(self): + results = SolverResults() + results.solver.status = SolverStatus.ok + results.solver.termination_condition = LegacyTerminationCondition.optimal + self.assertTrue(check_optimal_termination(results)) + results.solver.termination_condition = LegacyTerminationCondition.unknown + self.assertFalse(check_optimal_termination(results)) + results.solver.termination_condition = SolverStatus.aborted + self.assertFalse(check_optimal_termination(results)) + + # TODO: Left off here; need to make these tests + def test_assert_optimal_termination_new_interface(self): + pass + + def test_assert_optimal_termination_legacy_interface(self): + pass diff --git a/pyomo/contrib/solver/util.py b/pyomo/contrib/solver/util.py index f8641b06c50..807d66f569e 100644 --- a/pyomo/contrib/solver/util.py +++ b/pyomo/contrib/solver/util.py @@ -22,10 +22,20 @@ from pyomo.common.collections import ComponentMap from pyomo.common.timing import HierarchicalTimer from pyomo.core.expr.numvalue import NumericConstant +from pyomo.opt.results.solver import ( + SolverStatus, + TerminationCondition as LegacyTerminationCondition, +) + + from pyomo.contrib.solver.results import TerminationCondition, SolutionStatus def get_objective(block): + """ + Get current active objective on a block. If there is more than one active, + return an error. + """ obj = None for o in block.component_data_objects( Objective, descend_into=True, active=True, sort=True @@ -37,8 +47,6 @@ def get_objective(block): def check_optimal_termination(results): - # TODO: Make work for legacy and new results objects. - # Look at the original version of this function to make that happen. """ This function returns True if the termination condition for the solver is 'optimal'. @@ -51,11 +59,21 @@ def check_optimal_termination(results): ------- `bool` """ - if results.solution_status == SolutionStatus.optimal and ( - results.termination_condition - == TerminationCondition.convergenceCriteriaSatisfied - ): - return True + if hasattr(results, 'solution_status'): + if results.solution_status == SolutionStatus.optimal and ( + results.termination_condition + == TerminationCondition.convergenceCriteriaSatisfied + ): + return True + else: + if results.solver.status == SolverStatus.ok and ( + results.solver.termination_condition == LegacyTerminationCondition.optimal + or results.solver.termination_condition + == LegacyTerminationCondition.locallyOptimal + or results.solver.termination_condition + == LegacyTerminationCondition.globallyOptimal + ): + return True return False @@ -70,12 +88,20 @@ def assert_optimal_termination(results): results : Pyomo Results object returned from solver.solve """ if not check_optimal_termination(results): - msg = ( - 'Solver failed to return an optimal solution. ' - 'Solution status: {}, Termination condition: {}'.format( - results.solution_status, results.termination_condition + if hasattr(results, 'solution_status'): + msg = ( + 'Solver failed to return an optimal solution. ' + 'Solution status: {}, Termination condition: {}'.format( + results.solution_status, results.termination_condition + ) + ) + else: + msg = ( + 'Solver failed to return an optimal solution. ' + 'Solver status: {}, Termination condition: {}'.format( + results.solver.status, results.solver.termination_condition + ) ) - ) raise RuntimeError(msg) From 6923e3639c8c0b2faac479624bca1f9a3c92d123 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 1 Feb 2024 20:50:12 -0700 Subject: [PATCH 0878/1204] Raising an error for unrecognized active Suffixes, ignoring LocalVars suffix, and logging a message at the debug level about ignoring BigM Suffixes --- pyomo/gdp/plugins/multiple_bigm.py | 19 +++++++++++++++++ pyomo/gdp/tests/test_mbigm.py | 34 ++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index 48ec1177fe5..acd96c488b3 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -202,6 +202,7 @@ def __init__(self): super().__init__(logger) self._arg_list = {} self._set_up_expr_bound_visitor() + self.handlers[Suffix] = self._warn_for_active_suffix def _apply_to(self, instance, **kwds): self.used_args = ComponentMap() @@ -693,6 +694,24 @@ def _calculate_missing_M_values( return arg_Ms + def _warn_for_active_suffix(self, suffix, disjunct, active_disjuncts, Ms): + if suffix.local_name == 'BigM': + logger.debug( + "Found active 'BigM' Suffix on '{0}'. " + "The multiple bigM transformation does not currently " + "support specifying M's with Suffixes and is ignoring " + "this Suffix.".format(disjunct.name) + ) + elif suffix.local_name == 'LocalVars': + # This is fine, but this transformation doesn't need anything from it + pass + else: + raise GDP_Error( + "Found active Suffix '{0}' on Disjunct '{1}'. " + "The multiple bigM transformation does not currently " + "support Suffixes.".format(suffix.name, disjunct.name) + ) + # These are all functions to retrieve transformed components from # original ones and vice versa. diff --git a/pyomo/gdp/tests/test_mbigm.py b/pyomo/gdp/tests/test_mbigm.py index 0cdf004a445..33e8781ac63 100644 --- a/pyomo/gdp/tests/test_mbigm.py +++ b/pyomo/gdp/tests/test_mbigm.py @@ -352,6 +352,40 @@ def test_transformed_constraints_correct_Ms_specified(self): self.check_all_untightened_bounds_constraints(m, mbm) self.check_linear_func_constraints(m, mbm) + def test_local_var_suffix_ignored(self): + m = self.make_model() + m.y = Var(bounds=(2, 5)) + m.d1.another_thing = Constraint(expr=m.y == 3) + m.d1.LocalVars = Suffix(direction=Suffix.LOCAL) + m.d1.LocalVars[m.d1] = m.y + + mbigm = TransformationFactory('gdp.mbigm') + mbigm.apply_to(m, reduce_bound_constraints=True, + only_mbigm_bound_constraints=True) + + cons = mbigm.get_transformed_constraints(m.d1.x1_bounds) + self.check_pretty_bound_constraints( + cons[0], m.x1, {m.d1: 0.5, m.d2: 0.65, m.d3: 2}, lb=True + ) + self.check_pretty_bound_constraints( + cons[1], m.x1, {m.d1: 2, m.d2: 3, m.d3: 10}, lb=False + ) + + cons = mbigm.get_transformed_constraints(m.d1.x2_bounds) + self.check_pretty_bound_constraints( + cons[0], m.x2, {m.d1: 0.75, m.d2: 3, m.d3: 0.55}, lb=True + ) + self.check_pretty_bound_constraints( + cons[1], m.x2, {m.d1: 3, m.d2: 10, m.d3: 1}, lb=False + ) + + cons = mbigm.get_transformed_constraints(m.d1.another_thing) + self.assertEqual(len(cons), 2) + self.check_pretty_bound_constraints( + cons[0], m.y, {m.d1: 3, m.d2: 2, m.d3: 2}, lb=True) + self.check_pretty_bound_constraints( + cons[1], m.y, {m.d1: 3, m.d2: 5, m.d3: 5}, lb=False) + def test_pickle_transformed_model(self): m = self.make_model() TransformationFactory('gdp.mbigm').apply_to(m, bigM=self.get_Ms(m)) From 307caa875ad7f52268ed79c7130a9765c23707b2 Mon Sep 17 00:00:00 2001 From: Sakshi <73687517+Sakshi21299@users.noreply.github.com> Date: Fri, 2 Feb 2024 16:47:16 -0500 Subject: [PATCH 0879/1204] applied black --- pyomo/contrib/incidence_analysis/interface.py | 79 +++++++++---------- .../tests/test_interface.py | 42 +++++----- 2 files changed, 58 insertions(+), 63 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 177ca97a6b6..23178bdf14b 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -47,7 +47,7 @@ from pyomo.contrib.pynumero.asl import AmplInterface pyomo_nlp, pyomo_nlp_available = attempt_import( - 'pyomo.contrib.pynumero.interfaces.pyomo_nlp' + "pyomo.contrib.pynumero.interfaces.pyomo_nlp" ) asl_available = pyomo_nlp_available & AmplInterface.available() @@ -886,9 +886,9 @@ def plot(self, variables=None, constraints=None, title=None, show=True): edge_trace = plotly.graph_objects.Scatter( x=edge_x, y=edge_y, - line=dict(width=0.5, color='#888'), - hoverinfo='none', - mode='lines', + line=dict(width=0.5, color="#888"), + hoverinfo="none", + mode="lines", ) node_x = [] @@ -902,28 +902,28 @@ def plot(self, variables=None, constraints=None, title=None, show=True): if node < M: # According to convention, we are a constraint node c = constraints[node] - node_color.append('red') - body_text = '
'.join( + node_color.append("red") + body_text = "
".join( textwrap.wrap(str(c.body), width=120, subsequent_indent=" ") ) node_text.append( - f'{str(c)}
lb: {str(c.lower)}
body: {body_text}
' - f'ub: {str(c.upper)}
active: {str(c.active)}' + f"{str(c)}
lb: {str(c.lower)}
body: {body_text}
" + f"ub: {str(c.upper)}
active: {str(c.active)}" ) else: # According to convention, we are a variable node v = variables[node - M] - node_color.append('blue') + node_color.append("blue") node_text.append( - f'{str(v)}
lb: {str(v.lb)}
ub: {str(v.ub)}
' - f'value: {str(v.value)}
domain: {str(v.domain)}
' - f'fixed: {str(v.is_fixed())}' + f"{str(v)}
lb: {str(v.lb)}
ub: {str(v.ub)}
" + f"value: {str(v.value)}
domain: {str(v.domain)}
" + f"fixed: {str(v.is_fixed())}" ) node_trace = plotly.graph_objects.Scatter( x=node_x, y=node_y, - mode='markers', - hoverinfo='text', + mode="markers", + hoverinfo="text", text=node_text, marker=dict(color=node_color, size=10), ) @@ -932,17 +932,17 @@ def plot(self, variables=None, constraints=None, title=None, show=True): fig.update_layout(title=dict(text=title)) if show: fig.show() - + def add_edge_to_graph(self, node0, node1): """Adds an edge between node0 and node1 in the incidence graph - + Parameters --------- nodes0: VarData/ConstraintData - A node in the graph from the first bipartite set + A node in the graph from the first bipartite set (``bipartite=0``) node1: VarData/ConstraintData - A node in the graph from the second bipartite set + A node in the graph from the second bipartite set (``bipartite=1``) """ if self._incidence_graph is None: @@ -950,40 +950,35 @@ def add_edge_to_graph(self, node0, node1): "Attempting to add edge in an incidence graph from cached " "incidence graph,\nbut no incidence graph has been cached." ) - - if node0 not in ComponentSet(self._variables) and node0 not in ComponentSet(self._constraints): - raise RuntimeError( - "%s is not a node in the incidence graph" % node0 - ) - - if node1 not in ComponentSet(self._variables) and node1 not in ComponentSet(self._constraints): - raise RuntimeError( - "%s is not a node in the incidence graph" % node1 - ) - + + if node0 not in ComponentSet(self._variables) and node0 not in ComponentSet( + self._constraints + ): + raise RuntimeError("%s is not a node in the incidence graph" % node0) + + if node1 not in ComponentSet(self._variables) and node1 not in ComponentSet( + self._constraints + ): + raise RuntimeError("%s is not a node in the incidence graph" % node1) + if node0 in ComponentSet(self._variables): - node0_idx = self._var_index_map[node0] + len(self._con_index_map) + node0_idx = self._var_index_map[node0] + len(self._con_index_map) if node1 in ComponentSet(self._variables): raise RuntimeError( "%s & %s are both variables. Cannot add an edge between two" - "variables.\nThe resulting graph won't be bipartite" + "variables.\nThe resulting graph won't be bipartite" % (node0, node1) - ) + ) node1_idx = self._con_index_map[node1] - + if node0 in ComponentSet(self._constraints): node0_idx = self._con_index_map[node0] if node1 in ComponentSet(self._constraints): raise RuntimeError( "%s & %s are both constraints. Cannot add an edge between two" - "constraints.\nThe resulting graph won't be bipartite" + "constraints.\nThe resulting graph won't be bipartite" % (node0, node1) - ) - node1_idx = self._var_index_map[node1] + len(self._con_index_map) - + ) + node1_idx = self._var_index_map[node1] + len(self._con_index_map) + self._incidence_graph.add_edge(node0_idx, node1_idx) - - - - - diff --git a/pyomo/contrib/incidence_analysis/tests/test_interface.py b/pyomo/contrib/incidence_analysis/tests/test_interface.py index 63bc74ee6dc..7da563be28c 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_interface.py +++ b/pyomo/contrib/incidence_analysis/tests/test_interface.py @@ -638,13 +638,13 @@ def test_exception(self): variables = [model.P] constraints = [model.ideal_gas] igraph.maximum_matching(variables, constraints) - self.assertIn('must be unindexed', str(exc.exception)) + self.assertIn("must be unindexed", str(exc.exception)) with self.assertRaises(RuntimeError) as exc: variables = [model.P] constraints = [model.ideal_gas] igraph.block_triangularize(variables, constraints) - self.assertIn('must be unindexed', str(exc.exception)) + self.assertIn("must be unindexed", str(exc.exception)) @unittest.skipUnless(networkx_available, "networkx is not available.") @@ -889,13 +889,13 @@ def test_exception(self): variables = [model.P] constraints = [model.ideal_gas] igraph.maximum_matching(variables, constraints) - self.assertIn('must be unindexed', str(exc.exception)) + self.assertIn("must be unindexed", str(exc.exception)) with self.assertRaises(RuntimeError) as exc: variables = [model.P] constraints = [model.ideal_gas] igraph.block_triangularize(variables, constraints) - self.assertIn('must be unindexed', str(exc.exception)) + self.assertIn("must be unindexed", str(exc.exception)) @unittest.skipUnless(scipy_available, "scipy is not available.") def test_remove(self): @@ -1745,7 +1745,7 @@ def test_plot(self): m.c2 = pyo.Constraint(expr=m.z >= m.x) m.y.fix() igraph = IncidenceGraphInterface(m, include_inequality=True, include_fixed=True) - igraph.plot(title='test plot', show=False) + igraph.plot(title="test plot", show=False) def test_zero_coeff(self): m = pyo.ConcreteModel() @@ -1790,15 +1790,15 @@ def test_linear_only(self): self.assertEqual(len(matching), 2) self.assertIs(matching[m.eq2], m.x[2]) self.assertIs(matching[m.eq3], m.x[3]) - + def test_add_edge(self): m = pyo.ConcreteModel() m.x = pyo.Var([1, 2, 3, 4]) m.eq1 = pyo.Constraint(expr=m.x[1] ** 2 + m.x[2] ** 2 + m.x[3] ** 2 == 1) m.eq2 = pyo.Constraint(expr=m.x[2] + pyo.sqrt(m.x[1]) + pyo.exp(m.x[3]) == 1) m.eq3 = pyo.Constraint(expr=m.x[3] + m.x[2] + m.x[4] == 1) - m.eq4 = pyo.Constraint(expr=m.x[1] + m.x[2]**2 == 5) - + m.eq4 = pyo.Constraint(expr=m.x[1] + m.x[2] ** 2 == 5) + # nodes: component # 0 : eq1 # 1 : eq2 @@ -1807,41 +1807,41 @@ def test_add_edge(self): # 4 : x[1] # 5 : x[2] # 6 : x[3] - # 7 : x[4] - + # 7 : x[4] + igraph = IncidenceGraphInterface(m, linear_only=False) n_edges_original = igraph.n_edges - - #Test if there already exists an edge between two nodes, nothing is added + + # Test if there already exists an edge between two nodes, nothing is added igraph.add_edge_to_graph(m.eq3, m.x[4]) n_edges_new = igraph.n_edges self.assertEqual(n_edges_original, n_edges_new) - + igraph.add_edge_to_graph(m.x[1], m.eq3) n_edges_new = igraph.n_edges self.assertEqual(set(igraph._incidence_graph[2]), {6, 5, 7, 4}) - self.assertEqual(n_edges_original +1, n_edges_new) - + self.assertEqual(n_edges_original + 1, n_edges_new) + igraph.add_edge_to_graph(m.eq4, m.x[4]) n_edges_new = igraph.n_edges self.assertEqual(set(igraph._incidence_graph[3]), {4, 5, 7}) self.assertEqual(n_edges_original + 2, n_edges_new) - + def test_add_edge_linear_igraph(self): m = pyo.ConcreteModel() m.x = pyo.Var([1, 2, 3, 4]) - m.eq1 = pyo.Constraint(expr=m.x[1] + m.x[3] == 1) + m.eq1 = pyo.Constraint(expr=m.x[1] + m.x[3] == 1) m.eq2 = pyo.Constraint(expr=m.x[2] + pyo.sqrt(m.x[1]) + pyo.exp(m.x[3]) == 1) - m.eq3 = pyo.Constraint(expr=m.x[4]**2 + m.x[1] ** 3 + m.x[2] == 1) - - #Make sure error is raised when a variable is not in the igraph + m.eq3 = pyo.Constraint(expr=m.x[4] ** 2 + m.x[1] ** 3 + m.x[2] == 1) + + # Make sure error is raised when a variable is not in the igraph igraph = IncidenceGraphInterface(m, linear_only=True) n_edges_original = igraph.n_edges msg = "is not a node in the incidence graph" with self.assertRaisesRegex(RuntimeError, msg): igraph.add_edge_to_graph(m.x[4], m.eq2) - + @unittest.skipUnless(networkx_available, "networkx is not available.") class TestIndexedBlock(unittest.TestCase): From 5f8fb1e242b06247316d9d735e77ee801794f936 Mon Sep 17 00:00:00 2001 From: robbybp Date: Sun, 4 Feb 2024 23:17:32 -0700 Subject: [PATCH 0880/1204] initial implementation of IncidenceGraphInterface.subgraph method --- pyomo/contrib/incidence_analysis/interface.py | 70 +++++++++++++------ 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index b8a6c1275f9..4c970045814 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -138,31 +138,39 @@ def extract_bipartite_subgraph(graph, nodes0, nodes1): in the original graph. """ - subgraph = nx.Graph() - sub_M = len(nodes0) - sub_N = len(nodes1) - subgraph.add_nodes_from(range(sub_M), bipartite=0) - subgraph.add_nodes_from(range(sub_M, sub_M + sub_N), bipartite=1) - + subgraph = graph.subgraph(nodes0 + nodes1) old_new_map = {} for i, node in enumerate(nodes0 + nodes1): if node in old_new_map: raise RuntimeError("Node %s provided more than once.") old_new_map[node] = i - - for node1, node2 in graph.edges(): - if node1 in old_new_map and node2 in old_new_map: - new_node_1 = old_new_map[node1] - new_node_2 = old_new_map[node2] - if ( - subgraph.nodes[new_node_1]["bipartite"] - == subgraph.nodes[new_node_2]["bipartite"] - ): - raise RuntimeError( - "Subgraph is not bipartite. Found an edge between nodes" - " %s and %s (in the original graph)." % (node1, node2) - ) - subgraph.add_edge(new_node_1, new_node_2) + relabeled_subgraph = nx.relabel_nodes(subgraph, old_new_map) + return relabeled_subgraph + #subgraph = nx.Graph() + #sub_M = len(nodes0) + #sub_N = len(nodes1) + #subgraph.add_nodes_from(range(sub_M), bipartite=0) + #subgraph.add_nodes_from(range(sub_M, sub_M + sub_N), bipartite=1) + + #old_new_map = {} + #for i, node in enumerate(nodes0 + nodes1): + # if node in old_new_map: + # raise RuntimeError("Node %s provided more than once.") + # old_new_map[node] = i + + #for node1, node2 in graph.edges(): + # if node1 in old_new_map and node2 in old_new_map: + # new_node_1 = old_new_map[node1] + # new_node_2 = old_new_map[node2] + # if ( + # subgraph.nodes[new_node_1]["bipartite"] + # == subgraph.nodes[new_node_2]["bipartite"] + # ): + # raise RuntimeError( + # "Subgraph is not bipartite. Found an edge between nodes" + # " %s and %s (in the original graph)." % (node1, node2) + # ) + # subgraph.add_edge(new_node_1, new_node_2) return subgraph @@ -334,6 +342,22 @@ def __init__(self, model=None, active=True, include_inequality=True, **kwds): incidence_matrix = nlp.evaluate_jacobian_eq() nxb = nx.algorithms.bipartite self._incidence_graph = nxb.from_biadjacency_matrix(incidence_matrix) + elif isinstance(model, tuple): + # model is a tuple of (nx.Graph, list[pyo.Var], list[pyo.Constraint]) + # We could potentially accept a tuple (variables, constraints). + # TODO: Disallow kwargs if this type of "model" is provided? + nx_graph, variables, constraints = model + self._variables = list(variables) + self._constraints = list(constraints) + self._var_index_map = ComponentMap( + (var, i) for i, var in enumerate(self._variables) + ) + self._con_index_map = ComponentMap( + (con, i) for i, con in enumerate(self._constraints) + ) + # For now, don't check any properties of this graph. We could check + # for a bipartition that matches the variable and constraint lists. + self._incidence_graph = nx_graph else: raise TypeError( "Unsupported type for incidence graph. Expected PyomoNLP" @@ -468,6 +492,12 @@ def _extract_subgraph(self, variables, constraints): ) return subgraph + def subgraph(self, variables, constraints): + # TODO: copy=True argument we can use to optionally modify in-place? + nx_subgraph = self._extract_subgraph(variables, constraints) + subgraph = IncidenceGraphInterface((nx_subgraph, variables, constraints), **self._config) + return subgraph + @property def incidence_matrix(self): """The structural incidence matrix of variables and constraints. From f0c538f1690f01107a9dfc6fbf1a17eb06422bef Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 5 Feb 2024 00:33:11 -0700 Subject: [PATCH 0881/1204] remove unused code from extract_bipartite_subgraph --- pyomo/contrib/incidence_analysis/interface.py | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 4c970045814..6680c32d7c2 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -139,6 +139,7 @@ def extract_bipartite_subgraph(graph, nodes0, nodes1): """ subgraph = graph.subgraph(nodes0 + nodes1) + # TODO: Any error checking that nodes are valid bipartition? old_new_map = {} for i, node in enumerate(nodes0 + nodes1): if node in old_new_map: @@ -146,32 +147,6 @@ def extract_bipartite_subgraph(graph, nodes0, nodes1): old_new_map[node] = i relabeled_subgraph = nx.relabel_nodes(subgraph, old_new_map) return relabeled_subgraph - #subgraph = nx.Graph() - #sub_M = len(nodes0) - #sub_N = len(nodes1) - #subgraph.add_nodes_from(range(sub_M), bipartite=0) - #subgraph.add_nodes_from(range(sub_M, sub_M + sub_N), bipartite=1) - - #old_new_map = {} - #for i, node in enumerate(nodes0 + nodes1): - # if node in old_new_map: - # raise RuntimeError("Node %s provided more than once.") - # old_new_map[node] = i - - #for node1, node2 in graph.edges(): - # if node1 in old_new_map and node2 in old_new_map: - # new_node_1 = old_new_map[node1] - # new_node_2 = old_new_map[node2] - # if ( - # subgraph.nodes[new_node_1]["bipartite"] - # == subgraph.nodes[new_node_2]["bipartite"] - # ): - # raise RuntimeError( - # "Subgraph is not bipartite. Found an edge between nodes" - # " %s and %s (in the original graph)." % (node1, node2) - # ) - # subgraph.add_edge(new_node_1, new_node_2) - return subgraph def _generate_variables_in_constraints(constraints, **kwds): From 19bda95945964588fee8e161ea281f60dc955d62 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 5 Feb 2024 00:33:24 -0700 Subject: [PATCH 0882/1204] test for subgraph method --- .../tests/test_interface.py | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/tests/test_interface.py b/pyomo/contrib/incidence_analysis/tests/test_interface.py index 490ea94f63c..8a0049d4225 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_interface.py +++ b/pyomo/contrib/incidence_analysis/tests/test_interface.py @@ -1651,11 +1651,12 @@ def test_extract_exceptions(self): variables = list(m.v.values()) graph = get_bipartite_incidence_graph(variables, constraints) - sg_cons = [0, 2, 5] - sg_vars = [i + len(constraints) for i in [2, 3]] - msg = "Subgraph is not bipartite" - with self.assertRaisesRegex(RuntimeError, msg): - subgraph = extract_bipartite_subgraph(graph, sg_cons, sg_vars) + # TODO: Fix this test + #sg_cons = [0, 2, 5] + #sg_vars = [i + len(constraints) for i in [2, 3]] + #msg = "Subgraph is not bipartite" + #with self.assertRaisesRegex(RuntimeError, msg): + # subgraph = extract_bipartite_subgraph(graph, sg_cons, sg_vars) sg_cons = [0, 2, 5] sg_vars = [i + len(constraints) for i in [2, 0, 3]] @@ -1791,6 +1792,33 @@ def test_linear_only(self): self.assertIs(matching[m.eq2], m.x[2]) self.assertIs(matching[m.eq3], m.x[3]) + def test_subgraph(self): + m = pyo.ConcreteModel() + m.I = pyo.Set(initialize=[1, 2, 3, 4]) + m.v = pyo.Var(m.I, bounds=(0, None)) + m.eq1 = pyo.Constraint(expr=m.v[1] ** 2 + m.v[2] ** 2 == 1.0) + m.eq2 = pyo.Constraint(expr=m.v[1] + 2.0 == m.v[3]) + m.ineq1 = pyo.Constraint(expr=m.v[2] - m.v[3] ** 0.5 + m.v[4] ** 2 <= 1.0) + m.ineq2 = pyo.Constraint(expr=m.v[2] * m.v[4] >= 1.0) + m.ineq3 = pyo.Constraint(expr=m.v[1] >= m.v[4] ** 4) + m.obj = pyo.Objective(expr=-m.v[1] - m.v[2] + m.v[3] ** 2 + m.v[4] ** 2) + igraph = IncidenceGraphInterface(m) + eq_igraph = igraph.subgraph(igraph.variables, [m.eq1, m.eq2]) + for i in range(len(igraph.variables)): + self.assertIs(igraph.variables[i], eq_igraph.variables[i]) + self.assertEqual( + ComponentSet(eq_igraph.constraints), ComponentSet([m.eq1, m.eq2]) + ) + + subgraph = eq_igraph.subgraph([m.v[1], m.v[3]], [m.eq1, m.eq2]) + self.assertEqual( + ComponentSet(subgraph.get_adjacent_to(m.eq2)), + ComponentSet([m.v[1], m.v[3]]), + ) + self.assertEqual( + ComponentSet(subgraph.get_adjacent_to(m.eq1)), ComponentSet([m.v[1]]), + ) + @unittest.skipUnless(networkx_available, "networkx is not available.") class TestIndexedBlock(unittest.TestCase): From adfc543979d655a2a906185b07df509b41bc28bb Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 5 Feb 2024 00:37:32 -0700 Subject: [PATCH 0883/1204] reimplement error checking for bad bipartite sets and update test for new error message --- pyomo/contrib/incidence_analysis/interface.py | 14 ++++++++++++++ .../incidence_analysis/tests/test_interface.py | 13 ++++++------- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 6680c32d7c2..73264a0e113 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -140,6 +140,20 @@ def extract_bipartite_subgraph(graph, nodes0, nodes1): """ subgraph = graph.subgraph(nodes0 + nodes1) # TODO: Any error checking that nodes are valid bipartition? + for node in nodes0: + bipartite = graph.nodes[node]["bipartite"] + if bipartite != 0: + raise RuntimeError( + "Invalid bipartite sets. Node {node} in set 0 has" + " bipartite={bipartite}" + ) + for node in nodes1: + bipartite = graph.nodes[node]["bipartite"] + if bipartite != 1: + raise RuntimeError( + "Invalid bipartite sets. Node {node} in set 1 has" + " bipartite={bipartite}" + ) old_new_map = {} for i, node in enumerate(nodes0 + nodes1): if node in old_new_map: diff --git a/pyomo/contrib/incidence_analysis/tests/test_interface.py b/pyomo/contrib/incidence_analysis/tests/test_interface.py index 8a0049d4225..c7a74ae5784 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_interface.py +++ b/pyomo/contrib/incidence_analysis/tests/test_interface.py @@ -1651,14 +1651,13 @@ def test_extract_exceptions(self): variables = list(m.v.values()) graph = get_bipartite_incidence_graph(variables, constraints) - # TODO: Fix this test - #sg_cons = [0, 2, 5] - #sg_vars = [i + len(constraints) for i in [2, 3]] - #msg = "Subgraph is not bipartite" - #with self.assertRaisesRegex(RuntimeError, msg): - # subgraph = extract_bipartite_subgraph(graph, sg_cons, sg_vars) - sg_cons = [0, 2, 5] + sg_vars = [i + len(constraints) for i in [2, 3]] + msg = "Invalid bipartite sets." + with self.assertRaisesRegex(RuntimeError, msg): + subgraph = extract_bipartite_subgraph(graph, sg_cons, sg_vars) + + sg_cons = [0, 2, 0] sg_vars = [i + len(constraints) for i in [2, 0, 3]] msg = "provided more than once" with self.assertRaisesRegex(RuntimeError, msg): From da47a8bed30685e6a4b552e9afd65d50fa032a27 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 5 Feb 2024 00:42:11 -0700 Subject: [PATCH 0884/1204] docstring for subgraph method --- pyomo/contrib/incidence_analysis/interface.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 73264a0e113..d1eb90efbd7 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -482,7 +482,18 @@ def _extract_subgraph(self, variables, constraints): return subgraph def subgraph(self, variables, constraints): - # TODO: copy=True argument we can use to optionally modify in-place? + """Extract a subgraph defined by the provided variables and constraints + + Underlying data structures are copied, and constraints are not reinspected + for incidence variables (the edges from this incidence graph are used). + + Returns + ------- + ``IncidenceGraphInterface`` + A new incidence graph containing only the specified variables and + constraints, and the edges between pairs thereof. + + """ nx_subgraph = self._extract_subgraph(variables, constraints) subgraph = IncidenceGraphInterface((nx_subgraph, variables, constraints), **self._config) return subgraph From f4e9c974289f3991c63b6d122efbedda1e0f009c Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 5 Feb 2024 00:44:25 -0700 Subject: [PATCH 0885/1204] apply black --- pyomo/contrib/incidence_analysis/interface.py | 4 +++- pyomo/contrib/incidence_analysis/tests/test_interface.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index d1eb90efbd7..04b9e3c70f7 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -495,7 +495,9 @@ def subgraph(self, variables, constraints): """ nx_subgraph = self._extract_subgraph(variables, constraints) - subgraph = IncidenceGraphInterface((nx_subgraph, variables, constraints), **self._config) + subgraph = IncidenceGraphInterface( + (nx_subgraph, variables, constraints), **self._config + ) return subgraph @property diff --git a/pyomo/contrib/incidence_analysis/tests/test_interface.py b/pyomo/contrib/incidence_analysis/tests/test_interface.py index c7a74ae5784..2769a46a907 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_interface.py +++ b/pyomo/contrib/incidence_analysis/tests/test_interface.py @@ -1815,7 +1815,8 @@ def test_subgraph(self): ComponentSet([m.v[1], m.v[3]]), ) self.assertEqual( - ComponentSet(subgraph.get_adjacent_to(m.eq1)), ComponentSet([m.v[1]]), + ComponentSet(subgraph.get_adjacent_to(m.eq1)), + ComponentSet([m.v[1]]), ) From 149680e48dc773cf9caf19434d934d4f7128c1b1 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 5 Feb 2024 00:57:06 -0700 Subject: [PATCH 0886/1204] use the whole line to keep black happy --- pyomo/contrib/incidence_analysis/tests/test_interface.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/tests/test_interface.py b/pyomo/contrib/incidence_analysis/tests/test_interface.py index 2769a46a907..10777a35f78 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_interface.py +++ b/pyomo/contrib/incidence_analysis/tests/test_interface.py @@ -1815,8 +1815,7 @@ def test_subgraph(self): ComponentSet([m.v[1], m.v[3]]), ) self.assertEqual( - ComponentSet(subgraph.get_adjacent_to(m.eq1)), - ComponentSet([m.v[1]]), + ComponentSet(subgraph.get_adjacent_to(m.eq1)), ComponentSet([m.v[1]]) ) From 3760de2e36254457962d06b53d92046958ffa911 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 5 Feb 2024 11:39:45 -0700 Subject: [PATCH 0887/1204] Remove unneeded import --- pyomo/core/expr/logical_expr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/core/expr/logical_expr.py b/pyomo/core/expr/logical_expr.py index 875f5107f3a..48daa79a5b3 100644 --- a/pyomo/core/expr/logical_expr.py +++ b/pyomo/core/expr/logical_expr.py @@ -11,7 +11,7 @@ # ___________________________________________________________________________ import types -from itertools import combinations, islice +from itertools import islice import logging import traceback From b27dff5535c8c1241c41e7459bf71c3b082fae76 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 6 Feb 2024 13:10:57 -0700 Subject: [PATCH 0888/1204] revert --- pyomo/gdp/plugins/binary_multiplication.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyomo/gdp/plugins/binary_multiplication.py b/pyomo/gdp/plugins/binary_multiplication.py index 8489fa04808..dfdc87ded19 100644 --- a/pyomo/gdp/plugins/binary_multiplication.py +++ b/pyomo/gdp/plugins/binary_multiplication.py @@ -79,8 +79,7 @@ def _transform_disjunctionData( or_expr += disjunct.binary_indicator_var self._transform_disjunct(disjunct, transBlock) - # rhs = 1 if parent_disjunct is None else parent_disjunct.binary_indicator_var - rhs = 1 + rhs = 1 if parent_disjunct is None else parent_disjunct.binary_indicator_var if obj.xor: xorConstraint[index] = or_expr == rhs else: From d224bbe4df9f0a94010defce3f266b789590ba87 Mon Sep 17 00:00:00 2001 From: jasherma Date: Tue, 6 Feb 2024 19:48:45 -0500 Subject: [PATCH 0889/1204] Remove custom PyROS `ConfigDict` interfaces --- pyomo/contrib/pyros/pyros.py | 325 +++++------------------------------ 1 file changed, 44 insertions(+), 281 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 829184fc70c..f266b7451e6 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -13,7 +13,13 @@ import logging from textwrap import indent, dedent, wrap from pyomo.common.collections import Bunch, ComponentSet -from pyomo.common.config import ConfigDict, ConfigValue, In, NonNegativeFloat +from pyomo.common.config import ( + ConfigDict, + ConfigValue, + document_kwargs_from_configdict, + In, + NonNegativeFloat, +) from pyomo.core.base.block import Block from pyomo.core.expr import value from pyomo.core.base.var import Var, _VarData @@ -147,68 +153,6 @@ def __call__(self, obj): return ans -class PyROSConfigValue(ConfigValue): - """ - Subclass of ``common.collections.ConfigValue``, - with a few attributes added to facilitate documentation - of the PyROS solver. - An instance of this class is used for storing and - documenting an argument to the PyROS solver. - - Attributes - ---------- - is_optional : bool - Argument is optional. - document_default : bool, optional - Document the default value of the argument - in any docstring generated from this instance, - or a `ConfigDict` object containing this instance. - dtype_spec_str : None or str, optional - String documenting valid types for this argument. - If `None` is provided, then this string is automatically - determined based on the `domain` argument to the - constructor. - - NOTES - ----- - Cleaner way to access protected attributes - (particularly _doc, _description) inherited from ConfigValue? - - """ - - def __init__( - self, - default=None, - domain=None, - description=None, - doc=None, - visibility=0, - is_optional=True, - document_default=True, - dtype_spec_str=None, - ): - """Initialize self (see class docstring).""" - - # initialize base class attributes - super(self.__class__, self).__init__( - default=default, - domain=domain, - description=description, - doc=doc, - visibility=visibility, - ) - - self.is_optional = is_optional - self.document_default = document_default - - if dtype_spec_str is None: - self.dtype_spec_str = self.domain_name() - # except AttributeError: - # self.dtype_spec_str = repr(self._domain) - else: - self.dtype_spec_str = dtype_spec_str - - def pyros_config(): CONFIG = ConfigDict('PyROS') @@ -217,7 +161,7 @@ def pyros_config(): # ================================================ CONFIG.declare( 'time_limit', - PyROSConfigValue( + ConfigValue( default=None, domain=NonNegativeFloat, doc=( @@ -227,14 +171,11 @@ def pyros_config(): If `None` is provided, then no time limit is enforced. """ ), - is_optional=True, - document_default=False, - dtype_spec_str="None or NonNegativeFloat", ), ) CONFIG.declare( 'keepfiles', - PyROSConfigValue( + ConfigValue( default=False, domain=bool, description=( @@ -245,25 +186,19 @@ def pyros_config(): must also be specified. """ ), - is_optional=True, - document_default=True, - dtype_spec_str=None, ), ) CONFIG.declare( 'tee', - PyROSConfigValue( + ConfigValue( default=False, domain=bool, description="Output subordinate solver logs for all subproblems.", - is_optional=True, - document_default=True, - dtype_spec_str=None, ), ) CONFIG.declare( 'load_solution', - PyROSConfigValue( + ConfigValue( default=True, domain=bool, description=( @@ -272,9 +207,6 @@ def pyros_config(): provided. """ ), - is_optional=True, - document_default=True, - dtype_spec_str=None, ), ) @@ -283,27 +215,25 @@ def pyros_config(): # ================================================ CONFIG.declare( "first_stage_variables", - PyROSConfigValue( + ConfigValue( default=[], domain=InputDataStandardizer(Var, _VarData), description="First-stage (or design) variables.", - is_optional=False, - dtype_spec_str="list of Var", + visibility=1, ), ) CONFIG.declare( "second_stage_variables", - PyROSConfigValue( + ConfigValue( default=[], domain=InputDataStandardizer(Var, _VarData), description="Second-stage (or control) variables.", - is_optional=False, - dtype_spec_str="list of Var", + visibility=1, ), ) CONFIG.declare( "uncertain_params", - PyROSConfigValue( + ConfigValue( default=[], domain=InputDataStandardizer(Param, _ParamData), description=( @@ -313,13 +243,12 @@ def pyros_config(): objects should be set to True. """ ), - is_optional=False, - dtype_spec_str="list of Param", + visibility=1, ), ) CONFIG.declare( "uncertainty_set", - PyROSConfigValue( + ConfigValue( default=None, domain=uncertainty_sets, description=( @@ -329,28 +258,25 @@ def pyros_config(): to be robust. """ ), - is_optional=False, - dtype_spec_str="UncertaintySet", + visibility=1, ), ) CONFIG.declare( "local_solver", - PyROSConfigValue( + ConfigValue( default=None, domain=SolverResolvable(), description="Subordinate local NLP solver.", - is_optional=False, - dtype_spec_str="Solver", + visibility=1, ), ) CONFIG.declare( "global_solver", - PyROSConfigValue( + ConfigValue( default=None, domain=SolverResolvable(), description="Subordinate global NLP solver.", - is_optional=False, - dtype_spec_str="Solver", + visibility=1, ), ) # ================================================ @@ -358,7 +284,7 @@ def pyros_config(): # ================================================ CONFIG.declare( "objective_focus", - PyROSConfigValue( + ConfigValue( default=ObjectiveType.nominal, domain=ValidEnum(ObjectiveType), description=( @@ -388,14 +314,11 @@ def pyros_config(): feasibility is guaranteed. """ ), - is_optional=True, - document_default=False, - dtype_spec_str="ObjectiveType", ), ) CONFIG.declare( "nominal_uncertain_param_vals", - PyROSConfigValue( + ConfigValue( default=[], domain=list, doc=( @@ -407,14 +330,11 @@ def pyros_config(): objects specified through `uncertain_params` are chosen. """ ), - is_optional=True, - document_default=True, - dtype_spec_str="list of float", ), ) CONFIG.declare( "decision_rule_order", - PyROSConfigValue( + ConfigValue( default=0, domain=In([0, 1, 2]), description=( @@ -437,14 +357,11 @@ def pyros_config(): - 2: quadratic recourse """ ), - is_optional=True, - document_default=True, - dtype_spec_str=None, ), ) CONFIG.declare( "solve_master_globally", - PyROSConfigValue( + ConfigValue( default=False, domain=bool, doc=( @@ -460,14 +377,11 @@ def pyros_config(): by PyROS. Otherwise, only robust feasibility is guaranteed. """ ), - is_optional=True, - document_default=True, - dtype_spec_str=None, ), ) CONFIG.declare( "max_iter", - PyROSConfigValue( + ConfigValue( default=-1, domain=PositiveIntOrMinusOne, description=( @@ -476,14 +390,11 @@ def pyros_config(): limit is enforced. """ ), - is_optional=True, - document_default=True, - dtype_spec_str="int", ), ) CONFIG.declare( "robust_feasibility_tolerance", - PyROSConfigValue( + ConfigValue( default=1e-4, domain=NonNegativeFloat, description=( @@ -492,14 +403,11 @@ def pyros_config(): constraint violations during the GRCS separation step. """ ), - is_optional=True, - document_default=True, - dtype_spec_str=None, ), ) CONFIG.declare( "separation_priority_order", - PyROSConfigValue( + ConfigValue( default={}, domain=dict, doc=( @@ -514,14 +422,11 @@ def pyros_config(): priority. """ ), - is_optional=True, - document_default=True, - dtype_spec_str=None, ), ) CONFIG.declare( "progress_logger", - PyROSConfigValue( + ConfigValue( default=default_pyros_solver_logger, domain=a_logger, doc=( @@ -534,14 +439,11 @@ def pyros_config(): object of level ``logging.INFO``. """ ), - is_optional=True, - document_default=True, - dtype_spec_str="str or logging.Logger", ), ) CONFIG.declare( "backup_local_solvers", - PyROSConfigValue( + ConfigValue( default=[], domain=SolverResolvable(), doc=( @@ -551,14 +453,11 @@ def pyros_config(): to solve a subproblem to an acceptable termination condition. """ ), - is_optional=True, - document_default=True, - dtype_spec_str="list of Solver", ), ) CONFIG.declare( "backup_global_solvers", - PyROSConfigValue( + ConfigValue( default=[], domain=SolverResolvable(), doc=( @@ -568,14 +467,11 @@ def pyros_config(): to solve a subproblem to an acceptable termination condition. """ ), - is_optional=True, - document_default=True, - dtype_spec_str="list of Solver", ), ) CONFIG.declare( "subproblem_file_directory", - PyROSConfigValue( + ConfigValue( default=None, domain=str, description=( @@ -587,9 +483,6 @@ def pyros_config(): provided. """ ), - is_optional=True, - document_default=True, - dtype_spec_str="None, str, or path-like", ), ) @@ -598,7 +491,7 @@ def pyros_config(): # ================================================ CONFIG.declare( "bypass_local_separation", - PyROSConfigValue( + ConfigValue( default=False, domain=bool, description=( @@ -611,14 +504,11 @@ def pyros_config(): can quickly solve separation subproblems to global optimality. """ ), - is_optional=True, - document_default=True, - dtype_spec_str=None, ), ) CONFIG.declare( "bypass_global_separation", - PyROSConfigValue( + ConfigValue( default=False, domain=bool, doc=( @@ -635,14 +525,11 @@ def pyros_config(): optimality. """ ), - is_optional=True, - document_default=True, - dtype_spec_str=None, ), ) CONFIG.declare( "p_robustness", - PyROSConfigValue( + ConfigValue( default={}, domain=dict, doc=( @@ -660,9 +547,6 @@ def pyros_config(): the nominal parameter realization. """ ), - is_optional=True, - document_default=True, - dtype_spec_str=None, ), ) @@ -836,6 +720,13 @@ def _log_config(self, logger, config, exclude_options=None, **log_kwargs): logger.log(msg=f" {key}={val!r}", **log_kwargs) logger.log(msg="-" * self._LOG_LINE_LENGTH, **log_kwargs) + @document_kwargs_from_configdict( + config=CONFIG, + section="Keyword Arguments", + indent_spacing=4, + width=72, + visibility=0, + ) def solve( self, model, @@ -1085,131 +976,3 @@ def solve( config.progress_logger.info("=" * self._LOG_LINE_LENGTH) return return_soln - - -def _generate_filtered_docstring(): - """ - Add Numpy-style 'Keyword arguments' section to `PyROS.solve()` - docstring. - """ - cfg = PyROS.CONFIG() - - # mandatory args already documented - exclude_args = [ - "first_stage_variables", - "second_stage_variables", - "uncertain_params", - "uncertainty_set", - "local_solver", - "global_solver", - ] - - indent_by = 8 - width = 72 - before = PyROS.solve.__doc__ - section_name = "Keyword Arguments" - - indent_str = ' ' * indent_by - wrap_width = width - indent_by - cfg = pyros_config() - - arg_docs = [] - - def wrap_doc(doc, indent_by, width): - """ - Wrap a string, accounting for paragraph - breaks ('\n\n') and bullet points (paragraphs - which, when dedented, are such that each line - starts with '- ' or ' '). - """ - paragraphs = doc.split("\n\n") - wrapped_pars = [] - for par in paragraphs: - lines = dedent(par).split("\n") - has_bullets = all( - line.startswith("- ") or line.startswith(" ") - for line in lines - if line != "" - ) - if has_bullets: - # obtain strings of each bullet point - # (dedented, bullet dash and bullet indent removed) - bullet_groups = [] - new_group = False - group = "" - for line in lines: - new_group = line.startswith("- ") - if new_group: - bullet_groups.append(group) - group = "" - new_line = line[2:] - group += f"{new_line}\n" - if group != "": - # ensure last bullet not skipped - bullet_groups.append(group) - - # first entry is just ''; remove - bullet_groups = bullet_groups[1:] - - # wrap each bullet point, then add bullet - # and indents as necessary - wrapped_groups = [] - for group in bullet_groups: - wrapped_groups.append( - "\n".join( - f"{'- ' if idx == 0 else ' '}{line}" - for idx, line in enumerate( - wrap(group, width - 2 - indent_by) - ) - ) - ) - - # now combine bullets into single 'paragraph' - wrapped_pars.append( - indent("\n".join(wrapped_groups), prefix=' ' * indent_by) - ) - else: - wrapped_pars.append( - indent( - "\n".join(wrap(dedent(par), width=width - indent_by)), - prefix=' ' * indent_by, - ) - ) - - return "\n\n".join(wrapped_pars) - - section_header = indent(f"{section_name}\n" + "-" * len(section_name), indent_str) - for key, itm in cfg._data.items(): - if key in exclude_args: - continue - arg_name = key - arg_dtype = itm.dtype_spec_str - - if itm.is_optional: - if itm.document_default: - optional_str = f", default={repr(itm._default)}" - else: - optional_str = ", optional" - else: - optional_str = "" - - arg_header = f"{indent_str}{arg_name} : {arg_dtype}{optional_str}" - - # dedented_doc_str = dedent(itm.doc).replace("\n", ' ').strip() - if itm._doc is not None: - raw_arg_desc = itm._doc - else: - raw_arg_desc = itm._description - - arg_description = wrap_doc( - raw_arg_desc, width=wrap_width, indent_by=indent_by + 4 - ) - - arg_docs.append(f"{arg_header}\n{arg_description}") - - kwargs_section_doc = "\n".join([section_header] + arg_docs) - - return f"{before}\n{kwargs_section_doc}\n" - - -PyROS.solve.__doc__ = _generate_filtered_docstring() From d5fc7cbac76727c4e858aec9b6c360e2e42088bc Mon Sep 17 00:00:00 2001 From: jasherma Date: Tue, 6 Feb 2024 20:10:53 -0500 Subject: [PATCH 0890/1204] Create new module for config objects --- pyomo/contrib/pyros/config.py | 493 ++++++++++++++++++++++++++++++++++ pyomo/contrib/pyros/pyros.py | 481 +-------------------------------- 2 files changed, 498 insertions(+), 476 deletions(-) create mode 100644 pyomo/contrib/pyros/config.py diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py new file mode 100644 index 00000000000..1dc1608ab16 --- /dev/null +++ b/pyomo/contrib/pyros/config.py @@ -0,0 +1,493 @@ +""" +Interfaces for managing PyROS solver options. +""" + + +from pyomo.common.config import ( + ConfigDict, + ConfigValue, + In, + NonNegativeFloat, +) +from pyomo.core.base import ( + Var, + _VarData, +) +from pyomo.core.base.param import ( + Param, + _ParamData, +) +from pyomo.opt import SolverFactory +from pyomo.contrib.pyros.util import ( + a_logger, + ObjectiveType, + setup_pyros_logger, + ValidEnum, +) +from pyomo.contrib.pyros.uncertainty_sets import uncertainty_sets + + +default_pyros_solver_logger = setup_pyros_logger() + + +def NonNegIntOrMinusOne(obj): + ''' + if obj is a non-negative int, return the non-negative int + if obj is -1, return -1 + else, error + ''' + ans = int(obj) + if ans != float(obj) or (ans < 0 and ans != -1): + raise ValueError("Expected non-negative int, but received %s" % (obj,)) + return ans + + +def PositiveIntOrMinusOne(obj): + ''' + if obj is a positive int, return the int + if obj is -1, return -1 + else, error + ''' + ans = int(obj) + if ans != float(obj) or (ans <= 0 and ans != -1): + raise ValueError("Expected positive int, but received %s" % (obj,)) + return ans + + +class SolverResolvable(object): + def __call__(self, obj): + ''' + if obj is a string, return the Solver object for that solver name + if obj is a Solver object, return a copy of the Solver + if obj is a list, and each element of list is solver resolvable, + return list of solvers + ''' + if isinstance(obj, str): + return SolverFactory(obj.lower()) + elif callable(getattr(obj, "solve", None)): + return obj + elif isinstance(obj, list): + return [self(o) for o in obj] + else: + raise ValueError( + "Expected a Pyomo solver or string object, " + "instead received {0}".format(obj.__class__.__name__) + ) + + +class InputDataStandardizer(object): + def __init__(self, ctype, cdatatype): + self.ctype = ctype + self.cdatatype = cdatatype + + def __call__(self, obj): + if isinstance(obj, self.ctype): + return list(obj.values()) + if isinstance(obj, self.cdatatype): + return [obj] + ans = [] + for item in obj: + ans.extend(self.__call__(item)) + for _ in ans: + assert isinstance(_, self.cdatatype) + return ans + + +def pyros_config(): + CONFIG = ConfigDict('PyROS') + + # ================================================ + # === Options common to all solvers + # ================================================ + CONFIG.declare( + 'time_limit', + ConfigValue( + default=None, + domain=NonNegativeFloat, + doc=( + """ + Wall time limit for the execution of the PyROS solver + in seconds (including time spent by subsolvers). + If `None` is provided, then no time limit is enforced. + """ + ), + ), + ) + CONFIG.declare( + 'keepfiles', + ConfigValue( + default=False, + domain=bool, + description=( + """ + Export subproblems with a non-acceptable termination status + for debugging purposes. + If True is provided, then the argument `subproblem_file_directory` + must also be specified. + """ + ), + ), + ) + CONFIG.declare( + 'tee', + ConfigValue( + default=False, + domain=bool, + description="Output subordinate solver logs for all subproblems.", + ), + ) + CONFIG.declare( + 'load_solution', + ConfigValue( + default=True, + domain=bool, + description=( + """ + Load final solution(s) found by PyROS to the deterministic model + provided. + """ + ), + ), + ) + + # ================================================ + # === Required User Inputs + # ================================================ + CONFIG.declare( + "first_stage_variables", + ConfigValue( + default=[], + domain=InputDataStandardizer(Var, _VarData), + description="First-stage (or design) variables.", + visibility=1, + ), + ) + CONFIG.declare( + "second_stage_variables", + ConfigValue( + default=[], + domain=InputDataStandardizer(Var, _VarData), + description="Second-stage (or control) variables.", + visibility=1, + ), + ) + CONFIG.declare( + "uncertain_params", + ConfigValue( + default=[], + domain=InputDataStandardizer(Param, _ParamData), + description=( + """ + Uncertain model parameters. + The `mutable` attribute for all uncertain parameter + objects should be set to True. + """ + ), + visibility=1, + ), + ) + CONFIG.declare( + "uncertainty_set", + ConfigValue( + default=None, + domain=uncertainty_sets, + description=( + """ + Uncertainty set against which the + final solution(s) returned by PyROS should be certified + to be robust. + """ + ), + visibility=1, + ), + ) + CONFIG.declare( + "local_solver", + ConfigValue( + default=None, + domain=SolverResolvable(), + description="Subordinate local NLP solver.", + visibility=1, + ), + ) + CONFIG.declare( + "global_solver", + ConfigValue( + default=None, + domain=SolverResolvable(), + description="Subordinate global NLP solver.", + visibility=1, + ), + ) + # ================================================ + # === Optional User Inputs + # ================================================ + CONFIG.declare( + "objective_focus", + ConfigValue( + default=ObjectiveType.nominal, + domain=ValidEnum(ObjectiveType), + description=( + """ + Choice of objective focus to optimize in the master problems. + Choices are: `ObjectiveType.worst_case`, + `ObjectiveType.nominal`. + """ + ), + doc=( + """ + Objective focus for the master problems: + + - `ObjectiveType.nominal`: + Optimize the objective function subject to the nominal + uncertain parameter realization. + - `ObjectiveType.worst_case`: + Optimize the objective function subject to the worst-case + uncertain parameter realization. + + By default, `ObjectiveType.nominal` is chosen. + + A worst-case objective focus is required for certification + of robust optimality of the final solution(s) returned + by PyROS. + If a nominal objective focus is chosen, then only robust + feasibility is guaranteed. + """ + ), + ), + ) + CONFIG.declare( + "nominal_uncertain_param_vals", + ConfigValue( + default=[], + domain=list, + doc=( + """ + Nominal uncertain parameter realization. + Entries should be provided in an order consistent with the + entries of the argument `uncertain_params`. + If an empty list is provided, then the values of the `Param` + objects specified through `uncertain_params` are chosen. + """ + ), + ), + ) + CONFIG.declare( + "decision_rule_order", + ConfigValue( + default=0, + domain=In([0, 1, 2]), + description=( + """ + Order (or degree) of the polynomial decision rule functions used + for approximating the adjustability of the second stage + variables with respect to the uncertain parameters. + """ + ), + doc=( + """ + Order (or degree) of the polynomial decision rule functions used + for approximating the adjustability of the second stage + variables with respect to the uncertain parameters. + + Choices are: + + - 0: static recourse + - 1: affine recourse + - 2: quadratic recourse + """ + ), + ), + ) + CONFIG.declare( + "solve_master_globally", + ConfigValue( + default=False, + domain=bool, + doc=( + """ + True to solve all master problems with the subordinate + global solver, False to solve all master problems with + the subordinate local solver. + Along with a worst-case objective focus + (see argument `objective_focus`), + solving the master problems to global optimality is required + for certification + of robust optimality of the final solution(s) returned + by PyROS. Otherwise, only robust feasibility is guaranteed. + """ + ), + ), + ) + CONFIG.declare( + "max_iter", + ConfigValue( + default=-1, + domain=PositiveIntOrMinusOne, + description=( + """ + Iteration limit. If -1 is provided, then no iteration + limit is enforced. + """ + ), + ), + ) + CONFIG.declare( + "robust_feasibility_tolerance", + ConfigValue( + default=1e-4, + domain=NonNegativeFloat, + description=( + """ + Relative tolerance for assessing maximal inequality + constraint violations during the GRCS separation step. + """ + ), + ), + ) + CONFIG.declare( + "separation_priority_order", + ConfigValue( + default={}, + domain=dict, + doc=( + """ + Mapping from model inequality constraint names + to positive integers specifying the priorities + of their corresponding separation subproblems. + A higher integer value indicates a higher priority. + Constraints not referenced in the `dict` assume + a priority of 0. + Separation subproblems are solved in order of decreasing + priority. + """ + ), + ), + ) + CONFIG.declare( + "progress_logger", + ConfigValue( + default=default_pyros_solver_logger, + domain=a_logger, + doc=( + """ + Logger (or name thereof) used for reporting PyROS solver + progress. If a `str` is specified, then ``progress_logger`` + is cast to ``logging.getLogger(progress_logger)``. + In the default case, `progress_logger` is set to + a :class:`pyomo.contrib.pyros.util.PreformattedLogger` + object of level ``logging.INFO``. + """ + ), + ), + ) + CONFIG.declare( + "backup_local_solvers", + ConfigValue( + default=[], + domain=SolverResolvable(), + doc=( + """ + Additional subordinate local NLP optimizers to invoke + in the event the primary local NLP optimizer fails + to solve a subproblem to an acceptable termination condition. + """ + ), + ), + ) + CONFIG.declare( + "backup_global_solvers", + ConfigValue( + default=[], + domain=SolverResolvable(), + doc=( + """ + Additional subordinate global NLP optimizers to invoke + in the event the primary global NLP optimizer fails + to solve a subproblem to an acceptable termination condition. + """ + ), + ), + ) + CONFIG.declare( + "subproblem_file_directory", + ConfigValue( + default=None, + domain=str, + description=( + """ + Directory to which to export subproblems not successfully + solved to an acceptable termination condition. + In the event ``keepfiles=True`` is specified, a str or + path-like referring to an existing directory must be + provided. + """ + ), + ), + ) + + # ================================================ + # === Advanced Options + # ================================================ + CONFIG.declare( + "bypass_local_separation", + ConfigValue( + default=False, + domain=bool, + description=( + """ + This is an advanced option. + Solve all separation subproblems with the subordinate global + solver(s) only. + This option is useful for expediting PyROS + in the event that the subordinate global optimizer(s) provided + can quickly solve separation subproblems to global optimality. + """ + ), + ), + ) + CONFIG.declare( + "bypass_global_separation", + ConfigValue( + default=False, + domain=bool, + doc=( + """ + This is an advanced option. + Solve all separation subproblems with the subordinate local + solver(s) only. + If `True` is chosen, then robustness of the final solution(s) + returned by PyROS is not guaranteed, and a warning will + be issued at termination. + This option is useful for expediting PyROS + in the event that the subordinate global optimizer provided + cannot tractably solve separation subproblems to global + optimality. + """ + ), + ), + ) + CONFIG.declare( + "p_robustness", + ConfigValue( + default={}, + domain=dict, + doc=( + """ + This is an advanced option. + Add p-robustness constraints to all master subproblems. + If an empty dict is provided, then p-robustness constraints + are not added. + Otherwise, the dict must map a `str` of value ``'rho'`` + to a non-negative `float`. PyROS automatically + specifies ``1 + p_robustness['rho']`` + as an upper bound for the ratio of the + objective function value under any PyROS-sampled uncertain + parameter realization to the objective function under + the nominal parameter realization. + """ + ), + ), + ) + + return CONFIG diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index f266b7451e6..962ae79a436 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -11,23 +11,16 @@ # pyros.py: Generalized Robust Cutting-Set Algorithm for Pyomo import logging -from textwrap import indent, dedent, wrap +from pyomo.common.config import document_kwargs_from_configdict from pyomo.common.collections import Bunch, ComponentSet -from pyomo.common.config import ( - ConfigDict, - ConfigValue, - document_kwargs_from_configdict, - In, - NonNegativeFloat, -) from pyomo.core.base.block import Block from pyomo.core.expr import value -from pyomo.core.base.var import Var, _VarData -from pyomo.core.base.param import Param, _ParamData -from pyomo.core.base.objective import Objective, maximize -from pyomo.contrib.pyros.util import a_logger, time_code, get_main_elapsed_time +from pyomo.core.base.var import Var +from pyomo.core.base.objective import Objective +from pyomo.contrib.pyros.util import time_code from pyomo.common.modeling import unique_component_name from pyomo.opt import SolverFactory +from pyomo.contrib.pyros.config import pyros_config from pyomo.contrib.pyros.util import ( model_is_valid, recast_to_min_obj, @@ -35,7 +28,6 @@ add_decision_rule_variables, load_final_solution, pyrosTerminationCondition, - ValidEnum, ObjectiveType, validate_uncertainty_set, identify_objective_functions, @@ -49,7 +41,6 @@ ) from pyomo.contrib.pyros.solve_data import ROSolveResults from pyomo.contrib.pyros.pyros_algorithm_methods import ROSolver_iterative_solve -from pyomo.contrib.pyros.uncertainty_sets import uncertainty_sets from pyomo.core.base import Constraint from datetime import datetime @@ -91,468 +82,6 @@ def _get_pyomo_version_info(): return {"Pyomo version": pyomo_version, "Commit hash": commit_hash} -def NonNegIntOrMinusOne(obj): - ''' - if obj is a non-negative int, return the non-negative int - if obj is -1, return -1 - else, error - ''' - ans = int(obj) - if ans != float(obj) or (ans < 0 and ans != -1): - raise ValueError("Expected non-negative int, but received %s" % (obj,)) - return ans - - -def PositiveIntOrMinusOne(obj): - ''' - if obj is a positive int, return the int - if obj is -1, return -1 - else, error - ''' - ans = int(obj) - if ans != float(obj) or (ans <= 0 and ans != -1): - raise ValueError("Expected positive int, but received %s" % (obj,)) - return ans - - -class SolverResolvable(object): - def __call__(self, obj): - ''' - if obj is a string, return the Solver object for that solver name - if obj is a Solver object, return a copy of the Solver - if obj is a list, and each element of list is solver resolvable, return list of solvers - ''' - if isinstance(obj, str): - return SolverFactory(obj.lower()) - elif callable(getattr(obj, "solve", None)): - return obj - elif isinstance(obj, list): - return [self(o) for o in obj] - else: - raise ValueError( - "Expected a Pyomo solver or string object, " - "instead received {1}".format(obj.__class__.__name__) - ) - - -class InputDataStandardizer(object): - def __init__(self, ctype, cdatatype): - self.ctype = ctype - self.cdatatype = cdatatype - - def __call__(self, obj): - if isinstance(obj, self.ctype): - return list(obj.values()) - if isinstance(obj, self.cdatatype): - return [obj] - ans = [] - for item in obj: - ans.extend(self.__call__(item)) - for _ in ans: - assert isinstance(_, self.cdatatype) - return ans - - -def pyros_config(): - CONFIG = ConfigDict('PyROS') - - # ================================================ - # === Options common to all solvers - # ================================================ - CONFIG.declare( - 'time_limit', - ConfigValue( - default=None, - domain=NonNegativeFloat, - doc=( - """ - Wall time limit for the execution of the PyROS solver - in seconds (including time spent by subsolvers). - If `None` is provided, then no time limit is enforced. - """ - ), - ), - ) - CONFIG.declare( - 'keepfiles', - ConfigValue( - default=False, - domain=bool, - description=( - """ - Export subproblems with a non-acceptable termination status - for debugging purposes. - If True is provided, then the argument `subproblem_file_directory` - must also be specified. - """ - ), - ), - ) - CONFIG.declare( - 'tee', - ConfigValue( - default=False, - domain=bool, - description="Output subordinate solver logs for all subproblems.", - ), - ) - CONFIG.declare( - 'load_solution', - ConfigValue( - default=True, - domain=bool, - description=( - """ - Load final solution(s) found by PyROS to the deterministic model - provided. - """ - ), - ), - ) - - # ================================================ - # === Required User Inputs - # ================================================ - CONFIG.declare( - "first_stage_variables", - ConfigValue( - default=[], - domain=InputDataStandardizer(Var, _VarData), - description="First-stage (or design) variables.", - visibility=1, - ), - ) - CONFIG.declare( - "second_stage_variables", - ConfigValue( - default=[], - domain=InputDataStandardizer(Var, _VarData), - description="Second-stage (or control) variables.", - visibility=1, - ), - ) - CONFIG.declare( - "uncertain_params", - ConfigValue( - default=[], - domain=InputDataStandardizer(Param, _ParamData), - description=( - """ - Uncertain model parameters. - The `mutable` attribute for all uncertain parameter - objects should be set to True. - """ - ), - visibility=1, - ), - ) - CONFIG.declare( - "uncertainty_set", - ConfigValue( - default=None, - domain=uncertainty_sets, - description=( - """ - Uncertainty set against which the - final solution(s) returned by PyROS should be certified - to be robust. - """ - ), - visibility=1, - ), - ) - CONFIG.declare( - "local_solver", - ConfigValue( - default=None, - domain=SolverResolvable(), - description="Subordinate local NLP solver.", - visibility=1, - ), - ) - CONFIG.declare( - "global_solver", - ConfigValue( - default=None, - domain=SolverResolvable(), - description="Subordinate global NLP solver.", - visibility=1, - ), - ) - # ================================================ - # === Optional User Inputs - # ================================================ - CONFIG.declare( - "objective_focus", - ConfigValue( - default=ObjectiveType.nominal, - domain=ValidEnum(ObjectiveType), - description=( - """ - Choice of objective focus to optimize in the master problems. - Choices are: `ObjectiveType.worst_case`, - `ObjectiveType.nominal`. - """ - ), - doc=( - """ - Objective focus for the master problems: - - - `ObjectiveType.nominal`: - Optimize the objective function subject to the nominal - uncertain parameter realization. - - `ObjectiveType.worst_case`: - Optimize the objective function subject to the worst-case - uncertain parameter realization. - - By default, `ObjectiveType.nominal` is chosen. - - A worst-case objective focus is required for certification - of robust optimality of the final solution(s) returned - by PyROS. - If a nominal objective focus is chosen, then only robust - feasibility is guaranteed. - """ - ), - ), - ) - CONFIG.declare( - "nominal_uncertain_param_vals", - ConfigValue( - default=[], - domain=list, - doc=( - """ - Nominal uncertain parameter realization. - Entries should be provided in an order consistent with the - entries of the argument `uncertain_params`. - If an empty list is provided, then the values of the `Param` - objects specified through `uncertain_params` are chosen. - """ - ), - ), - ) - CONFIG.declare( - "decision_rule_order", - ConfigValue( - default=0, - domain=In([0, 1, 2]), - description=( - """ - Order (or degree) of the polynomial decision rule functions used - for approximating the adjustability of the second stage - variables with respect to the uncertain parameters. - """ - ), - doc=( - """ - Order (or degree) of the polynomial decision rule functions used - for approximating the adjustability of the second stage - variables with respect to the uncertain parameters. - - Choices are: - - - 0: static recourse - - 1: affine recourse - - 2: quadratic recourse - """ - ), - ), - ) - CONFIG.declare( - "solve_master_globally", - ConfigValue( - default=False, - domain=bool, - doc=( - """ - True to solve all master problems with the subordinate - global solver, False to solve all master problems with - the subordinate local solver. - Along with a worst-case objective focus - (see argument `objective_focus`), - solving the master problems to global optimality is required - for certification - of robust optimality of the final solution(s) returned - by PyROS. Otherwise, only robust feasibility is guaranteed. - """ - ), - ), - ) - CONFIG.declare( - "max_iter", - ConfigValue( - default=-1, - domain=PositiveIntOrMinusOne, - description=( - """ - Iteration limit. If -1 is provided, then no iteration - limit is enforced. - """ - ), - ), - ) - CONFIG.declare( - "robust_feasibility_tolerance", - ConfigValue( - default=1e-4, - domain=NonNegativeFloat, - description=( - """ - Relative tolerance for assessing maximal inequality - constraint violations during the GRCS separation step. - """ - ), - ), - ) - CONFIG.declare( - "separation_priority_order", - ConfigValue( - default={}, - domain=dict, - doc=( - """ - Mapping from model inequality constraint names - to positive integers specifying the priorities - of their corresponding separation subproblems. - A higher integer value indicates a higher priority. - Constraints not referenced in the `dict` assume - a priority of 0. - Separation subproblems are solved in order of decreasing - priority. - """ - ), - ), - ) - CONFIG.declare( - "progress_logger", - ConfigValue( - default=default_pyros_solver_logger, - domain=a_logger, - doc=( - """ - Logger (or name thereof) used for reporting PyROS solver - progress. If a `str` is specified, then ``progress_logger`` - is cast to ``logging.getLogger(progress_logger)``. - In the default case, `progress_logger` is set to - a :class:`pyomo.contrib.pyros.util.PreformattedLogger` - object of level ``logging.INFO``. - """ - ), - ), - ) - CONFIG.declare( - "backup_local_solvers", - ConfigValue( - default=[], - domain=SolverResolvable(), - doc=( - """ - Additional subordinate local NLP optimizers to invoke - in the event the primary local NLP optimizer fails - to solve a subproblem to an acceptable termination condition. - """ - ), - ), - ) - CONFIG.declare( - "backup_global_solvers", - ConfigValue( - default=[], - domain=SolverResolvable(), - doc=( - """ - Additional subordinate global NLP optimizers to invoke - in the event the primary global NLP optimizer fails - to solve a subproblem to an acceptable termination condition. - """ - ), - ), - ) - CONFIG.declare( - "subproblem_file_directory", - ConfigValue( - default=None, - domain=str, - description=( - """ - Directory to which to export subproblems not successfully - solved to an acceptable termination condition. - In the event ``keepfiles=True`` is specified, a str or - path-like referring to an existing directory must be - provided. - """ - ), - ), - ) - - # ================================================ - # === Advanced Options - # ================================================ - CONFIG.declare( - "bypass_local_separation", - ConfigValue( - default=False, - domain=bool, - description=( - """ - This is an advanced option. - Solve all separation subproblems with the subordinate global - solver(s) only. - This option is useful for expediting PyROS - in the event that the subordinate global optimizer(s) provided - can quickly solve separation subproblems to global optimality. - """ - ), - ), - ) - CONFIG.declare( - "bypass_global_separation", - ConfigValue( - default=False, - domain=bool, - doc=( - """ - This is an advanced option. - Solve all separation subproblems with the subordinate local - solver(s) only. - If `True` is chosen, then robustness of the final solution(s) - returned by PyROS is not guaranteed, and a warning will - be issued at termination. - This option is useful for expediting PyROS - in the event that the subordinate global optimizer provided - cannot tractably solve separation subproblems to global - optimality. - """ - ), - ), - ) - CONFIG.declare( - "p_robustness", - ConfigValue( - default={}, - domain=dict, - doc=( - """ - This is an advanced option. - Add p-robustness constraints to all master subproblems. - If an empty dict is provided, then p-robustness constraints - are not added. - Otherwise, the dict must map a `str` of value ``'rho'`` - to a non-negative `float`. PyROS automatically - specifies ``1 + p_robustness['rho']`` - as an upper bound for the ratio of the - objective function value under any PyROS-sampled uncertain - parameter realization to the objective function under - the nominal parameter realization. - """ - ), - ), - ) - - return CONFIG - - @SolverFactory.register( "pyros", doc="Robust optimization (RO) solver implementing " From 49fa433da7c47646919637a11d9affc0047bd15c Mon Sep 17 00:00:00 2001 From: jasherma Date: Tue, 6 Feb 2024 20:14:36 -0500 Subject: [PATCH 0891/1204] Apply black, PEP8 code --- pyomo/contrib/pyros/config.py | 37 ++++++++++++----------------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index 1dc1608ab16..bd87dc743f9 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -3,20 +3,9 @@ """ -from pyomo.common.config import ( - ConfigDict, - ConfigValue, - In, - NonNegativeFloat, -) -from pyomo.core.base import ( - Var, - _VarData, -) -from pyomo.core.base.param import ( - Param, - _ParamData, -) +from pyomo.common.config import ConfigDict, ConfigValue, In, NonNegativeFloat +from pyomo.core.base import Var, _VarData +from pyomo.core.base.param import Param, _ParamData from pyomo.opt import SolverFactory from pyomo.contrib.pyros.util import ( a_logger, @@ -122,8 +111,8 @@ def pyros_config(): """ Export subproblems with a non-acceptable termination status for debugging purposes. - If True is provided, then the argument `subproblem_file_directory` - must also be specified. + If True is provided, then the argument + `subproblem_file_directory` must also be specified. """ ), ), @@ -143,8 +132,8 @@ def pyros_config(): domain=bool, description=( """ - Load final solution(s) found by PyROS to the deterministic model - provided. + Load final solution(s) found by PyROS to the deterministic + model provided. """ ), ), @@ -246,7 +235,7 @@ def pyros_config(): uncertain parameter realization. By default, `ObjectiveType.nominal` is chosen. - + A worst-case objective focus is required for certification of robust optimality of the final solution(s) returned by PyROS. @@ -279,19 +268,19 @@ def pyros_config(): domain=In([0, 1, 2]), description=( """ - Order (or degree) of the polynomial decision rule functions used - for approximating the adjustability of the second stage + Order (or degree) of the polynomial decision rule functions + used for approximating the adjustability of the second stage variables with respect to the uncertain parameters. """ ), doc=( """ - Order (or degree) of the polynomial decision rule functions used + Order (or degree) of the polynomial decision rule functions for approximating the adjustability of the second stage variables with respect to the uncertain parameters. - + Choices are: - + - 0: static recourse - 1: affine recourse - 2: quadratic recourse From 1bda0d3c2d38c6423c0c698f87eec265d311794d Mon Sep 17 00:00:00 2001 From: jasherma Date: Tue, 6 Feb 2024 20:26:07 -0500 Subject: [PATCH 0892/1204] Update documentation of mandatory args --- pyomo/contrib/pyros/pyros.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 962ae79a436..316a5869057 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -273,21 +273,25 @@ def solve( ---------- model: ConcreteModel The deterministic model. - first_stage_variables: list of Var + first_stage_variables: VarData, Var, or iterable of VarData/Var First-stage model variables (or design variables). - second_stage_variables: list of Var + second_stage_variables: VarData, Var, or iterable of VarData/Var Second-stage model variables (or control variables). - uncertain_params: list of Param + uncertain_params: ParamData, Param, or iterable of ParamData/Param Uncertain model parameters. - The `mutable` attribute for every uncertain parameter - objects must be set to True. + The `mutable` attribute for all uncertain parameter objects + must be set to True. uncertainty_set: UncertaintySet Uncertainty set against which the solution(s) returned will be confirmed to be robust. - local_solver: Solver + local_solver: str or solver type Subordinate local NLP solver. - global_solver: Solver + If a `str` is passed, then the `str` is cast to + ``SolverFactory(local_solver)``. + global_solver: str or solver type Subordinate global NLP solver. + If a `str` is passed, then the `str` is cast to + ``SolverFactory(global_solver)``. Returns ------- From ecc2df8d91bc6e98cb612be631ca197e79adecf4 Mon Sep 17 00:00:00 2001 From: jasherma Date: Tue, 6 Feb 2024 22:19:59 -0500 Subject: [PATCH 0893/1204] Add more rigorous `InputDataStandardizer` checks --- pyomo/contrib/pyros/config.py | 177 +++++++++++++++++++++++++++++++--- 1 file changed, 164 insertions(+), 13 deletions(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index bd87dc743f9..c118b650208 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -3,6 +3,9 @@ """ +from collections.abc import Iterable + +from pyomo.common.collections import ComponentSet from pyomo.common.config import ConfigDict, ConfigValue, In, NonNegativeFloat from pyomo.core.base import Var, _VarData from pyomo.core.base.param import Param, _ParamData @@ -64,23 +67,166 @@ def __call__(self, obj): ) +def mutable_param_validator(param_obj): + """ + Check that Param-like object has attribute `mutable=True`. + + Parameters + ---------- + param_obj : Param or _ParamData + Param-like object of interest. + + Raises + ------ + ValueError + If lengths of the param object and the accompanying + index set do not match. This may occur if some entry + of the Param is not initialized. + ValueError + If attribute `mutable` is of value False. + """ + if len(param_obj) != len(param_obj.index_set()): + raise ValueError( + f"Length of Param component object with " + f"name {param_obj.name!r} is {len(param_obj)}, " + "and does not match that of its index set, " + f"which is of length {len(param_obj.index_set())}. " + "Check that all entries of the component object " + "have been initialized." + ) + if not param_obj.mutable: + raise ValueError( + f"Param object with name {param_obj.name!r} is immutable." + ) + + class InputDataStandardizer(object): - def __init__(self, ctype, cdatatype): + """ + Standardizer for objects castable to a list of Pyomo + component types. + + Parameters + ---------- + ctype : type + Pyomo component type, such as Component, Var or Param. + cdatatype : type + Corresponding Pyomo component data type, such as + _ComponentData, _VarData, or _ParamData. + ctype_validator : callable, optional + Validator function for objects of type `ctype`. + cdatatype_validator : callable, optional + Validator function for objects of type `cdatatype`. + allow_repeats : bool, optional + True to allow duplicate component data entries in final + list to which argument is cast, False otherwise. + + Attributes + ---------- + ctype + cdatatype + ctype_validator + cdatatype_validator + allow_repeats + """ + + def __init__( + self, + ctype, + cdatatype, + ctype_validator=None, + cdatatype_validator=None, + allow_repeats=False, + ): + """Initialize self (see class docstring).""" self.ctype = ctype self.cdatatype = cdatatype + self.ctype_validator = ctype_validator + self.cdatatype_validator = cdatatype_validator + self.allow_repeats = allow_repeats + + def standardize_ctype_obj(self, obj): + """ + Standardize object of type ``self.ctype`` to list + of objects of type ``self.cdatatype``. + """ + if self.ctype_validator is not None: + self.ctype_validator(obj) + return list(obj.values()) + + def standardize_cdatatype_obj(self, obj): + """ + Standarize object of type ``self.cdatatype`` to + ``[obj]``. + """ + if self.cdatatype_validator is not None: + self.cdatatype_validator(obj) + return [obj] + + def __call__(self, obj, from_iterable=None, allow_repeats=None): + """ + Cast object to a flat list of Pyomo component data type + entries. + + Parameters + ---------- + obj : object + Object to be cast. + from_iterable : Iterable or None, optional + Iterable from which `obj` obtained, if any. + allow_repeats : bool or None, optional + True if list can contain repeated entries, + False otherwise. + + Raises + ------ + TypeError + If all entries in the resulting list + are not of type ``self.cdatatype``. + ValueError + If the resulting list contains duplicate entries. + """ + if allow_repeats is None: + allow_repeats = self.allow_repeats - def __call__(self, obj): if isinstance(obj, self.ctype): - return list(obj.values()) - if isinstance(obj, self.cdatatype): - return [obj] - ans = [] - for item in obj: - ans.extend(self.__call__(item)) - for _ in ans: - assert isinstance(_, self.cdatatype) + ans = self.standardize_ctype_obj(obj) + elif isinstance(obj, self.cdatatype): + ans = self.standardize_cdatatype_obj(obj) + elif isinstance(obj, Iterable) and not isinstance(obj, str): + ans = [] + for item in obj: + ans.extend(self.__call__(item, from_iterable=obj)) + else: + from_iterable_qual = ( + f" (entry of iterable {from_iterable})" + if from_iterable is not None + else "" + ) + raise TypeError( + f"Input object {obj!r}{from_iterable_qual} " + "is not of valid component type " + f"{self.ctype.__name__} or component data type " + f"{self.cdatatype.__name__}." + ) + + # check for duplicates if desired + if not allow_repeats and len(ans) != len(ComponentSet(ans)): + comp_name_list = [comp.name for comp in ans] + raise ValueError( + f"Standardized component list {comp_name_list} " + f"derived from input {obj} " + "contains duplicate entries." + ) + return ans + def domain_name(self): + """Return str briefly describing domain encompassed by self.""" + return ( + f"{self.cdatatype.__name__}, {self.ctype.__name__}, " + f"or Iterable of {self.cdatatype.__name__}/{self.ctype.__name__}" + ) + def pyros_config(): CONFIG = ConfigDict('PyROS') @@ -146,7 +292,7 @@ def pyros_config(): "first_stage_variables", ConfigValue( default=[], - domain=InputDataStandardizer(Var, _VarData), + domain=InputDataStandardizer(Var, _VarData, allow_repeats=False), description="First-stage (or design) variables.", visibility=1, ), @@ -155,7 +301,7 @@ def pyros_config(): "second_stage_variables", ConfigValue( default=[], - domain=InputDataStandardizer(Var, _VarData), + domain=InputDataStandardizer(Var, _VarData, allow_repeats=False), description="Second-stage (or control) variables.", visibility=1, ), @@ -164,7 +310,12 @@ def pyros_config(): "uncertain_params", ConfigValue( default=[], - domain=InputDataStandardizer(Param, _ParamData), + domain=InputDataStandardizer( + ctype=Param, + cdatatype=_ParamData, + ctype_validator=mutable_param_validator, + allow_repeats=False, + ), description=( """ Uncertain model parameters. From bcf1730352471390d81fcd53d9e39d4e758d5336 Mon Sep 17 00:00:00 2001 From: jasherma Date: Tue, 6 Feb 2024 22:21:07 -0500 Subject: [PATCH 0894/1204] Add tests for `InputDataStandardizer` --- pyomo/contrib/pyros/tests/test_config.py | 267 +++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 pyomo/contrib/pyros/tests/test_config.py diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py new file mode 100644 index 00000000000..d0c378a52f0 --- /dev/null +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -0,0 +1,267 @@ +""" +Test objects for construction of PyROS ConfigDict. +""" + + +import unittest + +from pyomo.core.base import ( + ConcreteModel, + Var, + _VarData, +) +from pyomo.core.base.param import Param, _ParamData +from pyomo.contrib.pyros.config import ( + InputDataStandardizer, + mutable_param_validator, +) + + +class testInputDataStandardizer(unittest.TestCase): + """ + Test standardizer method for Pyomo component-type inputs. + """ + + def test_single_component_data(self): + """ + Test standardizer works for single component + data-type entry. + """ + mdl = ConcreteModel() + mdl.v = Var([0, 1]) + + standardizer_func = InputDataStandardizer(Var, _VarData) + + standardizer_input = mdl.v[0] + standardizer_output = standardizer_func(standardizer_input) + + self.assertIsInstance( + standardizer_output, + list, + msg=( + "Standardized output should be of type list, " + f"but is of type {standardizer_output.__class__.__name__}." + ), + ) + self.assertEqual( + len(standardizer_output), + 1, + msg="Length of standardizer output is not as expected.", + ) + self.assertIs( + standardizer_output[0], + mdl.v[0], + msg=( + f"Entry {standardizer_output[0]} (id {id(standardizer_output[0])}) " + "is not identical to " + f"input component data object {mdl.v[0]} " + f"(id {id(mdl.v[0])})" + ), + ) + + def test_standardizer_indexed_component(self): + """ + Test component standardizer works on indexed component. + """ + mdl = ConcreteModel() + mdl.v = Var([0, 1]) + + standardizer_func = InputDataStandardizer(Var, _VarData) + + standardizer_input = mdl.v + standardizer_output = standardizer_func(standardizer_input) + + self.assertIsInstance( + standardizer_output, + list, + msg=( + "Standardized output should be of type list, " + f"but is of type {standardizer_output.__class__.__name__}." + ), + ) + self.assertEqual( + len(standardizer_output), + 2, + msg="Length of standardizer output is not as expected.", + ) + enum_zip = enumerate(zip(standardizer_input.values(), standardizer_output)) + for idx, (input, output) in enum_zip: + self.assertIs( + input, + output, + msg=( + f"Entry {input} (id {id(input)}) " + "is not identical to " + f"input component data object {output} " + f"(id {id(output)})" + ), + ) + + def test_standardizer_multiple_components(self): + """ + Test standardizer works on sequence of components. + """ + mdl = ConcreteModel() + mdl.v = Var([0, 1]) + mdl.x = Var(["a", "b"]) + + standardizer_func = InputDataStandardizer(Var, _VarData) + + standardizer_input = [mdl.v[0], mdl.x] + standardizer_output = standardizer_func(standardizer_input) + expected_standardizer_output = [mdl.v[0], mdl.x["a"], mdl.x["b"]] + + self.assertIsInstance( + standardizer_output, + list, + msg=( + "Standardized output should be of type list, " + f"but is of type {standardizer_output.__class__.__name__}." + ), + ) + self.assertEqual( + len(standardizer_output), + len(expected_standardizer_output), + msg="Length of standardizer output is not as expected.", + ) + enum_zip = enumerate(zip(expected_standardizer_output, standardizer_output)) + for idx, (input, output) in enum_zip: + self.assertIs( + input, + output, + msg=( + f"Entry {input} (id {id(input)}) " + "is not identical to " + f"input component data object {output} " + f"(id {id(output)})" + ), + ) + + def test_standardizer_invalid_duplicates(self): + """ + Test standardizer raises exception if input contains duplicates + and duplicates are not allowed. + """ + mdl = ConcreteModel() + mdl.v = Var([0, 1]) + mdl.x = Var(["a", "b"]) + + standardizer_func = InputDataStandardizer(Var, _VarData, allow_repeats=False) + + exc_str = r"Standardized.*list.*contains duplicate entries\." + with self.assertRaisesRegex(ValueError, exc_str): + standardizer_func([mdl.x, mdl.v, mdl.x]) + + def test_standardizer_invalid_type(self): + """ + Test standardizer raises exception as expected + when input is of invalid type. + """ + standardizer_func = InputDataStandardizer(Var, _VarData) + + exc_str = r"Input object .*is not of valid component type.*" + with self.assertRaisesRegex(TypeError, exc_str): + standardizer_func(2) + + def test_standardizer_iterable_with_invalid_type(self): + """ + Test standardizer raises exception as expected + when input is an iterable with entries of invalid type. + """ + mdl = ConcreteModel() + mdl.v = Var([0, 1]) + standardizer_func = InputDataStandardizer(Var, _VarData) + + exc_str = r"Input object .*entry of iterable.*is not of valid component type.*" + with self.assertRaisesRegex(TypeError, exc_str): + standardizer_func([mdl.v, 2]) + + def test_standardizer_invalid_str_passed(self): + """ + Test standardizer raises exception as expected + when input is of invalid type str. + """ + standardizer_func = InputDataStandardizer(Var, _VarData) + + exc_str = r"Input object .*is not of valid component type.*" + with self.assertRaisesRegex(TypeError, exc_str): + standardizer_func("abcd") + + def test_standardizer_invalid_unintialized_params(self): + """ + Test standardizer raises exception when Param with + uninitialized entries passed. + """ + standardizer_func = InputDataStandardizer( + ctype=Param, cdatatype=_ParamData, ctype_validator=mutable_param_validator + ) + + mdl = ConcreteModel() + mdl.p = Param([0, 1]) + + exc_str = r"Length of .*does not match that of.*index set" + with self.assertRaisesRegex(ValueError, exc_str): + standardizer_func(mdl.p) + + def test_standardizer_invalid_immutable_params(self): + """ + Test standardizer raises exception when immutable + Param object(s) passed. + """ + standardizer_func = InputDataStandardizer( + ctype=Param, cdatatype=_ParamData, ctype_validator=mutable_param_validator + ) + + mdl = ConcreteModel() + mdl.p = Param([0, 1], initialize=1) + + exc_str = r"Param object with name .*immutable" + with self.assertRaisesRegex(ValueError, exc_str): + standardizer_func(mdl.p) + + def test_standardizer_valid_mutable_params(self): + """ + Test Param-like standardizer works as expected for sequence + of valid mutable Param objects. + """ + mdl = ConcreteModel() + mdl.p1 = Param([0, 1], initialize=0, mutable=True) + mdl.p2 = Param(["a", "b"], initialize=1, mutable=True) + + standardizer_func = InputDataStandardizer( + ctype=Param, cdatatype=_ParamData, ctype_validator=mutable_param_validator + ) + + standardizer_input = [mdl.p1[0], mdl.p2] + standardizer_output = standardizer_func(standardizer_input) + expected_standardizer_output = [mdl.p1[0], mdl.p2["a"], mdl.p2["b"]] + + self.assertIsInstance( + standardizer_output, + list, + msg=( + "Standardized output should be of type list, " + f"but is of type {standardizer_output.__class__.__name__}." + ), + ) + self.assertEqual( + len(standardizer_output), + len(expected_standardizer_output), + msg="Length of standardizer output is not as expected.", + ) + enum_zip = enumerate(zip(expected_standardizer_output, standardizer_output)) + for idx, (input, output) in enum_zip: + self.assertIs( + input, + output, + msg=( + f"Entry {input} (id {id(input)}) " + "is not identical to " + f"input component data object {output} " + f"(id {id(output)})" + ), + ) + + +if __name__ == "__main__": + unittest.main() From fc497bc6de62734f7d53895062c0fbcc014b944a Mon Sep 17 00:00:00 2001 From: jasherma Date: Tue, 6 Feb 2024 22:43:43 -0500 Subject: [PATCH 0895/1204] Refactor uncertainty set argument validation --- pyomo/contrib/pyros/config.py | 4 +-- pyomo/contrib/pyros/tests/test_config.py | 28 +++++++++++++++ pyomo/contrib/pyros/uncertainty_sets.py | 45 ++++++++++++++++++++---- 3 files changed, 69 insertions(+), 8 deletions(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index c118b650208..632a226b47b 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -16,7 +16,7 @@ setup_pyros_logger, ValidEnum, ) -from pyomo.contrib.pyros.uncertainty_sets import uncertainty_sets +from pyomo.contrib.pyros.uncertainty_sets import UncertaintySetDomain default_pyros_solver_logger = setup_pyros_logger() @@ -330,7 +330,7 @@ def pyros_config(): "uncertainty_set", ConfigValue( default=None, - domain=uncertainty_sets, + domain=UncertaintySetDomain(), description=( """ Uncertainty set against which the diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index d0c378a52f0..6d6c3caf76b 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -14,7 +14,9 @@ from pyomo.contrib.pyros.config import ( InputDataStandardizer, mutable_param_validator, + UncertaintySetDomain, ) +from pyomo.contrib.pyros.uncertainty_sets import BoxSet class testInputDataStandardizer(unittest.TestCase): @@ -263,5 +265,31 @@ def test_standardizer_valid_mutable_params(self): ) +class TestUncertaintySetDomain(unittest.TestCase): + """ + Test domain validator for uncertainty set arguments. + """ + def test_uncertainty_set_domain_valid_set(self): + """ + Test validator works for valid argument. + """ + standardizer_func = UncertaintySetDomain() + bset = BoxSet([[0, 1]]) + self.assertIs( + bset, + standardizer_func(bset), + msg="Output of uncertainty set domain not as expected.", + ) + + def test_uncertainty_set_domain_invalid_type(self): + """ + Test validator works for valid argument. + """ + standardizer_func = UncertaintySetDomain() + exc_str = "Expected an .*UncertaintySet object.*received object 2" + with self.assertRaisesRegex(ValueError, exc_str): + standardizer_func(2) + + if __name__ == "__main__": unittest.main() diff --git a/pyomo/contrib/pyros/uncertainty_sets.py b/pyomo/contrib/pyros/uncertainty_sets.py index 1b51e41fcaf..4a2f198bc17 100644 --- a/pyomo/contrib/pyros/uncertainty_sets.py +++ b/pyomo/contrib/pyros/uncertainty_sets.py @@ -272,12 +272,45 @@ def generate_shape_str(shape, required_shape): ) -def uncertainty_sets(obj): - if not isinstance(obj, UncertaintySet): - raise ValueError( - "Expected an UncertaintySet object, instead received %s" % (obj,) - ) - return obj +class UncertaintySetDomain: + """ + Domain validator for uncertainty set argument. + """ + def __call__(self, obj): + """ + Type validate uncertainty set object. + + Parameters + ---------- + obj : object + Object to validate. + + Returns + ------- + obj : object + Object that was passed, provided type validation successful. + + Raises + ------ + ValueError + If type validation failed. + """ + if not isinstance(obj, UncertaintySet): + raise ValueError( + f"Expected an {UncertaintySet.__name__} object, " + f"instead received object {obj}" + ) + return obj + + def domain_name(self): + """ + Domain name of self. + """ + return UncertaintySet.__name__ + + +# maintain compatibility with prior versions +uncertainty_sets = UncertaintySetDomain() def column(matrix, i): From 497628586141007f142b4a8aa840d57662cc8e94 Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 10:13:22 -0500 Subject: [PATCH 0896/1204] Add more rigorous checks for solver-like args --- pyomo/contrib/pyros/config.py | 282 +++++++++++++++++++++-- pyomo/contrib/pyros/tests/test_config.py | 222 ++++++++++++++++++ pyomo/contrib/pyros/tests/test_grcs.py | 89 ++++++- 3 files changed, 567 insertions(+), 26 deletions(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index 632a226b47b..b31e404f2f6 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -7,6 +7,7 @@ from pyomo.common.collections import ComponentSet from pyomo.common.config import ConfigDict, ConfigValue, In, NonNegativeFloat +from pyomo.common.errors import ApplicationError from pyomo.core.base import Var, _VarData from pyomo.core.base.param import Param, _ParamData from pyomo.opt import SolverFactory @@ -46,27 +47,6 @@ def PositiveIntOrMinusOne(obj): return ans -class SolverResolvable(object): - def __call__(self, obj): - ''' - if obj is a string, return the Solver object for that solver name - if obj is a Solver object, return a copy of the Solver - if obj is a list, and each element of list is solver resolvable, - return list of solvers - ''' - if isinstance(obj, str): - return SolverFactory(obj.lower()) - elif callable(getattr(obj, "solve", None)): - return obj - elif isinstance(obj, list): - return [self(o) for o in obj] - else: - raise ValueError( - "Expected a Pyomo solver or string object, " - "instead received {0}".format(obj.__class__.__name__) - ) - - def mutable_param_validator(param_obj): """ Check that Param-like object has attribute `mutable=True`. @@ -228,6 +208,247 @@ def domain_name(self): ) +class NotSolverResolvable(Exception): + """ + Exception type for failure to cast an object to a Pyomo solver. + """ + + +class SolverResolvable(object): + """ + Callable for casting an object (such as a str) + to a Pyomo solver. + + Parameters + ---------- + require_available : bool, optional + True if `available()` method of a standardized solver + object obtained through `self` must return `True`, + False otherwise. + solver_desc : str, optional + Descriptor for the solver obtained through `self`, + such as 'local solver' + or 'global solver'. This argument is used + for constructing error/exception messages. + + Attributes + ---------- + require_available + solver_desc + """ + + def __init__(self, require_available=True, solver_desc="solver"): + """Initialize self (see class docstring).""" + self.require_available = require_available + self.solver_desc = solver_desc + + @staticmethod + def is_solver_type(obj): + """ + Return True if object is considered a Pyomo solver, + False otherwise. + + An object is considered a Pyomo solver provided that + it has callable attributes named 'solve' and + 'available'. + """ + return callable(getattr(obj, "solve", None)) and callable( + getattr(obj, "available", None) + ) + + def __call__(self, obj, require_available=None, solver_desc=None): + """ + Cast object to a Pyomo solver. + + If `obj` is a string, then ``SolverFactory(obj.lower())`` + is returned. If `obj` is a Pyomo solver type, then + `obj` is returned. + + Parameters + ---------- + obj : object + Object to be cast to Pyomo solver type. + require_available : bool or None, optional + True if `available()` method of the resolved solver + object must return True, False otherwise. + If `None` is passed, then ``self.require_available`` + is used. + solver_desc : str or None, optional + Brief description of the solver, such as 'local solver' + or 'backup global solver'. This argument is used + for constructing error/exception messages. + If `None` is passed, then ``self.solver_desc`` + is used. + + Returns + ------- + Solver + Pyomo solver. + + Raises + ------ + NotSolverResolvable + If `obj` cannot be cast to a Pyomo solver because + it is neither a str nor a Pyomo solver type. + ApplicationError + In event that solver is not available, the + method `available(exception_flag=True)` of the + solver to which `obj` is cast should raise an + exception of this type. The present method + will also emit a more detailed error message + through the default PyROS logger. + """ + # resort to defaults if necessary + if require_available is None: + require_available = self.require_available + if solver_desc is None: + solver_desc = self.solver_desc + + # perform casting + if isinstance(obj, str): + solver = SolverFactory(obj.lower()) + elif self.is_solver_type(obj): + solver = obj + else: + raise NotSolverResolvable( + f"Cannot cast object `{obj!r}` to a Pyomo optimizer for use as " + f"{solver_desc}, as the object is neither a str nor a " + f"Pyomo Solver type (got type {type(obj).__name__})." + ) + + # availability check, if so desired + if require_available: + try: + solver.available(exception_flag=True) + except ApplicationError: + default_pyros_solver_logger.exception( + f"Output of `available()` method for {solver_desc} " + f"with repr {solver!r} resolved from object {obj} " + "is not `True`. " + "Check solver and any required dependencies " + "have been set up properly." + ) + raise + + return solver + + def domain_name(self): + """Return str briefly describing domain encompassed by self.""" + return "str or Solver" + + +class SolverIterable(object): + """ + Callable for casting an iterable (such as a list of strs) + to a list of Pyomo solvers. + + Parameters + ---------- + require_available : bool, optional + True if `available()` method of a standardized solver + object obtained through `self` must return `True`, + False otherwise. + filter_by_availability : bool, optional + True to remove standardized solvers for which `available()` + does not return True, False otherwise. + solver_desc : str, optional + Descriptor for the solver obtained through `self`, + such as 'backup local solver' + or 'backup global solver'. + """ + + def __init__( + self, + require_available=True, + filter_by_availability=True, + solver_desc="solver", + ): + """Initialize self (see class docstring). + + """ + self.require_available = require_available + self.filter_by_availability = filter_by_availability + self.solver_desc = solver_desc + + def __call__( + self, + obj, + require_available=None, + filter_by_availability=None, + solver_desc=None, + ): + """ + Cast iterable object to a list of Pyomo solver objects. + + Parameters + ---------- + obj : str, Solver, or Iterable of str/Solver + Object of interest. + require_available : bool or None, optional + True if `available()` method of each solver + object must return True, False otherwise. + If `None` is passed, then ``self.require_available`` + is used. + solver_desc : str or None, optional + Descriptor for the solver, such as 'backup local solver' + or 'backup global solver'. This argument is used + for constructing error/exception messages. + If `None` is passed, then ``self.solver_desc`` + is used. + + Returns + ------- + solvers : list of solver type + List of solver objects to which obj is cast. + + Raises + ------ + TypeError + If `obj` is a str. + """ + if require_available is None: + require_available = self.require_available + if filter_by_availability is None: + filter_by_availability = self.filter_by_availability + if solver_desc is None: + solver_desc = self.solver_desc + + solver_resolve_func = SolverResolvable() + + if isinstance(obj, str) or solver_resolve_func.is_solver_type(obj): + # single solver resolvable is cast to singleton list. + # perform explicit check for str, otherwise this method + # would attempt to resolve each character. + obj_as_list = [obj] + else: + obj_as_list = list(obj) + + solvers = [] + for idx, val in enumerate(obj_as_list): + solver_desc_str = f"{solver_desc} " f"(index {idx})" + opt = solver_resolve_func( + obj=val, + require_available=require_available, + solver_desc=solver_desc_str, + ) + if filter_by_availability and not opt.available(exception_flag=False): + default_pyros_solver_logger.warning( + f"Output of `available()` method for solver object {opt} " + f"resolved from object {val} of sequence {obj_as_list} " + f"to be used as {self.solver_desc} " + "is not `True`. " + "Removing from list of standardized solvers." + ) + else: + solvers.append(opt) + + return solvers + + def domain_name(self): + """Return str briefly describing domain encompassed by self.""" + return "str, solver type, or Iterable of str/solver type" + + def pyros_config(): CONFIG = ConfigDict('PyROS') @@ -345,7 +566,7 @@ def pyros_config(): "local_solver", ConfigValue( default=None, - domain=SolverResolvable(), + domain=SolverResolvable(solver_desc="local solver", require_available=True), description="Subordinate local NLP solver.", visibility=1, ), @@ -354,7 +575,10 @@ def pyros_config(): "global_solver", ConfigValue( default=None, - domain=SolverResolvable(), + domain=SolverResolvable( + solver_desc="global solver", + require_available=True, + ), description="Subordinate global NLP solver.", visibility=1, ), @@ -525,7 +749,11 @@ def pyros_config(): "backup_local_solvers", ConfigValue( default=[], - domain=SolverResolvable(), + domain=SolverIterable( + solver_desc="backup local solver", + require_available=False, + filter_by_availability=True, + ), doc=( """ Additional subordinate local NLP optimizers to invoke @@ -539,7 +767,11 @@ def pyros_config(): "backup_global_solvers", ConfigValue( default=[], - domain=SolverResolvable(), + domain=SolverIterable( + solver_desc="backup global solver", + require_available=False, + filter_by_availability=True, + ), doc=( """ Additional subordinate global NLP optimizers to invoke diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index 6d6c3caf76b..05ea35c3dda 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -3,6 +3,7 @@ """ +import logging import unittest from pyomo.core.base import ( @@ -10,12 +11,18 @@ Var, _VarData, ) +from pyomo.common.log import LoggingIntercept +from pyomo.common.errors import ApplicationError from pyomo.core.base.param import Param, _ParamData from pyomo.contrib.pyros.config import ( InputDataStandardizer, mutable_param_validator, + NotSolverResolvable, + SolverIterable, + SolverResolvable, UncertaintySetDomain, ) +from pyomo.opt import SolverFactory, SolverResults from pyomo.contrib.pyros.uncertainty_sets import BoxSet @@ -291,5 +298,220 @@ def test_uncertainty_set_domain_invalid_type(self): standardizer_func(2) +class UnavailableSolver: + def available(self, exception_flag=True): + if exception_flag: + raise ApplicationError(f"Solver {self.__class__} not available") + return False + + def solve(self, model, *args, **kwargs): + return SolverResults() + + +class TestSolverResolvable(unittest.TestCase): + """ + Test PyROS standardizer for solver-type objects. + """ + + def test_solver_resolvable_valid_str(self): + """ + Test solver resolvable class is valid for string + type. + """ + solver_str = "ipopt" + standardizer_func = SolverResolvable() + solver = standardizer_func(solver_str) + expected_solver_type = type(SolverFactory(solver_str)) + + self.assertIsInstance( + solver, + type(SolverFactory(solver_str)), + msg=( + "SolverResolvable object should be of type " + f"{expected_solver_type.__name__}, " + f"but got object of type {solver.__class__.__name__}." + ), + ) + + def test_solver_resolvable_valid_solver_type(self): + """ + Test solver resolvable class is valid for string + type. + """ + solver = SolverFactory("ipopt") + standardizer_func = SolverResolvable() + standardized_solver = standardizer_func(solver) + + self.assertIs( + solver, + standardized_solver, + msg=( + f"Test solver {solver} and standardized solver " + f"{standardized_solver} are not identical." + ), + ) + + def test_solver_resolvable_invalid_type(self): + """ + Test solver resolvable object raises expected + exception when invalid entry is provided. + """ + invalid_object = 2 + standardizer_func = SolverResolvable(solver_desc="local solver") + + exc_str = ( + r"Cannot cast object `2` to a Pyomo optimizer.*" + r"local solver.*got type int.*" + ) + with self.assertRaisesRegex(NotSolverResolvable, exc_str): + standardizer_func(invalid_object) + + def test_solver_resolvable_unavailable_solver(self): + """ + Test solver standardizer fails in event solver is + unavaiable. + """ + unavailable_solver = UnavailableSolver() + standardizer_func = SolverResolvable( + solver_desc="local solver", require_available=True + ) + + exc_str = r"Solver.*UnavailableSolver.*not available" + with self.assertRaisesRegex(ApplicationError, exc_str): + with LoggingIntercept(level=logging.ERROR) as LOG: + standardizer_func(unavailable_solver) + + error_msgs = LOG.getvalue()[:-1] + self.assertRegex( + error_msgs, r"Output of `available\(\)` method.*local solver.*" + ) + + +class TestSolverIterable(unittest.TestCase): + """ + Test standardizer method for iterable of solvers, + used to validate `backup_local_solvers` and `backup_global_solvers` + arguments. + """ + + def test_solver_iterable_valid_list(self): + """ + Test solver type standardizer works for list of valid + objects castable to solver. + """ + solver_list = ["ipopt", SolverFactory("ipopt")] + expected_solver_types = [type(SolverFactory("ipopt"))] * 2 + standardizer_func = SolverIterable() + + standardized_solver_list = standardizer_func(solver_list) + + # check list of solver types returned + for idx, standardized_solver in enumerate(standardized_solver_list): + self.assertIsInstance( + standardized_solver, + expected_solver_types[idx], + msg=( + f"Standardized solver {standardized_solver} " + f"(index {idx}) expected to be of type " + f"{expected_solver_types[idx].__name__}, " + f"but is of type {standardized_solver.__class__.__name__}" + ), + ) + + # second entry of standardized solver list should be the same + # object as that of input list, since the input solver is a Pyomo + # solver type + self.assertIs( + standardized_solver_list[1], + solver_list[1], + msg=( + f"Test solver {solver_list[1]} and standardized solver " + f"{standardized_solver_list[1]} should be identical." + ), + ) + + def test_solver_iterable_valid_str(self): + """ + Test SolverIterable raises exception when str passed. + """ + solver_str = "ipopt" + standardizer_func = SolverIterable() + + solver_list = standardizer_func(solver_str) + self.assertEqual( + len(solver_list), 1, "Standardized solver list is not of expected length" + ) + + def test_solver_iterable_unavailable_solver(self): + """ + Test SolverIterable addresses unavailable solvers appropriately. + """ + solvers = (SolverFactory("ipopt"), UnavailableSolver()) + + standardizer_func = SolverIterable( + require_available=True, + filter_by_availability=True, + solver_desc="example solver list", + ) + exc_str = r"Solver.*UnavailableSolver.* not available" + with self.assertRaisesRegex(ApplicationError, exc_str): + standardizer_func(solvers) + with self.assertRaisesRegex(ApplicationError, exc_str): + standardizer_func(solvers, filter_by_availability=False) + + standardized_solver_list = standardizer_func( + solvers, + filter_by_availability=True, + require_available=False, + ) + self.assertEqual( + len(standardized_solver_list), + 1, + msg=( + "Length of filtered standardized solver list not as " + "expected." + ), + ) + self.assertIs( + standardized_solver_list[0], + solvers[0], + msg="Entry of filtered standardized solver list not as expected.", + ) + + standardized_solver_list = standardizer_func( + solvers, + filter_by_availability=False, + require_available=False, + ) + self.assertEqual( + len(standardized_solver_list), + 2, + msg=( + "Length of filtered standardized solver list not as " + "expected." + ), + ) + self.assertEqual( + standardized_solver_list, + list(solvers), + msg="Entry of filtered standardized solver list not as expected.", + ) + + def test_solver_iterable_invalid_list(self): + """ + Test SolverIterable raises exception if iterable contains + at least one invalid object. + """ + invalid_object = ["ipopt", 2] + standardizer_func = SolverIterable(solver_desc="backup solver") + + exc_str = ( + r"Cannot cast object `2` to a Pyomo optimizer.*" + r"backup solver.*index 1.*got type int.*" + ) + with self.assertRaisesRegex(NotSolverResolvable, exc_str): + standardizer_func(invalid_object) + + if __name__ == "__main__": unittest.main() diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 8de1c2666b9..a05e5f06134 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -137,7 +137,7 @@ def __init__(self, calls_to_sleep, max_time, sub_solver): self.num_calls = 0 self.options = Bunch() - def available(self): + def available(self, exception_flag=True): return True def license_is_valid(self): @@ -6302,5 +6302,92 @@ def test_log_disclaimer(self): ) +class UnavailableSolver: + def available(self, exception_flag=True): + if exception_flag: + raise ApplicationError(f"Solver {self.__class__} not available") + return False + + def solve(self, model, *args, **kwargs): + return SolverResults() + + +class TestPyROSUnavailableSubsolvers(unittest.TestCase): + """ + Check that appropriate exceptionsa are raised if + PyROS is invoked with unavailable subsolvers. + """ + + def test_pyros_unavailable_subsolver(self): + """ + Test PyROS raises expected error message when + unavailable subsolver is passed. + """ + m = ConcreteModel() + m.p = Param(range(3), initialize=0, mutable=True) + m.z = Var([0, 1], initialize=0) + m.con = Constraint(expr=m.z[0] + m.z[1] >= m.p[0]) + m.obj = Objective(expr=m.z[0] + m.z[1]) + + pyros_solver = SolverFactory("pyros") + + exc_str = r".*Solver.*UnavailableSolver.*not available" + with self.assertRaisesRegex(ValueError, exc_str): + # note: ConfigDict interface raises ValueError + # once any exception is triggered, + # so we check for that instead of ApplicationError + with LoggingIntercept(level=logging.ERROR) as LOG: + pyros_solver.solve( + model=m, + first_stage_variables=[m.z[0]], + second_stage_variables=[m.z[1]], + uncertain_params=[m.p[0]], + uncertainty_set=BoxSet([[0, 1]]), + local_solver=SolverFactory("ipopt"), + global_solver=UnavailableSolver(), + ) + + error_msgs = LOG.getvalue()[:-1] + self.assertRegex( + error_msgs, r"Output of `available\(\)` method.*global solver.*" + ) + + def test_pyros_unavailable_backup_subsolver(self): + """ + Test PyROS raises expected error message when + unavailable backup subsolver is passed. + """ + m = ConcreteModel() + m.p = Param(range(3), initialize=0, mutable=True) + m.z = Var([0, 1], initialize=0) + m.con = Constraint(expr=m.z[0] + m.z[1] >= m.p[0]) + m.obj = Objective(expr=m.z[0] + m.z[1]) + + pyros_solver = SolverFactory("pyros") + + # note: ConfigDict interface raises ValueError + # once any exception is triggered, + # so we check for that instead of ApplicationError + with LoggingIntercept(level=logging.WARNING) as LOG: + pyros_solver.solve( + model=m, + first_stage_variables=[m.z[0]], + second_stage_variables=[m.z[1]], + uncertain_params=[m.p[0]], + uncertainty_set=BoxSet([[0, 1]]), + local_solver=SolverFactory("ipopt"), + global_solver=SolverFactory("ipopt"), + backup_global_solvers=[UnavailableSolver()], + bypass_global_separation=True, + ) + + error_msgs = LOG.getvalue()[:-1] + self.assertRegex( + error_msgs, + r"Output of `available\(\)` method.*backup global solver.*" + r"Removing from list.*" + ) + + if __name__ == "__main__": unittest.main() From 9e89fee1d9f2984b664fcc68715843410a9feb21 Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 10:38:23 -0500 Subject: [PATCH 0897/1204] Extend domain of objective focus argument --- pyomo/contrib/pyros/config.py | 5 ++-- pyomo/contrib/pyros/tests/test_config.py | 37 ++++++++++++++++++++++++ pyomo/contrib/pyros/util.py | 18 ------------ 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index b31e404f2f6..5c51ab546db 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -6,7 +6,7 @@ from collections.abc import Iterable from pyomo.common.collections import ComponentSet -from pyomo.common.config import ConfigDict, ConfigValue, In, NonNegativeFloat +from pyomo.common.config import ConfigDict, ConfigValue, In, NonNegativeFloat, InEnum from pyomo.common.errors import ApplicationError from pyomo.core.base import Var, _VarData from pyomo.core.base.param import Param, _ParamData @@ -15,7 +15,6 @@ a_logger, ObjectiveType, setup_pyros_logger, - ValidEnum, ) from pyomo.contrib.pyros.uncertainty_sets import UncertaintySetDomain @@ -590,7 +589,7 @@ def pyros_config(): "objective_focus", ConfigValue( default=ObjectiveType.nominal, - domain=ValidEnum(ObjectiveType), + domain=InEnum(ObjectiveType), description=( """ Choice of objective focus to optimize in the master problems. diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index 05ea35c3dda..727c5443315 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -21,7 +21,9 @@ SolverIterable, SolverResolvable, UncertaintySetDomain, + pyros_config, ) +from pyomo.contrib.pyros.util import ObjectiveType from pyomo.opt import SolverFactory, SolverResults from pyomo.contrib.pyros.uncertainty_sets import BoxSet @@ -513,5 +515,40 @@ def test_solver_iterable_invalid_list(self): standardizer_func(invalid_object) +class TestPyROSConfig(unittest.TestCase): + """ + Test PyROS ConfigDict behaves as expected. + """ + + CONFIG = pyros_config() + + def test_config_objective_focus(self): + """ + Test config parses objective focus as expected. + """ + config = self.CONFIG() + + for obj_focus_name in ["nominal", "worst_case"]: + config.objective_focus = obj_focus_name + self.assertEqual( + config.objective_focus, + ObjectiveType[obj_focus_name], + msg="Objective focus not set as expected." + ) + + for obj_focus in ObjectiveType: + config.objective_focus = obj_focus + self.assertEqual( + config.objective_focus, + obj_focus, + msg="Objective focus not set as expected." + ) + + invalid_focus = "test_example" + exc_str = f".*{invalid_focus!r} is not a valid ObjectiveType" + with self.assertRaisesRegex(ValueError, exc_str): + config.objective_focus = invalid_focus + + if __name__ == "__main__": unittest.main() diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index e2986ae18c7..e67d55dfb68 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -461,24 +461,6 @@ def a_logger(str_or_logger): return logging.getLogger(str_or_logger) -def ValidEnum(enum_class): - ''' - Python 3 dependent format string - ''' - - def fcn(obj): - if obj not in enum_class: - raise ValueError( - "Expected an {0} object, " - "instead received {1}".format( - enum_class.__name__, obj.__class__.__name__ - ) - ) - return obj - - return fcn - - class pyrosTerminationCondition(Enum): """Enumeration of all possible PyROS termination conditions.""" From 039171f13bcbc6efc913466d67db15c1c251156d Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 10:47:27 -0500 Subject: [PATCH 0898/1204] Extend domain for path-like args --- pyomo/contrib/pyros/config.py | 56 ++++++++++++++- pyomo/contrib/pyros/tests/test_config.py | 92 +++++++++++++++++++++++- 2 files changed, 145 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index 5c51ab546db..90d8e8cfb3e 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -4,9 +4,10 @@ from collections.abc import Iterable +import os from pyomo.common.collections import ComponentSet -from pyomo.common.config import ConfigDict, ConfigValue, In, NonNegativeFloat, InEnum +from pyomo.common.config import ConfigDict, ConfigValue, In, NonNegativeFloat, InEnum, Path from pyomo.common.errors import ApplicationError from pyomo.core.base import Var, _VarData from pyomo.core.base.param import Param, _ParamData @@ -46,6 +47,57 @@ def PositiveIntOrMinusOne(obj): return ans +class PathLikeOrNone: + """ + Validator for path-like objects. + + This interface is a wrapper around the domain validator + ``common.config.Path``, and extends the domain of interest to + to include: + - None + - objects following the Python ``os.PathLike`` protocol. + + Parameters + ---------- + **config_path_kwargs : dict + Keyword arguments to ``common.config.Path``. + """ + + def __init__(self, **config_path_kwargs): + """Initialize self (see class docstring).""" + self.config_path = Path(**config_path_kwargs) + + def __call__(self, path): + """ + Cast path to expanded string representation. + + Parameters + ---------- + path : None str, bytes, or path-like + Object to be cast. + + Returns + ------- + None + If obj is None. + str + String representation of path-like object. + """ + if path is None: + return path + + # prevent common.config.Path from invoking + # str() on the path-like object + path_str = os.fsdecode(path) + + # standardize path str as necessary + return self.config_path(path_str) + + def domain_name(self): + """Return str briefly describing domain encompassed by self.""" + return "path-like or None" + + def mutable_param_validator(param_obj): """ Check that Param-like object has attribute `mutable=True`. @@ -784,7 +836,7 @@ def pyros_config(): "subproblem_file_directory", ConfigValue( default=None, - domain=str, + domain=PathLikeOrNone(), description=( """ Directory to which to export subproblems not successfully diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index 727c5443315..2e957cc7df6 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -4,6 +4,7 @@ import logging +import os import unittest from pyomo.core.base import ( @@ -11,6 +12,7 @@ Var, _VarData, ) +from pyomo.common.config import Path from pyomo.common.log import LoggingIntercept from pyomo.common.errors import ApplicationError from pyomo.core.base.param import Param, _ParamData @@ -18,10 +20,11 @@ InputDataStandardizer, mutable_param_validator, NotSolverResolvable, + PathLikeOrNone, + pyros_config, SolverIterable, SolverResolvable, UncertaintySetDomain, - pyros_config, ) from pyomo.contrib.pyros.util import ObjectiveType from pyomo.opt import SolverFactory, SolverResults @@ -550,5 +553,92 @@ def test_config_objective_focus(self): config.objective_focus = invalid_focus +class testPathLikeOrNone(unittest.TestCase): + """ + Test interface for validating path-like arguments. + """ + + def test_none_valid(self): + """ + Test `None` is valid. + """ + standardizer_func = PathLikeOrNone() + + self.assertIs( + standardizer_func(None), + None, + msg="Output of `PathLikeOrNone` standardizer not as expected.", + ) + + def test_str_bytes_path_like_valid(self): + """ + Check path-like validator handles str, bytes, and path-like + inputs correctly. + """ + + class ExamplePathLike(os.PathLike): + """ + Path-like class for testing. Key feature: __fspath__ + and __str__ return different outputs. + """ + + def __init__(self, path_str_or_bytes): + self.path = path_str_or_bytes + + def __fspath__(self): + return self.path + + def __str__(self): + path_str = os.fsdecode(self.path) + return f"{type(self).__name__}({path_str})" + + path_standardization_func = PathLikeOrNone() + + # construct path arguments of different type + path_as_str = "example_output_dir/" + path_as_bytes = os.fsencode(path_as_str) + path_like_from_str = ExamplePathLike(path_as_str) + path_like_from_bytes = ExamplePathLike(path_as_bytes) + + # for all possible arguments, output should be + # the str returned by ``common.config.Path`` when + # string representation of the path is input. + expected_output = Path()(path_as_str) + + # check output is as expected in all cases + self.assertEqual( + path_standardization_func(path_as_str), + expected_output, + msg=( + "Path-like validator output from str input " + "does not match expected value." + ), + ) + self.assertEqual( + path_standardization_func(path_as_bytes), + expected_output, + msg=( + "Path-like validator output from bytes input " + "does not match expected value." + ), + ) + self.assertEqual( + path_standardization_func(path_like_from_str), + expected_output, + msg=( + "Path-like validator output from path-like input " + "derived from str does not match expected value." + ), + ) + self.assertEqual( + path_standardization_func(path_like_from_bytes), + expected_output, + msg=( + "Path-like validator output from path-like input " + "derived from bytes does not match expected value." + ), + ) + + if __name__ == "__main__": unittest.main() From c0f2a41d439e2a45ecc0b6bd853d8216c73f6b02 Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 10:56:15 -0500 Subject: [PATCH 0899/1204] Tweak domain name of path-like args --- pyomo/contrib/pyros/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index 90d8e8cfb3e..a6e387b62e9 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -95,7 +95,7 @@ def __call__(self, path): def domain_name(self): """Return str briefly describing domain encompassed by self.""" - return "path-like or None" + return "str, bytes, path-like or None" def mutable_param_validator(param_obj): From 154bbba87730f62d4e270f3bd80161f51c258e46 Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 10:59:23 -0500 Subject: [PATCH 0900/1204] Apply black --- pyomo/contrib/pyros/config.py | 41 +++++++++--------------- pyomo/contrib/pyros/tests/test_config.py | 29 +++++------------ pyomo/contrib/pyros/tests/test_grcs.py | 8 ++--- 3 files changed, 28 insertions(+), 50 deletions(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index a6e387b62e9..663e0252032 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -7,16 +7,19 @@ import os from pyomo.common.collections import ComponentSet -from pyomo.common.config import ConfigDict, ConfigValue, In, NonNegativeFloat, InEnum, Path +from pyomo.common.config import ( + ConfigDict, + ConfigValue, + In, + NonNegativeFloat, + InEnum, + Path, +) from pyomo.common.errors import ApplicationError from pyomo.core.base import Var, _VarData from pyomo.core.base.param import Param, _ParamData from pyomo.opt import SolverFactory -from pyomo.contrib.pyros.util import ( - a_logger, - ObjectiveType, - setup_pyros_logger, -) +from pyomo.contrib.pyros.util import a_logger, ObjectiveType, setup_pyros_logger from pyomo.contrib.pyros.uncertainty_sets import UncertaintySetDomain @@ -126,9 +129,7 @@ def mutable_param_validator(param_obj): "have been initialized." ) if not param_obj.mutable: - raise ValueError( - f"Param object with name {param_obj.name!r} is immutable." - ) + raise ValueError(f"Param object with name {param_obj.name!r} is immutable.") class InputDataStandardizer(object): @@ -409,25 +410,16 @@ class SolverIterable(object): """ def __init__( - self, - require_available=True, - filter_by_availability=True, - solver_desc="solver", - ): - """Initialize self (see class docstring). - - """ + self, require_available=True, filter_by_availability=True, solver_desc="solver" + ): + """Initialize self (see class docstring).""" self.require_available = require_available self.filter_by_availability = filter_by_availability self.solver_desc = solver_desc def __call__( - self, - obj, - require_available=None, - filter_by_availability=None, - solver_desc=None, - ): + self, obj, require_available=None, filter_by_availability=None, solver_desc=None + ): """ Cast iterable object to a list of Pyomo solver objects. @@ -627,8 +619,7 @@ def pyros_config(): ConfigValue( default=None, domain=SolverResolvable( - solver_desc="global solver", - require_available=True, + solver_desc="global solver", require_available=True ), description="Subordinate global NLP solver.", visibility=1, diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index 2e957cc7df6..938fbe8b8e1 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -7,11 +7,7 @@ import os import unittest -from pyomo.core.base import ( - ConcreteModel, - Var, - _VarData, -) +from pyomo.core.base import ConcreteModel, Var, _VarData from pyomo.common.config import Path from pyomo.common.log import LoggingIntercept from pyomo.common.errors import ApplicationError @@ -281,6 +277,7 @@ class TestUncertaintySetDomain(unittest.TestCase): """ Test domain validator for uncertainty set arguments. """ + def test_uncertainty_set_domain_valid_set(self): """ Test validator works for valid argument. @@ -465,17 +462,12 @@ def test_solver_iterable_unavailable_solver(self): standardizer_func(solvers, filter_by_availability=False) standardized_solver_list = standardizer_func( - solvers, - filter_by_availability=True, - require_available=False, + solvers, filter_by_availability=True, require_available=False ) self.assertEqual( len(standardized_solver_list), 1, - msg=( - "Length of filtered standardized solver list not as " - "expected." - ), + msg=("Length of filtered standardized solver list not as " "expected."), ) self.assertIs( standardized_solver_list[0], @@ -484,17 +476,12 @@ def test_solver_iterable_unavailable_solver(self): ) standardized_solver_list = standardizer_func( - solvers, - filter_by_availability=False, - require_available=False, + solvers, filter_by_availability=False, require_available=False ) self.assertEqual( len(standardized_solver_list), 2, - msg=( - "Length of filtered standardized solver list not as " - "expected." - ), + msg=("Length of filtered standardized solver list not as " "expected."), ) self.assertEqual( standardized_solver_list, @@ -536,7 +523,7 @@ def test_config_objective_focus(self): self.assertEqual( config.objective_focus, ObjectiveType[obj_focus_name], - msg="Objective focus not set as expected." + msg="Objective focus not set as expected.", ) for obj_focus in ObjectiveType: @@ -544,7 +531,7 @@ def test_config_objective_focus(self): self.assertEqual( config.objective_focus, obj_focus, - msg="Objective focus not set as expected." + msg="Objective focus not set as expected.", ) invalid_focus = "test_example" diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index a05e5f06134..a75aa4dcf41 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -3766,9 +3766,9 @@ def test_solve_master(self): master_data.master_model.scenarios[0, 0].second_stage_objective = Expression( expr=master_data.master_model.scenarios[0, 0].x ) - master_data.master_model.scenarios[0, 0].util.dr_var_to_exponent_map = ( - ComponentMap() - ) + master_data.master_model.scenarios[ + 0, 0 + ].util.dr_var_to_exponent_map = ComponentMap() master_data.iteration = 0 master_data.timing = TimingData() @@ -6385,7 +6385,7 @@ def test_pyros_unavailable_backup_subsolver(self): self.assertRegex( error_msgs, r"Output of `available\(\)` method.*backup global solver.*" - r"Removing from list.*" + r"Removing from list.*", ) From d7b41d5351b57da5ae377b271aff78098de964ea Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 12:03:24 -0500 Subject: [PATCH 0901/1204] Refactor checks for int-like args --- pyomo/contrib/pyros/config.py | 60 +++++++++++++++--------- pyomo/contrib/pyros/tests/test_config.py | 35 ++++++++++++++ 2 files changed, 72 insertions(+), 23 deletions(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index 663e0252032..19fe6c710ef 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -26,28 +26,42 @@ default_pyros_solver_logger = setup_pyros_logger() -def NonNegIntOrMinusOne(obj): - ''' - if obj is a non-negative int, return the non-negative int - if obj is -1, return -1 - else, error - ''' - ans = int(obj) - if ans != float(obj) or (ans < 0 and ans != -1): - raise ValueError("Expected non-negative int, but received %s" % (obj,)) - return ans - - -def PositiveIntOrMinusOne(obj): - ''' - if obj is a positive int, return the int - if obj is -1, return -1 - else, error - ''' - ans = int(obj) - if ans != float(obj) or (ans <= 0 and ans != -1): - raise ValueError("Expected positive int, but received %s" % (obj,)) - return ans +class PositiveIntOrMinusOne: + """ + Domain validator for objects castable to a + strictly positive int or -1. + """ + + def __call__(self, obj): + """ + Cast object to positive int or -1. + + Parameters + ---------- + obj : object + Object of interest. + + Returns + ------- + int + Positive int, or -1. + + Raises + ------ + ValueError + If object not castable to positive int, or -1. + """ + ans = int(obj) + if ans != float(obj) or (ans <= 0 and ans != -1): + raise ValueError( + "Expected positive int or -1, " + f"but received value {obj!r}" + ) + return ans + + def domain_name(self): + """Return str briefly describing domain encompassed by self.""" + return "positive int or -1" class PathLikeOrNone: @@ -729,7 +743,7 @@ def pyros_config(): "max_iter", ConfigValue( default=-1, - domain=PositiveIntOrMinusOne, + domain=PositiveIntOrMinusOne(), description=( """ Iteration limit. If -1 is provided, then no iteration diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index 938fbe8b8e1..4417966bf72 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -17,6 +17,7 @@ mutable_param_validator, NotSolverResolvable, PathLikeOrNone, + PositiveIntOrMinusOne, pyros_config, SolverIterable, SolverResolvable, @@ -627,5 +628,39 @@ def __str__(self): ) +class TestPositiveIntOrMinusOne(unittest.TestCase): + """ + Test validator for -1 or positive int works as expected. + """ + + def test_positive_int_or_minus_one(self): + """ + Test positive int or -1 validator works as expected. + """ + standardizer_func = PositiveIntOrMinusOne() + self.assertIs( + standardizer_func(1.0), + 1, + msg=( + f"{PositiveIntOrMinusOne.__name__} " + "does not standardize as expected." + ), + ) + self.assertEqual( + standardizer_func(-1.00), + -1, + msg=( + f"{PositiveIntOrMinusOne.__name__} " + "does not standardize as expected." + ), + ) + + exc_str = r"Expected positive int or -1, but received value.*" + with self.assertRaisesRegex(ValueError, exc_str): + standardizer_func(1.5) + with self.assertRaisesRegex(ValueError, exc_str): + standardizer_func(0) + + if __name__ == "__main__": unittest.main() From 2a0c7907a11972f5ec7e41a879a7ce6e3b8f18d7 Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 12:46:33 -0500 Subject: [PATCH 0902/1204] Refactor logger type validator --- pyomo/contrib/pyros/config.py | 40 ++++++++++++++++++++++-- pyomo/contrib/pyros/tests/test_config.py | 28 +++++++++++++++++ pyomo/contrib/pyros/util.py | 27 ---------------- 3 files changed, 65 insertions(+), 30 deletions(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index 19fe6c710ef..8bafb4ea6dd 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -4,6 +4,7 @@ from collections.abc import Iterable +import logging import os from pyomo.common.collections import ComponentSet @@ -19,13 +20,45 @@ from pyomo.core.base import Var, _VarData from pyomo.core.base.param import Param, _ParamData from pyomo.opt import SolverFactory -from pyomo.contrib.pyros.util import a_logger, ObjectiveType, setup_pyros_logger +from pyomo.contrib.pyros.util import ObjectiveType, setup_pyros_logger from pyomo.contrib.pyros.uncertainty_sets import UncertaintySetDomain default_pyros_solver_logger = setup_pyros_logger() +class LoggerType: + """ + Domain validator for objects castable to logging.Logger. + """ + + def __call__(self, obj): + """ + Cast object to logger. + + Parameters + ---------- + obj : object + Object to be cast. + + Returns + ------- + logging.Logger + If `str_or_logger` is of type `logging.Logger`,then + `str_or_logger` is returned. + Otherwise, ``logging.getLogger(str_or_logger)`` + is returned. + """ + if isinstance(obj, logging.Logger): + return obj + else: + return logging.getLogger(obj) + + def domain_name(self): + """Return str briefly describing domain encompassed by self.""" + return "None, str or logging.Logger" + + class PositiveIntOrMinusOne: """ Domain validator for objects castable to a @@ -788,11 +821,12 @@ def pyros_config(): "progress_logger", ConfigValue( default=default_pyros_solver_logger, - domain=a_logger, + domain=LoggerType(), doc=( """ Logger (or name thereof) used for reporting PyROS solver - progress. If a `str` is specified, then ``progress_logger`` + progress. If `None` or a `str` is provided, then + ``progress_logger`` is cast to ``logging.getLogger(progress_logger)``. In the default case, `progress_logger` is set to a :class:`pyomo.contrib.pyros.util.PreformattedLogger` diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index 4417966bf72..73a6678bb9d 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -15,6 +15,7 @@ from pyomo.contrib.pyros.config import ( InputDataStandardizer, mutable_param_validator, + LoggerType, NotSolverResolvable, PathLikeOrNone, PositiveIntOrMinusOne, @@ -662,5 +663,32 @@ def test_positive_int_or_minus_one(self): standardizer_func(0) +class TestLoggerType(unittest.TestCase): + """ + Test logger type validator. + """ + + def test_logger_type(self): + """ + Test logger type validator. + """ + standardizer_func = LoggerType() + mylogger = logging.getLogger("example") + self.assertIs( + standardizer_func(mylogger), + mylogger, + msg=f"{LoggerType.__name__} output not as expected", + ) + self.assertIs( + standardizer_func(mylogger.name), + mylogger, + msg=f"{LoggerType.__name__} output not as expected", + ) + + exc_str = r"A logger name must be a string" + with self.assertRaisesRegex(Exception, exc_str): + standardizer_func(2) + + if __name__ == "__main__": unittest.main() diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index e67d55dfb68..30b5d2df427 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -434,33 +434,6 @@ def setup_pyros_logger(name=DEFAULT_LOGGER_NAME): return logger -def a_logger(str_or_logger): - """ - Standardize a string or logger object to a logger object. - - Parameters - ---------- - str_or_logger : str or logging.Logger - String or logger object to normalize. - - Returns - ------- - logging.Logger - If `str_or_logger` is of type `logging.Logger`,then - `str_or_logger` is returned. - Otherwise, ``logging.getLogger(str_or_logger)`` - is returned. In the event `str_or_logger` is - the name of the default PyROS logger, the logger level - is set to `logging.INFO`, and a `PreformattedLogger` - instance is returned in lieu of a standard `Logger` - instance. - """ - if isinstance(str_or_logger, logging.Logger): - return logging.getLogger(str_or_logger.name) - else: - return logging.getLogger(str_or_logger) - - class pyrosTerminationCondition(Enum): """Enumeration of all possible PyROS termination conditions.""" From 14421fa02711efa171f3e1997d358e88c5b5bf70 Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 12:58:39 -0500 Subject: [PATCH 0903/1204] Apply black --- pyomo/contrib/pyros/config.py | 5 +---- pyomo/contrib/pyros/tests/test_config.py | 6 ++---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index 8bafb4ea6dd..17e4d3804d0 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -86,10 +86,7 @@ def __call__(self, obj): """ ans = int(obj) if ans != float(obj) or (ans <= 0 and ans != -1): - raise ValueError( - "Expected positive int or -1, " - f"but received value {obj!r}" - ) + raise ValueError(f"Expected positive int or -1, but received value {obj!r}") return ans def domain_name(self): diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index 73a6678bb9d..821b1fe7d1e 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -643,16 +643,14 @@ def test_positive_int_or_minus_one(self): standardizer_func(1.0), 1, msg=( - f"{PositiveIntOrMinusOne.__name__} " - "does not standardize as expected." + f"{PositiveIntOrMinusOne.__name__} does not standardize as expected." ), ) self.assertEqual( standardizer_func(-1.00), -1, msg=( - f"{PositiveIntOrMinusOne.__name__} " - "does not standardize as expected." + f"{PositiveIntOrMinusOne.__name__} does not standardize as expected." ), ) From be98fa9721c7a7015573671238cc62180f561983 Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 14:44:40 -0500 Subject: [PATCH 0904/1204] Restructure PyROS argument resolution and validation --- pyomo/contrib/pyros/config.py | 91 +++++++++++++++ pyomo/contrib/pyros/pyros.py | 71 +++++++++--- pyomo/contrib/pyros/tests/test_config.py | 66 +++++++++++ pyomo/contrib/pyros/tests/test_grcs.py | 140 +++++++++++++++++++++++ 4 files changed, 349 insertions(+), 19 deletions(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index 17e4d3804d0..c003d699255 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -950,3 +950,94 @@ def pyros_config(): ) return CONFIG + + +def resolve_keyword_arguments(prioritized_kwargs_dicts, func=None): + """ + Resolve the keyword arguments to a callable in the event + the arguments may have been passed in one or more possible + ways. + + A warning-level message is logged (through the default PyROS + logger) in the event an argument is specified in more than one + way. In this case, the value provided through the means with + the highest priority is selected. + + Parameters + ---------- + prioritized_kwargs_dicts : dict + Each entry maps a str to a dict of the keyword arguments + passed via the means described by the str. + Entries of `prioritized_kwargs_dicts` are taken to be + provided in descending order of priority of the means + by which the arguments may have been passed to the callable. + func : callable or None, optional + Callable to which the keyword arguments are/were passed. + Currently, only the `__name__` attribute is used, + for the purpose of logging warning-level messages. + If `None` is passed, then the warning messages + logged are slightly less informative. + + Returns + ------- + resolved_kwargs : dict + Resolved keyword arguments. + """ + # warnings are issued through logger object + default_logger = default_pyros_solver_logger + + # used for warning messages + func_desc = f"passed to {func.__name__}()" if func is not None else "passed" + + # we will loop through the priority dict. initialize: + # - resolved keyword arguments, taking into account the + # priority order and overlap + # - kwarg dicts already processed + # - sequence of kwarg dicts yet to be processed + resolved_kwargs = dict() + prev_prioritized_kwargs_dicts = dict() + remaining_kwargs_dicts = prioritized_kwargs_dicts.copy() + for curr_desc, curr_kwargs in remaining_kwargs_dicts.items(): + overlapping_args = dict() + overlapping_args_set = set() + + for prev_desc, prev_kwargs in prev_prioritized_kwargs_dicts.items(): + # determine overlap between currrent and previous + # set of kwargs, and remove overlap of current + # and higher priority sets from the result + curr_prev_overlapping_args = ( + set(curr_kwargs.keys()) & set(prev_kwargs.keys()) + ) - overlapping_args_set + if curr_prev_overlapping_args: + # if there is overlap, prepare overlapping args + # for when warning is to be issued + overlapping_args[prev_desc] = curr_prev_overlapping_args + + # update set of args overlapping with higher priority dicts + overlapping_args_set |= curr_prev_overlapping_args + + # ensure kwargs specified in higher priority + # dicts are not overwritten in resolved kwargs + resolved_kwargs.update( + { + kw: val + for kw, val in curr_kwargs.items() + if kw not in overlapping_args_set + } + ) + + # if there are overlaps, log warnings accordingly + # per priority level + for overlap_desc, args_set in overlapping_args.items(): + new_overlapping_args_str = ", ".join(f"{arg!r}" for arg in args_set) + default_logger.warning( + f"Arguments [{new_overlapping_args_str}] passed {curr_desc} " + f"already {func_desc} {overlap_desc}, " + "and will not be overwritten. " + "Consider modifying your arguments to remove the overlap." + ) + + # increment sequence of kwarg dicts already processed + prev_prioritized_kwargs_dicts[curr_desc] = curr_kwargs + + return resolved_kwargs diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 316a5869057..5dc0b1a3e39 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -20,7 +20,7 @@ from pyomo.contrib.pyros.util import time_code from pyomo.common.modeling import unique_component_name from pyomo.opt import SolverFactory -from pyomo.contrib.pyros.config import pyros_config +from pyomo.contrib.pyros.config import pyros_config, resolve_keyword_arguments from pyomo.contrib.pyros.util import ( model_is_valid, recast_to_min_obj, @@ -249,6 +249,48 @@ def _log_config(self, logger, config, exclude_options=None, **log_kwargs): logger.log(msg=f" {key}={val!r}", **log_kwargs) logger.log(msg="-" * self._LOG_LINE_LENGTH, **log_kwargs) + def _resolve_and_validate_pyros_args(self, model, **kwds): + """ + Resolve and validate arguments to ``self.solve()``. + + Parameters + ---------- + model : ConcreteModel + Deterministic model object passed to ``self.solve()``. + **kwds : dict + All other arguments to ``self.solve()``. + + Returns + ------- + config : ConfigDict + Standardized arguments. + + Note + ---- + This method can be broken down into three steps: + + 1. Resolve user arguments based on how they were passed + and order of precedence of the various means by which + they could be passed. + 2. Cast resolved arguments to ConfigDict. Argument-wise + validation is performed automatically. + 3. Inter-argument validation. + """ + options_dict = kwds.pop("options", {}) + dev_options_dict = kwds.pop("dev_options", {}) + resolved_kwds = resolve_keyword_arguments( + prioritized_kwargs_dicts={ + "explicitly": kwds, + "implicitly through argument 'options'": options_dict, + "implicitly through argument 'dev_options'": dev_options_dict, + }, + func=self.solve, + ) + config = self.CONFIG(resolved_kwds) + validate_kwarg_inputs(model, config) + + return config + @document_kwargs_from_configdict( config=CONFIG, section="Keyword Arguments", @@ -299,24 +341,15 @@ def solve( Summary of PyROS termination outcome. """ - - # === Add the explicit arguments to the config - config = self.CONFIG(kwds.pop('options', {})) - config.first_stage_variables = first_stage_variables - config.second_stage_variables = second_stage_variables - config.uncertain_params = uncertain_params - config.uncertainty_set = uncertainty_set - config.local_solver = local_solver - config.global_solver = global_solver - - dev_options = kwds.pop('dev_options', {}) - config.set_value(kwds) - config.set_value(dev_options) - - model = model - - # === Validate kwarg inputs - validate_kwarg_inputs(model, config) + kwds.update(dict( + first_stage_variables=first_stage_variables, + second_stage_variables=second_stage_variables, + uncertain_params=uncertain_params, + uncertainty_set=uncertainty_set, + local_solver=local_solver, + global_solver=global_solver, + )) + config = self._resolve_and_validate_pyros_args(model, **kwds) # === Validate ability of grcs RO solver to handle this model if not model_is_valid(model): diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index 821b1fe7d1e..8308708e080 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -20,6 +20,7 @@ PathLikeOrNone, PositiveIntOrMinusOne, pyros_config, + resolve_keyword_arguments, SolverIterable, SolverResolvable, UncertaintySetDomain, @@ -688,5 +689,70 @@ def test_logger_type(self): standardizer_func(2) +class TestResolveKeywordArguments(unittest.TestCase): + """ + Test keyword argument resolution function works as expected. + """ + + def test_resolve_kwargs_simple_dict(self): + """ + Test resolve kwargs works, simple example + where there is overlap. + """ + explicit_kwargs = dict(arg1=1) + implicit_kwargs_1 = dict(arg1=2, arg2=3) + implicit_kwargs_2 = dict(arg1=4, arg2=4, arg3=5) + + # expected answer + expected_resolved_kwargs = dict(arg1=1, arg2=3, arg3=5) + + # attempt kwargs resolve + with LoggingIntercept(level=logging.WARNING) as LOG: + resolved_kwargs = resolve_keyword_arguments( + prioritized_kwargs_dicts={ + "explicitly": explicit_kwargs, + "implicitly through set 1": implicit_kwargs_1, + "implicitly through set 2": implicit_kwargs_2, + } + ) + + # check kwargs resolved as expected + self.assertEqual( + resolved_kwargs, + expected_resolved_kwargs, + msg="Resolved kwargs do not match expected value.", + ) + + # extract logger warning messages + warning_msgs = LOG.getvalue().split("\n")[:-1] + + self.assertEqual( + len(warning_msgs), 3, msg="Number of warning messages is not as expected." + ) + + # check contents of warning msgs + self.assertRegex( + warning_msgs[0], + expected_regex=( + r"Arguments \['arg1'\] passed implicitly through set 1 " + r"already passed explicitly.*" + ), + ) + self.assertRegex( + warning_msgs[1], + expected_regex=( + r"Arguments \['arg1'\] passed implicitly through set 2 " + r"already passed explicitly.*" + ), + ) + self.assertRegex( + warning_msgs[2], + expected_regex=( + r"Arguments \['arg2'\] passed implicitly through set 2 " + r"already passed implicitly through set 1.*" + ), + ) + + if __name__ == "__main__": unittest.main() diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index a75aa4dcf41..071a579018b 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -6389,5 +6389,145 @@ def test_pyros_unavailable_backup_subsolver(self): ) +class TestPyROSResolveKwargs(unittest.TestCase): + """ + Test PyROS resolves kwargs as expected. + """ + + @unittest.skipUnless( + baron_license_is_valid, "Global NLP solver is not available and licensed." + ) + def test_pyros_kwargs_with_overlap(self): + """ + Test PyROS works as expected when there is overlap between + keyword arguments passed explicitly and implicitly + through `options` or `dev_options`. + """ + # define model + m = ConcreteModel() + m.x1 = Var(initialize=0, bounds=(0, None)) + m.x2 = Var(initialize=0, bounds=(0, None)) + m.x3 = Var(initialize=0, bounds=(None, None)) + m.u1 = Param(initialize=1.125, mutable=True) + m.u2 = Param(initialize=1, mutable=True) + + m.con1 = Constraint(expr=m.x1 * m.u1 ** (0.5) - m.x2 * m.u1 <= 2) + m.con2 = Constraint(expr=m.x1**2 - m.x2**2 * m.u1 == m.x3) + + m.obj = Objective(expr=(m.x1 - 4) ** 2 + (m.x2 - m.u2) ** 2) + + # Define the uncertainty set + # we take the parameter `u2` to be 'fixed' + ellipsoid = AxisAlignedEllipsoidalSet(center=[1.125, 1], half_lengths=[1, 0]) + + # Instantiate the PyROS solver + pyros_solver = SolverFactory("pyros") + + # Define subsolvers utilized in the algorithm + local_subsolver = SolverFactory('ipopt') + global_subsolver = SolverFactory("baron") + + # Call the PyROS solver + with LoggingIntercept(level=logging.WARNING) as LOG: + results = pyros_solver.solve( + model=m, + first_stage_variables=[m.x1, m.x2], + second_stage_variables=[], + uncertain_params=[m.u1, m.u2], + uncertainty_set=ellipsoid, + local_solver=local_subsolver, + global_solver=global_subsolver, + bypass_local_separation=True, + solve_master_globally=True, + options={ + "objective_focus": ObjectiveType.worst_case, + "solve_master_globally": False, + }, + dev_options={ + "objective_focus": ObjectiveType.nominal, + "solve_master_globally": False, + "max_iter": 1, + "time_limit": 1e3, + }, + ) + + # extract warning-level messages. + warning_msgs = LOG.getvalue().split("\n")[:-1] + resolve_kwargs_warning_msgs = [ + msg + for msg in warning_msgs + if msg.startswith("Arguments [") + and "Consider modifying your arguments" in msg + ] + self.assertEqual( + len(resolve_kwargs_warning_msgs), + 3, + msg="Number of warning-level messages not as expected.", + ) + + self.assertRegex( + resolve_kwargs_warning_msgs[0], + expected_regex=( + r"Arguments \['solve_master_globally'\] passed " + r"implicitly through argument 'options' " + r"already passed .*explicitly.*" + ), + ) + self.assertRegex( + resolve_kwargs_warning_msgs[1], + expected_regex=( + r"Arguments \['solve_master_globally'\] passed " + r"implicitly through argument 'dev_options' " + r"already passed .*explicitly.*" + ), + ) + self.assertRegex( + resolve_kwargs_warning_msgs[2], + expected_regex=( + r"Arguments \['objective_focus'\] passed " + r"implicitly through argument 'dev_options' " + r"already passed .*implicitly through argument 'options'.*" + ), + ) + + # check termination status as expected + self.assertEqual( + results.pyros_termination_condition, + pyrosTerminationCondition.max_iter, + msg="Termination condition not as expected", + ) + self.assertEqual( + results.iterations, 1, msg="Number of iterations not as expected" + ) + + # check config resolved as expected + config = results.config + self.assertEqual( + config.bypass_local_separation, + True, + msg="Resolved value of kwarg `bypass_local_separation` not as expected.", + ) + self.assertEqual( + config.solve_master_globally, + True, + msg="Resolved value of kwarg `solve_master_globally` not as expected.", + ) + self.assertEqual( + config.max_iter, + 1, + msg="Resolved value of kwarg `max_iter` not as expected.", + ) + self.assertEqual( + config.objective_focus, + ObjectiveType.worst_case, + msg="Resolved value of kwarg `objective_focus` not as expected.", + ) + self.assertEqual( + config.time_limit, + 1e3, + msg="Resolved value of kwarg `time_limit` not as expected.", + ) + + if __name__ == "__main__": unittest.main() From adc9d68ee4414ae5adf7568a36ad15d2a2f25aeb Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 15:56:24 -0500 Subject: [PATCH 0905/1204] Make advanced validation more rigorous --- pyomo/contrib/pyros/pyros.py | 26 +- pyomo/contrib/pyros/tests/test_grcs.py | 427 +++++++++++++++++++++++-- pyomo/contrib/pyros/util.py | 418 ++++++++++++++++++------ 3 files changed, 723 insertions(+), 148 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 5dc0b1a3e39..0b61798483c 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -22,16 +22,14 @@ from pyomo.opt import SolverFactory from pyomo.contrib.pyros.config import pyros_config, resolve_keyword_arguments from pyomo.contrib.pyros.util import ( - model_is_valid, recast_to_min_obj, add_decision_rule_constraints, add_decision_rule_variables, load_final_solution, pyrosTerminationCondition, ObjectiveType, - validate_uncertainty_set, identify_objective_functions, - validate_kwarg_inputs, + validate_pyros_inputs, transform_to_standard_form, turn_bounds_to_constraints, replace_uncertain_bounds_with_constraints, @@ -287,7 +285,7 @@ def _resolve_and_validate_pyros_args(self, model, **kwds): func=self.solve, ) config = self.CONFIG(resolved_kwds) - validate_kwarg_inputs(model, config) + validate_pyros_inputs(model, config) return config @@ -351,23 +349,6 @@ def solve( )) config = self._resolve_and_validate_pyros_args(model, **kwds) - # === Validate ability of grcs RO solver to handle this model - if not model_is_valid(model): - raise AttributeError( - "This model structure is not currently handled by the ROSolver." - ) - - # === Define nominal point if not specified - if len(config.nominal_uncertain_param_vals) == 0: - config.nominal_uncertain_param_vals = list( - p.value for p in config.uncertain_params - ) - elif len(config.nominal_uncertain_param_vals) != len(config.uncertain_params): - raise AttributeError( - "The nominal_uncertain_param_vals list must be the same length" - "as the uncertain_params list" - ) - # === Create data containers model_data = ROSolveResults() model_data.timing = Bunch() @@ -403,9 +384,6 @@ def solve( model.add_component(model_data.util_block, util) # Note: model.component(model_data.util_block) is util - # === Validate uncertainty set happens here, requires util block for Cardinality and FactorModel sets - validate_uncertainty_set(config=config) - # === Leads to a logger warning here for inactive obj when cloning model_data.original_model = model # === For keeping track of variables after cloning diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 071a579018b..b1546fc62e9 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -18,7 +18,6 @@ selective_clone, add_decision_rule_variables, add_decision_rule_constraints, - model_is_valid, turn_bounds_to_constraints, transform_to_standard_form, ObjectiveType, @@ -610,21 +609,6 @@ def test_dr_eqns_form_correct(self): ) -class testModelIsValid(unittest.TestCase): - def test_model_is_valid_via_possible_inputs(self): - m = ConcreteModel() - m.x = Var() - m.obj1 = Objective(expr=m.x**2) - self.assertTrue(model_is_valid(m)) - m.obj2 = Objective(expr=m.x) - self.assertFalse(model_is_valid(m)) - m.obj2.deactivate() - self.assertTrue(model_is_valid(m)) - m.del_component("obj1") - m.del_component("obj2") - self.assertFalse(model_is_valid(m)) - - class testTurnBoundsToConstraints(unittest.TestCase): def test_bounds_to_constraints(self): m = ConcreteModel() @@ -5406,16 +5390,14 @@ def test_multiple_objs(self): # check validation error raised due to multiple objectives with self.assertRaisesRegex( - AttributeError, - "This model structure is not currently handled by the ROSolver.", + ValueError, r"Expected model with exactly 1 active objective.*has 3" ): pyros_solver.solve(**solve_kwargs) # check validation error raised due to multiple objectives m.b.obj.deactivate() with self.assertRaisesRegex( - AttributeError, - "This model structure is not currently handled by the ROSolver.", + ValueError, r"Expected model with exactly 1 active objective.*has 2" ): pyros_solver.solve(**solve_kwargs) @@ -6529,5 +6511,410 @@ def test_pyros_kwargs_with_overlap(self): ) +class SimpleTestSolver: + """ + Simple test solver class with no actual solve() + functionality. Written to test unrelated aspects + of PyROS functionality. + """ + + def available(self, exception_flag=False): + """ + Check solver available. + """ + return True + + def solve(self, model, **kwds): + """ + Return SolverResults object with 'unknown' termination + condition. Model remains unchanged. + """ + res = SolverResults() + res.solver.termination_condition = TerminationCondition.unknown + + return res + + +class TestPyROSSolverAdvancedValidation(unittest.TestCase): + """ + Test PyROS solver returns expected exception messages + when arguments are invalid. + """ + + def build_simple_test_model(self): + """ + Build simple valid test model. + """ + m = ConcreteModel(name="test_model") + + m.x1 = Var(initialize=0, bounds=(0, None)) + m.x2 = Var(initialize=0, bounds=(0, None)) + m.u = Param(initialize=1.125, mutable=True) + + m.con1 = Constraint(expr=m.x1 * m.u ** (0.5) - m.x2 * m.u <= 2) + + m.obj = Objective(expr=(m.x1 - 4) ** 2 + (m.x2 - 1) ** 2) + + return m + + def test_pyros_invalid_model_type(self): + """ + Test PyROS fails if model is not of correct class. + """ + mdl = self.build_simple_test_model() + + local_solver = SimpleTestSolver() + global_solver = SimpleTestSolver() + + pyros = SolverFactory("pyros") + + exc_str = "Model should be of type.*but is of type.*" + with self.assertRaisesRegex(TypeError, exc_str): + pyros.solve( + model=2, + first_stage_variables=[mdl.x1], + second_stage_variables=[mdl.x2], + uncertain_params=[mdl.u], + uncertainty_set=BoxSet([[1 / 4, 2]]), + local_solver=local_solver, + global_solver=global_solver, + ) + + def test_pyros_multiple_objectives(self): + """ + Test PyROS raises exception if input model has multiple + objectives. + """ + mdl = self.build_simple_test_model() + mdl.obj2 = Objective(expr=(mdl.x1 + mdl.x2)) + + local_solver = SimpleTestSolver() + global_solver = SimpleTestSolver() + + pyros = SolverFactory("pyros") + + exc_str = "Expected model with exactly 1 active.*but.*has 2" + with self.assertRaisesRegex(ValueError, exc_str): + pyros.solve( + model=mdl, + first_stage_variables=[mdl.x1], + second_stage_variables=[mdl.x2], + uncertain_params=[mdl.u], + uncertainty_set=BoxSet([[1 / 4, 2]]), + local_solver=local_solver, + global_solver=global_solver, + ) + + def test_pyros_empty_dof_vars(self): + """ + Test PyROS solver raises exception raised if there are no + first-stage variables or second-stage variables. + """ + # build model + mdl = self.build_simple_test_model() + + # prepare solvers + pyros = SolverFactory("pyros") + local_solver = SimpleTestSolver() + global_solver = SimpleTestSolver() + + # perform checks + exc_str = ( + "Arguments `first_stage_variables` and " + "`second_stage_variables` are both empty lists." + ) + with self.assertRaisesRegex(ValueError, exc_str): + pyros.solve( + model=mdl, + first_stage_variables=[], + second_stage_variables=[], + uncertain_params=[mdl.u], + uncertainty_set=BoxSet([[1 / 4, 2]]), + local_solver=local_solver, + global_solver=global_solver, + ) + + def test_pyros_overlap_dof_vars(self): + """ + Test PyROS solver raises exception raised if there are Vars + passed as both first-stage and second-stage. + """ + # build model + mdl = self.build_simple_test_model() + + # prepare solvers + pyros = SolverFactory("pyros") + local_solver = SimpleTestSolver() + global_solver = SimpleTestSolver() + + # perform checks + exc_str = ( + "Arguments `first_stage_variables` and `second_stage_variables` " + "contain at least one common Var object." + ) + with LoggingIntercept(level=logging.ERROR) as LOG: + with self.assertRaisesRegex(ValueError, exc_str): + pyros.solve( + model=mdl, + first_stage_variables=[mdl.x1], + second_stage_variables=[mdl.x1, mdl.x2], + uncertain_params=[mdl.u], + uncertainty_set=BoxSet([[1 / 4, 2]]), + local_solver=local_solver, + global_solver=global_solver, + ) + + # check logger output is as expected + log_msgs = LOG.getvalue().split("\n")[:-1] + self.assertEqual( + len(log_msgs), 3, "Error message does not contain expected number of lines." + ) + self.assertRegex( + text=log_msgs[0], + expected_regex=( + "The following Vars were found in both `first_stage_variables`" + "and `second_stage_variables`.*" + ), + ) + self.assertRegex(text=log_msgs[1], expected_regex=" 'x1'") + self.assertRegex( + text=log_msgs[2], + expected_regex="Ensure no Vars are included in both arguments.", + ) + + def test_pyros_vars_not_in_model(self): + """ + Test PyROS appropriately raises exception if there are + variables not included in active model objective + or constraints which are not descended from model. + """ + # set up model + mdl = self.build_simple_test_model() + mdl.name = "model1" + mdl2 = self.build_simple_test_model() + mdl2.name = "model2" + + # set up solvers + local_solver = SimpleTestSolver() + global_solver = SimpleTestSolver() + pyros = SolverFactory("pyros") + + mdl.bad_con = Constraint(expr=mdl2.x1 + mdl2.x2 >= 1) + + desc_dof_map = [ + ("first-stage", [mdl2.x1], [], 2), + ("second-stage", [], [mdl2.x2], 2), + ("state", [mdl.x1], [], 3), + ] + + # now perform checks + for vardesc, first_stage_vars, second_stage_vars, numlines in desc_dof_map: + with LoggingIntercept(level=logging.ERROR) as LOG: + exc_str = ( + "Found entries of " + f"{vardesc} variables not descended from.*model.*" + ) + with self.assertRaisesRegex(ValueError, exc_str): + pyros.solve( + model=mdl, + first_stage_variables=first_stage_vars, + second_stage_variables=second_stage_vars, + uncertain_params=[mdl.u], + uncertainty_set=BoxSet([[1 / 4, 2]]), + local_solver=local_solver, + global_solver=global_solver, + ) + + log_msgs = LOG.getvalue().split("\n")[:-1] + + # check detailed log message is as expected + self.assertEqual( + len(log_msgs), + numlines, + "Error-level log message does not contain expected number of lines.", + ) + self.assertRegex( + text=log_msgs[0], + expected_regex=( + f"The following {vardesc} variables" + ".*not descended from.*model with name 'model1'" + ), + ) + + def test_pyros_non_continuous_vars(self): + """ + Test PyROS raises exception if model contains + non-continuous variables. + """ + # build model; make one variable discrete + mdl = self.build_simple_test_model() + mdl.x2.domain = NonNegativeIntegers + + # prepare solvers + pyros = SolverFactory("pyros") + local_solver = SimpleTestSolver() + global_solver = SimpleTestSolver() + + # perform checks + exc_str = "Model with name 'test_model' contains non-continuous Vars." + with LoggingIntercept(level=logging.ERROR) as LOG: + with self.assertRaisesRegex(ValueError, exc_str): + pyros.solve( + model=mdl, + first_stage_variables=[mdl.x1], + second_stage_variables=[mdl.x2], + uncertain_params=[mdl.u], + uncertainty_set=BoxSet([[1 / 4, 2]]), + local_solver=local_solver, + global_solver=global_solver, + ) + + # check logger output is as expected + log_msgs = LOG.getvalue().split("\n")[:-1] + self.assertEqual( + len(log_msgs), 3, "Error message does not contain expected number of lines." + ) + self.assertRegex( + text=log_msgs[0], + expected_regex=( + "The following Vars of model with name 'test_model' " + "are non-continuous:" + ), + ) + self.assertRegex(text=log_msgs[1], expected_regex=" 'x2'") + self.assertRegex( + text=log_msgs[2], + expected_regex=( + "Ensure all model variables passed to " "PyROS solver are continuous." + ), + ) + + def test_pyros_uncertainty_dimension_mismatch(self): + """ + Test PyROS solver raises exception if uncertainty + set dimension does not match the number + of uncertain parameters. + """ + # build model + mdl = self.build_simple_test_model() + + # prepare solvers + pyros = SolverFactory("pyros") + local_solver = SimpleTestSolver() + global_solver = SimpleTestSolver() + + # perform checks + exc_str = ( + r"Length of argument `uncertain_params` does not match dimension " + r"of argument `uncertainty_set` \(1 != 2\)." + ) + with self.assertRaisesRegex(ValueError, exc_str): + pyros.solve( + model=mdl, + first_stage_variables=[mdl.x1], + second_stage_variables=[mdl.x2], + uncertain_params=[mdl.u], + uncertainty_set=BoxSet([[1 / 4, 2], [0, 1]]), + local_solver=local_solver, + global_solver=global_solver, + ) + + def test_pyros_nominal_point_not_in_set(self): + """ + Test PyROS raises exception if nominal point is not in the + uncertainty set. + + NOTE: need executable solvers to solve set bounding problems + for validity checks. + """ + # build model + mdl = self.build_simple_test_model() + + # prepare solvers + pyros = SolverFactory("pyros") + local_solver = SolverFactory("ipopt") + global_solver = SolverFactory("ipopt") + + # perform checks + exc_str = ( + r"Nominal uncertain parameter realization \[0\] " + "is not a point in the uncertainty set.*" + ) + with self.assertRaisesRegex(ValueError, exc_str): + pyros.solve( + model=mdl, + first_stage_variables=[mdl.x1], + second_stage_variables=[mdl.x2], + uncertain_params=[mdl.u], + uncertainty_set=BoxSet([[1 / 4, 2]]), + local_solver=local_solver, + global_solver=global_solver, + nominal_uncertain_param_vals=[0], + ) + + def test_pyros_nominal_point_len_mismatch(self): + """ + Test PyROS raises exception if there is mismatch between length + of nominal uncertain parameter specification and number + of uncertain parameters. + """ + # build model + mdl = self.build_simple_test_model() + + # prepare solvers + pyros = SolverFactory("pyros") + local_solver = SolverFactory("ipopt") + global_solver = SolverFactory("ipopt") + + # perform checks + exc_str = ( + r"Lengths of arguments `uncertain_params` " + r"and `nominal_uncertain_param_vals` " + r"do not match \(1 != 2\)." + ) + with self.assertRaisesRegex(ValueError, exc_str): + pyros.solve( + model=mdl, + first_stage_variables=[mdl.x1], + second_stage_variables=[mdl.x2], + uncertain_params=[mdl.u], + uncertainty_set=BoxSet([[1 / 4, 2]]), + local_solver=local_solver, + global_solver=global_solver, + nominal_uncertain_param_vals=[0, 1], + ) + + def test_pyros_invalid_bypass_separation(self): + """ + Test PyROS raises exception if both local and + global separation are set to be bypassed. + """ + # build model + mdl = self.build_simple_test_model() + + # prepare solvers + pyros = SolverFactory("pyros") + local_solver = SolverFactory("ipopt") + global_solver = SolverFactory("ipopt") + + # perform checks + exc_str = ( + r"Arguments `bypass_local_separation` and `bypass_global_separation` " + r"cannot both be True." + ) + with self.assertRaisesRegex(ValueError, exc_str): + pyros.solve( + model=mdl, + first_stage_variables=[mdl.x1], + second_stage_variables=[mdl.x2], + uncertain_params=[mdl.u], + uncertainty_set=BoxSet([[1 / 4, 2]]), + local_solver=local_solver, + global_solver=global_solver, + bypass_local_separation=True, + bypass_global_separation=True, + ) + + if __name__ == "__main__": unittest.main() diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index 30b5d2df427..685ff9ca898 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -512,14 +512,6 @@ def recast_to_min_obj(model, obj): obj.sense = minimize -def model_is_valid(model): - """ - Assess whether model is valid on basis of the number of active - Objectives. A valid model must contain exactly one active Objective. - """ - return len(list(model.component_data_objects(Objective, active=True))) == 1 - - def turn_bounds_to_constraints(variable, model, config=None): ''' Turn the variable in question's "bounds" into direct inequality constraints on the model. @@ -603,41 +595,6 @@ def get_time_from_solver(results): return float("nan") if solve_time is None else solve_time -def validate_uncertainty_set(config): - ''' - Confirm expression output from uncertainty set function references all q in q. - Typecheck the uncertainty_set.q is Params referenced inside of m. - Give warning that the nominal point (default value in the model) is not in the specified uncertainty set. - :param config: solver config - ''' - # === Check that q in UncertaintySet object constraint expression is referencing q in model.uncertain_params - uncertain_params = config.uncertain_params - - # === Non-zero number of uncertain parameters - if len(uncertain_params) == 0: - raise AttributeError( - "Must provide uncertain params, uncertain_params list length is 0." - ) - # === No duplicate parameters - if len(uncertain_params) != len(ComponentSet(uncertain_params)): - raise AttributeError("No duplicates allowed for uncertain param objects.") - # === Ensure nominal point is in the set - if not config.uncertainty_set.point_in_set( - point=config.nominal_uncertain_param_vals - ): - raise AttributeError( - "Nominal point for uncertain parameters must be in the uncertainty set." - ) - # === Check set validity via boundedness and non-emptiness - if not config.uncertainty_set.is_valid(config=config): - raise AttributeError( - "Invalid uncertainty set detected. Check the uncertainty set object to " - "ensure non-emptiness and boundedness." - ) - - return - - def add_bounds_for_uncertain_parameters(model, config): ''' This function solves a set of optimization problems to determine bounds on the uncertain parameters @@ -817,98 +774,351 @@ def replace_uncertain_bounds_with_constraints(model, uncertain_params): v.setlb(None) -def validate_kwarg_inputs(model, config): - ''' - Confirm kwarg inputs satisfy PyROS requirements. - :param model: the deterministic model - :param config: the config for this PyROS instance - :return: - ''' - - # === Check if model is ConcreteModel object - if not isinstance(model, ConcreteModel): - raise ValueError("Model passed to PyROS solver must be a ConcreteModel object.") +def check_components_descended_from_model(model, components, components_name, config): + """ + Check all members in a provided sequence of Pyomo component + objects are descended from a given ConcreteModel object. - first_stage_variables = config.first_stage_variables - second_stage_variables = config.second_stage_variables - uncertain_params = config.uncertain_params + Parameters + ---------- + model : ConcreteModel + Model from which components should all be descended. + components : Iterable of Component + Components of interest. + components_name : str + Brief description or name for the sequence of components. + Used for constructing error messages. + config : ConfigDict + PyROS solver options. - if not config.first_stage_variables and not config.second_stage_variables: - # Must have non-zero DOF + Raises + ------ + ValueError + If at least one entry of `components` is not descended + from `model`. + """ + components_not_in_model = [comp for comp in components if comp.model() is not model] + if components_not_in_model: + comp_names_str = "\n ".join( + f"{comp.name!r}, from model with name {comp.model().name!r}" + for comp in components_not_in_model + ) + config.progress_logger.error( + f"The following {components_name} " + "are not descended from the " + f"input deterministic model with name {model.name!r}:\n " + f"{comp_names_str}" + ) raise ValueError( - "first_stage_variables and " - "second_stage_variables cannot both be empty lists." + f"Found entries of {components_name} " + "not descended from input model. " + "Check logger output messages." ) - if ComponentSet(first_stage_variables) != ComponentSet( - config.first_stage_variables - ): + +def get_state_vars(blk, first_stage_variables, second_stage_variables): + """ + Get state variables of a modeling block. + + The state variables with respect to `blk` are the unfixed + `_VarData` objects participating in the active objective + or constraints descended from `blk` which are not + first-stage variables or second-stage variables. + + Parameters + ---------- + blk : ScalarBlock + Block of interest. + first_stage_variables : Iterable of VarData + First-stage variables. + second_stage_variables : Iterable of VarData + Second-stage variables. + + Yields + ------ + _VarData + State variable. + """ + dof_var_set = ( + ComponentSet(first_stage_variables) + | ComponentSet(second_stage_variables) + ) + for var in get_vars_from_component(blk, (Objective, Constraint)): + is_state_var = not var.fixed and var not in dof_var_set + if is_state_var: + yield var + + +def check_variables_continuous(model, vars, config): + """ + Check that all DOF and state variables of the model + are continuous. + + Parameters + ---------- + model : ConcreteModel + Input deterministic model. + config : ConfigDict + PyROS solver options. + + Raises + ------ + ValueError + If at least one variable is found to not be continuous. + + Note + ---- + A variable is considered continuous if the `is_continuous()` + method returns True. + """ + non_continuous_vars = [var for var in vars if not var.is_continuous()] + if non_continuous_vars: + non_continuous_vars_str = "\n ".join( + f"{var.name!r}" for var in non_continuous_vars + ) + config.progress_logger.error( + f"The following Vars of model with name {model.name!r} " + f"are non-continuous:\n {non_continuous_vars_str}\n" + "Ensure all model variables passed to PyROS solver are continuous." + ) raise ValueError( - "All elements in first_stage_variables must be Var members of the model object." + f"Model with name {model.name!r} contains non-continuous Vars." ) - if ComponentSet(second_stage_variables) != ComponentSet( - config.second_stage_variables - ): + +def validate_model(model, config): + """ + Validate deterministic model passed to PyROS solver. + + Parameters + ---------- + model : ConcreteModel + Determinstic model. Should have only one active Objective. + config : ConfigDict + PyROS solver options. + + Returns + ------- + ComponentSet + The variables participating in the active Objective + and Constraint expressions of `model`. + + Raises + ------ + TypeError + If model is not of type ConcreteModel. + ValueError + If model does not have exactly one active Objective + component. + """ + # note: only support ConcreteModel. no support for Blocks + if not isinstance(model, ConcreteModel): + raise TypeError( + f"Model should be of type {ConcreteModel.__name__}, " + f"but is of type {type(model).__name__}." + ) + + # active objectives check + active_objs_list = list( + model.component_data_objects(Objective, active=True, descend_into=True) + ) + if len(active_objs_list) != 1: raise ValueError( - "All elements in second_stage_variables must be Var members of the model object." + "Expected model with exactly 1 active objective, but " + f"model provided has {len(active_objs_list)}." ) - if any( - v in ComponentSet(second_stage_variables) - for v in ComponentSet(first_stage_variables) - ): + +def validate_variable_partitioning(model, config): + """ + Check that partitioning of the first-stage variables, + second-stage variables, and uncertain parameters + is valid. + + Parameters + ---------- + model : ConcreteModel + Input deterministic model. + config : ConfigDict + PyROS solver options. + + Returns + ------- + list of _VarData + State variables of the model. + + Raises + ------ + ValueError + If first-stage variables and second-stage variables + overlap, or there are no first-stage variables + and no second-stage variables. + """ + # at least one DOF required + if not config.first_stage_variables and not config.second_stage_variables: raise ValueError( - "No common elements allowed between first_stage_variables and second_stage_variables." + "Arguments `first_stage_variables` and " + "`second_stage_variables` are both empty lists." ) - if ComponentSet(uncertain_params) != ComponentSet(config.uncertain_params): + # ensure no overlap between DOF var sets + overlapping_vars = ComponentSet(config.first_stage_variables) & ComponentSet( + config.second_stage_variables + ) + if overlapping_vars: + overlapping_var_list = "\n ".join(f"{var.name!r}" for var in overlapping_vars) + config.progress_logger.error( + "The following Vars were found in both `first_stage_variables`" + f"and `second_stage_variables`:\n {overlapping_var_list}" + "\nEnsure no Vars are included in both arguments." + ) raise ValueError( - "uncertain_params must be mutable Param members of the model object." + "Arguments `first_stage_variables` and `second_stage_variables` " + "contain at least one common Var object." ) - if not config.uncertainty_set: + state_vars = list(get_state_vars( + model, + first_stage_variables=config.first_stage_variables, + second_stage_variables=config.second_stage_variables, + )) + var_type_list_map = { + "first-stage variables": config.first_stage_variables, + "second-stage variables": config.second_stage_variables, + "state variables": state_vars, + } + for desc, vars in var_type_list_map.items(): + check_components_descended_from_model( + model=model, + components=vars, + components_name=desc, + config=config, + ) + + all_vars = ( + config.first_stage_variables + + config.second_stage_variables + + state_vars + ) + check_variables_continuous(model, all_vars, config) + + return state_vars + + +def validate_uncertainty_specification(model, config): + """ + Validate specification of uncertain parameters and uncertainty + set. + + Parameters + ---------- + model : ConcreteModel + Input deterministic model. + config : ConfigDict + PyROS solver options. + + Raises + ------ + ValueError + If at least one of the following holds: + + - dimension of uncertainty set does not equal number of + uncertain parameters + - uncertainty set `is_valid()` method does not return + true. + - nominal parameter realization is not in the uncertainty set. + """ + check_components_descended_from_model( + model=model, + components=config.uncertain_params, + components_name="uncertain parameters", + config=config, + ) + + if len(config.uncertain_params) != config.uncertainty_set.dim: raise ValueError( - "An UncertaintySet object must be provided to the PyROS solver." + "Length of argument `uncertain_params` does not match dimension " + "of argument `uncertainty_set` " + f"({len(config.uncertain_params)} != {config.uncertainty_set.dim})." ) - non_mutable_params = [] - for p in config.uncertain_params: - if not ( - not p.is_constant() and p.is_fixed() and not p.is_potentially_variable() - ): - non_mutable_params.append(p) - if non_mutable_params: - raise ValueError( - "Param objects which are uncertain must have attribute mutable=True. " - "Offending Params: %s" % [p.name for p in non_mutable_params] - ) + # validate uncertainty set + if not config.uncertainty_set.is_valid(config=config): + raise ValueError( + f"Uncertainty set {config.uncertainty_set} is invalid, " + "as it is either empty or unbounded." + ) - # === Solvers provided check - if not config.local_solver or not config.global_solver: + # fill-in nominal point as necessary, if not provided. + # otherwise, check length matches uncertainty dimension + if not config.nominal_uncertain_param_vals: + config.nominal_uncertain_param_vals = [ + value(param, exception=True) for param in config.uncertain_params + ] + elif len(config.nominal_uncertain_param_vals) != len(config.uncertain_params): raise ValueError( - "User must designate both a local and global optimization solver via the local_solver" - " and global_solver options." + "Lengths of arguments `uncertain_params` and " + "`nominal_uncertain_param_vals` " + "do not match " + f"({len(config.uncertain_params)} != " + f"{len(config.nominal_uncertain_param_vals)})." ) - if config.bypass_local_separation and config.bypass_global_separation: + # uncertainty set should contain nominal point + nominal_point_in_set = config.uncertainty_set.point_in_set( + point=config.nominal_uncertain_param_vals + ) + if not nominal_point_in_set: raise ValueError( - "User cannot simultaneously enable options " - "'bypass_local_separation' and " - "'bypass_global_separation'." + "Nominal uncertain parameter realization " + f"{config.nominal_uncertain_param_vals} " + "is not a point in the uncertainty set " + f"{config.uncertainty_set!r}." ) - # === Degrees of freedom provided check - if len(config.first_stage_variables) + len(config.second_stage_variables) == 0: + +def validate_separation_problem_options(model, config): + """ + Validate separation problem arguments to the PyROS solver. + + Parameters + ---------- + model : ConcreteModel + Input deterministic model. + config : ConfigDict + PyROS solver options. + + Raises + ------ + ValueError + If options `bypass_local_separation` and + `bypass_global_separation` are set to False. + """ + if config.bypass_local_separation and config.bypass_global_separation: raise ValueError( - "User must designate at least one first- and/or second-stage variable." + "Arguments `bypass_local_separation` " + "and `bypass_global_separation` " + "cannot both be True." ) - # === Uncertain params provided check - if len(config.uncertain_params) == 0: - raise ValueError("User must designate at least one uncertain parameter.") - return +def validate_pyros_inputs(model, config): + """ + Perform advanced validation of PyROS solver arguments. + + Parameters + ---------- + model : ConcreteModel + Input deterministic model. + config : ConfigDict + PyROS solver options. + """ + validate_model(model, config) + state_vars = validate_variable_partitioning(model, config) + validate_uncertainty_specification(model, config) + validate_separation_problem_options(model, config) + + return state_vars def substitute_ssv_in_dr_constraints(model, constraint): From 4f64c4fa651ffd48cd6a6e5b7436e1002310b539 Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 16:01:31 -0500 Subject: [PATCH 0906/1204] Simplify assembly of state variables --- pyomo/contrib/pyros/pyros.py | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 0b61798483c..05512ec777b 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -12,7 +12,7 @@ # pyros.py: Generalized Robust Cutting-Set Algorithm for Pyomo import logging from pyomo.common.config import document_kwargs_from_configdict -from pyomo.common.collections import Bunch, ComponentSet +from pyomo.common.collections import Bunch from pyomo.core.base.block import Block from pyomo.core.expr import value from pyomo.core.base.var import Var @@ -285,9 +285,9 @@ def _resolve_and_validate_pyros_args(self, model, **kwds): func=self.solve, ) config = self.CONFIG(resolved_kwds) - validate_pyros_inputs(model, config) + state_vars = validate_pyros_inputs(model, config) - return config + return config, state_vars @document_kwargs_from_configdict( config=CONFIG, @@ -347,7 +347,7 @@ def solve( local_solver=local_solver, global_solver=global_solver, )) - config = self._resolve_and_validate_pyros_args(model, **kwds) + config, state_vars = self._resolve_and_validate_pyros_args(model, **kwds) # === Create data containers model_data = ROSolveResults() @@ -378,6 +378,7 @@ def solve( util = Block(concrete=True) util.first_stage_variables = config.first_stage_variables util.second_stage_variables = config.second_stage_variables + util.state_vars = state_vars util.uncertain_params = config.uncertain_params model_data.util_block = unique_component_name(model, 'util') @@ -425,22 +426,10 @@ def solve( # === Move bounds on control variables to explicit ineq constraints wm_util = model_data.working_model - # === Every non-fixed variable that is neither first-stage - # nor second-stage is taken to be a state variable - fsv = ComponentSet(model_data.working_model.util.first_stage_variables) - ssv = ComponentSet(model_data.working_model.util.second_stage_variables) - sv = ComponentSet() - model_data.working_model.util.state_vars = [] - for v in model_data.working_model.component_data_objects(Var): - if not v.fixed and v not in fsv | ssv | sv: - model_data.working_model.util.state_vars.append(v) - sv.add(v) - - # Bounds on second stage variables and state variables are separation objectives, - # they are brought in this was as explicit constraints + # cast bounds on second-stage and state variables to + # explicit constraints for separation objectives for c in model_data.working_model.util.second_stage_variables: turn_bounds_to_constraints(c, wm_util, config) - for c in model_data.working_model.util.state_vars: turn_bounds_to_constraints(c, wm_util, config) From 969aac9d0fbe684af4ed74fdb4371a93416bdd34 Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 16:45:33 -0500 Subject: [PATCH 0907/1204] Apply black --- pyomo/contrib/pyros/pyros.py | 18 ++++++++------- pyomo/contrib/pyros/tests/test_config.py | 8 ++----- pyomo/contrib/pyros/util.py | 28 ++++++++++-------------- 3 files changed, 23 insertions(+), 31 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 05512ec777b..0b37b8e9615 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -339,14 +339,16 @@ def solve( Summary of PyROS termination outcome. """ - kwds.update(dict( - first_stage_variables=first_stage_variables, - second_stage_variables=second_stage_variables, - uncertain_params=uncertain_params, - uncertainty_set=uncertainty_set, - local_solver=local_solver, - global_solver=global_solver, - )) + kwds.update( + dict( + first_stage_variables=first_stage_variables, + second_stage_variables=second_stage_variables, + uncertain_params=uncertain_params, + uncertainty_set=uncertainty_set, + local_solver=local_solver, + global_solver=global_solver, + ) + ) config, state_vars = self._resolve_and_validate_pyros_args(model, **kwds) # === Create data containers diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index 8308708e080..37587fcce58 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -643,16 +643,12 @@ def test_positive_int_or_minus_one(self): self.assertIs( standardizer_func(1.0), 1, - msg=( - f"{PositiveIntOrMinusOne.__name__} does not standardize as expected." - ), + msg=(f"{PositiveIntOrMinusOne.__name__} does not standardize as expected."), ) self.assertEqual( standardizer_func(-1.00), -1, - msg=( - f"{PositiveIntOrMinusOne.__name__} does not standardize as expected." - ), + msg=(f"{PositiveIntOrMinusOne.__name__} does not standardize as expected."), ) exc_str = r"Expected positive int or -1, but received value.*" diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index 685ff9ca898..bcd2363bc43 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -839,9 +839,8 @@ def get_state_vars(blk, first_stage_variables, second_stage_variables): _VarData State variable. """ - dof_var_set = ( - ComponentSet(first_stage_variables) - | ComponentSet(second_stage_variables) + dof_var_set = ComponentSet(first_stage_variables) | ComponentSet( + second_stage_variables ) for var in get_vars_from_component(blk, (Objective, Constraint)): is_state_var = not var.fixed and var not in dof_var_set @@ -977,11 +976,13 @@ def validate_variable_partitioning(model, config): "contain at least one common Var object." ) - state_vars = list(get_state_vars( - model, - first_stage_variables=config.first_stage_variables, - second_stage_variables=config.second_stage_variables, - )) + state_vars = list( + get_state_vars( + model, + first_stage_variables=config.first_stage_variables, + second_stage_variables=config.second_stage_variables, + ) + ) var_type_list_map = { "first-stage variables": config.first_stage_variables, "second-stage variables": config.second_stage_variables, @@ -989,17 +990,10 @@ def validate_variable_partitioning(model, config): } for desc, vars in var_type_list_map.items(): check_components_descended_from_model( - model=model, - components=vars, - components_name=desc, - config=config, + model=model, components=vars, components_name=desc, config=config ) - all_vars = ( - config.first_stage_variables - + config.second_stage_variables - + state_vars - ) + all_vars = config.first_stage_variables + config.second_stage_variables + state_vars check_variables_continuous(model, all_vars, config) return state_vars From 6f1a0552388f25727563908abde9f1b405b6e4b0 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 7 Feb 2024 15:39:39 -0700 Subject: [PATCH 0908/1204] Update ExitNodeDispatcher to be compatible with inherited expression types --- pyomo/repn/util.py | 67 +++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index b65aa9427d5..108bb0ab972 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -387,42 +387,49 @@ def __init__(self, *args, **kwargs): super().__init__(None, *args, **kwargs) def __missing__(self, key): - return functools.partial(self.register_dispatcher, key=key) - - def register_dispatcher(self, visitor, node, *data, key=None): + if type(key) is tuple: + node_class = key[0] + else: + node_class = key + bases = node_class.__mro__ + # Note: if we add an `etype`, then this special-case can be removed if ( - isinstance(node, _named_subexpression_types) - or type(node) is kernel.expression.noclone + issubclass(node_class, _named_subexpression_types) + or node_class is kernel.expression.noclone ): - base_type = Expression - elif not node.is_potentially_variable(): - base_type = node.potentially_variable_base_class() - else: - base_type = node.__class__ - if isinstance(key, tuple): - base_key = (base_type,) + key[1:] - # Only cache handlers for unary, binary and ternary operators - cache = len(key) <= 4 - else: - base_key = base_type - cache = True - if base_key in self: - fcn = self[base_key] - elif base_type in self: - fcn = self[base_type] - elif any((k[0] if k.__class__ is tuple else k) is base_type for k in self): - raise DeveloperError( - f"Base expression key '{base_key}' not found when inserting dispatcher" - f" for node '{type(node).__name__}' while walking expression tree." - ) - else: + bases = [Expression] + fcn = None + for base_type in bases: + if isinstance(key, tuple): + base_key = (base_type,) + key[1:] + # Only cache handlers for unary, binary and ternary operators + cache = len(key) <= 4 + else: + base_key = base_type + cache = True + if base_key in self: + fcn = self[base_key] + elif base_type in self: + fcn = self[base_type] + elif any((k[0] if type(k) is tuple else k) is base_type for k in self): + raise DeveloperError( + f"Base expression key '{base_key}' not found when inserting " + f"dispatcher for node '{node_class.__name__}' while walking " + "expression tree." + ) + if fcn is None: + if type(key) is tuple: + node_class = key[0] + else: + node_class = key raise DeveloperError( - f"Unexpected expression node type '{type(node).__name__}' " - "found while walking expression tree." + f"Unexpected expression node type '{node_class.__name__}' " + f"found while walking expression tree." ) + return self.unexpected_expression_type(key) if cache: self[key] = fcn - return fcn(visitor, node, *data) + return fcn def apply_node_operation(node, args): From 51d23370f198a98481d0260c4b72f93b757c9406 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 7 Feb 2024 15:40:24 -0700 Subject: [PATCH 0909/1204] Refactor ExitNodeDispatcher to provide hook for unknown classes --- pyomo/repn/util.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index 108bb0ab972..cb67dd92494 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -418,19 +418,21 @@ def __missing__(self, key): "expression tree." ) if fcn is None: - if type(key) is tuple: - node_class = key[0] - else: - node_class = key - raise DeveloperError( - f"Unexpected expression node type '{node_class.__name__}' " - f"found while walking expression tree." - ) return self.unexpected_expression_type(key) if cache: self[key] = fcn return fcn + def unexpected_expression_type(self, key): + if type(key) is tuple: + node_class = key[0] + else: + node_class = key + raise DeveloperError( + f"Unexpected expression node type '{node_class.__name__}' " + f"found while walking expression tree." + ) + def apply_node_operation(node, args): try: From 8201978d5536c84e465e1470cf72ea4c02868cfc Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 17:40:34 -0500 Subject: [PATCH 0910/1204] Make first char of test class names uppercase --- pyomo/contrib/pyros/tests/test_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index 37587fcce58..50152abbacd 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -30,7 +30,7 @@ from pyomo.contrib.pyros.uncertainty_sets import BoxSet -class testInputDataStandardizer(unittest.TestCase): +class TestInputDataStandardizer(unittest.TestCase): """ Test standardizer method for Pyomo component-type inputs. """ @@ -543,7 +543,7 @@ def test_config_objective_focus(self): config.objective_focus = invalid_focus -class testPathLikeOrNone(unittest.TestCase): +class TestPathLikeOrNone(unittest.TestCase): """ Test interface for validating path-like arguments. """ From ce7a6b54256f03c24a1089223d355003869bfa63 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 7 Feb 2024 15:40:39 -0700 Subject: [PATCH 0911/1204] Add tests for inherited classes --- pyomo/repn/tests/test_util.py | 36 +++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/pyomo/repn/tests/test_util.py b/pyomo/repn/tests/test_util.py index 47cc6b1a63a..3f455aad13f 100644 --- a/pyomo/repn/tests/test_util.py +++ b/pyomo/repn/tests/test_util.py @@ -19,6 +19,7 @@ from pyomo.common.errors import DeveloperError, InvalidValueError from pyomo.common.log import LoggingIntercept from pyomo.core.expr import ( + NumericExpression, ProductExpression, NPV_ProductExpression, SumExpression, @@ -671,16 +672,6 @@ def test_ExitNodeDispatcher_registration(self): self.assertEqual(len(end), 4) self.assertIn(NPV_ProductExpression, end) - class NewProductExpression(ProductExpression): - pass - - node = NewProductExpression((6, 7)) - with self.assertRaisesRegex( - DeveloperError, r".*Unexpected expression node type 'NewProductExpression'" - ): - end[node.__class__](None, node, *node.args) - self.assertEqual(len(end), 4) - end[SumExpression, 2] = lambda v, n, *d: 2 * sum(d) self.assertEqual(len(end), 5) @@ -710,6 +701,31 @@ class NewProductExpression(ProductExpression): self.assertEqual(len(end), 7) self.assertNotIn((SumExpression, 3, 4, 5, 6), end) + class NewProductExpression(ProductExpression): + pass + + node = NewProductExpression((6, 7)) + self.assertEqual(end[node.__class__](None, node, *node.args), 42) + self.assertEqual(len(end), 8) + self.assertIn(NewProductExpression, end) + + class UnknownExpression(NumericExpression): + pass + + node = UnknownExpression((6, 7)) + with self.assertRaisesRegex( + DeveloperError, r".*Unexpected expression node type 'UnknownExpression'" + ): + end[node.__class__](None, node, *node.args) + self.assertEqual(len(end), 8) + + node = UnknownExpression((6, 7)) + with self.assertRaisesRegex( + DeveloperError, r".*Unexpected expression node type 'UnknownExpression'" + ): + end[node.__class__, 6, 7](None, node, *node.args) + self.assertEqual(len(end), 8) + def test_BeforeChildDispatcher_registration(self): class BeforeChildDispatcherTester(BeforeChildDispatcher): @staticmethod From 012e319dbbea4554cf44c4fc5cb255ae8a014eee Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 17:47:10 -0500 Subject: [PATCH 0912/1204] Remove support for solver argument `dev_options` --- pyomo/contrib/pyros/pyros.py | 2 -- pyomo/contrib/pyros/tests/test_grcs.py | 24 ++---------------------- 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 0b37b8e9615..69a6ce315da 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -275,12 +275,10 @@ def _resolve_and_validate_pyros_args(self, model, **kwds): 3. Inter-argument validation. """ options_dict = kwds.pop("options", {}) - dev_options_dict = kwds.pop("dev_options", {}) resolved_kwds = resolve_keyword_arguments( prioritized_kwargs_dicts={ "explicitly": kwds, "implicitly through argument 'options'": options_dict, - "implicitly through argument 'dev_options'": dev_options_dict, }, func=self.solve, ) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index b1546fc62e9..5727df70a52 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -6424,12 +6424,8 @@ def test_pyros_kwargs_with_overlap(self): options={ "objective_focus": ObjectiveType.worst_case, "solve_master_globally": False, - }, - dev_options={ - "objective_focus": ObjectiveType.nominal, - "solve_master_globally": False, "max_iter": 1, - "time_limit": 1e3, + "time_limit": 1000, }, ) @@ -6443,7 +6439,7 @@ def test_pyros_kwargs_with_overlap(self): ] self.assertEqual( len(resolve_kwargs_warning_msgs), - 3, + 1, msg="Number of warning-level messages not as expected.", ) @@ -6455,22 +6451,6 @@ def test_pyros_kwargs_with_overlap(self): r"already passed .*explicitly.*" ), ) - self.assertRegex( - resolve_kwargs_warning_msgs[1], - expected_regex=( - r"Arguments \['solve_master_globally'\] passed " - r"implicitly through argument 'dev_options' " - r"already passed .*explicitly.*" - ), - ) - self.assertRegex( - resolve_kwargs_warning_msgs[2], - expected_regex=( - r"Arguments \['objective_focus'\] passed " - r"implicitly through argument 'dev_options' " - r"already passed .*implicitly through argument 'options'.*" - ), - ) # check termination status as expected self.assertEqual( From 2c4b89e1116a79718c2b8d72650b88ca8a09e6bb Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 17:48:04 -0500 Subject: [PATCH 0913/1204] Remove `dev_options` from test docstring --- pyomo/contrib/pyros/tests/test_grcs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 5727df70a52..f8c4078f4ee 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -6383,7 +6383,7 @@ def test_pyros_kwargs_with_overlap(self): """ Test PyROS works as expected when there is overlap between keyword arguments passed explicitly and implicitly - through `options` or `dev_options`. + through `options`. """ # define model m = ConcreteModel() From 55884a4ab15b8cc895a5eb3ad0af360f4029bcc0 Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 18:48:55 -0500 Subject: [PATCH 0914/1204] Apply black 24.1.1 --- pyomo/contrib/pyros/config.py | 1 - pyomo/contrib/pyros/tests/test_config.py | 1 - pyomo/contrib/pyros/tests/test_grcs.py | 6 +++--- pyomo/contrib/pyros/uncertainty_sets.py | 1 + 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index c003d699255..42b4ddc29a0 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -2,7 +2,6 @@ Interfaces for managing PyROS solver options. """ - from collections.abc import Iterable import logging import os diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index 50152abbacd..a7f40ca37e8 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -2,7 +2,6 @@ Test objects for construction of PyROS ConfigDict. """ - import logging import os import unittest diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index f8c4078f4ee..70f8a9dfb60 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -3750,9 +3750,9 @@ def test_solve_master(self): master_data.master_model.scenarios[0, 0].second_stage_objective = Expression( expr=master_data.master_model.scenarios[0, 0].x ) - master_data.master_model.scenarios[ - 0, 0 - ].util.dr_var_to_exponent_map = ComponentMap() + master_data.master_model.scenarios[0, 0].util.dr_var_to_exponent_map = ( + ComponentMap() + ) master_data.iteration = 0 master_data.timing = TimingData() diff --git a/pyomo/contrib/pyros/uncertainty_sets.py b/pyomo/contrib/pyros/uncertainty_sets.py index 4a2f198bc17..963abebb60c 100644 --- a/pyomo/contrib/pyros/uncertainty_sets.py +++ b/pyomo/contrib/pyros/uncertainty_sets.py @@ -276,6 +276,7 @@ class UncertaintySetDomain: """ Domain validator for uncertainty set argument. """ + def __call__(self, obj): """ Type validate uncertainty set object. From cabe4250813473132dd7dc27aab624be584a6194 Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 19:03:16 -0500 Subject: [PATCH 0915/1204] Fix typos --- pyomo/contrib/pyros/config.py | 2 +- pyomo/contrib/pyros/tests/test_config.py | 4 ++-- pyomo/contrib/pyros/util.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index 42b4ddc29a0..261e4069c03 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -1001,7 +1001,7 @@ def resolve_keyword_arguments(prioritized_kwargs_dicts, func=None): overlapping_args_set = set() for prev_desc, prev_kwargs in prev_prioritized_kwargs_dicts.items(): - # determine overlap between currrent and previous + # determine overlap between current and previous # set of kwargs, and remove overlap of current # and higher priority sets from the result curr_prev_overlapping_args = ( diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index a7f40ca37e8..ec377f96ca6 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -199,7 +199,7 @@ def test_standardizer_invalid_str_passed(self): with self.assertRaisesRegex(TypeError, exc_str): standardizer_func("abcd") - def test_standardizer_invalid_unintialized_params(self): + def test_standardizer_invalid_uninitialized_params(self): """ Test standardizer raises exception when Param with uninitialized entries passed. @@ -373,7 +373,7 @@ def test_solver_resolvable_invalid_type(self): def test_solver_resolvable_unavailable_solver(self): """ Test solver standardizer fails in event solver is - unavaiable. + unavailable. """ unavailable_solver = UnavailableSolver() standardizer_func = SolverResolvable( diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index bcd2363bc43..e0ed552aab4 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -892,7 +892,7 @@ def validate_model(model, config): Parameters ---------- model : ConcreteModel - Determinstic model. Should have only one active Objective. + Deterministic model. Should have only one active Objective. config : ConfigDict PyROS solver options. From f7c8e4af1724f0dd8362467e12444f71110c684c Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 19:05:14 -0500 Subject: [PATCH 0916/1204] Fix another typo --- pyomo/contrib/pyros/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index 261e4069c03..f12fb3d0be0 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -230,7 +230,7 @@ def standardize_ctype_obj(self, obj): def standardize_cdatatype_obj(self, obj): """ - Standarize object of type ``self.cdatatype`` to + Standardize object of type ``self.cdatatype`` to ``[obj]``. """ if self.cdatatype_validator is not None: From 7fdcf248c6961aa97de924fc885b437a32270597 Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 19:45:54 -0500 Subject: [PATCH 0917/1204] Check IPOPT available before advanced validation tests --- pyomo/contrib/pyros/tests/test_grcs.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 70f8a9dfb60..904c981ed93 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -119,6 +119,9 @@ scip_license_is_valid = False scip_version = (0, 0, 0) +_ipopt = SolverFactory("ipopt") +ipopt_available = _ipopt.available(exception_flag=False) + # @SolverFactory.register("time_delay_solver") class TimeDelaySolver(object): @@ -3533,10 +3536,7 @@ class behaves like a regular Python list. # assigning to slices should work fine all_sets[3:] = [BoxSet([[1, 1.5]]), BoxSet([[1, 3]])] - @unittest.skipUnless( - SolverFactory('ipopt').available(exception_flag=False), - "Local NLP solver is not available.", - ) + @unittest.skipUnless(ipopt_available, "IPOPT is not available.") def test_uncertainty_set_with_correct_params(self): ''' Case in which the UncertaintySet is constructed using the uncertain_param objects from the model to @@ -3575,10 +3575,7 @@ def test_uncertainty_set_with_correct_params(self): " be the same uncertain param Var objects in the original model.", ) - @unittest.skipUnless( - SolverFactory('ipopt').available(exception_flag=False), - "Local NLP solver is not available.", - ) + @unittest.skipUnless(ipopt_available, "IPOPT is not available.") def test_uncertainty_set_with_incorrect_params(self): ''' Case in which the set is constructed using uncertain_param objects which are Params instead of @@ -6799,6 +6796,7 @@ def test_pyros_uncertainty_dimension_mismatch(self): global_solver=global_solver, ) + @unittest.skipUnless(ipopt_available, "IPOPT is not available.") def test_pyros_nominal_point_not_in_set(self): """ Test PyROS raises exception if nominal point is not in the @@ -6832,6 +6830,7 @@ def test_pyros_nominal_point_not_in_set(self): nominal_uncertain_param_vals=[0], ) + @unittest.skipUnless(ipopt_available, "IPOPT is not available.") def test_pyros_nominal_point_len_mismatch(self): """ Test PyROS raises exception if there is mismatch between length @@ -6864,6 +6863,7 @@ def test_pyros_nominal_point_len_mismatch(self): nominal_uncertain_param_vals=[0, 1], ) + @unittest.skipUnless(ipopt_available, "IPOPT is not available.") def test_pyros_invalid_bypass_separation(self): """ Test PyROS raises exception if both local and From ec0ad71db2944ec65bfa37f3f64dea94fc942cea Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 20:24:47 -0500 Subject: [PATCH 0918/1204] Remove IPOPT from solver validation tests --- pyomo/contrib/pyros/tests/test_config.py | 40 +++++++++++++++++++----- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index ec377f96ca6..142a14c2122 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -302,6 +302,29 @@ def test_uncertainty_set_domain_invalid_type(self): standardizer_func(2) +AVAILABLE_SOLVER_TYPE_NAME = "available_pyros_test_solver" + + +@SolverFactory.register(name=AVAILABLE_SOLVER_TYPE_NAME) +class AvailableSolver: + """ + Perenially avaiable placeholder solver. + """ + + def available(self, exception_flag=False): + """ + Check solver available. + """ + return True + + def solve(self, model, **kwds): + """ + Return SolverResults object with 'unknown' termination + condition. Model remains unchanged. + """ + return SolverResults() + + class UnavailableSolver: def available(self, exception_flag=True): if exception_flag: @@ -322,7 +345,7 @@ def test_solver_resolvable_valid_str(self): Test solver resolvable class is valid for string type. """ - solver_str = "ipopt" + solver_str = AVAILABLE_SOLVER_TYPE_NAME standardizer_func = SolverResolvable() solver = standardizer_func(solver_str) expected_solver_type = type(SolverFactory(solver_str)) @@ -342,7 +365,7 @@ def test_solver_resolvable_valid_solver_type(self): Test solver resolvable class is valid for string type. """ - solver = SolverFactory("ipopt") + solver = SolverFactory(AVAILABLE_SOLVER_TYPE_NAME) standardizer_func = SolverResolvable() standardized_solver = standardizer_func(solver) @@ -403,8 +426,11 @@ def test_solver_iterable_valid_list(self): Test solver type standardizer works for list of valid objects castable to solver. """ - solver_list = ["ipopt", SolverFactory("ipopt")] - expected_solver_types = [type(SolverFactory("ipopt"))] * 2 + solver_list = [ + AVAILABLE_SOLVER_TYPE_NAME, + SolverFactory(AVAILABLE_SOLVER_TYPE_NAME), + ] + expected_solver_types = [AvailableSolver] * 2 standardizer_func = SolverIterable() standardized_solver_list = standardizer_func(solver_list) @@ -438,7 +464,7 @@ def test_solver_iterable_valid_str(self): """ Test SolverIterable raises exception when str passed. """ - solver_str = "ipopt" + solver_str = AVAILABLE_SOLVER_TYPE_NAME standardizer_func = SolverIterable() solver_list = standardizer_func(solver_str) @@ -450,7 +476,7 @@ def test_solver_iterable_unavailable_solver(self): """ Test SolverIterable addresses unavailable solvers appropriately. """ - solvers = (SolverFactory("ipopt"), UnavailableSolver()) + solvers = (AvailableSolver(), UnavailableSolver()) standardizer_func = SolverIterable( require_available=True, @@ -496,7 +522,7 @@ def test_solver_iterable_invalid_list(self): Test SolverIterable raises exception if iterable contains at least one invalid object. """ - invalid_object = ["ipopt", 2] + invalid_object = [AVAILABLE_SOLVER_TYPE_NAME, 2] standardizer_func = SolverIterable(solver_desc="backup solver") exc_str = ( From 445d4cf7067d08b67d6d73f60a3b8c6a6880b8c7 Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 20:49:32 -0500 Subject: [PATCH 0919/1204] Fix typos --- pyomo/contrib/pyros/tests/test_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index 142a14c2122..bff098742b6 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -308,7 +308,7 @@ def test_uncertainty_set_domain_invalid_type(self): @SolverFactory.register(name=AVAILABLE_SOLVER_TYPE_NAME) class AvailableSolver: """ - Perenially avaiable placeholder solver. + Perennially available placeholder solver. """ def available(self, exception_flag=False): From 7b26268cb626b6e881849b0f429382e0362f6cce Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 22:02:09 -0500 Subject: [PATCH 0920/1204] Fix test solver registration --- pyomo/contrib/pyros/tests/test_config.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index bff098742b6..adae2dbb1e5 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -305,7 +305,6 @@ def test_uncertainty_set_domain_invalid_type(self): AVAILABLE_SOLVER_TYPE_NAME = "available_pyros_test_solver" -@SolverFactory.register(name=AVAILABLE_SOLVER_TYPE_NAME) class AvailableSolver: """ Perennially available placeholder solver. @@ -340,6 +339,12 @@ class TestSolverResolvable(unittest.TestCase): Test PyROS standardizer for solver-type objects. """ + def setUp(self): + SolverFactory.register(AVAILABLE_SOLVER_TYPE_NAME)(AvailableSolver) + + def tearDown(self): + SolverFactory.unregister(AVAILABLE_SOLVER_TYPE_NAME) + def test_solver_resolvable_valid_str(self): """ Test solver resolvable class is valid for string @@ -421,6 +426,12 @@ class TestSolverIterable(unittest.TestCase): arguments. """ + def setUp(self): + SolverFactory.register(AVAILABLE_SOLVER_TYPE_NAME)(AvailableSolver) + + def tearDown(self): + SolverFactory.unregister(AVAILABLE_SOLVER_TYPE_NAME) + def test_solver_iterable_valid_list(self): """ Test solver type standardizer works for list of valid From b899744ddb4812ee5a76417b4fccf9acc84b3b12 Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 22:15:48 -0500 Subject: [PATCH 0921/1204] Check numpy available for uncertainty set test --- pyomo/contrib/pyros/tests/test_config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index adae2dbb1e5..3113afaac89 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -27,6 +27,7 @@ from pyomo.contrib.pyros.util import ObjectiveType from pyomo.opt import SolverFactory, SolverResults from pyomo.contrib.pyros.uncertainty_sets import BoxSet +from pyomo.common.dependencies import numpy_available class TestInputDataStandardizer(unittest.TestCase): @@ -280,6 +281,7 @@ class TestUncertaintySetDomain(unittest.TestCase): Test domain validator for uncertainty set arguments. """ + @unittest.skipUnless(numpy_available, "Numpy is not available.") def test_uncertainty_set_domain_valid_set(self): """ Test validator works for valid argument. From 655c06dc713c3fa7155c501663b74d3f1116ae89 Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 22:51:55 -0500 Subject: [PATCH 0922/1204] Add IPOPT availability checks to solve tests --- pyomo/contrib/pyros/tests/test_grcs.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 904c981ed93..ef029d0f352 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -6322,7 +6322,7 @@ def test_pyros_unavailable_subsolver(self): second_stage_variables=[m.z[1]], uncertain_params=[m.p[0]], uncertainty_set=BoxSet([[0, 1]]), - local_solver=SolverFactory("ipopt"), + local_solver=SimpleTestSolver(), global_solver=UnavailableSolver(), ) @@ -6331,6 +6331,7 @@ def test_pyros_unavailable_subsolver(self): error_msgs, r"Output of `available\(\)` method.*global solver.*" ) + @unittest.skipUnless(ipopt_available, "IPOPT is not available.") def test_pyros_unavailable_backup_subsolver(self): """ Test PyROS raises expected error message when @@ -6373,8 +6374,10 @@ class TestPyROSResolveKwargs(unittest.TestCase): Test PyROS resolves kwargs as expected. """ + @unittest.skipUnless(ipopt_available, "IPOPT is not available.") @unittest.skipUnless( - baron_license_is_valid, "Global NLP solver is not available and licensed." + baron_license_is_valid, + "Global NLP solver is not available and licensed." ) def test_pyros_kwargs_with_overlap(self): """ From 0f727ab36a8135472092d1e0161161936a16e618 Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 7 Feb 2024 23:06:31 -0500 Subject: [PATCH 0923/1204] Apply black to tests --- pyomo/contrib/pyros/tests/test_grcs.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index ef029d0f352..a94b4d9d408 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -6376,8 +6376,7 @@ class TestPyROSResolveKwargs(unittest.TestCase): @unittest.skipUnless(ipopt_available, "IPOPT is not available.") @unittest.skipUnless( - baron_license_is_valid, - "Global NLP solver is not available and licensed." + baron_license_is_valid, "Global NLP solver is not available and licensed." ) def test_pyros_kwargs_with_overlap(self): """ From 08596e575854b5d7414612c804e8babfbb7e6936 Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 8 Feb 2024 13:55:05 -0500 Subject: [PATCH 0924/1204] Update version number, changelog --- pyomo/contrib/pyros/CHANGELOG.txt | 8 ++++++++ pyomo/contrib/pyros/pyros.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/CHANGELOG.txt b/pyomo/contrib/pyros/CHANGELOG.txt index 7d4678f0ba3..94f4848edb2 100644 --- a/pyomo/contrib/pyros/CHANGELOG.txt +++ b/pyomo/contrib/pyros/CHANGELOG.txt @@ -2,6 +2,13 @@ PyROS CHANGELOG =============== +------------------------------------------------------------------------------- +PyROS 1.2.10 07 Feb 2024 +------------------------------------------------------------------------------- +- Update argument resolution and validation routines of `PyROS.solve()` +- Use methods of `common.config` for docstring of `PyROS.solve()` + + ------------------------------------------------------------------------------- PyROS 1.2.9 15 Dec 2023 ------------------------------------------------------------------------------- @@ -14,6 +21,7 @@ PyROS 1.2.9 15 Dec 2023 - Refactor DR polishing routine; initialize auxiliary variables to values they are meant to represent + ------------------------------------------------------------------------------- PyROS 1.2.8 12 Oct 2023 ------------------------------------------------------------------------------- diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 69a6ce315da..0659ab43a64 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -44,7 +44,7 @@ from datetime import datetime -__version__ = "1.2.9" +__version__ = "1.2.10" default_pyros_solver_logger = setup_pyros_logger() From db3b3abc132b6c45545248cca4a4b48cc8d22214 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 8 Feb 2024 15:38:22 -0700 Subject: [PATCH 0925/1204] Reorganizing the dispatcher structure to better support nested types --- .../contrib/fbbt/expression_bounds_walker.py | 37 +++++++++++-------- .../tests/test_expression_bounds_walker.py | 14 +++++-- pyomo/core/expr/__init__.py | 1 + pyomo/repn/tests/test_linear.py | 2 +- pyomo/repn/tests/test_util.py | 4 +- pyomo/repn/util.py | 34 ++++++++++------- 6 files changed, 57 insertions(+), 35 deletions(-) diff --git a/pyomo/contrib/fbbt/expression_bounds_walker.py b/pyomo/contrib/fbbt/expression_bounds_walker.py index 426d30f0ee6..22ebf28ae81 100644 --- a/pyomo/contrib/fbbt/expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/expression_bounds_walker.py @@ -60,6 +60,14 @@ def _before_external_function(visitor, child): # this then this should use them return False, (-inf, inf) + @staticmethod + def _before_native_numeric(visitor, child): + return False, (child, child) + + @staticmethod + def _before_native_logical(visitor, child): + return False, (Bool(child), Bool(child)) + @staticmethod def _before_var(visitor, child): leaf_bounds = visitor.leaf_bounds @@ -67,12 +75,15 @@ def _before_var(visitor, child): pass elif child.is_fixed() and visitor.use_fixed_var_values_as_bounds: val = child.value - if val is None: + try: + ans = visitor._before_child_handlers[val.__class__](visitor, val) + except ValueError: raise ValueError( "Var '%s' is fixed to None. This value cannot be used to " "calculate bounds." % child.name - ) - leaf_bounds[child] = (child.value, child.value) + ) from None + leaf_bounds[child] = ans[1] + return ans else: lb = child.lb ub = child.ub @@ -93,23 +104,20 @@ def _before_named_expression(visitor, child): @staticmethod def _before_param(visitor, child): - return False, (child.value, child.value) - - @staticmethod - def _before_native(visitor, child): - return False, (child, child) + val = child.value + return visitor._before_child_handlers[val.__class__](visitor, val) @staticmethod def _before_string(visitor, child): raise ValueError( - f"{child!r} ({type(child)}) is not a valid numeric type. " + f"{child!r} ({type(child).__name__}) is not a valid numeric type. " f"Cannot compute bounds on expression." ) @staticmethod def _before_invalid(visitor, child): raise ValueError( - f"{child!r} ({type(child)}) is not a valid numeric type. " + f"{child!r} ({type(child).__name__}) is not a valid numeric type. " f"Cannot compute bounds on expression." ) @@ -123,10 +131,7 @@ def _before_complex(visitor, child): @staticmethod def _before_npv(visitor, child): val = value(child) - return False, (val, val) - - -_before_child_handlers = ExpressionBoundsBeforeChildDispatcher() + return visitor._before_child_handlers[val.__class__](visitor, val) def _handle_ProductExpression(visitor, node, arg1, arg2): @@ -277,7 +282,7 @@ def initializeWalker(self, expr): return True, expr def beforeChild(self, node, child, child_idx): - return _before_child_handlers[child.__class__](self, child) + return self._before_child_handlers[child.__class__](self, child) def exitNode(self, node, data): - return _operator_dispatcher[node.__class__](self, node, *data) + return self._operator_dispatcher[node.__class__](self, node, *data) diff --git a/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py b/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py index c51230155a7..612a3101ef7 100644 --- a/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py @@ -273,11 +273,19 @@ def test_npv_expression(self): def test_invalid_numeric_type(self): m = self.make_model() - m.p = Param(initialize=True, domain=Any) + m.p = Param(initialize=True, mutable=True, domain=Any) visitor = ExpressionBoundsVisitor() with self.assertRaisesRegex( ValueError, - r"True \(\) is not a valid numeric type. " + r"True \(bool\) is not a valid numeric type. " + r"Cannot compute bounds on expression.", + ): + lb, ub = visitor.walk_expression(m.p + m.y) + + m.p.set_value(None) + with self.assertRaisesRegex( + ValueError, + r"None \(NoneType\) is not a valid numeric type. " r"Cannot compute bounds on expression.", ): lb, ub = visitor.walk_expression(m.p + m.y) @@ -288,7 +296,7 @@ def test_invalid_string(self): visitor = ExpressionBoundsVisitor() with self.assertRaisesRegex( ValueError, - r"'True' \(\) is not a valid numeric type. " + r"'True' \(str\) is not a valid numeric type. " r"Cannot compute bounds on expression.", ): lb, ub = visitor.walk_expression(m.p + m.y) diff --git a/pyomo/core/expr/__init__.py b/pyomo/core/expr/__init__.py index bd6d1b995a1..a03578de957 100644 --- a/pyomo/core/expr/__init__.py +++ b/pyomo/core/expr/__init__.py @@ -56,6 +56,7 @@ # BooleanValue, BooleanConstant, + BooleanExpression, BooleanExpressionBase, # UnaryBooleanExpression, diff --git a/pyomo/repn/tests/test_linear.py b/pyomo/repn/tests/test_linear.py index 0eec8a1541c..d4f268ae182 100644 --- a/pyomo/repn/tests/test_linear.py +++ b/pyomo/repn/tests/test_linear.py @@ -1517,7 +1517,7 @@ def test_type_registrations(self): bcd.register_dispatcher(visitor, 5), (False, (linear._CONSTANT, 5)) ) self.assertEqual(len(bcd), 1) - self.assertIs(bcd[int], bcd._before_native) + self.assertIs(bcd[int], bcd._before_native_numeric) # complex type self.assertEqual( bcd.register_dispatcher(visitor, 5j), (False, (linear._CONSTANT, 5j)) diff --git a/pyomo/repn/tests/test_util.py b/pyomo/repn/tests/test_util.py index 3f455aad13f..8ea6bda83b9 100644 --- a/pyomo/repn/tests/test_util.py +++ b/pyomo/repn/tests/test_util.py @@ -750,7 +750,7 @@ def evaluate(self, node): node = 5 self.assertEqual(bcd[node.__class__](None, node), (False, (_CONSTANT, 5))) - self.assertIs(bcd[int], bcd._before_native) + self.assertIs(bcd[int], bcd._before_native_numeric) self.assertEqual(len(bcd), 1) node = 'string' @@ -787,7 +787,7 @@ class new_int(int): node = new_int(5) self.assertEqual(bcd[node.__class__](None, node), (False, (_CONSTANT, 5))) - self.assertIs(bcd[new_int], bcd._before_native) + self.assertIs(bcd[new_int], bcd._before_native_numeric) self.assertEqual(len(bcd), 5) node = [] diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index cb67dd92494..634b4d1d640 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -25,6 +25,7 @@ native_types, native_numeric_types, native_complex_types, + native_logical_types, ) from pyomo.core.pyomoobject import PyomoObject from pyomo.core.base import ( @@ -265,7 +266,9 @@ def __missing__(self, key): def register_dispatcher(self, visitor, child): child_type = type(child) if child_type in native_numeric_types: - self[child_type] = self._before_native + self[child_type] = self._before_native_numeric + elif child_type in native_logical_types: + self[child_type] = self._before_native_logical elif issubclass(child_type, str): self[child_type] = self._before_string elif child_type in native_types: @@ -275,7 +278,7 @@ def register_dispatcher(self, visitor, child): self[child_type] = self._before_invalid elif not hasattr(child, 'is_expression_type'): if check_if_numeric_type(child): - self[child_type] = self._before_native + self[child_type] = self._before_native_numeric else: self[child_type] = self._before_invalid elif not child.is_expression_type(): @@ -306,9 +309,18 @@ def _before_general_expression(visitor, child): return True, None @staticmethod - def _before_native(visitor, child): + def _before_native_numeric(visitor, child): return False, (_CONSTANT, child) + @staticmethod + def _before_native_logical(visitor, child): + return False, ( + _CONSTANT, + InvalidNumber( + child, f"{child!r} ({type(child).__name__}) is not a valid numeric type" + ), + ) + @staticmethod def _before_complex(visitor, child): return False, (_CONSTANT, complex_number_error(child, visitor, child)) @@ -318,7 +330,7 @@ def _before_invalid(visitor, child): return False, ( _CONSTANT, InvalidNumber( - child, f"{child!r} ({type(child)}) is not a valid numeric type" + child, f"{child!r} ({type(child).__name__}) is not a valid numeric type" ), ) @@ -327,7 +339,7 @@ def _before_string(visitor, child): return False, ( _CONSTANT, InvalidNumber( - child, f"{child!r} ({type(child)}) is not a valid numeric type" + child, f"{child!r} ({type(child).__name__}) is not a valid numeric type" ), ) @@ -418,19 +430,15 @@ def __missing__(self, key): "expression tree." ) if fcn is None: - return self.unexpected_expression_type(key) + fcn = self.unexpected_expression_type if cache: self[key] = fcn return fcn - def unexpected_expression_type(self, key): - if type(key) is tuple: - node_class = key[0] - else: - node_class = key + def unexpected_expression_type(self, visitor, node, *arg): raise DeveloperError( - f"Unexpected expression node type '{node_class.__name__}' " - f"found while walking expression tree." + f"Unexpected expression node type '{type(node).__name__}' " + f"found while walking expression tree in {type(visitor).__name__}." ) From 3a9fc9fda5d567a8af40de383a84352050bdc687 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 8 Feb 2024 15:39:43 -0700 Subject: [PATCH 0926/1204] Additional dispatcher restructuring --- .../contrib/fbbt/expression_bounds_walker.py | 59 ++++++++++++++----- .../tests/test_expression_bounds_walker.py | 29 ++++++++- 2 files changed, 71 insertions(+), 17 deletions(-) diff --git a/pyomo/contrib/fbbt/expression_bounds_walker.py b/pyomo/contrib/fbbt/expression_bounds_walker.py index 22ebf28ae81..31e52e0dc79 100644 --- a/pyomo/contrib/fbbt/expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/expression_bounds_walker.py @@ -9,6 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +import logging from math import pi from pyomo.common.collections import ComponentMap from pyomo.contrib.fbbt.interval import ( @@ -30,6 +31,7 @@ ) from pyomo.core.base.expression import Expression from pyomo.core.expr.numeric_expr import ( + NumericExpression, NegationExpression, ProductExpression, DivisionExpression, @@ -40,12 +42,20 @@ LinearExpression, SumExpression, ExternalFunctionExpression, + Expr_ifExpression, +) +from pyomo.core.expr.logical_expr import BooleanExpression +from pyomo.core.expr.relational_expr import ( + EqualityExpression, + InequalityExpression, + RangedExpression, ) from pyomo.core.expr.numvalue import native_numeric_types, native_types, value from pyomo.core.expr.visitor import StreamBasedExpressionVisitor from pyomo.repn.util import BeforeChildDispatcher, ExitNodeDispatcher inf = float('inf') +logger = logging.getLogger(__name__) class ExpressionBoundsBeforeChildDispatcher(BeforeChildDispatcher): @@ -226,20 +236,20 @@ def _handle_named_expression(visitor, node, arg): } -_operator_dispatcher = ExitNodeDispatcher( - { - ProductExpression: _handle_ProductExpression, - DivisionExpression: _handle_DivisionExpression, - PowExpression: _handle_PowExpression, - AbsExpression: _handle_AbsExpression, - SumExpression: _handle_SumExpression, - MonomialTermExpression: _handle_ProductExpression, - NegationExpression: _handle_NegationExpression, - UnaryFunctionExpression: _handle_UnaryFunctionExpression, - LinearExpression: _handle_SumExpression, - Expression: _handle_named_expression, - } -) +class ExpressionBoundsExitNodeDispatcher(ExitNodeDispatcher): + def unexpected_expression_type(self, visitor, node, *args): + if isinstance(node, NumericExpression): + ans = -inf, inf + elif isinstance(node, BooleanExpression): + ans = Bool(False), Bool(True) + else: + super().unexpected_expression_type(visitor, node, *args) + logger.warning( + f"Unexpected expression node type '{type(node).__name__}' " + f"found while walking expression tree; returning {ans} " + "for the expression bounds." + ) + return ans class ExpressionBoundsVisitor(StreamBasedExpressionVisitor): @@ -264,6 +274,27 @@ class ExpressionBoundsVisitor(StreamBasedExpressionVisitor): the computed bounds should be valid. """ + _before_child_handlers = ExpressionBoundsBeforeChildDispatcher() + _operator_dispatcher = ExpressionBoundsExitNodeDispatcher( + { + ProductExpression: _handle_ProductExpression, + DivisionExpression: _handle_DivisionExpression, + PowExpression: _handle_PowExpression, + AbsExpression: _handle_AbsExpression, + SumExpression: _handle_SumExpression, + MonomialTermExpression: _handle_ProductExpression, + NegationExpression: _handle_NegationExpression, + UnaryFunctionExpression: _handle_UnaryFunctionExpression, + LinearExpression: _handle_SumExpression, + Expression: _handle_named_expression, + ExternalFunctionExpression: _handle_unknowable_bounds, + EqualityExpression: _handle_equality, + InequalityExpression: _handle_inequality, + RangedExpression: _handle_ranged, + Expr_ifExpression: _handle_expr_if, + } + ) + def __init__( self, leaf_bounds=None, diff --git a/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py b/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py index 612a3101ef7..8b30ffdef4b 100644 --- a/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py @@ -10,10 +10,33 @@ # ___________________________________________________________________________ import math -from pyomo.environ import exp, log, log10, sin, cos, tan, asin, acos, atan, sqrt import pyomo.common.unittest as unittest -from pyomo.contrib.fbbt.expression_bounds_walker import ExpressionBoundsVisitor -from pyomo.core import Any, ConcreteModel, Expression, Param, Var + +from pyomo.environ import ( + exp, + log, + log10, + sin, + cos, + tan, + asin, + acos, + atan, + sqrt, + inequality, + Expr_if, + Any, + ConcreteModel, + Expression, + Param, + Var, +) + +from pyomo.common.errors import DeveloperError +from pyomo.common.log import LoggingIntercept +from pyomo.contrib.fbbt.expression_bounds_walker import ExpressionBoundsVisitor, inf +from pyomo.contrib.fbbt.interval import _true, _false +from pyomo.core.expr import ExpressionBase, NumericExpression, BooleanExpression class TestExpressionBoundsWalker(unittest.TestCase): From 64d4f473d566b22f27369a1c1b1c5aaa01198467 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 8 Feb 2024 15:40:22 -0700 Subject: [PATCH 0927/1204] Add support for walking logical expressions --- .../contrib/fbbt/expression_bounds_walker.py | 25 ++++++ pyomo/contrib/fbbt/interval.py | 89 +++++++++++++++++++ .../tests/test_expression_bounds_walker.py | 79 ++++++++++++++++ 3 files changed, 193 insertions(+) diff --git a/pyomo/contrib/fbbt/expression_bounds_walker.py b/pyomo/contrib/fbbt/expression_bounds_walker.py index 31e52e0dc79..a32d138c52b 100644 --- a/pyomo/contrib/fbbt/expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/expression_bounds_walker.py @@ -13,6 +13,11 @@ from math import pi from pyomo.common.collections import ComponentMap from pyomo.contrib.fbbt.interval import ( + Bool, + eq, + ineq, + ranged, + if_, add, acos, asin, @@ -222,6 +227,26 @@ def _handle_named_expression(visitor, node, arg): return arg +def _handle_unknowable_bounds(visitor, node, arg): + return -inf, inf + + +def _handle_equality(visitor, node, arg1, arg2): + return eq(*arg1, *arg2) + + +def _handle_inequality(visitor, node, arg1, arg2): + return ineq(*arg1, *arg2) + + +def _handle_ranged(visitor, node, arg1, arg2, arg3): + return ranged(*arg1, *arg2, *arg3) + + +def _handle_expr_if(visitor, node, arg1, arg2, arg3): + return if_(*arg1, *arg2, *arg3) + + _unary_function_dispatcher = { 'exp': _handle_exp, 'log': _handle_log, diff --git a/pyomo/contrib/fbbt/interval.py b/pyomo/contrib/fbbt/interval.py index fd86af4c106..53c236850d9 100644 --- a/pyomo/contrib/fbbt/interval.py +++ b/pyomo/contrib/fbbt/interval.py @@ -17,6 +17,95 @@ inf = float('inf') +class bool_(object): + def __init__(self, val): + self._val = val + + def __bool__(self): + return self._val + + def _op(self, *others): + raise ValueError( + f"{self._val!r} ({type(self._val).__name__}) is not a valid numeric type. " + f"Cannot compute bounds on expression." + ) + + def __repr__(self): + return repr(self._val) + + __float__ = _op + __int__ = _op + __abs__ = _op + __neg__ = _op + __add__ = _op + __sub__ = _op + __mul__ = _op + __div__ = _op + __pow__ = _op + __radd__ = _op + __rsub__ = _op + __rmul__ = _op + __rdiv__ = _op + __rpow__ = _op + + +_true = bool_(True) +_false = bool_(False) + + +def Bool(val): + return _true if val else _false + + +def ineq(xl, xu, yl, yu): + ans = [] + if yl < xu: + ans.append(_false) + if xl <= yu: + ans.append(_true) + assert ans + if len(ans) == 1: + ans.append(ans[0]) + return tuple(ans) + + +def eq(xl, xu, yl, yu): + ans = [] + if xl != xu or yl != yu or xl != yl: + ans.append(_false) + if xl <= yu and yl <= xu: + ans.append(_true) + assert ans + if len(ans) == 1: + ans.append(ans[0]) + return tuple(ans) + + +def ranged(xl, xu, yl, yu, zl, zu): + lb = ineq(xl, xu, yl, yu) + ub = ineq(yl, yu, zl, zu) + ans = [] + if not lb[0] or not ub[0]: + ans.append(_false) + if lb[1] and ub[1]: + ans.append(_true) + if len(ans) == 1: + ans.append(ans[0]) + return tuple(ans) + + +def if_(il, iu, tl, tu, fl, fu): + l = [] + u = [] + if iu: + l.append(tl) + u.append(tu) + if not il: + l.append(fl) + u.append(fu) + return min(l), max(u) + + def add(xl, xu, yl, yu): return xl + yl, xu + yu diff --git a/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py b/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py index 8b30ffdef4b..75d273422d1 100644 --- a/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py @@ -334,3 +334,82 @@ def test_invalid_complex(self): r"complex numbers. Encountered when processing \(4\+5j\)", ): lb, ub = visitor.walk_expression(m.p + m.y) + + def test_inequality(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + self.assertEqual(visitor.walk_expression(m.z <= m.y), (_true, _true)) + self.assertEqual(visitor.walk_expression(m.y <= m.z), (_false, _false)) + self.assertEqual(visitor.walk_expression(m.y <= m.x), (_false, _true)) + + def test_equality(self): + m = self.make_model() + m.p = Param(initialize=5) + visitor = ExpressionBoundsVisitor() + self.assertEqual(visitor.walk_expression(m.y == m.z), (_false, _false)) + self.assertEqual(visitor.walk_expression(m.y == m.x), (_false, _true)) + self.assertEqual(visitor.walk_expression(m.p == m.p), (_true, _true)) + + def test_ranged(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + self.assertEqual( + visitor.walk_expression(inequality(m.z, m.y, 5)), (_true, _true) + ) + self.assertEqual( + visitor.walk_expression(inequality(m.y, m.z, m.y)), (_false, _false) + ) + self.assertEqual( + visitor.walk_expression(inequality(m.y, m.x, m.y)), (_false, _true) + ) + + def test_expr_if(self): + m = self.make_model() + visitor = ExpressionBoundsVisitor() + self.assertEqual( + visitor.walk_expression(Expr_if(IF=m.z <= m.y, THEN=m.z, ELSE=m.y)), + m.z.bounds, + ) + self.assertEqual( + visitor.walk_expression(Expr_if(IF=m.z >= m.y, THEN=m.z, ELSE=m.y)), + m.y.bounds, + ) + self.assertEqual( + visitor.walk_expression(Expr_if(IF=m.y <= m.x, THEN=m.y, ELSE=m.x)), (-2, 5) + ) + + def test_unknown_classes(self): + class UnknownNumeric(NumericExpression): + pass + + class UnknownLogic(BooleanExpression): + def nargs(self): + return 0 + + class UnknownOther(ExpressionBase): + @property + def args(self): + return () + + def nargs(self): + return 0 + + visitor = ExpressionBoundsVisitor() + with LoggingIntercept() as LOG: + self.assertEqual(visitor.walk_expression(UnknownNumeric(())), (-inf, inf)) + self.assertEqual( + LOG.getvalue(), + "Unexpected expression node type 'UnknownNumeric' found while walking " + "expression tree; returning (-inf, inf) for the expression bounds.\n", + ) + with LoggingIntercept() as LOG: + self.assertEqual(visitor.walk_expression(UnknownLogic(())), (_false, _true)) + self.assertEqual( + LOG.getvalue(), + "Unexpected expression node type 'UnknownLogic' found while walking " + "expression tree; returning (False, True) for the expression bounds.\n", + ) + with self.assertRaisesRegex( + DeveloperError, "Unexpected expression node type 'UnknownOther' found" + ): + visitor.walk_expression(UnknownOther()) From 45ca395d213dfa74393cd52c094e73992bccfac3 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 8 Feb 2024 17:17:51 -0700 Subject: [PATCH 0928/1204] Updating tests to reflect changes in the before child dispatcher --- pyomo/repn/tests/test_util.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyomo/repn/tests/test_util.py b/pyomo/repn/tests/test_util.py index 8ea6bda83b9..48d78c60d6e 100644 --- a/pyomo/repn/tests/test_util.py +++ b/pyomo/repn/tests/test_util.py @@ -717,14 +717,14 @@ class UnknownExpression(NumericExpression): DeveloperError, r".*Unexpected expression node type 'UnknownExpression'" ): end[node.__class__](None, node, *node.args) - self.assertEqual(len(end), 8) + self.assertEqual(len(end), 9) node = UnknownExpression((6, 7)) with self.assertRaisesRegex( DeveloperError, r".*Unexpected expression node type 'UnknownExpression'" ): end[node.__class__, 6, 7](None, node, *node.args) - self.assertEqual(len(end), 8) + self.assertEqual(len(end), 10) def test_BeforeChildDispatcher_registration(self): class BeforeChildDispatcherTester(BeforeChildDispatcher): @@ -758,7 +758,7 @@ def evaluate(self, node): self.assertEqual(ans, (False, (_CONSTANT, InvalidNumber(node)))) self.assertEqual( ''.join(ans[1][1].causes), - "'string' () is not a valid numeric type", + "'string' (str) is not a valid numeric type", ) self.assertIs(bcd[str], bcd._before_string) self.assertEqual(len(bcd), 2) @@ -768,9 +768,9 @@ def evaluate(self, node): self.assertEqual(ans, (False, (_CONSTANT, InvalidNumber(node)))) self.assertEqual( ''.join(ans[1][1].causes), - "True () is not a valid numeric type", + "True (bool) is not a valid numeric type", ) - self.assertIs(bcd[bool], bcd._before_invalid) + self.assertIs(bcd[bool], bcd._before_native_logical) self.assertEqual(len(bcd), 3) node = 1j @@ -794,7 +794,7 @@ class new_int(int): ans = bcd[node.__class__](None, node) self.assertEqual(ans, (False, (_CONSTANT, InvalidNumber([])))) self.assertEqual( - ''.join(ans[1][1].causes), "[] () is not a valid numeric type" + ''.join(ans[1][1].causes), "[] (list) is not a valid numeric type" ) self.assertIs(bcd[list], bcd._before_invalid) self.assertEqual(len(bcd), 6) From 67acc27477914b7e254fa4df5c978281448f50ee Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 9 Feb 2024 09:05:48 -0700 Subject: [PATCH 0929/1204] NFC: apply black --- pyomo/repn/tests/test_util.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pyomo/repn/tests/test_util.py b/pyomo/repn/tests/test_util.py index 48d78c60d6e..cce10e58334 100644 --- a/pyomo/repn/tests/test_util.py +++ b/pyomo/repn/tests/test_util.py @@ -757,8 +757,7 @@ def evaluate(self, node): ans = bcd[node.__class__](None, node) self.assertEqual(ans, (False, (_CONSTANT, InvalidNumber(node)))) self.assertEqual( - ''.join(ans[1][1].causes), - "'string' (str) is not a valid numeric type", + ''.join(ans[1][1].causes), "'string' (str) is not a valid numeric type" ) self.assertIs(bcd[str], bcd._before_string) self.assertEqual(len(bcd), 2) @@ -767,8 +766,7 @@ def evaluate(self, node): ans = bcd[node.__class__](None, node) self.assertEqual(ans, (False, (_CONSTANT, InvalidNumber(node)))) self.assertEqual( - ''.join(ans[1][1].causes), - "True (bool) is not a valid numeric type", + ''.join(ans[1][1].causes), "True (bool) is not a valid numeric type" ) self.assertIs(bcd[bool], bcd._before_native_logical) self.assertEqual(len(bcd), 3) From 73e1e2865c82a57d04bd86c662694dc79513b419 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 9 Feb 2024 14:19:46 -0500 Subject: [PATCH 0930/1204] Limit visibility of option `p_robustness` --- pyomo/contrib/pyros/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index f12fb3d0be0..3256a333fdc 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -945,6 +945,7 @@ def pyros_config(): the nominal parameter realization. """ ), + visibility=1, ), ) From 227836df546cef9729f6af5dbb32664e75f3f3ac Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Fri, 9 Feb 2024 13:11:31 -0700 Subject: [PATCH 0931/1204] remove unused imports --- pyomo/contrib/incidence_analysis/config.py | 2 +- pyomo/contrib/incidence_analysis/incidence.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/config.py b/pyomo/contrib/incidence_analysis/config.py index d055be478fe..72d1a41ac74 100644 --- a/pyomo/contrib/incidence_analysis/config.py +++ b/pyomo/contrib/incidence_analysis/config.py @@ -14,7 +14,7 @@ import enum from pyomo.common.config import ConfigDict, ConfigValue, InEnum from pyomo.common.modeling import NOTSET -from pyomo.repn.plugins.nl_writer import AMPLRepnVisitor, AMPLRepn, text_nl_template +from pyomo.repn.plugins.nl_writer import AMPLRepnVisitor, text_nl_template from pyomo.repn.util import FileDeterminism, FileDeterminism_to_SortComponents diff --git a/pyomo/contrib/incidence_analysis/incidence.py b/pyomo/contrib/incidence_analysis/incidence.py index 636a400def4..13e9997d6c3 100644 --- a/pyomo/contrib/incidence_analysis/incidence.py +++ b/pyomo/contrib/incidence_analysis/incidence.py @@ -16,9 +16,8 @@ from pyomo.core.expr.visitor import identify_variables from pyomo.core.expr.numvalue import value as pyo_value from pyomo.repn import generate_standard_repn -from pyomo.repn.plugins.nl_writer import AMPLRepnVisitor, AMPLRepn, text_nl_template -from pyomo.repn.util import FileDeterminism, FileDeterminism_to_SortComponents from pyomo.util.subsystems import TemporarySubsystemManager +from pyomo.repn.plugins.nl_writer import AMPLRepn from pyomo.contrib.incidence_analysis.config import ( IncidenceMethod, get_config_from_kwds, From cbbcceb7a1eae4d215053b77d38cb47bc6bc95b5 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 9 Feb 2024 15:11:49 -0500 Subject: [PATCH 0932/1204] Add note on `options` arg to docs --- doc/OnlineDocs/contributed_packages/pyros.rst | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/doc/OnlineDocs/contributed_packages/pyros.rst b/doc/OnlineDocs/contributed_packages/pyros.rst index 3ff1bfccf0e..d741bb26b5c 100644 --- a/doc/OnlineDocs/contributed_packages/pyros.rst +++ b/doc/OnlineDocs/contributed_packages/pyros.rst @@ -142,6 +142,7 @@ PyROS Solver Interface Otherwise, the solution returned is certified to only be robust feasible. + PyROS Uncertainty Sets ----------------------------- Uncertainty sets are represented by subclasses of @@ -518,7 +519,7 @@ correspond to first-stage degrees of freedom. >>> # === Designate which variables correspond to first-stage >>> # and second-stage degrees of freedom === - >>> first_stage_variables =[ + >>> first_stage_variables = [ ... m.x1, m.x2, m.x3, m.x4, m.x5, m.x6, ... m.x19, m.x20, m.x21, m.x22, m.x23, m.x24, m.x31, ... ] @@ -657,6 +658,54 @@ For this example, we notice a ~25% decrease in the final objective value when switching from a static decision rule (no second-stage recourse) to an affine decision rule. + +Specifying Arguments Indirectly Through ``options`` +""""""""""""""""""""""""""""""""""""""""""""""""""" +Like other Pyomo solver interface methods, +:meth:`~pyomo.contrib.pyros.PyROS.solve` +provides support for specifying options indirectly by passing +a keyword argument ``options``, whose value must be a :class:`dict` +mapping names of arguments to :meth:`~pyomo.contrib.pyros.PyROS.solve` +to their desired values. +For example, the ``solve()`` statement in the +:ref:`two-stage problem example ` +could have been equivalently written as: + +.. doctest:: + :skipif: not (baron.available() and baron.license_is_valid()) + + >>> results_2 = pyros_solver.solve( + ... model=m, + ... first_stage_variables=first_stage_variables, + ... second_stage_variables=second_stage_variables, + ... uncertain_params=uncertain_parameters, + ... uncertainty_set=box_uncertainty_set, + ... local_solver=local_solver, + ... global_solver=global_solver, + ... options={ + ... "objective_focus": pyros.ObjectiveType.worst_case, + ... "solve_master_globally": True, + ... "decision_rule_order": 1, + ... }, + ... ) + ============================================================================== + PyROS: The Pyomo Robust Optimization Solver. + ... + ------------------------------------------------------------------------------ + Robust optimal solution identified. + ------------------------------------------------------------------------------ + ... + ------------------------------------------------------------------------------ + All done. Exiting PyROS. + ============================================================================== + +In the event an argument is passed directly +by position or keyword, *and* indirectly through ``options``, +an appropriate warning is issued, +and the value passed directly takes precedence over the value +passed through ``options``. + + The Price of Robustness """""""""""""""""""""""" In conjunction with standard Python control flow tools, From d4d8d32c31fbb34c77f7b87946c2e26f1b03b715 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 9 Feb 2024 15:12:59 -0500 Subject: [PATCH 0933/1204] Tweak new note wording --- doc/OnlineDocs/contributed_packages/pyros.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/OnlineDocs/contributed_packages/pyros.rst b/doc/OnlineDocs/contributed_packages/pyros.rst index d741bb26b5c..b5b71020a9c 100644 --- a/doc/OnlineDocs/contributed_packages/pyros.rst +++ b/doc/OnlineDocs/contributed_packages/pyros.rst @@ -668,7 +668,7 @@ a keyword argument ``options``, whose value must be a :class:`dict` mapping names of arguments to :meth:`~pyomo.contrib.pyros.PyROS.solve` to their desired values. For example, the ``solve()`` statement in the -:ref:`two-stage problem example ` +:ref:`two-stage problem snippet ` could have been equivalently written as: .. doctest:: From 677ebc9e67d363d364fa6f771fa610fe2bda2bbc Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Fri, 9 Feb 2024 13:14:24 -0700 Subject: [PATCH 0934/1204] remove unused imports --- pyomo/contrib/incidence_analysis/interface.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 41f0ece3a75..b6e6583da88 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -45,8 +45,6 @@ ) from pyomo.contrib.incidence_analysis.incidence import get_incident_variables from pyomo.contrib.pynumero.asl import AmplInterface -from pyomo.repn.plugins.nl_writer import AMPLRepnVisitor, AMPLRepn, text_nl_template -from pyomo.repn.util import FileDeterminism, FileDeterminism_to_SortComponents pyomo_nlp, pyomo_nlp_available = attempt_import( 'pyomo.contrib.pynumero.interfaces.pyomo_nlp' From 3ca70d16458afcfe85e9d9cb9dc9936489cdbd22 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Sat, 10 Feb 2024 21:31:15 -0700 Subject: [PATCH 0935/1204] porting appsi_gurobi to contrib/solver --- pyomo/contrib/solver/gurobi.py | 1495 +++++++++++++++++ pyomo/contrib/solver/plugins.py | 2 + pyomo/contrib/solver/sol_reader.py | 4 +- .../tests/solvers/test_gurobi_persistent.py | 691 ++++++++ .../solver/tests/solvers/test_solvers.py | 1350 +++++++++++++++ pyomo/contrib/solver/util.py | 62 +- 6 files changed, 3549 insertions(+), 55 deletions(-) create mode 100644 pyomo/contrib/solver/gurobi.py create mode 100644 pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py create mode 100644 pyomo/contrib/solver/tests/solvers/test_solvers.py diff --git a/pyomo/contrib/solver/gurobi.py b/pyomo/contrib/solver/gurobi.py new file mode 100644 index 00000000000..2dcdacd320d --- /dev/null +++ b/pyomo/contrib/solver/gurobi.py @@ -0,0 +1,1495 @@ +from collections.abc import Iterable +import logging +import math +from typing import List, Dict, Optional +from pyomo.common.collections import ComponentSet, ComponentMap, OrderedSet +from pyomo.common.log import LogStream +from pyomo.common.dependencies import attempt_import +from pyomo.common.errors import PyomoException +from pyomo.common.tee import capture_output, TeeStream +from pyomo.common.timing import HierarchicalTimer +from pyomo.common.shutdown import python_is_shutting_down +from pyomo.common.config import ConfigValue, NonNegativeInt +from pyomo.core.kernel.objective import minimize, maximize +from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler +from pyomo.core.base.var import Var, _GeneralVarData +from pyomo.core.base.constraint import _GeneralConstraintData +from pyomo.core.base.sos import _SOSConstraintData +from pyomo.core.base.param import _ParamData +from pyomo.core.expr.numvalue import value, is_constant, is_fixed, native_numeric_types +from pyomo.repn import generate_standard_repn +from pyomo.core.expr.numeric_expr import NPV_MaxExpression, NPV_MinExpression +from pyomo.contrib.solver.base import PersistentSolverBase +from pyomo.contrib.solver.results import Results, TerminationCondition, SolutionStatus +from pyomo.contrib.solver.config import PersistentBranchAndBoundConfig +from pyomo.contrib.solver.util import PersistentSolverUtils +from pyomo.contrib.solver.solution import PersistentSolutionLoader +from pyomo.core.staleflag import StaleFlagManager +import sys +import datetime +import io +from pyomo.contrib.solver.factory import SolverFactory + +logger = logging.getLogger(__name__) + + +def _import_gurobipy(): + try: + import gurobipy + except ImportError: + Gurobi._available = Gurobi.Availability.NotFound + raise + if gurobipy.GRB.VERSION_MAJOR < 7: + Gurobi._available = Gurobi.Availability.BadVersion + raise ImportError('The APPSI Gurobi interface requires gurobipy>=7.0.0') + return gurobipy + + +gurobipy, gurobipy_available = attempt_import('gurobipy', importer=_import_gurobipy) + + +class DegreeError(PyomoException): + pass + + +class GurobiConfig(PersistentBranchAndBoundConfig): + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + super(GurobiConfig, self).__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, + ) + self.use_mipstart: bool = self.declare( + 'use_mipstart', + ConfigValue( + default=False, + domain=bool, + description="If True, the values of the integer variables will be passed to Gurobi.", + ) + ) + + +class GurobiSolutionLoader(PersistentSolutionLoader): + def load_vars(self, vars_to_load=None, solution_number=0): + self._assert_solution_still_valid() + self._solver._load_vars( + vars_to_load=vars_to_load, solution_number=solution_number + ) + + def get_primals(self, vars_to_load=None, solution_number=0): + self._assert_solution_still_valid() + return self._solver._get_primals( + vars_to_load=vars_to_load, solution_number=solution_number + ) + + +class _MutableLowerBound(object): + def __init__(self, expr): + self.var = None + self.expr = expr + + def update(self): + self.var.setAttr('lb', value(self.expr)) + + +class _MutableUpperBound(object): + def __init__(self, expr): + self.var = None + self.expr = expr + + def update(self): + self.var.setAttr('ub', value(self.expr)) + + +class _MutableLinearCoefficient(object): + def __init__(self): + self.expr = None + self.var = None + self.con = None + self.gurobi_model = None + + def update(self): + self.gurobi_model.chgCoeff(self.con, self.var, value(self.expr)) + + +class _MutableRangeConstant(object): + def __init__(self): + self.lhs_expr = None + self.rhs_expr = None + self.con = None + self.slack_name = None + self.gurobi_model = None + + def update(self): + rhs_val = value(self.rhs_expr) + lhs_val = value(self.lhs_expr) + self.con.rhs = rhs_val + slack = self.gurobi_model.getVarByName(self.slack_name) + slack.ub = rhs_val - lhs_val + + +class _MutableConstant(object): + def __init__(self): + self.expr = None + self.con = None + + def update(self): + self.con.rhs = value(self.expr) + + +class _MutableQuadraticConstraint(object): + def __init__( + self, gurobi_model, gurobi_con, constant, linear_coefs, quadratic_coefs + ): + self.con = gurobi_con + self.gurobi_model = gurobi_model + self.constant = constant + self.last_constant_value = value(self.constant.expr) + self.linear_coefs = linear_coefs + self.last_linear_coef_values = [value(i.expr) for i in self.linear_coefs] + self.quadratic_coefs = quadratic_coefs + self.last_quadratic_coef_values = [value(i.expr) for i in self.quadratic_coefs] + + def get_updated_expression(self): + gurobi_expr = self.gurobi_model.getQCRow(self.con) + for ndx, coef in enumerate(self.linear_coefs): + current_coef_value = value(coef.expr) + incremental_coef_value = ( + current_coef_value - self.last_linear_coef_values[ndx] + ) + gurobi_expr += incremental_coef_value * coef.var + self.last_linear_coef_values[ndx] = current_coef_value + for ndx, coef in enumerate(self.quadratic_coefs): + current_coef_value = value(coef.expr) + incremental_coef_value = ( + current_coef_value - self.last_quadratic_coef_values[ndx] + ) + gurobi_expr += incremental_coef_value * coef.var1 * coef.var2 + self.last_quadratic_coef_values[ndx] = current_coef_value + return gurobi_expr + + def get_updated_rhs(self): + return value(self.constant.expr) + + +class _MutableObjective(object): + def __init__(self, gurobi_model, constant, linear_coefs, quadratic_coefs): + self.gurobi_model = gurobi_model + self.constant = constant + self.linear_coefs = linear_coefs + self.quadratic_coefs = quadratic_coefs + self.last_quadratic_coef_values = [value(i.expr) for i in self.quadratic_coefs] + + def get_updated_expression(self): + for ndx, coef in enumerate(self.linear_coefs): + coef.var.obj = value(coef.expr) + self.gurobi_model.ObjCon = value(self.constant.expr) + + gurobi_expr = None + for ndx, coef in enumerate(self.quadratic_coefs): + if value(coef.expr) != self.last_quadratic_coef_values[ndx]: + if gurobi_expr is None: + self.gurobi_model.update() + gurobi_expr = self.gurobi_model.getObjective() + current_coef_value = value(coef.expr) + incremental_coef_value = ( + current_coef_value - self.last_quadratic_coef_values[ndx] + ) + gurobi_expr += incremental_coef_value * coef.var1 * coef.var2 + self.last_quadratic_coef_values[ndx] = current_coef_value + return gurobi_expr + + +class _MutableQuadraticCoefficient(object): + def __init__(self): + self.expr = None + self.var1 = None + self.var2 = None + + +class Gurobi(PersistentSolverUtils, PersistentSolverBase): + """ + Interface to Gurobi + """ + + CONFIG = GurobiConfig() + + _available = None + _num_instances = 0 + + def __init__(self, **kwds): + PersistentSolverUtils.__init__(self) + PersistentSolverBase.__init__(self, **kwds) + self._num_instances += 1 + self._solver_model = None + self._symbol_map = SymbolMap() + self._labeler = None + self._pyomo_var_to_solver_var_map = dict() + self._pyomo_con_to_solver_con_map = dict() + self._solver_con_to_pyomo_con_map = dict() + self._pyomo_sos_to_solver_sos_map = dict() + self._range_constraints = OrderedSet() + self._mutable_helpers = dict() + self._mutable_bounds = dict() + self._mutable_quadratic_helpers = dict() + self._mutable_objective = None + self._needs_updated = True + self._callback = None + self._callback_func = None + self._constraints_added_since_update = OrderedSet() + self._vars_added_since_update = ComponentSet() + self._last_results_object: Optional[Results] = None + self._config: Optional[GurobiConfig] = None + + def available(self): + if not gurobipy_available: # this triggers the deferred import + return self.Availability.NotFound + elif self._available == self.Availability.BadVersion: + return self.Availability.BadVersion + else: + return self._check_license() + + def _check_license(self): + avail = False + try: + # Gurobipy writes out license file information when creating + # the environment + with capture_output(capture_fd=True): + m = gurobipy.Model() + if self._solver_model is None: + self._solver_model = m + avail = True + except gurobipy.GurobiError: + avail = False + + if avail: + if self._available is None: + res = Gurobi._check_full_license() + self._available = res + return res + else: + return self._available + else: + return self.Availability.BadLicense + + @classmethod + def _check_full_license(cls): + m = gurobipy.Model() + m.setParam('OutputFlag', 0) + try: + m.addVars(range(2001)) + m.optimize() + return cls.Availability.FullLicense + except gurobipy.GurobiError: + return cls.Availability.LimitedLicense + + def release_license(self): + self._reinit() + if gurobipy_available: + with capture_output(capture_fd=True): + gurobipy.disposeDefaultEnv() + + def __del__(self): + if not python_is_shutting_down(): + self._num_instances -= 1 + if self._num_instances == 0: + self.release_license() + + def version(self): + version = ( + gurobipy.GRB.VERSION_MAJOR, + gurobipy.GRB.VERSION_MINOR, + gurobipy.GRB.VERSION_TECHNICAL, + ) + return version + + @property + def symbol_map(self): + return self._symbol_map + + def _solve(self): + config = self._config + timer = config.timer + ostreams = [io.StringIO()] + if config.tee: + ostreams.append(sys.stdout) + if config.log_solver_output: + ostreams.append(LogStream(level=logging.INFO, logger=logger)) + + with TeeStream(*ostreams) as t: + with capture_output(output=t.STDOUT, capture_fd=False): + options = config.solver_options + + self._solver_model.setParam('LogToConsole', 1) + + if config.threads is not None: + self._solver_model.setParam('Threads', config.threads) + if config.time_limit is not None: + self._solver_model.setParam('TimeLimit', config.time_limit) + if config.rel_gap is not None: + self._solver_model.setParam('MIPGap', config.rel_gap) + if config.abs_gap is not None: + self._solver_model.setParam('MIPGapAbs', config.abs_gap) + + if config.use_mipstart: + for pyomo_var_id, gurobi_var in self._pyomo_var_to_solver_var_map.items(): + pyomo_var = self._vars[pyomo_var_id][0] + if pyomo_var.is_integer() and pyomo_var.value is not None: + self.set_var_attr(pyomo_var, 'Start', pyomo_var.value) + + for key, option in options.items(): + self._solver_model.setParam(key, option) + + timer.start('optimize') + self._solver_model.optimize(self._callback) + timer.stop('optimize') + + self._needs_updated = False + res = self._postsolve(timer) + res.solver_configuration = config + res.solver_name = 'Gurobi' + res.solver_version = self.version() + res.solver_log = ostreams[0].getvalue() + return res + + def solve(self, model, **kwds) -> Results: + start_timestamp = datetime.datetime.now(datetime.timezone.utc) + self._config = config = self.config(value=kwds, preserve_implicit=True) + StaleFlagManager.mark_all_as_stale() + # Note: solver availability check happens in set_instance(), + # which will be called (either by the user before this call, or + # below) before this method calls self._solve. + if self._last_results_object is not None: + self._last_results_object.solution_loader.invalidate() + if config.timer is None: + config.timer = HierarchicalTimer() + timer = config.timer + if model is not self._model: + timer.start('set_instance') + self.set_instance(model) + timer.stop('set_instance') + else: + timer.start('update') + self.update(timer=timer) + timer.stop('update') + res = self._solve() + self._last_results_object = res + end_timestamp = datetime.datetime.now(datetime.timezone.utc) + res.timing_info.start_timestamp = start_timestamp + res.timing_info.wall_time = (end_timestamp - start_timestamp).total_seconds() + res.timing_info.timer = timer + return res + + def _process_domain_and_bounds( + self, var, var_id, mutable_lbs, mutable_ubs, ndx, gurobipy_var + ): + _v, _lb, _ub, _fixed, _domain_interval, _value = self._vars[id(var)] + lb, ub, step = _domain_interval + if lb is None: + lb = -gurobipy.GRB.INFINITY + if ub is None: + ub = gurobipy.GRB.INFINITY + if step == 0: + vtype = gurobipy.GRB.CONTINUOUS + elif step == 1: + if lb == 0 and ub == 1: + vtype = gurobipy.GRB.BINARY + else: + vtype = gurobipy.GRB.INTEGER + else: + raise ValueError( + f'Unrecognized domain step: {step} (should be either 0 or 1)' + ) + if _fixed: + lb = _value + ub = _value + else: + if _lb is not None: + if not is_constant(_lb): + mutable_bound = _MutableLowerBound(NPV_MaxExpression((_lb, lb))) + if gurobipy_var is None: + mutable_lbs[ndx] = mutable_bound + else: + mutable_bound.var = gurobipy_var + self._mutable_bounds[var_id, 'lb'] = (var, mutable_bound) + lb = max(value(_lb), lb) + if _ub is not None: + if not is_constant(_ub): + mutable_bound = _MutableUpperBound(NPV_MinExpression((_ub, ub))) + if gurobipy_var is None: + mutable_ubs[ndx] = mutable_bound + else: + mutable_bound.var = gurobipy_var + self._mutable_bounds[var_id, 'ub'] = (var, mutable_bound) + ub = min(value(_ub), ub) + + return lb, ub, vtype + + def _add_variables(self, variables: List[_GeneralVarData]): + var_names = list() + vtypes = list() + lbs = list() + ubs = list() + mutable_lbs = dict() + mutable_ubs = dict() + for ndx, var in enumerate(variables): + varname = self._symbol_map.getSymbol(var, self._labeler) + lb, ub, vtype = self._process_domain_and_bounds( + var, id(var), mutable_lbs, mutable_ubs, ndx, None + ) + var_names.append(varname) + vtypes.append(vtype) + lbs.append(lb) + ubs.append(ub) + + gurobi_vars = self._solver_model.addVars( + len(variables), lb=lbs, ub=ubs, vtype=vtypes, name=var_names + ) + + for ndx, pyomo_var in enumerate(variables): + gurobi_var = gurobi_vars[ndx] + self._pyomo_var_to_solver_var_map[id(pyomo_var)] = gurobi_var + for ndx, mutable_bound in mutable_lbs.items(): + mutable_bound.var = gurobi_vars[ndx] + for ndx, mutable_bound in mutable_ubs.items(): + mutable_bound.var = gurobi_vars[ndx] + self._vars_added_since_update.update(variables) + self._needs_updated = True + + def _add_params(self, params: List[_ParamData]): + pass + + def _reinit(self): + saved_config = self.config + saved_tmp_config = self._config + self.__init__() + self.config = saved_config + self._config = saved_tmp_config + + def set_instance(self, model): + if self._last_results_object is not None: + self._last_results_object.solution_loader.invalidate() + if not self.available(): + c = self.__class__ + raise PyomoException( + f'Solver {c.__module__}.{c.__qualname__} is not available ' + f'({self.available()}).' + ) + self._reinit() + self._model = model + + if self.config.symbolic_solver_labels: + self._labeler = TextLabeler() + else: + self._labeler = NumericLabeler('x') + + if model.name is not None: + self._solver_model = gurobipy.Model(model.name) + else: + self._solver_model = gurobipy.Model() + + self.add_block(model) + if self._objective is None: + self.set_objective(None) + + def _get_expr_from_pyomo_expr(self, expr): + mutable_linear_coefficients = list() + mutable_quadratic_coefficients = list() + repn = generate_standard_repn(expr, quadratic=True, compute_values=False) + + degree = repn.polynomial_degree() + if (degree is None) or (degree > 2): + raise DegreeError( + 'GurobiAuto does not support expressions of degree {0}.'.format(degree) + ) + + if len(repn.linear_vars) > 0: + linear_coef_vals = list() + for ndx, coef in enumerate(repn.linear_coefs): + if not is_constant(coef): + mutable_linear_coefficient = _MutableLinearCoefficient() + mutable_linear_coefficient.expr = coef + mutable_linear_coefficient.var = self._pyomo_var_to_solver_var_map[ + id(repn.linear_vars[ndx]) + ] + mutable_linear_coefficients.append(mutable_linear_coefficient) + linear_coef_vals.append(value(coef)) + new_expr = gurobipy.LinExpr( + linear_coef_vals, + [self._pyomo_var_to_solver_var_map[id(i)] for i in repn.linear_vars], + ) + else: + new_expr = 0.0 + + for ndx, v in enumerate(repn.quadratic_vars): + x, y = v + gurobi_x = self._pyomo_var_to_solver_var_map[id(x)] + gurobi_y = self._pyomo_var_to_solver_var_map[id(y)] + coef = repn.quadratic_coefs[ndx] + if not is_constant(coef): + mutable_quadratic_coefficient = _MutableQuadraticCoefficient() + mutable_quadratic_coefficient.expr = coef + mutable_quadratic_coefficient.var1 = gurobi_x + mutable_quadratic_coefficient.var2 = gurobi_y + mutable_quadratic_coefficients.append(mutable_quadratic_coefficient) + coef_val = value(coef) + new_expr += coef_val * gurobi_x * gurobi_y + + return ( + new_expr, + repn.constant, + mutable_linear_coefficients, + mutable_quadratic_coefficients, + ) + + def _add_constraints(self, cons: List[_GeneralConstraintData]): + for con in cons: + conname = self._symbol_map.getSymbol(con, self._labeler) + ( + gurobi_expr, + repn_constant, + mutable_linear_coefficients, + mutable_quadratic_coefficients, + ) = self._get_expr_from_pyomo_expr(con.body) + + if ( + gurobi_expr.__class__ in {gurobipy.LinExpr, gurobipy.Var} + or gurobi_expr.__class__ in native_numeric_types + ): + if con.equality: + rhs_expr = con.lower - repn_constant + rhs_val = value(rhs_expr) + gurobipy_con = self._solver_model.addLConstr( + gurobi_expr, gurobipy.GRB.EQUAL, rhs_val, name=conname + ) + if not is_constant(rhs_expr): + mutable_constant = _MutableConstant() + mutable_constant.expr = rhs_expr + mutable_constant.con = gurobipy_con + self._mutable_helpers[con] = [mutable_constant] + elif con.has_lb() and con.has_ub(): + lhs_expr = con.lower - repn_constant + rhs_expr = con.upper - repn_constant + lhs_val = value(lhs_expr) + rhs_val = value(rhs_expr) + gurobipy_con = self._solver_model.addRange( + gurobi_expr, lhs_val, rhs_val, name=conname + ) + self._range_constraints.add(con) + if not is_constant(lhs_expr) or not is_constant(rhs_expr): + mutable_range_constant = _MutableRangeConstant() + mutable_range_constant.lhs_expr = lhs_expr + mutable_range_constant.rhs_expr = rhs_expr + mutable_range_constant.con = gurobipy_con + mutable_range_constant.slack_name = 'Rg' + conname + mutable_range_constant.gurobi_model = self._solver_model + self._mutable_helpers[con] = [mutable_range_constant] + elif con.has_lb(): + rhs_expr = con.lower - repn_constant + rhs_val = value(rhs_expr) + gurobipy_con = self._solver_model.addLConstr( + gurobi_expr, gurobipy.GRB.GREATER_EQUAL, rhs_val, name=conname + ) + if not is_constant(rhs_expr): + mutable_constant = _MutableConstant() + mutable_constant.expr = rhs_expr + mutable_constant.con = gurobipy_con + self._mutable_helpers[con] = [mutable_constant] + elif con.has_ub(): + rhs_expr = con.upper - repn_constant + rhs_val = value(rhs_expr) + gurobipy_con = self._solver_model.addLConstr( + gurobi_expr, gurobipy.GRB.LESS_EQUAL, rhs_val, name=conname + ) + if not is_constant(rhs_expr): + mutable_constant = _MutableConstant() + mutable_constant.expr = rhs_expr + mutable_constant.con = gurobipy_con + self._mutable_helpers[con] = [mutable_constant] + else: + raise ValueError( + "Constraint does not have a lower " + "or an upper bound: {0} \n".format(con) + ) + for tmp in mutable_linear_coefficients: + tmp.con = gurobipy_con + tmp.gurobi_model = self._solver_model + if len(mutable_linear_coefficients) > 0: + if con not in self._mutable_helpers: + self._mutable_helpers[con] = mutable_linear_coefficients + else: + self._mutable_helpers[con].extend(mutable_linear_coefficients) + elif gurobi_expr.__class__ is gurobipy.QuadExpr: + if con.equality: + rhs_expr = con.lower - repn_constant + rhs_val = value(rhs_expr) + gurobipy_con = self._solver_model.addQConstr( + gurobi_expr, gurobipy.GRB.EQUAL, rhs_val, name=conname + ) + elif con.has_lb() and con.has_ub(): + raise NotImplementedError( + 'Quadratic range constraints are not supported' + ) + elif con.has_lb(): + rhs_expr = con.lower - repn_constant + rhs_val = value(rhs_expr) + gurobipy_con = self._solver_model.addQConstr( + gurobi_expr, gurobipy.GRB.GREATER_EQUAL, rhs_val, name=conname + ) + elif con.has_ub(): + rhs_expr = con.upper - repn_constant + rhs_val = value(rhs_expr) + gurobipy_con = self._solver_model.addQConstr( + gurobi_expr, gurobipy.GRB.LESS_EQUAL, rhs_val, name=conname + ) + else: + raise ValueError( + "Constraint does not have a lower " + "or an upper bound: {0} \n".format(con) + ) + if ( + len(mutable_linear_coefficients) > 0 + or len(mutable_quadratic_coefficients) > 0 + or not is_constant(repn_constant) + ): + mutable_constant = _MutableConstant() + mutable_constant.expr = rhs_expr + mutable_quadratic_constraint = _MutableQuadraticConstraint( + self._solver_model, + gurobipy_con, + mutable_constant, + mutable_linear_coefficients, + mutable_quadratic_coefficients, + ) + self._mutable_quadratic_helpers[con] = mutable_quadratic_constraint + else: + raise ValueError( + 'Unrecognized Gurobi expression type: ' + str(gurobi_expr.__class__) + ) + + self._pyomo_con_to_solver_con_map[con] = gurobipy_con + self._solver_con_to_pyomo_con_map[id(gurobipy_con)] = con + self._constraints_added_since_update.update(cons) + self._needs_updated = True + + def _add_sos_constraints(self, cons: List[_SOSConstraintData]): + for con in cons: + conname = self._symbol_map.getSymbol(con, self._labeler) + level = con.level + if level == 1: + sos_type = gurobipy.GRB.SOS_TYPE1 + elif level == 2: + sos_type = gurobipy.GRB.SOS_TYPE2 + else: + raise ValueError( + "Solver does not support SOS level {0} constraints".format(level) + ) + + gurobi_vars = [] + weights = [] + + for v, w in con.get_items(): + v_id = id(v) + gurobi_vars.append(self._pyomo_var_to_solver_var_map[v_id]) + weights.append(w) + + gurobipy_con = self._solver_model.addSOS(sos_type, gurobi_vars, weights) + self._pyomo_sos_to_solver_sos_map[con] = gurobipy_con + self._constraints_added_since_update.update(cons) + self._needs_updated = True + + def _remove_constraints(self, cons: List[_GeneralConstraintData]): + for con in cons: + if con in self._constraints_added_since_update: + self._update_gurobi_model() + solver_con = self._pyomo_con_to_solver_con_map[con] + self._solver_model.remove(solver_con) + self._symbol_map.removeSymbol(con) + del self._pyomo_con_to_solver_con_map[con] + del self._solver_con_to_pyomo_con_map[id(solver_con)] + self._range_constraints.discard(con) + self._mutable_helpers.pop(con, None) + self._mutable_quadratic_helpers.pop(con, None) + self._needs_updated = True + + def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): + for con in cons: + if con in self._constraints_added_since_update: + self._update_gurobi_model() + solver_sos_con = self._pyomo_sos_to_solver_sos_map[con] + self._solver_model.remove(solver_sos_con) + self._symbol_map.removeSymbol(con) + del self._pyomo_sos_to_solver_sos_map[con] + self._needs_updated = True + + def _remove_variables(self, variables: List[_GeneralVarData]): + for var in variables: + v_id = id(var) + if var in self._vars_added_since_update: + self._update_gurobi_model() + solver_var = self._pyomo_var_to_solver_var_map[v_id] + self._solver_model.remove(solver_var) + self._symbol_map.removeSymbol(var) + del self._pyomo_var_to_solver_var_map[v_id] + self._mutable_bounds.pop(v_id, None) + self._needs_updated = True + + def _remove_params(self, params: List[_ParamData]): + pass + + def _update_variables(self, variables: List[_GeneralVarData]): + for var in variables: + var_id = id(var) + if var_id not in self._pyomo_var_to_solver_var_map: + raise ValueError( + 'The Var provided to update_var needs to be added first: {0}'.format( + var + ) + ) + self._mutable_bounds.pop((var_id, 'lb'), None) + self._mutable_bounds.pop((var_id, 'ub'), None) + gurobipy_var = self._pyomo_var_to_solver_var_map[var_id] + lb, ub, vtype = self._process_domain_and_bounds( + var, var_id, None, None, None, gurobipy_var + ) + gurobipy_var.setAttr('lb', lb) + gurobipy_var.setAttr('ub', ub) + gurobipy_var.setAttr('vtype', vtype) + self._needs_updated = True + + def update_params(self): + for con, helpers in self._mutable_helpers.items(): + for helper in helpers: + helper.update() + for k, (v, helper) in self._mutable_bounds.items(): + helper.update() + + for con, helper in self._mutable_quadratic_helpers.items(): + if con in self._constraints_added_since_update: + self._update_gurobi_model() + gurobi_con = helper.con + new_gurobi_expr = helper.get_updated_expression() + new_rhs = helper.get_updated_rhs() + new_sense = gurobi_con.qcsense + pyomo_con = self._solver_con_to_pyomo_con_map[id(gurobi_con)] + name = self._symbol_map.getSymbol(pyomo_con, self._labeler) + self._solver_model.remove(gurobi_con) + new_con = self._solver_model.addQConstr( + new_gurobi_expr, new_sense, new_rhs, name=name + ) + self._pyomo_con_to_solver_con_map[id(pyomo_con)] = new_con + del self._solver_con_to_pyomo_con_map[id(gurobi_con)] + self._solver_con_to_pyomo_con_map[id(new_con)] = pyomo_con + helper.con = new_con + self._constraints_added_since_update.add(con) + + helper = self._mutable_objective + pyomo_obj = self._objective + new_gurobi_expr = helper.get_updated_expression() + if new_gurobi_expr is not None: + if pyomo_obj.sense == minimize: + sense = gurobipy.GRB.MINIMIZE + else: + sense = gurobipy.GRB.MAXIMIZE + self._solver_model.setObjective(new_gurobi_expr, sense=sense) + + def _set_objective(self, obj): + if obj is None: + sense = gurobipy.GRB.MINIMIZE + gurobi_expr = 0 + repn_constant = 0 + mutable_linear_coefficients = list() + mutable_quadratic_coefficients = list() + else: + if obj.sense == minimize: + sense = gurobipy.GRB.MINIMIZE + elif obj.sense == maximize: + sense = gurobipy.GRB.MAXIMIZE + else: + raise ValueError( + 'Objective sense is not recognized: {0}'.format(obj.sense) + ) + + ( + gurobi_expr, + repn_constant, + mutable_linear_coefficients, + mutable_quadratic_coefficients, + ) = self._get_expr_from_pyomo_expr(obj.expr) + + mutable_constant = _MutableConstant() + mutable_constant.expr = repn_constant + mutable_objective = _MutableObjective( + self._solver_model, + mutable_constant, + mutable_linear_coefficients, + mutable_quadratic_coefficients, + ) + self._mutable_objective = mutable_objective + + # These two lines are needed as a workaround + # see PR #2454 + self._solver_model.setObjective(0) + self._solver_model.update() + + self._solver_model.setObjective(gurobi_expr + value(repn_constant), sense=sense) + self._needs_updated = True + + def _postsolve(self, timer: HierarchicalTimer): + config = self._config + + gprob = self._solver_model + grb = gurobipy.GRB + status = gprob.Status + + results = Results() + results.solution_loader = GurobiSolutionLoader(self) + results.timing_info.gurobi_time = gprob.Runtime + + if gprob.SolCount > 0: + if status == grb.OPTIMAL: + results.solution_status = SolutionStatus.optimal + else: + results.solution_status = SolutionStatus.feasible + else: + results.solution_status = SolutionStatus.noSolution + + if status == grb.LOADED: # problem is loaded, but no solution + results.termination_condition = TerminationCondition.unknown + elif status == grb.OPTIMAL: # optimal + results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied + elif status == grb.INFEASIBLE: + results.termination_condition = TerminationCondition.provenInfeasible + elif status == grb.INF_OR_UNBD: + results.termination_condition = TerminationCondition.infeasibleOrUnbounded + elif status == grb.UNBOUNDED: + results.termination_condition = TerminationCondition.unbounded + elif status == grb.CUTOFF: + results.termination_condition = TerminationCondition.objectiveLimit + elif status == grb.ITERATION_LIMIT: + results.termination_condition = TerminationCondition.iterationLimit + elif status == grb.NODE_LIMIT: + results.termination_condition = TerminationCondition.iterationLimit + elif status == grb.TIME_LIMIT: + results.termination_condition = TerminationCondition.maxTimeLimit + elif status == grb.SOLUTION_LIMIT: + results.termination_condition = TerminationCondition.unknown + elif status == grb.INTERRUPTED: + results.termination_condition = TerminationCondition.interrupted + elif status == grb.NUMERIC: + results.termination_condition = TerminationCondition.unknown + elif status == grb.SUBOPTIMAL: + results.termination_condition = TerminationCondition.unknown + elif status == grb.USER_OBJ_LIMIT: + results.termination_condition = TerminationCondition.objectiveLimit + else: + results.termination_condition = TerminationCondition.unknown + + if results.termination_condition != TerminationCondition.convergenceCriteriaSatisfied and config.raise_exception_on_nonoptimal_result: + raise RuntimeError( + 'Solver did not find the optimal solution. Set opt.config.raise_exception_on_nonoptimal_result = False to bypass this error.' + ) + + results.incumbent_objective = None + results.objective_bound = None + if self._objective is not None: + try: + results.incumbent_objective = gprob.ObjVal + except (gurobipy.GurobiError, AttributeError): + results.incumbent_objective = None + try: + results.objective_bound = gprob.ObjBound + except (gurobipy.GurobiError, AttributeError): + if self._objective.sense == minimize: + results.objective_bound = -math.inf + else: + results.objective_bound = math.inf + + if results.incumbent_objective is not None and not math.isfinite( + results.incumbent_objective + ): + results.incumbent_objective = None + + results.iteration_count = gprob.getAttr('IterCount') + + timer.start('load solution') + if config.load_solutions: + if gprob.SolCount > 0: + self._load_vars() + else: + raise RuntimeError( + 'A feasible solution was not found, so no solution can be loaded.' + 'Please set opt.config.load_solutions=False and check ' + 'results.solution_status and ' + 'results.incumbent_objective before loading a solution.' + ) + timer.stop('load solution') + + return results + + def _load_suboptimal_mip_solution(self, vars_to_load, solution_number): + if ( + self.get_model_attr('NumIntVars') == 0 + and self.get_model_attr('NumBinVars') == 0 + ): + raise ValueError( + 'Cannot obtain suboptimal solutions for a continuous model' + ) + var_map = self._pyomo_var_to_solver_var_map + ref_vars = self._referenced_variables + original_solution_number = self.get_gurobi_param_info('SolutionNumber')[2] + self.set_gurobi_param('SolutionNumber', solution_number) + gurobi_vars_to_load = [var_map[pyomo_var] for pyomo_var in vars_to_load] + vals = self._solver_model.getAttr("Xn", gurobi_vars_to_load) + res = ComponentMap() + for var_id, val in zip(vars_to_load, vals): + using_cons, using_sos, using_obj = ref_vars[var_id] + if using_cons or using_sos or (using_obj is not None): + res[self._vars[var_id][0]] = val + self.set_gurobi_param('SolutionNumber', original_solution_number) + return res + + def _load_vars(self, vars_to_load=None, solution_number=0): + for v, val in self._get_primals( + vars_to_load=vars_to_load, solution_number=solution_number + ).items(): + v.set_value(val, skip_validation=True) + StaleFlagManager.mark_all_as_stale(delayed=True) + + def _get_primals(self, vars_to_load=None, solution_number=0): + if self._needs_updated: + self._update_gurobi_model() # this is needed to ensure that solutions cannot be loaded after the model has been changed + + if self._solver_model.SolCount == 0: + raise RuntimeError( + 'Solver does not currently have a valid solution. Please ' + 'check the termination condition.' + ) + + var_map = self._pyomo_var_to_solver_var_map + ref_vars = self._referenced_variables + if vars_to_load is None: + vars_to_load = self._pyomo_var_to_solver_var_map.keys() + else: + vars_to_load = [id(v) for v in vars_to_load] + + if solution_number != 0: + return self._load_suboptimal_mip_solution( + vars_to_load=vars_to_load, solution_number=solution_number + ) + else: + gurobi_vars_to_load = [ + var_map[pyomo_var_id] for pyomo_var_id in vars_to_load + ] + vals = self._solver_model.getAttr("X", gurobi_vars_to_load) + + res = ComponentMap() + for var_id, val in zip(vars_to_load, vals): + using_cons, using_sos, using_obj = ref_vars[var_id] + if using_cons or using_sos or (using_obj is not None): + res[self._vars[var_id][0]] = val + return res + + def _get_reduced_costs(self, vars_to_load=None): + if self._needs_updated: + self._update_gurobi_model() + + if self._solver_model.Status != gurobipy.GRB.OPTIMAL: + raise RuntimeError( + 'Solver does not currently have valid reduced costs. Please ' + 'check the termination condition.' + ) + + var_map = self._pyomo_var_to_solver_var_map + ref_vars = self._referenced_variables + res = ComponentMap() + if vars_to_load is None: + vars_to_load = self._pyomo_var_to_solver_var_map.keys() + else: + vars_to_load = [id(v) for v in vars_to_load] + + gurobi_vars_to_load = [var_map[pyomo_var_id] for pyomo_var_id in vars_to_load] + vals = self._solver_model.getAttr("Rc", gurobi_vars_to_load) + + for var_id, val in zip(vars_to_load, vals): + using_cons, using_sos, using_obj = ref_vars[var_id] + if using_cons or using_sos or (using_obj is not None): + res[self._vars[var_id][0]] = val + + return res + + def _get_duals(self, cons_to_load=None): + if self._needs_updated: + self._update_gurobi_model() + + if self._solver_model.Status != gurobipy.GRB.OPTIMAL: + raise RuntimeError( + 'Solver does not currently have valid duals. Please ' + 'check the termination condition.' + ) + + con_map = self._pyomo_con_to_solver_con_map + reverse_con_map = self._solver_con_to_pyomo_con_map + dual = dict() + + if cons_to_load is None: + linear_cons_to_load = self._solver_model.getConstrs() + quadratic_cons_to_load = self._solver_model.getQConstrs() + else: + gurobi_cons_to_load = OrderedSet( + [con_map[pyomo_con] for pyomo_con in cons_to_load] + ) + linear_cons_to_load = list( + gurobi_cons_to_load.intersection( + OrderedSet(self._solver_model.getConstrs()) + ) + ) + quadratic_cons_to_load = list( + gurobi_cons_to_load.intersection( + OrderedSet(self._solver_model.getQConstrs()) + ) + ) + linear_vals = self._solver_model.getAttr("Pi", linear_cons_to_load) + quadratic_vals = self._solver_model.getAttr("QCPi", quadratic_cons_to_load) + + for gurobi_con, val in zip(linear_cons_to_load, linear_vals): + pyomo_con = reverse_con_map[id(gurobi_con)] + dual[pyomo_con] = val + for gurobi_con, val in zip(quadratic_cons_to_load, quadratic_vals): + pyomo_con = reverse_con_map[id(gurobi_con)] + dual[pyomo_con] = val + + return dual + + def update(self, timer: HierarchicalTimer = None): + if self._needs_updated: + self._update_gurobi_model() + super(Gurobi, self).update(timer=timer) + self._update_gurobi_model() + + def _update_gurobi_model(self): + self._solver_model.update() + self._constraints_added_since_update = OrderedSet() + self._vars_added_since_update = ComponentSet() + self._needs_updated = False + + def get_model_attr(self, attr): + """ + Get the value of an attribute on the Gurobi model. + + Parameters + ---------- + attr: str + The attribute to get. See Gurobi documentation for descriptions of the attributes. + """ + if self._needs_updated: + self._update_gurobi_model() + return self._solver_model.getAttr(attr) + + def write(self, filename): + """ + Write the model to a file (e.g., and lp file). + + Parameters + ---------- + filename: str + Name of the file to which the model should be written. + """ + self._solver_model.write(filename) + self._constraints_added_since_update = OrderedSet() + self._vars_added_since_update = ComponentSet() + self._needs_updated = False + + def set_linear_constraint_attr(self, con, attr, val): + """ + Set the value of an attribute on a gurobi linear constraint. + + Parameters + ---------- + con: pyomo.core.base.constraint._GeneralConstraintData + The pyomo constraint for which the corresponding gurobi constraint attribute + should be modified. + attr: str + The attribute to be modified. Options are: + CBasis + DStart + Lazy + val: any + See gurobi documentation for acceptable values. + """ + if attr in {'Sense', 'RHS', 'ConstrName'}: + raise ValueError( + 'Linear constraint attr {0} cannot be set with' + + ' the set_linear_constraint_attr method. Please use' + + ' the remove_constraint and add_constraint methods.'.format(attr) + ) + self._pyomo_con_to_solver_con_map[con].setAttr(attr, val) + self._needs_updated = True + + def set_var_attr(self, var, attr, val): + """ + Set the value of an attribute on a gurobi variable. + + Parameters + ---------- + var: pyomo.core.base.var._GeneralVarData + The pyomo var for which the corresponding gurobi var attribute + should be modified. + attr: str + The attribute to be modified. Options are: + Start + VarHintVal + VarHintPri + BranchPriority + VBasis + PStart + val: any + See gurobi documentation for acceptable values. + """ + if attr in {'LB', 'UB', 'VType', 'VarName'}: + raise ValueError( + 'Var attr {0} cannot be set with' + + ' the set_var_attr method. Please use' + + ' the update_var method.'.format(attr) + ) + if attr == 'Obj': + raise ValueError( + 'Var attr Obj cannot be set with' + + ' the set_var_attr method. Please use' + + ' the set_objective method.' + ) + self._pyomo_var_to_solver_var_map[id(var)].setAttr(attr, val) + self._needs_updated = True + + def get_var_attr(self, var, attr): + """ + Get the value of an attribute on a gurobi var. + + Parameters + ---------- + var: pyomo.core.base.var._GeneralVarData + The pyomo var for which the corresponding gurobi var attribute + should be retrieved. + attr: str + The attribute to get. See gurobi documentation + """ + if self._needs_updated: + self._update_gurobi_model() + return self._pyomo_var_to_solver_var_map[id(var)].getAttr(attr) + + def get_linear_constraint_attr(self, con, attr): + """ + Get the value of an attribute on a gurobi linear constraint. + + Parameters + ---------- + con: pyomo.core.base.constraint._GeneralConstraintData + The pyomo constraint for which the corresponding gurobi constraint attribute + should be retrieved. + attr: str + The attribute to get. See the Gurobi documentation + """ + if self._needs_updated: + self._update_gurobi_model() + return self._pyomo_con_to_solver_con_map[con].getAttr(attr) + + def get_sos_attr(self, con, attr): + """ + Get the value of an attribute on a gurobi sos constraint. + + Parameters + ---------- + con: pyomo.core.base.sos._SOSConstraintData + The pyomo SOS constraint for which the corresponding gurobi SOS constraint attribute + should be retrieved. + attr: str + The attribute to get. See the Gurobi documentation + """ + if self._needs_updated: + self._update_gurobi_model() + return self._pyomo_sos_to_solver_sos_map[con].getAttr(attr) + + def get_quadratic_constraint_attr(self, con, attr): + """ + Get the value of an attribute on a gurobi quadratic constraint. + + Parameters + ---------- + con: pyomo.core.base.constraint._GeneralConstraintData + The pyomo constraint for which the corresponding gurobi constraint attribute + should be retrieved. + attr: str + The attribute to get. See the Gurobi documentation + """ + if self._needs_updated: + self._update_gurobi_model() + return self._pyomo_con_to_solver_con_map[con].getAttr(attr) + + def set_gurobi_param(self, param, val): + """ + Set a gurobi parameter. + + Parameters + ---------- + param: str + The gurobi parameter to set. Options include any gurobi parameter. + Please see the Gurobi documentation for options. + val: any + The value to set the parameter to. See Gurobi documentation for possible values. + """ + self._solver_model.setParam(param, val) + + def get_gurobi_param_info(self, param): + """ + Get information about a gurobi parameter. + + Parameters + ---------- + param: str + The gurobi parameter to get info for. See Gurobi documentation for possible options. + + Returns + ------- + six-tuple containing the parameter name, type, value, minimum value, maximum value, and default value. + """ + return self._solver_model.getParamInfo(param) + + def _intermediate_callback(self): + def f(gurobi_model, where): + self._callback_func(self._model, self, where) + + return f + + def set_callback(self, func=None): + """ + Specify a callback for gurobi to use. + + Parameters + ---------- + func: function + The function to call. The function should have three arguments. The first will be the pyomo model being + solved. The second will be the GurobiPersistent instance. The third will be an enum member of + gurobipy.GRB.Callback. This will indicate where in the branch and bound algorithm gurobi is at. For + example, suppose we want to solve + + .. math:: + + min 2*x + y + + s.t. + + y >= (x-2)**2 + + 0 <= x <= 4 + + y >= 0 + + y integer + + as an MILP using extended cutting planes in callbacks. + + >>> from gurobipy import GRB # doctest:+SKIP + >>> import pyomo.environ as pe + >>> from pyomo.core.expr.taylor_series import taylor_series_expansion + >>> from pyomo.contrib import appsi + >>> + >>> m = pe.ConcreteModel() + >>> m.x = pe.Var(bounds=(0, 4)) + >>> m.y = pe.Var(within=pe.Integers, bounds=(0, None)) + >>> m.obj = pe.Objective(expr=2*m.x + m.y) + >>> m.cons = pe.ConstraintList() # for the cutting planes + >>> + >>> def _add_cut(xval): + ... # a function to generate the cut + ... m.x.value = xval + ... return m.cons.add(m.y >= taylor_series_expansion((m.x - 2)**2)) + ... + >>> _c = _add_cut(0) # start with 2 cuts at the bounds of x + >>> _c = _add_cut(4) # this is an arbitrary choice + >>> + >>> opt = appsi.solvers.Gurobi() + >>> opt.config.stream_solver = True + >>> opt.set_instance(m) # doctest:+SKIP + >>> opt.gurobi_options['PreCrush'] = 1 + >>> opt.gurobi_options['LazyConstraints'] = 1 + >>> + >>> def my_callback(cb_m, cb_opt, cb_where): + ... if cb_where == GRB.Callback.MIPSOL: + ... cb_opt.cbGetSolution(vars=[m.x, m.y]) + ... if m.y.value < (m.x.value - 2)**2 - 1e-6: + ... cb_opt.cbLazy(_add_cut(m.x.value)) + ... + >>> opt.set_callback(my_callback) + >>> res = opt.solve(m) # doctest:+SKIP + + """ + if func is not None: + self._callback_func = func + self._callback = self._intermediate_callback() + else: + self._callback = None + self._callback_func = None + + def cbCut(self, con): + """ + Add a cut within a callback. + + Parameters + ---------- + con: pyomo.core.base.constraint._GeneralConstraintData + The cut to add + """ + if not con.active: + raise ValueError('cbCut expected an active constraint.') + + if is_fixed(con.body): + raise ValueError('cbCut expected a non-trivial constraint') + + ( + gurobi_expr, + repn_constant, + mutable_linear_coefficients, + mutable_quadratic_coefficients, + ) = self._get_expr_from_pyomo_expr(con.body) + + if con.has_lb(): + if con.has_ub(): + raise ValueError('Range constraints are not supported in cbCut.') + if not is_fixed(con.lower): + raise ValueError( + 'Lower bound of constraint {0} is not constant.'.format(con) + ) + if con.has_ub(): + if not is_fixed(con.upper): + raise ValueError( + 'Upper bound of constraint {0} is not constant.'.format(con) + ) + + if con.equality: + self._solver_model.cbCut( + lhs=gurobi_expr, + sense=gurobipy.GRB.EQUAL, + rhs=value(con.lower - repn_constant), + ) + elif con.has_lb() and (value(con.lower) > -float('inf')): + self._solver_model.cbCut( + lhs=gurobi_expr, + sense=gurobipy.GRB.GREATER_EQUAL, + rhs=value(con.lower - repn_constant), + ) + elif con.has_ub() and (value(con.upper) < float('inf')): + self._solver_model.cbCut( + lhs=gurobi_expr, + sense=gurobipy.GRB.LESS_EQUAL, + rhs=value(con.upper - repn_constant), + ) + else: + raise ValueError( + 'Constraint does not have a lower or an upper bound {0} \n'.format(con) + ) + + def cbGet(self, what): + return self._solver_model.cbGet(what) + + def cbGetNodeRel(self, vars): + """ + Parameters + ---------- + vars: Var or iterable of Var + """ + if not isinstance(vars, Iterable): + vars = [vars] + gurobi_vars = [self._pyomo_var_to_solver_var_map[id(i)] for i in vars] + var_values = self._solver_model.cbGetNodeRel(gurobi_vars) + for i, v in enumerate(vars): + v.set_value(var_values[i], skip_validation=True) + + def cbGetSolution(self, vars): + """ + Parameters + ---------- + vars: iterable of vars + """ + if not isinstance(vars, Iterable): + vars = [vars] + gurobi_vars = [self._pyomo_var_to_solver_var_map[id(i)] for i in vars] + var_values = self._solver_model.cbGetSolution(gurobi_vars) + for i, v in enumerate(vars): + v.set_value(var_values[i], skip_validation=True) + + def cbLazy(self, con): + """ + Parameters + ---------- + con: pyomo.core.base.constraint._GeneralConstraintData + The lazy constraint to add + """ + if not con.active: + raise ValueError('cbLazy expected an active constraint.') + + if is_fixed(con.body): + raise ValueError('cbLazy expected a non-trivial constraint') + + ( + gurobi_expr, + repn_constant, + mutable_linear_coefficients, + mutable_quadratic_coefficients, + ) = self._get_expr_from_pyomo_expr(con.body) + + if con.has_lb(): + if con.has_ub(): + raise ValueError('Range constraints are not supported in cbLazy.') + if not is_fixed(con.lower): + raise ValueError( + 'Lower bound of constraint {0} is not constant.'.format(con) + ) + if con.has_ub(): + if not is_fixed(con.upper): + raise ValueError( + 'Upper bound of constraint {0} is not constant.'.format(con) + ) + + if con.equality: + self._solver_model.cbLazy( + lhs=gurobi_expr, + sense=gurobipy.GRB.EQUAL, + rhs=value(con.lower - repn_constant), + ) + elif con.has_lb() and (value(con.lower) > -float('inf')): + self._solver_model.cbLazy( + lhs=gurobi_expr, + sense=gurobipy.GRB.GREATER_EQUAL, + rhs=value(con.lower - repn_constant), + ) + elif con.has_ub() and (value(con.upper) < float('inf')): + self._solver_model.cbLazy( + lhs=gurobi_expr, + sense=gurobipy.GRB.LESS_EQUAL, + rhs=value(con.upper - repn_constant), + ) + else: + raise ValueError( + 'Constraint does not have a lower or an upper bound {0} \n'.format(con) + ) + + def cbSetSolution(self, vars, solution): + if not isinstance(vars, Iterable): + vars = [vars] + gurobi_vars = [self._pyomo_var_to_solver_var_map[id(i)] for i in vars] + self._solver_model.cbSetSolution(gurobi_vars, solution) + + def cbUseSolution(self): + return self._solver_model.cbUseSolution() + + def reset(self): + self._solver_model.reset() diff --git a/pyomo/contrib/solver/plugins.py b/pyomo/contrib/solver/plugins.py index 54d03eaf74b..e66818482b4 100644 --- a/pyomo/contrib/solver/plugins.py +++ b/pyomo/contrib/solver/plugins.py @@ -12,9 +12,11 @@ from .factory import SolverFactory from .ipopt import ipopt +from .gurobi import Gurobi def load(): SolverFactory.register(name='ipopt_v2', doc='The IPOPT NLP solver (new interface)')( ipopt ) + SolverFactory.register(name='gurobi_v2', doc='New interface to Gurobi')(Gurobi) diff --git a/pyomo/contrib/solver/sol_reader.py b/pyomo/contrib/solver/sol_reader.py index 68654a4e9d7..a2e4d90b898 100644 --- a/pyomo/contrib/solver/sol_reader.py +++ b/pyomo/contrib/solver/sol_reader.py @@ -122,7 +122,7 @@ def parse_sol_file( # TODO: this is solver dependent # But this was the way in the previous version - and has been fine thus far? result.solution_status = SolutionStatus.infeasible - result.termination_condition = TerminationCondition.iterationLimit + result.termination_condition = TerminationCondition.iterationLimit # this is not always correct elif (exit_code[1] >= 500) and (exit_code[1] <= 599): exit_code_message = ( "FAILURE: the solver stopped by an error condition " @@ -205,4 +205,4 @@ def parse_sol_file( ) line = sol_file.readline() - return result, sol_data + return result, sol_data diff --git a/pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py b/pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py new file mode 100644 index 00000000000..f53088506f9 --- /dev/null +++ b/pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py @@ -0,0 +1,691 @@ +from pyomo.common.errors import PyomoException +import pyomo.common.unittest as unittest +import pyomo.environ as pe +from pyomo.contrib.solver.gurobi import Gurobi +from pyomo.contrib.solver.results import TerminationCondition, SolutionStatus +from pyomo.core.expr.numeric_expr import LinearExpression +from pyomo.core.expr.taylor_series import taylor_series_expansion + + +opt = Gurobi() +if not opt.available(): + raise unittest.SkipTest +import gurobipy + + +def create_pmedian_model(): + d_dict = { + (1, 1): 1.777356642700564, + (1, 2): 1.6698255595592497, + (1, 3): 1.099139603924817, + (1, 4): 1.3529705111901453, + (1, 5): 1.467907742900842, + (1, 6): 1.5346837414708774, + (2, 1): 1.9783090609123972, + (2, 2): 1.130315350158659, + (2, 3): 1.6712434682302661, + (2, 4): 1.3642294159473756, + (2, 5): 1.4888357071619858, + (2, 6): 1.2030122107340537, + (3, 1): 1.6661983755713592, + (3, 2): 1.227663031206932, + (3, 3): 1.4580640582967632, + (3, 4): 1.0407223975549575, + (3, 5): 1.9742897953778287, + (3, 6): 1.4874760742689066, + (4, 1): 1.4616138636373597, + (4, 2): 1.7141471558082002, + (4, 3): 1.4157281494999725, + (4, 4): 1.888011688001529, + (4, 5): 1.0232934487237717, + (4, 6): 1.8335062677845464, + (5, 1): 1.468494740997508, + (5, 2): 1.8114798126442795, + (5, 3): 1.9455914886158723, + (5, 4): 1.983088378194899, + (5, 5): 1.1761820755785306, + (5, 6): 1.698655759576308, + (6, 1): 1.108855711312383, + (6, 2): 1.1602637342062019, + (6, 3): 1.0928602740245892, + (6, 4): 1.3140620798928404, + (6, 5): 1.0165386843386672, + (6, 6): 1.854049125736362, + (7, 1): 1.2910160386456968, + (7, 2): 1.7800475863350327, + (7, 3): 1.5480965161255695, + (7, 4): 1.1943306766997612, + (7, 5): 1.2920382721805297, + (7, 6): 1.3194527773994338, + (8, 1): 1.6585982235379078, + (8, 2): 1.2315210354122292, + (8, 3): 1.6194303369953538, + (8, 4): 1.8953386098022103, + (8, 5): 1.8694342085696831, + (8, 6): 1.2938069356684523, + (9, 1): 1.4582048085805495, + (9, 2): 1.484979797871119, + (9, 3): 1.2803882693587225, + (9, 4): 1.3289569463506004, + (9, 5): 1.9842424240265042, + (9, 6): 1.0119441379208745, + (10, 1): 1.1429007682932852, + (10, 2): 1.6519772165446711, + (10, 3): 1.0749931799469326, + (10, 4): 1.2920787022811089, + (10, 5): 1.7934429721917704, + (10, 6): 1.9115931008709737, + } + + model = pe.ConcreteModel() + model.N = pe.Param(initialize=10) + model.Locations = pe.RangeSet(1, model.N) + model.P = pe.Param(initialize=3) + model.M = pe.Param(initialize=6) + model.Customers = pe.RangeSet(1, model.M) + model.d = pe.Param( + model.Locations, model.Customers, initialize=d_dict, within=pe.Reals + ) + model.x = pe.Var(model.Locations, model.Customers, bounds=(0.0, 1.0)) + model.y = pe.Var(model.Locations, within=pe.Binary) + + def rule(model): + return sum( + model.d[n, m] * model.x[n, m] + for n in model.Locations + for m in model.Customers + ) + + model.obj = pe.Objective(rule=rule) + + def rule(model, m): + return (sum(model.x[n, m] for n in model.Locations), 1.0) + + model.single_x = pe.Constraint(model.Customers, rule=rule) + + def rule(model, n, m): + return (None, model.x[n, m] - model.y[n], 0.0) + + model.bound_y = pe.Constraint(model.Locations, model.Customers, rule=rule) + + def rule(model): + return (sum(model.y[n] for n in model.Locations) - model.P, 0.0) + + model.num_facilities = pe.Constraint(rule=rule) + + return model + + +class TestGurobiPersistentSimpleLPUpdates(unittest.TestCase): + def setUp(self): + self.m = pe.ConcreteModel() + m = self.m + m.x = pe.Var() + m.y = pe.Var() + m.p1 = pe.Param(mutable=True) + m.p2 = pe.Param(mutable=True) + m.p3 = pe.Param(mutable=True) + m.p4 = pe.Param(mutable=True) + m.obj = pe.Objective(expr=m.x + m.y) + m.c1 = pe.Constraint(expr=m.y - m.p1 * m.x >= m.p2) + m.c2 = pe.Constraint(expr=m.y - m.p3 * m.x >= m.p4) + + def get_solution(self): + try: + import numpy as np + except: + raise unittest.SkipTest('numpy is not available') + p1 = self.m.p1.value + p2 = self.m.p2.value + p3 = self.m.p3.value + p4 = self.m.p4.value + A = np.array([[1, -p1], [1, -p3]]) + rhs = np.array([p2, p4]) + sol = np.linalg.solve(A, rhs) + x = float(sol[1]) + y = float(sol[0]) + return x, y + + def set_params(self, p1, p2, p3, p4): + self.m.p1.value = p1 + self.m.p2.value = p2 + self.m.p3.value = p3 + self.m.p4.value = p4 + + def test_lp(self): + self.set_params(-1, -2, 0.1, -2) + x, y = self.get_solution() + opt = Gurobi() + res = opt.solve(self.m) + self.assertAlmostEqual(x + y, res.incumbent_objective) + self.assertAlmostEqual(x + y, res.objective_bound) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertTrue(res.incumbent_objective is not None) + self.assertAlmostEqual(x, self.m.x.value) + self.assertAlmostEqual(y, self.m.y.value) + + self.set_params(-1.25, -1, 0.5, -2) + opt.config.load_solutions = False + res = opt.solve(self.m) + self.assertAlmostEqual(x, self.m.x.value) + self.assertAlmostEqual(y, self.m.y.value) + x, y = self.get_solution() + self.assertNotAlmostEqual(x, self.m.x.value) + self.assertNotAlmostEqual(y, self.m.y.value) + res.solution_loader.load_vars() + self.assertAlmostEqual(x, self.m.x.value) + self.assertAlmostEqual(y, self.m.y.value) + + +class TestGurobiPersistent(unittest.TestCase): + def test_nonconvex_qcp_objective_bound_1(self): + # the goal of this test is to ensure we can get an objective bound + # for nonconvex but continuous problems even if a feasible solution + # is not found + # + # This is a fragile test because it could fail if Gurobi's algorithms improve + # (e.g., a heuristic solution is found before an objective bound of -8 is reached + m = pe.ConcreteModel() + m.x = pe.Var(bounds=(-5, 5)) + m.y = pe.Var(bounds=(-5, 5)) + m.obj = pe.Objective(expr=-m.x**2 - m.y) + m.c1 = pe.Constraint(expr=m.y <= -2 * m.x + 1) + m.c2 = pe.Constraint(expr=m.y <= m.x - 2) + opt = Gurobi() + opt.config.solver_options['nonconvex'] = 2 + opt.config.solver_options['BestBdStop'] = -8 + opt.config.load_solutions = False + opt.config.raise_exception_on_nonoptimal_result = False + res = opt.solve(m) + self.assertEqual(res.incumbent_objective, None) + self.assertAlmostEqual(res.objective_bound, -8) + + def test_nonconvex_qcp_objective_bound_2(self): + # the goal of this test is to ensure we can objective_bound properly + # for nonconvex but continuous problems when the solver terminates with a nonzero gap + # + # This is a fragile test because it could fail if Gurobi's algorithms change + m = pe.ConcreteModel() + m.x = pe.Var(bounds=(-5, 5)) + m.y = pe.Var(bounds=(-5, 5)) + m.obj = pe.Objective(expr=-m.x**2 - m.y) + m.c1 = pe.Constraint(expr=m.y <= -2 * m.x + 1) + m.c2 = pe.Constraint(expr=m.y <= m.x - 2) + opt = Gurobi() + opt.config.solver_options['nonconvex'] = 2 + opt.config.solver_options['MIPGap'] = 0.5 + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, -4) + self.assertAlmostEqual(res.objective_bound, -6) + + def test_range_constraints(self): + m = pe.ConcreteModel() + m.x = pe.Var() + m.xl = pe.Param(initialize=-1, mutable=True) + m.xu = pe.Param(initialize=1, mutable=True) + m.c = pe.Constraint(expr=pe.inequality(m.xl, m.x, m.xu)) + m.obj = pe.Objective(expr=m.x) + + opt = Gurobi() + opt.set_instance(m) + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, -1) + + m.xl.value = -3 + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, -3) + + del m.obj + m.obj = pe.Objective(expr=m.x, sense=pe.maximize) + + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 1) + + m.xu.value = 3 + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 3) + + def test_quadratic_constraint_with_params(self): + m = pe.ConcreteModel() + m.a = pe.Param(initialize=1, mutable=True) + m.b = pe.Param(initialize=1, mutable=True) + m.c = pe.Param(initialize=1, mutable=True) + m.x = pe.Var() + m.y = pe.Var() + m.obj = pe.Objective(expr=m.y) + m.con = pe.Constraint(expr=m.y >= m.a * m.x**2 + m.b * m.x + m.c) + + opt = Gurobi() + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, -m.b.value / (2 * m.a.value)) + self.assertAlmostEqual( + m.y.value, m.a.value * m.x.value**2 + m.b.value * m.x.value + m.c.value + ) + + m.a.value = 2 + m.b.value = 4 + m.c.value = -1 + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, -m.b.value / (2 * m.a.value)) + self.assertAlmostEqual( + m.y.value, m.a.value * m.x.value**2 + m.b.value * m.x.value + m.c.value + ) + + def test_quadratic_objective(self): + m = pe.ConcreteModel() + m.a = pe.Param(initialize=1, mutable=True) + m.b = pe.Param(initialize=1, mutable=True) + m.c = pe.Param(initialize=1, mutable=True) + m.x = pe.Var() + m.obj = pe.Objective(expr=m.a * m.x**2 + m.b * m.x + m.c) + + opt = Gurobi() + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, -m.b.value / (2 * m.a.value)) + self.assertAlmostEqual( + res.incumbent_objective, + m.a.value * m.x.value**2 + m.b.value * m.x.value + m.c.value, + ) + + m.a.value = 2 + m.b.value = 4 + m.c.value = -1 + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, -m.b.value / (2 * m.a.value)) + self.assertAlmostEqual( + res.incumbent_objective, + m.a.value * m.x.value**2 + m.b.value * m.x.value + m.c.value, + ) + + def test_var_bounds(self): + m = pe.ConcreteModel() + m.x = pe.Var(bounds=(-1, 1)) + m.obj = pe.Objective(expr=m.x) + + opt = Gurobi() + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, -1) + + m.x.setlb(-3) + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, -3) + + del m.obj + m.obj = pe.Objective(expr=m.x, sense=pe.maximize) + + opt = Gurobi() + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 1) + + m.x.setub(3) + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 3) + + def test_fixed_var(self): + m = pe.ConcreteModel() + m.a = pe.Param(initialize=1, mutable=True) + m.b = pe.Param(initialize=1, mutable=True) + m.c = pe.Param(initialize=1, mutable=True) + m.x = pe.Var() + m.y = pe.Var() + m.obj = pe.Objective(expr=m.y) + m.con = pe.Constraint(expr=m.y >= m.a * m.x**2 + m.b * m.x + m.c) + + m.x.fix(1) + opt = Gurobi() + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 1) + self.assertAlmostEqual(m.y.value, 3) + + m.x.value = 2 + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 2) + self.assertAlmostEqual(m.y.value, 7) + + m.x.unfix() + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, -m.b.value / (2 * m.a.value)) + self.assertAlmostEqual( + m.y.value, m.a.value * m.x.value**2 + m.b.value * m.x.value + m.c.value + ) + + def test_linear_constraint_attr(self): + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.c = pe.Constraint(expr=m.x + m.y == 1) + + opt = Gurobi() + opt.set_instance(m) + opt.set_linear_constraint_attr(m.c, 'Lazy', 1) + self.assertEqual(opt.get_linear_constraint_attr(m.c, 'Lazy'), 1) + + def test_quadratic_constraint_attr(self): + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.c = pe.Constraint(expr=m.y >= m.x**2) + + opt = Gurobi() + opt.set_instance(m) + self.assertEqual(opt.get_quadratic_constraint_attr(m.c, 'QCRHS'), 0) + + def test_var_attr(self): + m = pe.ConcreteModel() + m.x = pe.Var(within=pe.Binary) + m.obj = pe.Objective(expr=m.x) + + opt = Gurobi() + opt.set_instance(m) + opt.set_var_attr(m.x, 'Start', 1) + self.assertEqual(opt.get_var_attr(m.x, 'Start'), 1) + + def test_callback(self): + m = pe.ConcreteModel() + m.x = pe.Var(bounds=(0, 4)) + m.y = pe.Var(within=pe.Integers, bounds=(0, None)) + m.obj = pe.Objective(expr=2 * m.x + m.y) + m.cons = pe.ConstraintList() + + def _add_cut(xval): + m.x.value = xval + return m.cons.add(m.y >= taylor_series_expansion((m.x - 2) ** 2)) + + _add_cut(0) + _add_cut(4) + + opt = Gurobi() + opt.set_instance(m) + opt.set_gurobi_param('PreCrush', 1) + opt.set_gurobi_param('LazyConstraints', 1) + + def _my_callback(cb_m, cb_opt, cb_where): + if cb_where == gurobipy.GRB.Callback.MIPSOL: + cb_opt.cbGetSolution(vars=[m.x, m.y]) + if m.y.value < (m.x.value - 2) ** 2 - 1e-6: + cb_opt.cbLazy(_add_cut(m.x.value)) + + opt.set_callback(_my_callback) + opt.solve(m) + self.assertAlmostEqual(m.x.value, 1) + self.assertAlmostEqual(m.y.value, 1) + + def test_nonconvex(self): + if gurobipy.GRB.VERSION_MAJOR < 9: + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.obj = pe.Objective(expr=m.x**2 + m.y**2) + m.c = pe.Constraint(expr=m.y == (m.x - 1) ** 2 - 2) + opt = Gurobi() + opt.config.solver_options['nonconvex'] = 2 + opt.solve(m) + self.assertAlmostEqual(m.x.value, -0.3660254037844423, 2) + self.assertAlmostEqual(m.y.value, -0.13397459621555508, 2) + + def test_nonconvex2(self): + if gurobipy.GRB.VERSION_MAJOR < 9: + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.obj = pe.Objective(expr=m.x**2 + m.y**2) + m.c1 = pe.Constraint(expr=0 <= -m.y + (m.x - 1) ** 2 - 2) + m.c2 = pe.Constraint(expr=0 >= -m.y + (m.x - 1) ** 2 - 2) + opt = Gurobi() + opt.config.solver_options['nonconvex'] = 2 + opt.solve(m) + self.assertAlmostEqual(m.x.value, -0.3660254037844423, 2) + self.assertAlmostEqual(m.y.value, -0.13397459621555508, 2) + + def test_solution_number(self): + m = create_pmedian_model() + opt = Gurobi() + opt.config.solver_options['PoolSolutions'] = 3 + opt.config.solver_options['PoolSearchMode'] = 2 + res = opt.solve(m) + num_solutions = opt.get_model_attr('SolCount') + self.assertEqual(num_solutions, 3) + res.solution_loader.load_vars(solution_number=0) + self.assertAlmostEqual(pe.value(m.obj.expr), 6.431184939357673) + res.solution_loader.load_vars(solution_number=1) + self.assertAlmostEqual(pe.value(m.obj.expr), 6.584793218502477) + res.solution_loader.load_vars(solution_number=2) + self.assertAlmostEqual(pe.value(m.obj.expr), 6.592304628123309) + + def test_zero_time_limit(self): + m = create_pmedian_model() + opt = Gurobi() + opt.config.time_limit = 0 + opt.config.load_solutions = False + opt.config.raise_exception_on_nonoptimal_result = False + res = opt.solve(m) + num_solutions = opt.get_model_attr('SolCount') + + # Behavior is different on different platforms, so + # we have to see if there are any solutions + # This means that there is no guarantee we are testing + # what we are trying to test. Unfortunately, I'm + # not sure of a good way to guarantee that + if num_solutions == 0: + self.assertIsNone(res.incumbent_objective) + + +class TestManualModel(unittest.TestCase): + def setUp(self): + opt = Gurobi() + opt.config.auto_updates.check_for_new_or_removed_params = False + opt.config.auto_updates.check_for_new_or_removed_vars = False + opt.config.auto_updates.check_for_new_or_removed_constraints = False + opt.config.auto_updates.update_params = False + opt.config.auto_updates.update_vars = False + opt.config.auto_updates.update_constraints = False + opt.config.auto_updates.update_named_expressions = False + self.opt = opt + + def test_basics(self): + m = pe.ConcreteModel() + m.x = pe.Var(bounds=(-10, 10)) + m.y = pe.Var() + m.obj = pe.Objective(expr=m.x**2 + m.y**2) + m.c1 = pe.Constraint(expr=m.y >= 2 * m.x + 1) + + opt = self.opt + opt.set_instance(m) + + self.assertEqual(opt.get_model_attr('NumVars'), 2) + self.assertEqual(opt.get_model_attr('NumConstrs'), 1) + self.assertEqual(opt.get_model_attr('NumQConstrs'), 0) + self.assertEqual(opt.get_var_attr(m.x, 'LB'), -10) + self.assertEqual(opt.get_var_attr(m.x, 'UB'), 10) + + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, -0.4) + self.assertAlmostEqual(m.y.value, 0.2) + duals = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.c1], -0.4) + + m.c2 = pe.Constraint(expr=m.y >= -m.x + 1) + opt.add_constraints([m.c2]) + self.assertEqual(opt.get_model_attr('NumVars'), 2) + self.assertEqual(opt.get_model_attr('NumConstrs'), 2) + self.assertEqual(opt.get_model_attr('NumQConstrs'), 0) + + opt.config.load_solutions = False + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, -0.4) + self.assertAlmostEqual(m.y.value, 0.2) + res.solution_loader.load_vars() + self.assertAlmostEqual(m.x.value, 0) + self.assertAlmostEqual(m.y.value, 1) + + opt.remove_constraints([m.c2]) + m.del_component(m.c2) + self.assertEqual(opt.get_model_attr('NumVars'), 2) + self.assertEqual(opt.get_model_attr('NumConstrs'), 1) + self.assertEqual(opt.get_model_attr('NumQConstrs'), 0) + + self.assertEqual(opt.get_gurobi_param_info('FeasibilityTol')[2], 1e-6) + opt.config.solver_options['FeasibilityTol'] = 1e-7 + opt.config.load_solutions = True + res = opt.solve(m) + self.assertEqual(opt.get_gurobi_param_info('FeasibilityTol')[2], 1e-7) + self.assertAlmostEqual(m.x.value, -0.4) + self.assertAlmostEqual(m.y.value, 0.2) + + m.x.setlb(-5) + m.x.setub(5) + opt.update_variables([m.x]) + self.assertEqual(opt.get_var_attr(m.x, 'LB'), -5) + self.assertEqual(opt.get_var_attr(m.x, 'UB'), 5) + + m.x.fix(0) + opt.update_variables([m.x]) + self.assertEqual(opt.get_var_attr(m.x, 'LB'), 0) + self.assertEqual(opt.get_var_attr(m.x, 'UB'), 0) + + m.x.unfix() + opt.update_variables([m.x]) + self.assertEqual(opt.get_var_attr(m.x, 'LB'), -5) + self.assertEqual(opt.get_var_attr(m.x, 'UB'), 5) + + m.c2 = pe.Constraint(expr=m.y >= m.x**2) + opt.add_constraints([m.c2]) + self.assertEqual(opt.get_model_attr('NumVars'), 2) + self.assertEqual(opt.get_model_attr('NumConstrs'), 1) + self.assertEqual(opt.get_model_attr('NumQConstrs'), 1) + + opt.remove_constraints([m.c2]) + m.del_component(m.c2) + self.assertEqual(opt.get_model_attr('NumVars'), 2) + self.assertEqual(opt.get_model_attr('NumConstrs'), 1) + self.assertEqual(opt.get_model_attr('NumQConstrs'), 0) + + m.z = pe.Var() + opt.add_variables([m.z]) + self.assertEqual(opt.get_model_attr('NumVars'), 3) + opt.remove_variables([m.z]) + del m.z + self.assertEqual(opt.get_model_attr('NumVars'), 2) + + def test_update1(self): + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.z = pe.Var() + m.obj = pe.Objective(expr=m.z) + m.c1 = pe.Constraint(expr=m.z >= m.x**2 + m.y**2) + + opt = self.opt + opt.set_instance(m) + self.assertEqual(opt._solver_model.getAttr('NumQConstrs'), 1) + + opt.remove_constraints([m.c1]) + opt.update() + self.assertEqual(opt._solver_model.getAttr('NumQConstrs'), 0) + + opt.add_constraints([m.c1]) + self.assertEqual(opt._solver_model.getAttr('NumQConstrs'), 0) + opt.update() + self.assertEqual(opt._solver_model.getAttr('NumQConstrs'), 1) + + def test_update2(self): + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.z = pe.Var() + m.obj = pe.Objective(expr=m.z) + m.c2 = pe.Constraint(expr=m.x + m.y == 1) + + opt = self.opt + opt.config.symbolic_solver_labels = True + opt.set_instance(m) + self.assertEqual(opt._solver_model.getAttr('NumConstrs'), 1) + + opt.remove_constraints([m.c2]) + opt.update() + self.assertEqual(opt._solver_model.getAttr('NumConstrs'), 0) + + opt.add_constraints([m.c2]) + self.assertEqual(opt._solver_model.getAttr('NumConstrs'), 0) + opt.update() + self.assertEqual(opt._solver_model.getAttr('NumConstrs'), 1) + + def test_update3(self): + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.z = pe.Var() + m.obj = pe.Objective(expr=m.z) + m.c1 = pe.Constraint(expr=m.z >= m.x**2 + m.y**2) + + opt = self.opt + opt.set_instance(m) + opt.update() + self.assertEqual(opt._solver_model.getAttr('NumQConstrs'), 1) + m.c2 = pe.Constraint(expr=m.y >= m.x**2) + opt.add_constraints([m.c2]) + self.assertEqual(opt._solver_model.getAttr('NumQConstrs'), 1) + opt.remove_constraints([m.c2]) + opt.update() + self.assertEqual(opt._solver_model.getAttr('NumQConstrs'), 1) + + def test_update4(self): + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.z = pe.Var() + m.obj = pe.Objective(expr=m.z) + m.c1 = pe.Constraint(expr=m.z >= m.x + m.y) + + opt = self.opt + opt.set_instance(m) + opt.update() + self.assertEqual(opt._solver_model.getAttr('NumConstrs'), 1) + m.c2 = pe.Constraint(expr=m.y >= m.x) + opt.add_constraints([m.c2]) + self.assertEqual(opt._solver_model.getAttr('NumConstrs'), 1) + opt.remove_constraints([m.c2]) + opt.update() + self.assertEqual(opt._solver_model.getAttr('NumConstrs'), 1) + + def test_update5(self): + m = pe.ConcreteModel() + m.a = pe.Set(initialize=[1, 2, 3], ordered=True) + m.x = pe.Var(m.a, within=pe.Binary) + m.y = pe.Var(within=pe.Binary) + m.obj = pe.Objective(expr=m.y) + m.c1 = pe.SOSConstraint(var=m.x, sos=1) + + opt = self.opt + opt.set_instance(m) + self.assertEqual(opt._solver_model.getAttr('NumSOS'), 1) + + opt.remove_sos_constraints([m.c1]) + opt.update() + self.assertEqual(opt._solver_model.getAttr('NumSOS'), 0) + + opt.add_sos_constraints([m.c1]) + self.assertEqual(opt._solver_model.getAttr('NumSOS'), 0) + opt.update() + self.assertEqual(opt._solver_model.getAttr('NumSOS'), 1) + + def test_update6(self): + m = pe.ConcreteModel() + m.a = pe.Set(initialize=[1, 2, 3], ordered=True) + m.x = pe.Var(m.a, within=pe.Binary) + m.y = pe.Var(within=pe.Binary) + m.obj = pe.Objective(expr=m.y) + m.c1 = pe.SOSConstraint(var=m.x, sos=1) + + opt = self.opt + opt.set_instance(m) + opt.update() + self.assertEqual(opt._solver_model.getAttr('NumSOS'), 1) + m.c2 = pe.SOSConstraint(var=m.x, sos=2) + opt.add_sos_constraints([m.c2]) + self.assertEqual(opt._solver_model.getAttr('NumSOS'), 1) + opt.remove_sos_constraints([m.c2]) + opt.update() + self.assertEqual(opt._solver_model.getAttr('NumSOS'), 1) diff --git a/pyomo/contrib/solver/tests/solvers/test_solvers.py b/pyomo/contrib/solver/tests/solvers/test_solvers.py new file mode 100644 index 00000000000..0499f1abb5d --- /dev/null +++ b/pyomo/contrib/solver/tests/solvers/test_solvers.py @@ -0,0 +1,1350 @@ +import pyomo.environ as pe +from pyomo.common.dependencies import attempt_import +import pyomo.common.unittest as unittest + +parameterized, param_available = attempt_import('parameterized') +parameterized = parameterized.parameterized +from pyomo.contrib.solver.results import TerminationCondition, SolutionStatus, Results +from pyomo.contrib.solver.base import SolverBase +from pyomo.contrib.solver.ipopt import ipopt +from pyomo.contrib.solver.gurobi import Gurobi +from typing import Type +from pyomo.core.expr.numeric_expr import LinearExpression +import os +import math + +numpy, numpy_available = attempt_import('numpy') +import random +from pyomo import gdp + + +if not param_available: + raise unittest.SkipTest('Parameterized is not available.') + +all_solvers = [ + ('gurobi', Gurobi), + ('ipopt', ipopt), +] +mip_solvers = [('gurobi', Gurobi)] +nlp_solvers = [('ipopt', ipopt)] +qcp_solvers = [('gurobi', Gurobi), ('ipopt', ipopt)] +miqcqp_solvers = [('gurobi', Gurobi)] + + +def _load_tests(solver_list): + res = list() + for solver_name, solver in solver_list: + test_name = f"{solver_name}" + res.append((test_name, solver)) + return res + + +@unittest.skipUnless(numpy_available, 'numpy is not available') +class TestSolvers(unittest.TestCase): + @parameterized.expand(input=_load_tests(all_solvers)) + def test_remove_variable_and_objective( + self, name: str, opt_class: Type[SolverBase], + ): + # this test is for issue #2888 + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var(bounds=(2, None)) + m.obj = pe.Objective(expr=m.x) + res = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(m.x.value, 2) + + del m.x + del m.obj + m.x = pe.Var(bounds=(2, None)) + m.obj = pe.Objective(expr=m.x) + res = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(m.x.value, 2) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_stale_vars( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.z = pe.Var() + m.obj = pe.Objective(expr=m.y) + m.c1 = pe.Constraint(expr=m.y >= m.x) + m.c2 = pe.Constraint(expr=m.y >= -m.x) + m.x.value = 1 + m.y.value = 1 + m.z.value = 1 + self.assertFalse(m.x.stale) + self.assertFalse(m.y.stale) + self.assertFalse(m.z.stale) + + res = opt.solve(m) + self.assertFalse(m.x.stale) + self.assertFalse(m.y.stale) + self.assertTrue(m.z.stale) + + opt.config.load_solutions = False + res = opt.solve(m) + self.assertTrue(m.x.stale) + self.assertTrue(m.y.stale) + self.assertTrue(m.z.stale) + res.solution_loader.load_vars() + self.assertFalse(m.x.stale) + self.assertFalse(m.y.stale) + self.assertTrue(m.z.stale) + + res = opt.solve(m) + self.assertTrue(m.x.stale) + self.assertTrue(m.y.stale) + self.assertTrue(m.z.stale) + res.solution_loader.load_vars([m.y]) + self.assertFalse(m.y.stale) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_range_constraint( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.obj = pe.Objective(expr=m.x) + m.c = pe.Constraint(expr=(-1, m.x, 1)) + res = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(m.x.value, -1) + duals = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.c], 1) + m.obj.sense = pe.maximize + res = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(m.x.value, 1) + duals = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.c], 1) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_reduced_costs( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var(bounds=(-1, 1)) + m.y = pe.Var(bounds=(-2, 2)) + m.obj = pe.Objective(expr=3 * m.x + 4 * m.y) + res = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(m.x.value, -1) + self.assertAlmostEqual(m.y.value, -2) + rc = res.solution_loader.get_reduced_costs() + self.assertAlmostEqual(rc[m.x], 3) + self.assertAlmostEqual(rc[m.y], 4) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_reduced_costs2( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var(bounds=(-1, 1)) + m.obj = pe.Objective(expr=m.x) + res = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(m.x.value, -1) + rc = res.solution_loader.get_reduced_costs() + self.assertAlmostEqual(rc[m.x], 1) + m.obj.sense = pe.maximize + res = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(m.x.value, 1) + rc = res.solution_loader.get_reduced_costs() + self.assertAlmostEqual(rc[m.x], 1) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_param_changes( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.a1 = pe.Param(mutable=True) + m.a2 = pe.Param(mutable=True) + m.b1 = pe.Param(mutable=True) + m.b2 = pe.Param(mutable=True) + m.obj = pe.Objective(expr=m.y) + m.c1 = pe.Constraint(expr=(0, m.y - m.a1 * m.x - m.b1, None)) + m.c2 = pe.Constraint(expr=(None, -m.y + m.a2 * m.x + m.b2, 0)) + + params_to_test = [(1, -1, 2, 1), (1, -2, 2, 1), (1, -1, 3, 1)] + for a1, a2, b1, b2 in params_to_test: + m.a1.value = a1 + m.a2.value = a2 + m.b1.value = b1 + m.b2.value = b2 + res: Results = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) + self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) + self.assertAlmostEqual(res.incumbent_objective, m.y.value) + if res.objective_bound is None: + bound = -math.inf + else: + bound = res.objective_bound + self.assertTrue(bound <= m.y.value) + duals = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.c1], (1 + a1 / (a2 - a1))) + self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_immutable_param( + self, name: str, opt_class: Type[SolverBase], + ): + """ + This test is important because component_data_objects returns immutable params as floats. + We want to make sure we process these correctly. + """ + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.a1 = pe.Param(mutable=True) + m.a2 = pe.Param(initialize=-1) + m.b1 = pe.Param(mutable=True) + m.b2 = pe.Param(mutable=True) + m.obj = pe.Objective(expr=m.y) + m.c1 = pe.Constraint(expr=(0, m.y - m.a1 * m.x - m.b1, None)) + m.c2 = pe.Constraint(expr=(None, -m.y + m.a2 * m.x + m.b2, 0)) + + params_to_test = [(1, 2, 1), (1, 2, 1), (1, 3, 1)] + for a1, b1, b2 in params_to_test: + a2 = m.a2.value + m.a1.value = a1 + m.b1.value = b1 + m.b2.value = b2 + res: Results = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) + self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) + self.assertAlmostEqual(res.incumbent_objective, m.y.value) + if res.objective_bound is None: + bound = -math.inf + else: + bound = res.objective_bound + self.assertTrue(bound <= m.y.value) + duals = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.c1], (1 + a1 / (a2 - a1))) + self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_equality( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + if isinstance(opt, ipopt): + opt.config.writer_config.linear_presolve = False + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.a1 = pe.Param(mutable=True) + m.a2 = pe.Param(mutable=True) + m.b1 = pe.Param(mutable=True) + m.b2 = pe.Param(mutable=True) + m.obj = pe.Objective(expr=m.y) + m.c1 = pe.Constraint(expr=m.y == m.a1 * m.x + m.b1) + m.c2 = pe.Constraint(expr=m.y == m.a2 * m.x + m.b2) + + params_to_test = [(1, -1, 2, 1), (1, -2, 2, 1), (1, -1, 3, 1)] + for a1, a2, b1, b2 in params_to_test: + m.a1.value = a1 + m.a2.value = a2 + m.b1.value = b1 + m.b2.value = b2 + res: Results = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) + self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) + self.assertAlmostEqual(res.incumbent_objective, m.y.value) + if res.objective_bound is None: + bound = -math.inf + else: + bound = res.objective_bound + self.assertTrue(bound <= m.y.value) + duals = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.c1], (1 + a1 / (a2 - a1))) + self.assertAlmostEqual(duals[m.c2], -a1 / (a2 - a1)) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_linear_expression( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.a1 = pe.Param(mutable=True) + m.a2 = pe.Param(mutable=True) + m.b1 = pe.Param(mutable=True) + m.b2 = pe.Param(mutable=True) + m.obj = pe.Objective(expr=m.y) + e = LinearExpression( + constant=m.b1, linear_coefs=[-1, m.a1], linear_vars=[m.y, m.x] + ) + m.c1 = pe.Constraint(expr=e == 0) + e = LinearExpression( + constant=m.b2, linear_coefs=[-1, m.a2], linear_vars=[m.y, m.x] + ) + m.c2 = pe.Constraint(expr=e == 0) + + params_to_test = [(1, -1, 2, 1), (1, -2, 2, 1), (1, -1, 3, 1)] + for a1, a2, b1, b2 in params_to_test: + m.a1.value = a1 + m.a2.value = a2 + m.b1.value = b1 + m.b2.value = b2 + res: Results = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) + self.assertAlmostEqual(res.incumbent_objective, m.y.value) + if res.objective_bound is None: + bound = -math.inf + else: + bound = res.objective_bound + self.assertTrue(bound <= m.y.value) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_no_objective( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.a1 = pe.Param(mutable=True) + m.a2 = pe.Param(mutable=True) + m.b1 = pe.Param(mutable=True) + m.b2 = pe.Param(mutable=True) + m.c1 = pe.Constraint(expr=m.y == m.a1 * m.x + m.b1) + m.c2 = pe.Constraint(expr=m.y == m.a2 * m.x + m.b2) + + params_to_test = [(1, -1, 2, 1), (1, -2, 2, 1), (1, -1, 3, 1)] + for a1, a2, b1, b2 in params_to_test: + m.a1.value = a1 + m.a2.value = a2 + m.b1.value = b1 + m.b2.value = b2 + res: Results = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) + self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) + self.assertEqual(res.incumbent_objective, None) + self.assertEqual(res.objective_bound, None) + duals = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.c1], 0) + self.assertAlmostEqual(duals[m.c2], 0) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_add_remove_cons( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + a1 = -1 + a2 = 1 + b1 = 1 + b2 = 2 + a3 = 1 + b3 = 3 + m.obj = pe.Objective(expr=m.y) + m.c1 = pe.Constraint(expr=m.y >= a1 * m.x + b1) + m.c2 = pe.Constraint(expr=m.y >= a2 * m.x + b2) + res = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) + self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) + self.assertAlmostEqual(res.incumbent_objective, m.y.value) + if res.objective_bound is None: + bound = -math.inf + else: + bound = res.objective_bound + self.assertTrue(bound <= m.y.value) + duals = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.c1], -(1 + a1 / (a2 - a1))) + self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) + + m.c3 = pe.Constraint(expr=m.y >= a3 * m.x + b3) + res = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(m.x.value, (b3 - b1) / (a1 - a3)) + self.assertAlmostEqual(m.y.value, a1 * (b3 - b1) / (a1 - a3) + b1) + self.assertAlmostEqual(res.incumbent_objective, m.y.value) + self.assertTrue(res.objective_bound is None or res.objective_bound <= m.y.value) + duals = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.c1], -(1 + a1 / (a3 - a1))) + self.assertAlmostEqual(duals[m.c2], 0) + self.assertAlmostEqual(duals[m.c3], a1 / (a3 - a1)) + + del m.c3 + res = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) + self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) + self.assertAlmostEqual(res.incumbent_objective, m.y.value) + self.assertTrue(res.objective_bound is None or res.objective_bound <= m.y.value) + duals = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.c1], -(1 + a1 / (a2 - a1))) + self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_results_infeasible( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.obj = pe.Objective(expr=m.y) + m.c1 = pe.Constraint(expr=m.y >= m.x) + m.c2 = pe.Constraint(expr=m.y <= m.x - 1) + with self.assertRaises(Exception): + res = opt.solve(m) + opt.config.load_solutions = False + opt.config.raise_exception_on_nonoptimal_result = False + res = opt.solve(m) + self.assertNotEqual(res.solution_status, SolutionStatus.optimal) + if isinstance(opt, ipopt): + acceptable_termination_conditions = { + TerminationCondition.locallyInfeasible, + TerminationCondition.unbounded, + } + else: + acceptable_termination_conditions = { + TerminationCondition.provenInfeasible, + TerminationCondition.infeasibleOrUnbounded, + } + self.assertIn(res.termination_condition, acceptable_termination_conditions) + self.assertAlmostEqual(m.x.value, None) + self.assertAlmostEqual(m.y.value, None) + self.assertTrue(res.incumbent_objective is None) + + if not isinstance(opt, ipopt): + # ipopt can return the values of the variables/duals at the last iterate + # even if it did not converge; raise_exception_on_nonoptimal_result + # is set to False, so we are free to load infeasible solutions + with self.assertRaisesRegex( + RuntimeError, '.*does not currently have a valid solution.*' + ): + res.solution_loader.load_vars() + with self.assertRaisesRegex( + RuntimeError, '.*does not currently have valid duals.*' + ): + res.solution_loader.get_duals() + with self.assertRaisesRegex( + RuntimeError, '.*does not currently have valid reduced costs.*' + ): + res.solution_loader.get_reduced_costs() + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_duals(self, name: str, opt_class: Type[SolverBase],): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.obj = pe.Objective(expr=m.y) + m.c1 = pe.Constraint(expr=m.y - m.x >= 0) + m.c2 = pe.Constraint(expr=m.y + m.x - 2 >= 0) + + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 1) + self.assertAlmostEqual(m.y.value, 1) + duals = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.c1], 0.5) + self.assertAlmostEqual(duals[m.c2], 0.5) + + duals = res.solution_loader.get_duals(cons_to_load=[m.c1]) + self.assertAlmostEqual(duals[m.c1], 0.5) + self.assertNotIn(m.c2, duals) + + @parameterized.expand(input=_load_tests(qcp_solvers)) + def test_mutable_quadratic_coefficient( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.a = pe.Param(initialize=1, mutable=True) + m.b = pe.Param(initialize=-1, mutable=True) + m.obj = pe.Objective(expr=m.x**2 + m.y**2) + m.c = pe.Constraint(expr=m.y >= (m.a * m.x + m.b) ** 2) + + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 0.41024548525899274, 4) + self.assertAlmostEqual(m.y.value, 0.34781038127030117, 4) + m.a.value = 2 + m.b.value = -0.5 + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 0.10256137418973625, 4) + self.assertAlmostEqual(m.y.value, 0.0869525991355825, 4) + + @parameterized.expand(input=_load_tests(qcp_solvers)) + def test_mutable_quadratic_objective( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.a = pe.Param(initialize=1, mutable=True) + m.b = pe.Param(initialize=-1, mutable=True) + m.c = pe.Param(initialize=1, mutable=True) + m.d = pe.Param(initialize=1, mutable=True) + m.obj = pe.Objective(expr=m.x**2 + m.c * m.y**2 + m.d * m.x) + m.ccon = pe.Constraint(expr=m.y >= (m.a * m.x + m.b) ** 2) + + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 0.2719178742733325, 4) + self.assertAlmostEqual(m.y.value, 0.5301035741688002, 4) + m.c.value = 3.5 + m.d.value = -1 + res = opt.solve(m) + + self.assertAlmostEqual(m.x.value, 0.6962249634573562, 4) + self.assertAlmostEqual(m.y.value, 0.09227926676152151, 4) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_fixed_vars( + self, name: str, opt_class: Type[SolverBase], + ): + for treat_fixed_vars_as_params in [True, False]: + opt: SolverBase = opt_class() + if opt.is_persistent(): + opt.config.auto_updates.treat_fixed_vars_as_params = treat_fixed_vars_as_params + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.x.fix(0) + m.y = pe.Var() + a1 = 1 + a2 = -1 + b1 = 1 + b2 = 2 + m.obj = pe.Objective(expr=m.y) + m.c1 = pe.Constraint(expr=m.y >= a1 * m.x + b1) + m.c2 = pe.Constraint(expr=m.y >= a2 * m.x + b2) + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 0) + self.assertAlmostEqual(m.y.value, 2) + m.x.unfix() + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) + self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) + m.x.fix(0) + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 0) + self.assertAlmostEqual(m.y.value, 2) + m.x.value = 2 + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 2) + self.assertAlmostEqual(m.y.value, 3) + m.x.value = 0 + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 0) + self.assertAlmostEqual(m.y.value, 2) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_fixed_vars_2( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if opt.is_persistent(): + opt.config.auto_updates.treat_fixed_vars_as_params = True + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.x.fix(0) + m.y = pe.Var() + a1 = 1 + a2 = -1 + b1 = 1 + b2 = 2 + m.obj = pe.Objective(expr=m.y) + m.c1 = pe.Constraint(expr=m.y >= a1 * m.x + b1) + m.c2 = pe.Constraint(expr=m.y >= a2 * m.x + b2) + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 0) + self.assertAlmostEqual(m.y.value, 2) + m.x.unfix() + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) + self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) + m.x.fix(0) + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 0) + self.assertAlmostEqual(m.y.value, 2) + m.x.value = 2 + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 2) + self.assertAlmostEqual(m.y.value, 3) + m.x.value = 0 + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 0) + self.assertAlmostEqual(m.y.value, 2) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_fixed_vars_3( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if opt.is_persistent(): + opt.config.auto_updates.treat_fixed_vars_as_params = True + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.obj = pe.Objective(expr=m.x + m.y) + m.c1 = pe.Constraint(expr=m.x == 2 / m.y) + m.y.fix(1) + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 2) + + @parameterized.expand(input=_load_tests(nlp_solvers)) + def test_fixed_vars_4( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if opt.is_persistent(): + opt.config.auto_updates.treat_fixed_vars_as_params = True + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.obj = pe.Objective(expr=m.x**2 + m.y**2) + m.c1 = pe.Constraint(expr=m.x == 2 / m.y) + m.y.fix(1) + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 2) + m.y.unfix() + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 2**0.5) + self.assertAlmostEqual(m.y.value, 2**0.5) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_mutable_param_with_range( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + try: + import numpy as np + except: + raise unittest.SkipTest('numpy is not available') + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.a1 = pe.Param(initialize=0, mutable=True) + m.a2 = pe.Param(initialize=0, mutable=True) + m.b1 = pe.Param(initialize=0, mutable=True) + m.b2 = pe.Param(initialize=0, mutable=True) + m.c1 = pe.Param(initialize=0, mutable=True) + m.c2 = pe.Param(initialize=0, mutable=True) + m.obj = pe.Objective(expr=m.y) + m.con1 = pe.Constraint(expr=(m.b1, m.y - m.a1 * m.x, m.c1)) + m.con2 = pe.Constraint(expr=(m.b2, m.y - m.a2 * m.x, m.c2)) + + np.random.seed(0) + params_to_test = [ + ( + np.random.uniform(0, 10), + np.random.uniform(-10, 0), + np.random.uniform(-5, 2.5), + np.random.uniform(-5, 2.5), + np.random.uniform(2.5, 10), + np.random.uniform(2.5, 10), + pe.minimize, + ), + ( + np.random.uniform(0, 10), + np.random.uniform(-10, 0), + np.random.uniform(-5, 2.5), + np.random.uniform(-5, 2.5), + np.random.uniform(2.5, 10), + np.random.uniform(2.5, 10), + pe.maximize, + ), + ( + np.random.uniform(0, 10), + np.random.uniform(-10, 0), + np.random.uniform(-5, 2.5), + np.random.uniform(-5, 2.5), + np.random.uniform(2.5, 10), + np.random.uniform(2.5, 10), + pe.minimize, + ), + ( + np.random.uniform(0, 10), + np.random.uniform(-10, 0), + np.random.uniform(-5, 2.5), + np.random.uniform(-5, 2.5), + np.random.uniform(2.5, 10), + np.random.uniform(2.5, 10), + pe.maximize, + ), + ] + for a1, a2, b1, b2, c1, c2, sense in params_to_test: + m.a1.value = float(a1) + m.a2.value = float(a2) + m.b1.value = float(b1) + m.b2.value = float(b2) + m.c1.value = float(c1) + m.c2.value = float(c2) + m.obj.sense = sense + res: Results = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + if sense is pe.minimize: + self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2), 6) + self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1, 6) + self.assertAlmostEqual(res.incumbent_objective, m.y.value, 6) + self.assertTrue(res.objective_bound is None or res.objective_bound <= m.y.value + 1e-12) + duals = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.con1], (1 + a1 / (a2 - a1)), 6) + self.assertAlmostEqual(duals[m.con2], -a1 / (a2 - a1), 6) + else: + self.assertAlmostEqual(m.x.value, (c2 - c1) / (a1 - a2), 6) + self.assertAlmostEqual(m.y.value, a1 * (c2 - c1) / (a1 - a2) + c1, 6) + self.assertAlmostEqual(res.incumbent_objective, m.y.value, 6) + self.assertTrue(res.objective_bound is None or res.objective_bound >= m.y.value - 1e-12) + duals = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.con1], (1 + a1 / (a2 - a1)), 6) + self.assertAlmostEqual(duals[m.con2], -a1 / (a2 - a1), 6) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_add_and_remove_vars( + self, name: str, opt_class: Type[SolverBase], + ): + opt = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.y = pe.Var(bounds=(-1, None)) + m.obj = pe.Objective(expr=m.y) + if opt.is_persistent(): + opt.config.auto_updates.update_params = False + opt.config.auto_updates.update_vars = False + opt.config.auto_updates.update_constraints = False + opt.config.auto_updates.update_named_expressions = False + opt.config.auto_updates.check_for_new_or_removed_params = False + opt.config.auto_updates.check_for_new_or_removed_constraints = False + opt.config.auto_updates.check_for_new_or_removed_vars = False + opt.config.load_solutions = False + res = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + res.solution_loader.load_vars() + self.assertAlmostEqual(m.y.value, -1) + m.x = pe.Var() + a1 = 1 + a2 = -1 + b1 = 2 + b2 = 1 + m.c1 = pe.Constraint(expr=(0, m.y - a1 * m.x - b1, None)) + m.c2 = pe.Constraint(expr=(None, -m.y + a2 * m.x + b2, 0)) + if opt.is_persistent(): + opt.add_constraints([m.c1, m.c2]) + res = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + res.solution_loader.load_vars() + self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) + self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) + m.c1.deactivate() + m.c2.deactivate() + if opt.is_persistent(): + opt.remove_constraints([m.c1, m.c2]) + m.x.value = None + res = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + res.solution_loader.load_vars() + self.assertEqual(m.x.value, None) + self.assertAlmostEqual(m.y.value, -1) + + @parameterized.expand(input=_load_tests(nlp_solvers)) + def test_exp(self, name: str, opt_class: Type[SolverBase],): + opt = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.obj = pe.Objective(expr=m.x**2 + m.y**2) + m.c1 = pe.Constraint(expr=m.y >= pe.exp(m.x)) + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, -0.42630274815985264) + self.assertAlmostEqual(m.y.value, 0.6529186341994245) + + @parameterized.expand(input=_load_tests(nlp_solvers)) + def test_log(self, name: str, opt_class: Type[SolverBase],): + opt = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var(initialize=1) + m.y = pe.Var() + m.obj = pe.Objective(expr=m.x**2 + m.y**2) + m.c1 = pe.Constraint(expr=m.y <= pe.log(m.x)) + res = opt.solve(m) + self.assertAlmostEqual(m.x.value, 0.6529186341994245) + self.assertAlmostEqual(m.y.value, -0.42630274815985264) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_with_numpy( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.obj = pe.Objective(expr=m.y) + a1 = 1 + b1 = 3 + a2 = -2 + b2 = 1 + m.c1 = pe.Constraint( + expr=(numpy.float64(0), m.y - numpy.int64(1) * m.x - numpy.float32(3), None) + ) + m.c2 = pe.Constraint( + expr=( + None, + -m.y + numpy.int32(-2) * m.x + numpy.float64(1), + numpy.float16(0), + ) + ) + res = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) + self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_bounds_with_params( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.y = pe.Var() + m.p = pe.Param(mutable=True) + m.y.setlb(m.p) + m.p.value = 1 + m.obj = pe.Objective(expr=m.y) + res = opt.solve(m) + self.assertAlmostEqual(m.y.value, 1) + m.p.value = -1 + res = opt.solve(m) + self.assertAlmostEqual(m.y.value, -1) + m.y.setlb(None) + m.y.setub(m.p) + m.obj.sense = pe.maximize + m.p.value = 5 + res = opt.solve(m) + self.assertAlmostEqual(m.y.value, 5) + m.p.value = 4 + res = opt.solve(m) + self.assertAlmostEqual(m.y.value, 4) + m.y.setub(None) + m.y.setlb(m.p) + m.obj.sense = pe.minimize + m.p.value = 3 + res = opt.solve(m) + self.assertAlmostEqual(m.y.value, 3) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_solution_loader( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var(bounds=(1, None)) + m.y = pe.Var() + m.obj = pe.Objective(expr=m.y) + m.c1 = pe.Constraint(expr=(0, m.y - m.x, None)) + m.c2 = pe.Constraint(expr=(0, m.y - m.x + 1, None)) + opt.config.load_solutions = False + res = opt.solve(m) + self.assertIsNone(m.x.value) + self.assertIsNone(m.y.value) + res.solution_loader.load_vars() + self.assertAlmostEqual(m.x.value, 1) + self.assertAlmostEqual(m.y.value, 1) + m.x.value = None + m.y.value = None + res.solution_loader.load_vars([m.y]) + self.assertAlmostEqual(m.y.value, 1) + primals = res.solution_loader.get_primals() + self.assertIn(m.x, primals) + self.assertIn(m.y, primals) + self.assertAlmostEqual(primals[m.x], 1) + self.assertAlmostEqual(primals[m.y], 1) + primals = res.solution_loader.get_primals([m.y]) + self.assertNotIn(m.x, primals) + self.assertIn(m.y, primals) + self.assertAlmostEqual(primals[m.y], 1) + reduced_costs = res.solution_loader.get_reduced_costs() + self.assertIn(m.x, reduced_costs) + self.assertIn(m.y, reduced_costs) + self.assertAlmostEqual(reduced_costs[m.x], 1) + self.assertAlmostEqual(reduced_costs[m.y], 0) + reduced_costs = res.solution_loader.get_reduced_costs([m.y]) + self.assertNotIn(m.x, reduced_costs) + self.assertIn(m.y, reduced_costs) + self.assertAlmostEqual(reduced_costs[m.y], 0) + duals = res.solution_loader.get_duals() + self.assertIn(m.c1, duals) + self.assertIn(m.c2, duals) + self.assertAlmostEqual(duals[m.c1], 1) + self.assertAlmostEqual(duals[m.c2], 0) + duals = res.solution_loader.get_duals([m.c1]) + self.assertNotIn(m.c2, duals) + self.assertIn(m.c1, duals) + self.assertAlmostEqual(duals[m.c1], 1) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_time_limit( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + from sys import platform + + if platform == 'win32': + raise unittest.SkipTest + + N = 30 + m = pe.ConcreteModel() + m.jobs = pe.Set(initialize=list(range(N))) + m.tasks = pe.Set(initialize=list(range(N))) + m.x = pe.Var(m.jobs, m.tasks, bounds=(0, 1)) + + random.seed(0) + coefs = list() + lin_vars = list() + for j in m.jobs: + for t in m.tasks: + coefs.append(random.uniform(0, 10)) + lin_vars.append(m.x[j, t]) + obj_expr = LinearExpression( + linear_coefs=coefs, linear_vars=lin_vars, constant=0 + ) + m.obj = pe.Objective(expr=obj_expr, sense=pe.maximize) + + m.c1 = pe.Constraint(m.jobs) + m.c2 = pe.Constraint(m.tasks) + for j in m.jobs: + expr = LinearExpression( + linear_coefs=[1] * N, + linear_vars=[m.x[j, t] for t in m.tasks], + constant=0, + ) + m.c1[j] = expr == 1 + for t in m.tasks: + expr = LinearExpression( + linear_coefs=[1] * N, + linear_vars=[m.x[j, t] for j in m.jobs], + constant=0, + ) + m.c2[t] = expr == 1 + if isinstance(opt, ipopt): + opt.config.time_limit = 1e-6 + else: + opt.config.time_limit = 0 + opt.config.load_solutions = False + opt.config.raise_exception_on_nonoptimal_result = False + res = opt.solve(m) + self.assertIn( + res.termination_condition, {TerminationCondition.maxTimeLimit, TerminationCondition.iterationLimit} + ) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_objective_changes( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.c1 = pe.Constraint(expr=m.y >= m.x + 1) + m.c2 = pe.Constraint(expr=m.y >= -m.x + 1) + m.obj = pe.Objective(expr=m.y) + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 1) + del m.obj + m.obj = pe.Objective(expr=2 * m.y) + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 2) + m.obj.expr = 3 * m.y + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 3) + m.obj.sense = pe.maximize + opt.config.raise_exception_on_nonoptimal_result = False + opt.config.load_solutions = False + res = opt.solve(m) + self.assertIn( + res.termination_condition, + { + TerminationCondition.unbounded, + TerminationCondition.infeasibleOrUnbounded, + }, + ) + m.obj.sense = pe.minimize + opt.config.load_solutions = True + del m.obj + m.obj = pe.Objective(expr=m.x * m.y) + m.x.fix(2) + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 6, 6) + m.x.fix(3) + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 12, 6) + m.x.unfix() + m.y.fix(2) + m.x.setlb(-3) + m.x.setub(5) + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, -2, 6) + m.y.unfix() + m.x.setlb(None) + m.x.setub(None) + m.e = pe.Expression(expr=2) + del m.obj + m.obj = pe.Objective(expr=m.e * m.y) + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 2) + m.e.expr = 3 + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 3) + if opt.is_persistent(): + opt.config.auto_updates.check_for_new_objective = False + m.e.expr = 4 + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 4) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_domain( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var(bounds=(1, None), domain=pe.NonNegativeReals) + m.obj = pe.Objective(expr=m.x) + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 1) + m.x.setlb(-1) + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 0) + m.x.setlb(1) + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 1) + m.x.setlb(-1) + m.x.domain = pe.Reals + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, -1) + m.x.domain = pe.NonNegativeReals + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 0) + + @parameterized.expand(input=_load_tests(mip_solvers)) + def test_domain_with_integers( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var(bounds=(-1, None), domain=pe.NonNegativeIntegers) + m.obj = pe.Objective(expr=m.x) + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 0) + m.x.setlb(0.5) + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 1) + m.x.setlb(-5.5) + m.x.domain = pe.Integers + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, -5) + m.x.domain = pe.Binary + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 0) + m.x.setlb(0.5) + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 1) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_fixed_binaries( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var(domain=pe.Binary) + m.y = pe.Var() + m.obj = pe.Objective(expr=m.y) + m.c = pe.Constraint(expr=m.y >= m.x) + m.x.fix(0) + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 0) + m.x.fix(1) + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 1) + + opt: SolverBase = opt_class() + if opt.is_persistent(): + opt.config.auto_updates.treat_fixed_vars_as_params = False + m.x.fix(0) + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 0) + m.x.fix(1) + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 1) + + @parameterized.expand(input=_load_tests(mip_solvers)) + def test_with_gdp( + self, name: str, opt_class: Type[SolverBase], + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + + m = pe.ConcreteModel() + m.x = pe.Var(bounds=(-10, 10)) + m.y = pe.Var(bounds=(-10, 10)) + m.obj = pe.Objective(expr=m.y) + m.d1 = gdp.Disjunct() + m.d1.c1 = pe.Constraint(expr=m.y >= m.x + 2) + m.d1.c2 = pe.Constraint(expr=m.y >= -m.x + 2) + m.d2 = gdp.Disjunct() + m.d2.c1 = pe.Constraint(expr=m.y >= m.x + 1) + m.d2.c2 = pe.Constraint(expr=m.y >= -m.x + 1) + m.disjunction = gdp.Disjunction(expr=[m.d2, m.d1]) + pe.TransformationFactory("gdp.bigm").apply_to(m) + + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 1) + self.assertAlmostEqual(m.x.value, 0) + self.assertAlmostEqual(m.y.value, 1) + + opt: SolverBase = opt_class() + opt.use_extensions = True + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 1) + self.assertAlmostEqual(m.x.value, 0) + self.assertAlmostEqual(m.y.value, 1) + + @parameterized.expand(input=all_solvers) + def test_variables_elsewhere(self, name: str, opt_class: Type[SolverBase]): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.b = pe.Block() + m.b.obj = pe.Objective(expr=m.y) + m.b.c1 = pe.Constraint(expr=m.y >= m.x + 2) + m.b.c2 = pe.Constraint(expr=m.y >= -m.x) + + res = opt.solve(m.b) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(res.incumbent_objective, 1) + self.assertAlmostEqual(m.x.value, -1) + self.assertAlmostEqual(m.y.value, 1) + + m.x.setlb(0) + res = opt.solve(m.b) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(res.incumbent_objective, 2) + self.assertAlmostEqual(m.x.value, 0) + self.assertAlmostEqual(m.y.value, 2) + + @parameterized.expand(input=all_solvers) + def test_variables_elsewhere2(self, name: str, opt_class: Type[SolverBase]): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.z = pe.Var() + + m.obj = pe.Objective(expr=m.y) + m.c1 = pe.Constraint(expr=m.y >= m.x) + m.c2 = pe.Constraint(expr=m.y >= -m.x) + m.c3 = pe.Constraint(expr=m.y >= m.z + 1) + m.c4 = pe.Constraint(expr=m.y >= -m.z + 1) + + res = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(res.incumbent_objective, 1) + sol = res.solution_loader.get_primals() + self.assertIn(m.x, sol) + self.assertIn(m.y, sol) + self.assertIn(m.z, sol) + + del m.c3 + del m.c4 + res = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(res.incumbent_objective, 0) + sol = res.solution_loader.get_primals() + self.assertIn(m.x, sol) + self.assertIn(m.y, sol) + self.assertNotIn(m.z, sol) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_bug_1(self, name: str, opt_class: Type[SolverBase],): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + + m = pe.ConcreteModel() + m.x = pe.Var(bounds=(3, 7)) + m.y = pe.Var(bounds=(-10, 10)) + m.p = pe.Param(mutable=True, initialize=0) + + m.obj = pe.Objective(expr=m.y) + m.c = pe.Constraint(expr=m.y >= m.p * m.x) + + res = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(res.incumbent_objective, 0) + + m.p.value = 1 + res = opt.solve(m) + self.assertEqual(res.solution_status, SolutionStatus.optimal) + self.assertAlmostEqual(res.incumbent_objective, 3) + + @parameterized.expand(input=_load_tests(all_solvers)) + def test_bug_2(self, name: str, opt_class: Type[SolverBase],): + """ + This test is for a bug where an objective containing a fixed variable does + not get updated properly when the variable is unfixed. + """ + for fixed_var_option in [True, False]: + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest + if opt.is_persistent(): + opt.config.auto_updates.treat_fixed_vars_as_params = fixed_var_option + + m = pe.ConcreteModel() + m.x = pe.Var(bounds=(-10, 10)) + m.y = pe.Var() + m.obj = pe.Objective(expr=3 * m.y - m.x) + m.c = pe.Constraint(expr=m.y >= m.x) + + m.x.fix(1) + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 2, 5) + + m.x.unfix() + m.x.setlb(-9) + m.x.setub(9) + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, -18, 5) + + +class TestLegacySolverInterface(unittest.TestCase): + @parameterized.expand(input=all_solvers) + def test_param_updates(self, name: str, opt_class: Type[SolverBase]): + opt = pe.SolverFactory(name + '_v2') + if not opt.available(exception_flag=False): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.a1 = pe.Param(mutable=True) + m.a2 = pe.Param(mutable=True) + m.b1 = pe.Param(mutable=True) + m.b2 = pe.Param(mutable=True) + m.obj = pe.Objective(expr=m.y) + m.c1 = pe.Constraint(expr=(0, m.y - m.a1 * m.x - m.b1, None)) + m.c2 = pe.Constraint(expr=(None, -m.y + m.a2 * m.x + m.b2, 0)) + m.dual = pe.Suffix(direction=pe.Suffix.IMPORT) + + params_to_test = [(1, -1, 2, 1), (1, -2, 2, 1), (1, -1, 3, 1)] + for a1, a2, b1, b2 in params_to_test: + m.a1.value = a1 + m.a2.value = a2 + m.b1.value = b1 + m.b2.value = b2 + res = opt.solve(m) + pe.assert_optimal_termination(res) + self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2)) + self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) + self.assertAlmostEqual(m.dual[m.c1], (1 + a1 / (a2 - a1))) + self.assertAlmostEqual(m.dual[m.c2], a1 / (a2 - a1)) + + @parameterized.expand(input=all_solvers) + def test_load_solutions(self, name: str, opt_class: Type[SolverBase]): + opt = pe.SolverFactory(name + '_v2') + if not opt.available(exception_flag=False): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.x = pe.Var() + m.obj = pe.Objective(expr=m.x) + m.c = pe.Constraint(expr=(-1, m.x, 1)) + m.dual = pe.Suffix(direction=pe.Suffix.IMPORT) + res = opt.solve(m, load_solutions=False) + pe.assert_optimal_termination(res) + self.assertIsNone(m.x.value) + self.assertNotIn(m.c, m.dual) + m.solutions.load_from(res) + self.assertAlmostEqual(m.x.value, -1) + self.assertAlmostEqual(m.dual[m.c], 1) diff --git a/pyomo/contrib/solver/util.py b/pyomo/contrib/solver/util.py index 807d66f569e..c4d13ae31d2 100644 --- a/pyomo/contrib/solver/util.py +++ b/pyomo/contrib/solver/util.py @@ -166,7 +166,7 @@ class DirectSolverUtils: class PersistentSolverUtils(abc.ABC): - def __init__(self, only_child_vars=False): + def __init__(self): self._model = None self._active_constraints = {} # maps constraint to (lower, body, upper) self._vars = {} # maps var id to (var, lb, ub, fixed, domain, value) @@ -185,11 +185,10 @@ def __init__(self, only_child_vars=False): self._vars_referenced_by_con = {} self._vars_referenced_by_obj = [] self._expr_types = None - self._only_child_vars = only_child_vars def set_instance(self, model): saved_config = self.config - self.__init__(only_child_vars=self._only_child_vars) + self.__init__() self.config = saved_config self._model = model self.add_block(model) @@ -257,8 +256,7 @@ def add_constraints(self, cons: List[_GeneralConstraintData]): self._active_constraints[con] = (con.lower, con.body, con.upper) tmp = collect_vars_and_named_exprs(con.body) named_exprs, variables, fixed_vars, external_functions = tmp - if not self._only_child_vars: - self._check_for_new_vars(variables) + self._check_for_new_vars(variables) self._named_expressions[con] = [(e, e.expr) for e in named_exprs] if len(external_functions) > 0: self._external_functions[con] = external_functions @@ -285,8 +283,7 @@ def add_sos_constraints(self, cons: List[_SOSConstraintData]): ) self._active_constraints[con] = tuple() variables = con.get_variables() - if not self._only_child_vars: - self._check_for_new_vars(variables) + self._check_for_new_vars(variables) self._named_expressions[con] = [] self._vars_referenced_by_con[con] = variables for v in variables: @@ -301,8 +298,7 @@ def set_objective(self, obj: _GeneralObjectiveData): if self._objective is not None: for v in self._vars_referenced_by_obj: self._referenced_variables[id(v)][2] = None - if not self._only_child_vars: - self._check_to_remove_vars(self._vars_referenced_by_obj) + self._check_to_remove_vars(self._vars_referenced_by_obj) self._external_functions.pop(self._objective, None) if obj is not None: self._objective = obj @@ -310,8 +306,7 @@ def set_objective(self, obj: _GeneralObjectiveData): self._objective_sense = obj.sense tmp = collect_vars_and_named_exprs(obj.expr) named_exprs, variables, fixed_vars, external_functions = tmp - if not self._only_child_vars: - self._check_for_new_vars(variables) + self._check_for_new_vars(variables) self._obj_named_expressions = [(i, i.expr) for i in named_exprs] if len(external_functions) > 0: self._external_functions[obj] = external_functions @@ -339,15 +334,6 @@ def add_block(self, block): for _p in p.values(): param_dict[id(_p)] = _p self.add_params(list(param_dict.values())) - if self._only_child_vars: - self.add_variables( - list( - dict( - (id(var), var) - for var in block.component_data_objects(Var, descend_into=True) - ).values() - ) - ) self.add_constraints( list( block.component_data_objects(Constraint, descend_into=True, active=True) @@ -379,8 +365,7 @@ def remove_constraints(self, cons: List[_GeneralConstraintData]): ) for v in self._vars_referenced_by_con[con]: self._referenced_variables[id(v)][0].pop(con) - if not self._only_child_vars: - self._check_to_remove_vars(self._vars_referenced_by_con[con]) + self._check_to_remove_vars(self._vars_referenced_by_con[con]) del self._active_constraints[con] del self._named_expressions[con] self._external_functions.pop(con, None) @@ -454,17 +439,6 @@ def remove_block(self, block): ) ) ) - if self._only_child_vars: - self.remove_variables( - list( - dict( - (id(var), var) - for var in block.component_data_objects( - ctype=Var, descend_into=True - ) - ).values() - ) - ) self.remove_params( list( dict( @@ -512,20 +486,7 @@ def update(self, timer: HierarchicalTimer = None): current_cons_dict = {} current_sos_dict = {} timer.start('vars') - if self._only_child_vars and ( - config.check_for_new_or_removed_vars or config.update_vars - ): - current_vars_dict = { - id(v): v - for v in self._model.component_data_objects(Var, descend_into=True) - } - for v_id, v in current_vars_dict.items(): - if v_id not in self._vars: - new_vars.append(v) - for v_id, v_tuple in self._vars.items(): - if v_id not in current_vars_dict: - old_vars.append(v_tuple[0]) - elif config.update_vars: + if config.update_vars: start_vars = {v_id: v_tuple[0] for v_id, v_tuple in self._vars.items()} timer.stop('vars') timer.start('params') @@ -636,12 +597,7 @@ def update(self, timer: HierarchicalTimer = None): self.add_sos_constraints(sos_to_update) timer.stop('cons') timer.start('vars') - if self._only_child_vars and config.update_vars: - vars_to_check = [] - for v_id, v in current_vars_dict.items(): - if v_id not in new_vars_set: - vars_to_check.append(v) - elif config.update_vars: + if config.update_vars: end_vars = {v_id: v_tuple[0] for v_id, v_tuple in self._vars.items()} vars_to_check = [v for v_id, v in end_vars.items() if v_id in start_vars] if config.update_vars: From 4471b7caab4b120c4a00c627bc015fb9995962b3 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Sat, 10 Feb 2024 21:33:18 -0700 Subject: [PATCH 0936/1204] run black --- pyomo/contrib/solver/gurobi.py | 25 ++- pyomo/contrib/solver/ipopt.py | 16 +- pyomo/contrib/solver/sol_reader.py | 4 +- .../solver/tests/solvers/test_solvers.py | 144 ++++++------------ 4 files changed, 76 insertions(+), 113 deletions(-) diff --git a/pyomo/contrib/solver/gurobi.py b/pyomo/contrib/solver/gurobi.py index 2dcdacd320d..50d241e1e88 100644 --- a/pyomo/contrib/solver/gurobi.py +++ b/pyomo/contrib/solver/gurobi.py @@ -69,12 +69,12 @@ def __init__( visibility=visibility, ) self.use_mipstart: bool = self.declare( - 'use_mipstart', + 'use_mipstart', ConfigValue( - default=False, - domain=bool, + default=False, + domain=bool, description="If True, the values of the integer variables will be passed to Gurobi.", - ) + ), ) @@ -339,9 +339,12 @@ def _solve(self): self._solver_model.setParam('MIPGap', config.rel_gap) if config.abs_gap is not None: self._solver_model.setParam('MIPGapAbs', config.abs_gap) - + if config.use_mipstart: - for pyomo_var_id, gurobi_var in self._pyomo_var_to_solver_var_map.items(): + for ( + pyomo_var_id, + gurobi_var, + ) in self._pyomo_var_to_solver_var_map.items(): pyomo_var = self._vars[pyomo_var_id][0] if pyomo_var.is_integer() and pyomo_var.value is not None: self.set_var_attr(pyomo_var, 'Start', pyomo_var.value) @@ -866,7 +869,9 @@ def _postsolve(self, timer: HierarchicalTimer): if status == grb.LOADED: # problem is loaded, but no solution results.termination_condition = TerminationCondition.unknown elif status == grb.OPTIMAL: # optimal - results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied + results.termination_condition = ( + TerminationCondition.convergenceCriteriaSatisfied + ) elif status == grb.INFEASIBLE: results.termination_condition = TerminationCondition.provenInfeasible elif status == grb.INF_OR_UNBD: @@ -894,7 +899,11 @@ def _postsolve(self, timer: HierarchicalTimer): else: results.termination_condition = TerminationCondition.unknown - if results.termination_condition != TerminationCondition.convergenceCriteriaSatisfied and config.raise_exception_on_nonoptimal_result: + if ( + results.termination_condition + != TerminationCondition.convergenceCriteriaSatisfied + and config.raise_exception_on_nonoptimal_result + ): raise RuntimeError( 'Solver did not find the optimal solution. Set opt.config.raise_exception_on_nonoptimal_result = False to bypass this error.' ) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 4c4b932381d..5eb877f0867 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -89,15 +89,15 @@ def __init__( implicit_domain=implicit_domain, visibility=visibility, ) - self.timing_info.no_function_solve_time: Optional[float] = ( - self.timing_info.declare( - 'no_function_solve_time', ConfigValue(domain=NonNegativeFloat) - ) + self.timing_info.no_function_solve_time: Optional[ + float + ] = self.timing_info.declare( + 'no_function_solve_time', ConfigValue(domain=NonNegativeFloat) ) - self.timing_info.function_solve_time: Optional[float] = ( - self.timing_info.declare( - 'function_solve_time', ConfigValue(domain=NonNegativeFloat) - ) + self.timing_info.function_solve_time: Optional[ + float + ] = self.timing_info.declare( + 'function_solve_time', ConfigValue(domain=NonNegativeFloat) ) diff --git a/pyomo/contrib/solver/sol_reader.py b/pyomo/contrib/solver/sol_reader.py index a2e4d90b898..04f12feb25e 100644 --- a/pyomo/contrib/solver/sol_reader.py +++ b/pyomo/contrib/solver/sol_reader.py @@ -122,7 +122,9 @@ def parse_sol_file( # TODO: this is solver dependent # But this was the way in the previous version - and has been fine thus far? result.solution_status = SolutionStatus.infeasible - result.termination_condition = TerminationCondition.iterationLimit # this is not always correct + result.termination_condition = ( + TerminationCondition.iterationLimit + ) # this is not always correct elif (exit_code[1] >= 500) and (exit_code[1] <= 599): exit_code_message = ( "FAILURE: the solver stopped by an error condition " diff --git a/pyomo/contrib/solver/tests/solvers/test_solvers.py b/pyomo/contrib/solver/tests/solvers/test_solvers.py index 0499f1abb5d..658aaf41b13 100644 --- a/pyomo/contrib/solver/tests/solvers/test_solvers.py +++ b/pyomo/contrib/solver/tests/solvers/test_solvers.py @@ -21,10 +21,7 @@ if not param_available: raise unittest.SkipTest('Parameterized is not available.') -all_solvers = [ - ('gurobi', Gurobi), - ('ipopt', ipopt), -] +all_solvers = [('gurobi', Gurobi), ('ipopt', ipopt)] mip_solvers = [('gurobi', Gurobi)] nlp_solvers = [('ipopt', ipopt)] qcp_solvers = [('gurobi', Gurobi), ('ipopt', ipopt)] @@ -43,7 +40,7 @@ def _load_tests(solver_list): class TestSolvers(unittest.TestCase): @parameterized.expand(input=_load_tests(all_solvers)) def test_remove_variable_and_objective( - self, name: str, opt_class: Type[SolverBase], + self, name: str, opt_class: Type[SolverBase] ): # this test is for issue #2888 opt: SolverBase = opt_class() @@ -65,9 +62,7 @@ def test_remove_variable_and_objective( self.assertAlmostEqual(m.x.value, 2) @parameterized.expand(input=_load_tests(all_solvers)) - def test_stale_vars( - self, name: str, opt_class: Type[SolverBase], - ): + def test_stale_vars(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -108,9 +103,7 @@ def test_stale_vars( self.assertFalse(m.y.stale) @parameterized.expand(input=_load_tests(all_solvers)) - def test_range_constraint( - self, name: str, opt_class: Type[SolverBase], - ): + def test_range_constraint(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -131,9 +124,7 @@ def test_range_constraint( self.assertAlmostEqual(duals[m.c], 1) @parameterized.expand(input=_load_tests(all_solvers)) - def test_reduced_costs( - self, name: str, opt_class: Type[SolverBase], - ): + def test_reduced_costs(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -150,9 +141,7 @@ def test_reduced_costs( self.assertAlmostEqual(rc[m.y], 4) @parameterized.expand(input=_load_tests(all_solvers)) - def test_reduced_costs2( - self, name: str, opt_class: Type[SolverBase], - ): + def test_reduced_costs2(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -172,9 +161,7 @@ def test_reduced_costs2( self.assertAlmostEqual(rc[m.x], 1) @parameterized.expand(input=_load_tests(all_solvers)) - def test_param_changes( - self, name: str, opt_class: Type[SolverBase], - ): + def test_param_changes(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -210,9 +197,7 @@ def test_param_changes( self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) @parameterized.expand(input=_load_tests(all_solvers)) - def test_immutable_param( - self, name: str, opt_class: Type[SolverBase], - ): + def test_immutable_param(self, name: str, opt_class: Type[SolverBase]): """ This test is important because component_data_objects returns immutable params as floats. We want to make sure we process these correctly. @@ -252,9 +237,7 @@ def test_immutable_param( self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) @parameterized.expand(input=_load_tests(all_solvers)) - def test_equality( - self, name: str, opt_class: Type[SolverBase], - ): + def test_equality(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -292,9 +275,7 @@ def test_equality( self.assertAlmostEqual(duals[m.c2], -a1 / (a2 - a1)) @parameterized.expand(input=_load_tests(all_solvers)) - def test_linear_expression( - self, name: str, opt_class: Type[SolverBase], - ): + def test_linear_expression(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -332,9 +313,7 @@ def test_linear_expression( self.assertTrue(bound <= m.y.value) @parameterized.expand(input=_load_tests(all_solvers)) - def test_no_objective( - self, name: str, opt_class: Type[SolverBase], - ): + def test_no_objective(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -365,9 +344,7 @@ def test_no_objective( self.assertAlmostEqual(duals[m.c2], 0) @parameterized.expand(input=_load_tests(all_solvers)) - def test_add_remove_cons( - self, name: str, opt_class: Type[SolverBase], - ): + def test_add_remove_cons(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -421,9 +398,7 @@ def test_add_remove_cons( self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) @parameterized.expand(input=_load_tests(all_solvers)) - def test_results_infeasible( - self, name: str, opt_class: Type[SolverBase], - ): + def test_results_infeasible(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -472,7 +447,7 @@ def test_results_infeasible( res.solution_loader.get_reduced_costs() @parameterized.expand(input=_load_tests(all_solvers)) - def test_duals(self, name: str, opt_class: Type[SolverBase],): + def test_duals(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -496,7 +471,7 @@ def test_duals(self, name: str, opt_class: Type[SolverBase],): @parameterized.expand(input=_load_tests(qcp_solvers)) def test_mutable_quadratic_coefficient( - self, name: str, opt_class: Type[SolverBase], + self, name: str, opt_class: Type[SolverBase] ): opt: SolverBase = opt_class() if not opt.available(): @@ -519,9 +494,7 @@ def test_mutable_quadratic_coefficient( self.assertAlmostEqual(m.y.value, 0.0869525991355825, 4) @parameterized.expand(input=_load_tests(qcp_solvers)) - def test_mutable_quadratic_objective( - self, name: str, opt_class: Type[SolverBase], - ): + def test_mutable_quadratic_objective(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -546,13 +519,13 @@ def test_mutable_quadratic_objective( self.assertAlmostEqual(m.y.value, 0.09227926676152151, 4) @parameterized.expand(input=_load_tests(all_solvers)) - def test_fixed_vars( - self, name: str, opt_class: Type[SolverBase], - ): + def test_fixed_vars(self, name: str, opt_class: Type[SolverBase]): for treat_fixed_vars_as_params in [True, False]: opt: SolverBase = opt_class() if opt.is_persistent(): - opt.config.auto_updates.treat_fixed_vars_as_params = treat_fixed_vars_as_params + opt.config.auto_updates.treat_fixed_vars_as_params = ( + treat_fixed_vars_as_params + ) if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() @@ -587,9 +560,7 @@ def test_fixed_vars( self.assertAlmostEqual(m.y.value, 2) @parameterized.expand(input=_load_tests(all_solvers)) - def test_fixed_vars_2( - self, name: str, opt_class: Type[SolverBase], - ): + def test_fixed_vars_2(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if opt.is_persistent(): opt.config.auto_updates.treat_fixed_vars_as_params = True @@ -627,9 +598,7 @@ def test_fixed_vars_2( self.assertAlmostEqual(m.y.value, 2) @parameterized.expand(input=_load_tests(all_solvers)) - def test_fixed_vars_3( - self, name: str, opt_class: Type[SolverBase], - ): + def test_fixed_vars_3(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if opt.is_persistent(): opt.config.auto_updates.treat_fixed_vars_as_params = True @@ -645,9 +614,7 @@ def test_fixed_vars_3( self.assertAlmostEqual(m.x.value, 2) @parameterized.expand(input=_load_tests(nlp_solvers)) - def test_fixed_vars_4( - self, name: str, opt_class: Type[SolverBase], - ): + def test_fixed_vars_4(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if opt.is_persistent(): opt.config.auto_updates.treat_fixed_vars_as_params = True @@ -667,9 +634,7 @@ def test_fixed_vars_4( self.assertAlmostEqual(m.y.value, 2**0.5) @parameterized.expand(input=_load_tests(all_solvers)) - def test_mutable_param_with_range( - self, name: str, opt_class: Type[SolverBase], - ): + def test_mutable_param_with_range(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -743,7 +708,10 @@ def test_mutable_param_with_range( self.assertAlmostEqual(m.x.value, (b2 - b1) / (a1 - a2), 6) self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1, 6) self.assertAlmostEqual(res.incumbent_objective, m.y.value, 6) - self.assertTrue(res.objective_bound is None or res.objective_bound <= m.y.value + 1e-12) + self.assertTrue( + res.objective_bound is None + or res.objective_bound <= m.y.value + 1e-12 + ) duals = res.solution_loader.get_duals() self.assertAlmostEqual(duals[m.con1], (1 + a1 / (a2 - a1)), 6) self.assertAlmostEqual(duals[m.con2], -a1 / (a2 - a1), 6) @@ -751,15 +719,16 @@ def test_mutable_param_with_range( self.assertAlmostEqual(m.x.value, (c2 - c1) / (a1 - a2), 6) self.assertAlmostEqual(m.y.value, a1 * (c2 - c1) / (a1 - a2) + c1, 6) self.assertAlmostEqual(res.incumbent_objective, m.y.value, 6) - self.assertTrue(res.objective_bound is None or res.objective_bound >= m.y.value - 1e-12) + self.assertTrue( + res.objective_bound is None + or res.objective_bound >= m.y.value - 1e-12 + ) duals = res.solution_loader.get_duals() self.assertAlmostEqual(duals[m.con1], (1 + a1 / (a2 - a1)), 6) self.assertAlmostEqual(duals[m.con2], -a1 / (a2 - a1), 6) @parameterized.expand(input=_load_tests(all_solvers)) - def test_add_and_remove_vars( - self, name: str, opt_class: Type[SolverBase], - ): + def test_add_and_remove_vars(self, name: str, opt_class: Type[SolverBase]): opt = opt_class() if not opt.available(): raise unittest.SkipTest @@ -805,7 +774,7 @@ def test_add_and_remove_vars( self.assertAlmostEqual(m.y.value, -1) @parameterized.expand(input=_load_tests(nlp_solvers)) - def test_exp(self, name: str, opt_class: Type[SolverBase],): + def test_exp(self, name: str, opt_class: Type[SolverBase]): opt = opt_class() if not opt.available(): raise unittest.SkipTest @@ -819,7 +788,7 @@ def test_exp(self, name: str, opt_class: Type[SolverBase],): self.assertAlmostEqual(m.y.value, 0.6529186341994245) @parameterized.expand(input=_load_tests(nlp_solvers)) - def test_log(self, name: str, opt_class: Type[SolverBase],): + def test_log(self, name: str, opt_class: Type[SolverBase]): opt = opt_class() if not opt.available(): raise unittest.SkipTest @@ -833,9 +802,7 @@ def test_log(self, name: str, opt_class: Type[SolverBase],): self.assertAlmostEqual(m.y.value, -0.42630274815985264) @parameterized.expand(input=_load_tests(all_solvers)) - def test_with_numpy( - self, name: str, opt_class: Type[SolverBase], - ): + def test_with_numpy(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -863,9 +830,7 @@ def test_with_numpy( self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) @parameterized.expand(input=_load_tests(all_solvers)) - def test_bounds_with_params( - self, name: str, opt_class: Type[SolverBase], - ): + def test_bounds_with_params(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -897,9 +862,7 @@ def test_bounds_with_params( self.assertAlmostEqual(m.y.value, 3) @parameterized.expand(input=_load_tests(all_solvers)) - def test_solution_loader( - self, name: str, opt_class: Type[SolverBase], - ): + def test_solution_loader(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -949,9 +912,7 @@ def test_solution_loader( self.assertAlmostEqual(duals[m.c1], 1) @parameterized.expand(input=_load_tests(all_solvers)) - def test_time_limit( - self, name: str, opt_class: Type[SolverBase], - ): + def test_time_limit(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -1002,13 +963,12 @@ def test_time_limit( opt.config.raise_exception_on_nonoptimal_result = False res = opt.solve(m) self.assertIn( - res.termination_condition, {TerminationCondition.maxTimeLimit, TerminationCondition.iterationLimit} + res.termination_condition, + {TerminationCondition.maxTimeLimit, TerminationCondition.iterationLimit}, ) @parameterized.expand(input=_load_tests(all_solvers)) - def test_objective_changes( - self, name: str, opt_class: Type[SolverBase], - ): + def test_objective_changes(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -1072,9 +1032,7 @@ def test_objective_changes( self.assertAlmostEqual(res.incumbent_objective, 4) @parameterized.expand(input=_load_tests(all_solvers)) - def test_domain( - self, name: str, opt_class: Type[SolverBase], - ): + def test_domain(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -1098,9 +1056,7 @@ def test_domain( self.assertAlmostEqual(res.incumbent_objective, 0) @parameterized.expand(input=_load_tests(mip_solvers)) - def test_domain_with_integers( - self, name: str, opt_class: Type[SolverBase], - ): + def test_domain_with_integers(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -1124,9 +1080,7 @@ def test_domain_with_integers( self.assertAlmostEqual(res.incumbent_objective, 1) @parameterized.expand(input=_load_tests(all_solvers)) - def test_fixed_binaries( - self, name: str, opt_class: Type[SolverBase], - ): + def test_fixed_binaries(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -1153,9 +1107,7 @@ def test_fixed_binaries( self.assertAlmostEqual(res.incumbent_objective, 1) @parameterized.expand(input=_load_tests(mip_solvers)) - def test_with_gdp( - self, name: str, opt_class: Type[SolverBase], - ): + def test_with_gdp(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -1248,7 +1200,7 @@ def test_variables_elsewhere2(self, name: str, opt_class: Type[SolverBase]): self.assertNotIn(m.z, sol) @parameterized.expand(input=_load_tests(all_solvers)) - def test_bug_1(self, name: str, opt_class: Type[SolverBase],): + def test_bug_1(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest @@ -1271,7 +1223,7 @@ def test_bug_1(self, name: str, opt_class: Type[SolverBase],): self.assertAlmostEqual(res.incumbent_objective, 3) @parameterized.expand(input=_load_tests(all_solvers)) - def test_bug_2(self, name: str, opt_class: Type[SolverBase],): + def test_bug_2(self, name: str, opt_class: Type[SolverBase]): """ This test is for a bug where an objective containing a fixed variable does not get updated properly when the variable is unfixed. From cfc78cb2a532b3239b349d2517e7870636d2d1f1 Mon Sep 17 00:00:00 2001 From: Sakshi <73687517+Sakshi21299@users.noreply.github.com> Date: Sun, 11 Feb 2024 21:08:03 -0500 Subject: [PATCH 0937/1204] Changed add_edge inputs to explicitly be variables and constraints --- pyomo/contrib/incidence_analysis/interface.py | 49 +++++-------------- 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 23178bdf14b..20b3928f979 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -933,17 +933,15 @@ def plot(self, variables=None, constraints=None, title=None, show=True): if show: fig.show() - def add_edge_to_graph(self, node0, node1): + def add_edge(self, variable, constraint): """Adds an edge between node0 and node1 in the incidence graph Parameters --------- - nodes0: VarData/ConstraintData - A node in the graph from the first bipartite set - (``bipartite=0``) - node1: VarData/ConstraintData - A node in the graph from the second bipartite set - (``bipartite=1``) + variable: VarData + A variable in the graph + constraint: ConstraintData + A constraint in the graph """ if self._incidence_graph is None: raise RuntimeError( @@ -951,34 +949,13 @@ def add_edge_to_graph(self, node0, node1): "incidence graph,\nbut no incidence graph has been cached." ) - if node0 not in ComponentSet(self._variables) and node0 not in ComponentSet( - self._constraints - ): - raise RuntimeError("%s is not a node in the incidence graph" % node0) + if variable not in self._var_index_map: + raise RuntimeError("%s is not a variable in the incidence graph" % variable) - if node1 not in ComponentSet(self._variables) and node1 not in ComponentSet( - self._constraints - ): - raise RuntimeError("%s is not a node in the incidence graph" % node1) + if constraint not in self._con_index_map: + raise RuntimeError("%s is not a constraint in the incidence graph" % constraint) - if node0 in ComponentSet(self._variables): - node0_idx = self._var_index_map[node0] + len(self._con_index_map) - if node1 in ComponentSet(self._variables): - raise RuntimeError( - "%s & %s are both variables. Cannot add an edge between two" - "variables.\nThe resulting graph won't be bipartite" - % (node0, node1) - ) - node1_idx = self._con_index_map[node1] - - if node0 in ComponentSet(self._constraints): - node0_idx = self._con_index_map[node0] - if node1 in ComponentSet(self._constraints): - raise RuntimeError( - "%s & %s are both constraints. Cannot add an edge between two" - "constraints.\nThe resulting graph won't be bipartite" - % (node0, node1) - ) - node1_idx = self._var_index_map[node1] + len(self._con_index_map) - - self._incidence_graph.add_edge(node0_idx, node1_idx) + var_id = self._var_index_map[variable] + len(self._con_index_map) + con_id = self._con_index_map[constraint] + + self._incidence_graph.add_edge(var_id, con_id) From 6348ac170f8f5f5a6dace9824a0c46306589b11b Mon Sep 17 00:00:00 2001 From: Sakshi <73687517+Sakshi21299@users.noreply.github.com> Date: Sun, 11 Feb 2024 21:08:56 -0500 Subject: [PATCH 0938/1204] Add a test for variable-constraint elimination --- .../tests/test_interface.py | 63 +++++++++++-------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/tests/test_interface.py b/pyomo/contrib/incidence_analysis/tests/test_interface.py index 7da563be28c..ec518a342c7 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_interface.py +++ b/pyomo/contrib/incidence_analysis/tests/test_interface.py @@ -1799,50 +1799,59 @@ def test_add_edge(self): m.eq3 = pyo.Constraint(expr=m.x[3] + m.x[2] + m.x[4] == 1) m.eq4 = pyo.Constraint(expr=m.x[1] + m.x[2] ** 2 == 5) - # nodes: component - # 0 : eq1 - # 1 : eq2 - # 2 : eq3 - # 3 : eq4 - # 4 : x[1] - # 5 : x[2] - # 6 : x[3] - # 7 : x[4] - igraph = IncidenceGraphInterface(m, linear_only=False) n_edges_original = igraph.n_edges - # Test if there already exists an edge between two nodes, nothing is added - igraph.add_edge_to_graph(m.eq3, m.x[4]) - n_edges_new = igraph.n_edges - self.assertEqual(n_edges_original, n_edges_new) - - igraph.add_edge_to_graph(m.x[1], m.eq3) + #Test edge is added between previously unconnectes nodes + igraph.add_edge(m.x[1], m.eq3) n_edges_new = igraph.n_edges - self.assertEqual(set(igraph._incidence_graph[2]), {6, 5, 7, 4}) + assert ComponentSet(igraph.get_adjacent_to(m.eq3)) == ComponentSet(m.x[:]) self.assertEqual(n_edges_original + 1, n_edges_new) - igraph.add_edge_to_graph(m.eq4, m.x[4]) - n_edges_new = igraph.n_edges - self.assertEqual(set(igraph._incidence_graph[3]), {4, 5, 7}) - self.assertEqual(n_edges_original + 2, n_edges_new) + #Test no edge is added if there exists a previous edge between nodes + igraph.add_edge(m.x[2], m.eq3) + n_edges2 = igraph.n_edges + self.assertEqual(n_edges_new, n_edges2) def test_add_edge_linear_igraph(self): m = pyo.ConcreteModel() m.x = pyo.Var([1, 2, 3, 4]) m.eq1 = pyo.Constraint(expr=m.x[1] + m.x[3] == 1) m.eq2 = pyo.Constraint(expr=m.x[2] + pyo.sqrt(m.x[1]) + pyo.exp(m.x[3]) == 1) - m.eq3 = pyo.Constraint(expr=m.x[4] ** 2 + m.x[1] ** 3 + m.x[2] == 1) + m.eq3 = pyo.Constraint(expr=m.x[4] ** 2 + m.x[1] ** 3 + m.x[2]**2 == 1) # Make sure error is raised when a variable is not in the igraph igraph = IncidenceGraphInterface(m, linear_only=True) - n_edges_original = igraph.n_edges - msg = "is not a node in the incidence graph" + msg = "is not a variable in the incidence graph" with self.assertRaisesRegex(RuntimeError, msg): - igraph.add_edge_to_graph(m.x[4], m.eq2) - - + igraph.add_edge(m.x[4], m.eq2) + + def test_var_elim(self): + m = pyo.ConcreteModel() + m.x = pyo.Var([1, 2, 3, 4]) + m.eq1 = pyo.Constraint(expr=m.x[1] ** 2 + m.x[2] ** 2 + m.x[3] ** 2 == 1) + m.eq2 = pyo.Constraint(expr=pyo.sqrt(m.x[1]) + pyo.exp(m.x[3]) == 1) + m.eq3 = pyo.Constraint(expr=m.x[3] + m.x[2] + m.x[4] == 1) + m.eq4 = pyo.Constraint(expr=m.x[1] == 5*m.x[2]) + + igraph = IncidenceGraphInterface(m) + #Eliminate x[1] usinf eq4 + for adj_con in igraph.get_adjacent_to(m.x[1]): + for adj_var in igraph.get_adjacent_to(m.eq4): + igraph.add_edge(adj_var, adj_con) + igraph.remove_nodes([m.x[1], m.eq4]) + + assert ComponentSet(igraph.variables) == ComponentSet([m.x[2], m.x[3], m.x[4]]) + assert ComponentSet(igraph.constraints) == ComponentSet([m.eq1, m.eq2, m.eq3]) + self.assertEqual(7, igraph.n_edges) + + assert m.x[2] in ComponentSet(igraph.get_adjacent_to(m.eq1)) + assert m.x[2] in ComponentSet(igraph.get_adjacent_to(m.eq2)) + + + + @unittest.skipUnless(networkx_available, "networkx is not available.") class TestIndexedBlock(unittest.TestCase): def test_block_data_obj(self): From b8e309da1599ba1eeb6c357e5529d58449ed5cf2 Mon Sep 17 00:00:00 2001 From: Sakshi <73687517+Sakshi21299@users.noreply.github.com> Date: Sun, 11 Feb 2024 21:10:15 -0500 Subject: [PATCH 0939/1204] run black --- pyomo/contrib/incidence_analysis/interface.py | 12 +++++---- .../tests/test_interface.py | 26 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 20b3928f979..5bf7ec71e09 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -939,9 +939,9 @@ def add_edge(self, variable, constraint): Parameters --------- variable: VarData - A variable in the graph + A variable in the graph constraint: ConstraintData - A constraint in the graph + A constraint in the graph """ if self._incidence_graph is None: raise RuntimeError( @@ -953,9 +953,11 @@ def add_edge(self, variable, constraint): raise RuntimeError("%s is not a variable in the incidence graph" % variable) if constraint not in self._con_index_map: - raise RuntimeError("%s is not a constraint in the incidence graph" % constraint) + raise RuntimeError( + "%s is not a constraint in the incidence graph" % constraint + ) - var_id = self._var_index_map[variable] + len(self._con_index_map) + var_id = self._var_index_map[variable] + len(self._con_index_map) con_id = self._con_index_map[constraint] - + self._incidence_graph.add_edge(var_id, con_id) diff --git a/pyomo/contrib/incidence_analysis/tests/test_interface.py b/pyomo/contrib/incidence_analysis/tests/test_interface.py index ec518a342c7..01c15c9c84d 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_interface.py +++ b/pyomo/contrib/incidence_analysis/tests/test_interface.py @@ -1802,13 +1802,13 @@ def test_add_edge(self): igraph = IncidenceGraphInterface(m, linear_only=False) n_edges_original = igraph.n_edges - #Test edge is added between previously unconnectes nodes + # Test edge is added between previously unconnectes nodes igraph.add_edge(m.x[1], m.eq3) n_edges_new = igraph.n_edges assert ComponentSet(igraph.get_adjacent_to(m.eq3)) == ComponentSet(m.x[:]) self.assertEqual(n_edges_original + 1, n_edges_new) - #Test no edge is added if there exists a previous edge between nodes + # Test no edge is added if there exists a previous edge between nodes igraph.add_edge(m.x[2], m.eq3) n_edges2 = igraph.n_edges self.assertEqual(n_edges_new, n_edges2) @@ -1818,7 +1818,7 @@ def test_add_edge_linear_igraph(self): m.x = pyo.Var([1, 2, 3, 4]) m.eq1 = pyo.Constraint(expr=m.x[1] + m.x[3] == 1) m.eq2 = pyo.Constraint(expr=m.x[2] + pyo.sqrt(m.x[1]) + pyo.exp(m.x[3]) == 1) - m.eq3 = pyo.Constraint(expr=m.x[4] ** 2 + m.x[1] ** 3 + m.x[2]**2 == 1) + m.eq3 = pyo.Constraint(expr=m.x[4] ** 2 + m.x[1] ** 3 + m.x[2] ** 2 == 1) # Make sure error is raised when a variable is not in the igraph igraph = IncidenceGraphInterface(m, linear_only=True) @@ -1826,32 +1826,30 @@ def test_add_edge_linear_igraph(self): msg = "is not a variable in the incidence graph" with self.assertRaisesRegex(RuntimeError, msg): igraph.add_edge(m.x[4], m.eq2) - + def test_var_elim(self): m = pyo.ConcreteModel() m.x = pyo.Var([1, 2, 3, 4]) m.eq1 = pyo.Constraint(expr=m.x[1] ** 2 + m.x[2] ** 2 + m.x[3] ** 2 == 1) m.eq2 = pyo.Constraint(expr=pyo.sqrt(m.x[1]) + pyo.exp(m.x[3]) == 1) m.eq3 = pyo.Constraint(expr=m.x[3] + m.x[2] + m.x[4] == 1) - m.eq4 = pyo.Constraint(expr=m.x[1] == 5*m.x[2]) - - igraph = IncidenceGraphInterface(m) - #Eliminate x[1] usinf eq4 + m.eq4 = pyo.Constraint(expr=m.x[1] == 5 * m.x[2]) + + igraph = IncidenceGraphInterface(m) + # Eliminate x[1] usinf eq4 for adj_con in igraph.get_adjacent_to(m.x[1]): for adj_var in igraph.get_adjacent_to(m.eq4): igraph.add_edge(adj_var, adj_con) igraph.remove_nodes([m.x[1], m.eq4]) - + assert ComponentSet(igraph.variables) == ComponentSet([m.x[2], m.x[3], m.x[4]]) assert ComponentSet(igraph.constraints) == ComponentSet([m.eq1, m.eq2, m.eq3]) self.assertEqual(7, igraph.n_edges) - + assert m.x[2] in ComponentSet(igraph.get_adjacent_to(m.eq1)) assert m.x[2] in ComponentSet(igraph.get_adjacent_to(m.eq2)) - - - - + + @unittest.skipUnless(networkx_available, "networkx is not available.") class TestIndexedBlock(unittest.TestCase): def test_block_data_obj(self): From 9a208616cc54f844a41effa42bb60f788b8ad4ff Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 12 Feb 2024 14:51:47 -0700 Subject: [PATCH 0940/1204] NFC: fix comment typo --- pyomo/core/base/set.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index f1a24b8fd03..6b850a6366e 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -2958,7 +2958,7 @@ def __init__(self, *args, **kwds): pass def __str__(self): - # Named, components should return their name e.g., Reals + # Named components should return their name e.g., Reals if self._name is not None: return self.name # Unconstructed floating components return their type From b90fb0c1b18ac14d15121c857f344ac1875f32a7 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 12 Feb 2024 17:40:35 -0700 Subject: [PATCH 0941/1204] NFC: add docstrings, reformat long lines --- pyomo/contrib/fbbt/interval.py | 197 +++++++++++++++++++++------------ 1 file changed, 129 insertions(+), 68 deletions(-) diff --git a/pyomo/contrib/fbbt/interval.py b/pyomo/contrib/fbbt/interval.py index 53c236850d9..339d547b7d4 100644 --- a/pyomo/contrib/fbbt/interval.py +++ b/pyomo/contrib/fbbt/interval.py @@ -58,6 +58,14 @@ def Bool(val): def ineq(xl, xu, yl, yu): + """Compute the "bounds" on an InequalityExpression + + Note this is *not* performing interval arithmetic: we are + calculating the "bounds" on a RelationalExpression (whose domain is + {True, False}). Therefore we are determining if `x` can be less + than `y`, `x` can not be less than `y`, or both. + + """ ans = [] if yl < xu: ans.append(_false) @@ -70,6 +78,14 @@ def ineq(xl, xu, yl, yu): def eq(xl, xu, yl, yu): + """Compute the "bounds" on an EqualityExpression + + Note this is *not* performing interval arithmetic: we are + calculating the "bounds" on a RelationalExpression (whose domain is + {True, False}). Therefore we are determining if `x` can be equal to + `y`, `x` can not be equal to `y`, or both. + + """ ans = [] if xl != xu or yl != yu or xl != yl: ans.append(_false) @@ -82,6 +98,14 @@ def eq(xl, xu, yl, yu): def ranged(xl, xu, yl, yu, zl, zu): + """Compute the "bounds" on a RangedExpression + + Note this is *not* performing interval arithmetic: we are + calculating the "bounds" on a RelationalExpression (whose domain is + {True, False}). Therefore we are determining if `y` can be between + `z` and `z`, `y` can be outside the range `x` and `z`, or both. + + """ lb = ineq(xl, xu, yl, yu) ub = ineq(yl, yu, zl, zu) ans = [] @@ -128,12 +152,18 @@ def mul(xl, xu, yl, yu): def inv(xl, xu, feasibility_tol): - """ - The case where xl is very slightly positive but should be very slightly negative (or xu is very slightly negative - but should be very slightly positive) should not be an issue. Suppose xu is 2 and xl is 1e-15 but should be -1e-15. - The bounds obtained from this function will be [0.5, 1e15] or [0.5, inf), depending on the value of - feasibility_tol. The true bounds are (-inf, -1e15] U [0.5, inf), where U is union. The exclusion of (-inf, -1e15] - should be acceptable. Additionally, it very important to return a non-negative interval when xl is non-negative. + """Compute the inverse of an interval + + The case where xl is very slightly positive but should be very + slightly negative (or xu is very slightly negative but should be + very slightly positive) should not be an issue. Suppose xu is 2 and + xl is 1e-15 but should be -1e-15. The bounds obtained from this + function will be [0.5, 1e15] or [0.5, inf), depending on the value + of feasibility_tol. The true bounds are (-inf, -1e15] U [0.5, inf), + where U is union. The exclusion of (-inf, -1e15] should be + acceptable. Additionally, it very important to return a non-negative + interval when xl is non-negative. + """ if xu - xl <= -feasibility_tol: raise InfeasibleConstraintException( @@ -178,9 +208,8 @@ def power(xl, xu, yl, yu, feasibility_tol): Compute bounds on x**y. """ if xl > 0: - """ - If x is always positive, things are simple. We only need to worry about the sign of y. - """ + # If x is always positive, things are simple. We only need to + # worry about the sign of y. if yl < 0 < yu: lb = min(xu**yl, xl**yu) ub = max(xl**yl, xu**yu) @@ -270,14 +299,15 @@ def power(xl, xu, yl, yu, feasibility_tol): def _inverse_power1(zl, zu, yl, yu, orig_xl, orig_xu, feasibility_tol): - """ - z = x**y => compute bounds on x. + """z = x**y => compute bounds on x. First, start by computing bounds on x with x = exp(ln(z) / y) - However, if y is an integer, then x can be negative, so there are several special cases. See the docs below. + However, if y is an integer, then x can be negative, so there are + several special cases. See the docs below. + """ xl, xu = log(zl, zu) xl, xu = div(xl, xu, yl, yu, feasibility_tol) @@ -288,22 +318,31 @@ def _inverse_power1(zl, zu, yl, yu, orig_xl, orig_xu, feasibility_tol): y = yl if y == 0: # Anything to the power of 0 is 1, so if y is 0, then x can be anything - # (assuming zl <= 1 <= zu, which is enforced when traversing the tree in the other direction) + # (assuming zl <= 1 <= zu, which is enforced when traversing + # the tree in the other direction) xl = -inf xu = inf elif y % 2 == 0: - """ - if y is even, then there are two primary cases (note that it is much easier to walk through these - while looking at plots): + """if y is even, then there are two primary cases (note that it is much + easier to walk through these while looking at plots): + case 1: y is positive - x**y is convex, positive, and symmetric. The bounds on x depend on the lower bound of z. If zl <= 0, - then xl should simply be -xu. However, if zl > 0, then we may be able to say something better. For - example, if the original lower bound on x is positive, then we can keep xl computed from - x = exp(ln(z) / y). Furthermore, if the original lower bound on x is larger than -xl computed from - x = exp(ln(z) / y), then we can still keep the xl computed from x = exp(ln(z) / y). Similar logic - applies to the upper bound of x. + + x**y is convex, positive, and symmetric. The bounds on x + depend on the lower bound of z. If zl <= 0, then xl + should simply be -xu. However, if zl > 0, then we may be + able to say something better. For example, if the + original lower bound on x is positive, then we can keep + xl computed from x = exp(ln(z) / y). Furthermore, if the + original lower bound on x is larger than -xl computed + from x = exp(ln(z) / y), then we can still keep the xl + computed from x = exp(ln(z) / y). Similar logic applies + to the upper bound of x. + case 2: y is negative + The ideas are similar to case 1. + """ if zu + feasibility_tol < 0: raise InfeasibleConstraintException( @@ -351,16 +390,25 @@ def _inverse_power1(zl, zu, yl, yu, orig_xl, orig_xu, feasibility_tol): xl = _xl xu = _xu else: # y % 2 == 1 - """ - y is odd. + """y is odd. + Case 1: y is positive - x**y is monotonically increasing. If y is positive, then we can can compute the bounds on x using - x = z**(1/y) and the signs on xl and xu depend on the signs of zl and zu. + + x**y is monotonically increasing. If y is positive, then + we can can compute the bounds on x using x = z**(1/y) + and the signs on xl and xu depend on the signs of zl and + zu. + Case 2: y is negative - Again, this is easier to visualize with a plot. x**y approaches zero when x approaches -inf or inf. - Thus, if zl < 0 < zu, then no bounds can be inferred for x. If z is positive (zl >=0 ) then we can - use the bounds computed from x = exp(ln(z) / y). If z is negative (zu <= 0), then we live in the - bottom left quadrant, xl depends on zu, and xu depends on zl. + + Again, this is easier to visualize with a plot. x**y + approaches zero when x approaches -inf or inf. Thus, if + zl < 0 < zu, then no bounds can be inferred for x. If z + is positive (zl >=0 ) then we can use the bounds + computed from x = exp(ln(z) / y). If z is negative (zu + <= 0), then we live in the bottom left quadrant, xl + depends on zu, and xu depends on zl. + """ if y > 0: xl = abs(zl) ** (1.0 / y) @@ -387,12 +435,13 @@ def _inverse_power1(zl, zu, yl, yu, orig_xl, orig_xu, feasibility_tol): def _inverse_power2(zl, zu, xl, xu, feasiblity_tol): - """ - z = x**y => compute bounds on y + """z = x**y => compute bounds on y y = ln(z) / ln(x) - This function assumes the exponent can be fractional, so x must be positive. This method should not be called - if the exponent is an integer. + This function assumes the exponent can be fractional, so x must be + positive. This method should not be called if the exponent is an + integer. + """ if xu <= 0: raise IntervalException( @@ -480,10 +529,12 @@ def sin(xl, xu): ub: float """ - # if there is a minimum between xl and xu, then the lower bound is -1. Minimums occur at 2*pi*n - pi/2 - # find the minimum value of i such that 2*pi*i - pi/2 >= xl. Then round i up. If 2*pi*i - pi/2 is still less - # than or equal to xu, then there is a minimum between xl and xu. Thus the lb is -1. Otherwise, the minimum - # occurs at either xl or xu + # if there is a minimum between xl and xu, then the lower bound is + # -1. Minimums occur at 2*pi*n - pi/2 find the minimum value of i + # such that 2*pi*i - pi/2 >= xl. Then round i up. If 2*pi*i - pi/2 + # is still less than or equal to xu, then there is a minimum between + # xl and xu. Thus the lb is -1. Otherwise, the minimum occurs at + # either xl or xu if xl <= -inf or xu >= inf: return -1, 1 pi = math.pi @@ -495,7 +546,8 @@ def sin(xl, xu): else: lb = min(math.sin(xl), math.sin(xu)) - # if there is a maximum between xl and xu, then the upper bound is 1. Maximums occur at 2*pi*n + pi/2 + # if there is a maximum between xl and xu, then the upper bound is + # 1. Maximums occur at 2*pi*n + pi/2 i = (xu - pi / 2) / (2 * pi) i = math.floor(i) x_at_max = 2 * pi * i + pi / 2 @@ -521,10 +573,12 @@ def cos(xl, xu): ub: float """ - # if there is a minimum between xl and xu, then the lower bound is -1. Minimums occur at 2*pi*n - pi - # find the minimum value of i such that 2*pi*i - pi >= xl. Then round i up. If 2*pi*i - pi/2 is still less - # than or equal to xu, then there is a minimum between xl and xu. Thus the lb is -1. Otherwise, the minimum - # occurs at either xl or xu + # if there is a minimum between xl and xu, then the lower bound is + # -1. Minimums occur at 2*pi*n - pi find the minimum value of i such + # that 2*pi*i - pi >= xl. Then round i up. If 2*pi*i - pi/2 is still + # less than or equal to xu, then there is a minimum between xl and + # xu. Thus the lb is -1. Otherwise, the minimum occurs at either xl + # or xu if xl <= -inf or xu >= inf: return -1, 1 pi = math.pi @@ -536,7 +590,8 @@ def cos(xl, xu): else: lb = min(math.cos(xl), math.cos(xu)) - # if there is a maximum between xl and xu, then the upper bound is 1. Maximums occur at 2*pi*n + # if there is a maximum between xl and xu, then the upper bound is + # 1. Maximums occur at 2*pi*n i = (xu) / (2 * pi) i = math.floor(i) x_at_max = 2 * pi * i @@ -562,10 +617,12 @@ def tan(xl, xu): ub: float """ - # tan goes to -inf and inf at every pi*i + pi/2 (integer i). If one of these values is between xl and xu, then - # the lb is -inf and the ub is inf. Otherwise the minimum occurs at xl and the maximum occurs at xu. - # find the minimum value of i such that pi*i + pi/2 >= xl. Then round i up. If pi*i + pi/2 is still less - # than or equal to xu, then there is an undefined point between xl and xu. + # tan goes to -inf and inf at every pi*i + pi/2 (integer i). If one + # of these values is between xl and xu, then the lb is -inf and the + # ub is inf. Otherwise the minimum occurs at xl and the maximum + # occurs at xu. find the minimum value of i such that pi*i + pi/2 + # >= xl. Then round i up. If pi*i + pi/2 is still less than or equal + # to xu, then there is an undefined point between xl and xu. if xl <= -inf or xu >= inf: return -inf, inf pi = math.pi @@ -609,12 +666,12 @@ def asin(xl, xu, yl, yu, feasibility_tol): if yl <= -inf: lb = yl elif xl <= math.sin(yl) <= xu: - # if sin(yl) >= xl then yl satisfies the bounds on x, and the lower bound of y cannot be improved + # if sin(yl) >= xl then yl satisfies the bounds on x, and the + # lower bound of y cannot be improved lb = yl elif math.sin(yl) < xl: - """ - we can only push yl up from its current value to the next lowest value such that xl = sin(y). In other words, - we need to + """we can only push yl up from its current value to the next lowest + value such that xl = sin(y). In other words, we need to min y s.t. @@ -622,19 +679,21 @@ def asin(xl, xu, yl, yu, feasibility_tol): y >= yl globally. + """ - # first find the next minimum of x = sin(y). Minimums occur at y = 2*pi*n - pi/2 for integer n. + # first find the next minimum of x = sin(y). Minimums occur at y + # = 2*pi*n - pi/2 for integer n. i = (yl + pi / 2) / (2 * pi) i1 = math.floor(i) i2 = math.ceil(i) i1 = 2 * pi * i1 - pi / 2 i2 = 2 * pi * i2 - pi / 2 - # now find the next value of y such that xl = sin(y). This can be computed by a distance from the minimum (i). + # now find the next value of y such that xl = sin(y). This can + # be computed by a distance from the minimum (i). y_tmp = math.asin(xl) # this will give me a value between -pi/2 and pi/2 - dist = y_tmp - ( - -pi / 2 - ) # this is the distance between the minimum of the sin function and a value that - # satisfies xl = sin(y) + dist = y_tmp - (-pi / 2) + # this is the distance between the minimum of the sin function + # and a value that satisfies xl = sin(y) lb1 = i1 + dist lb2 = i2 + dist if lb1 >= yl - feasibility_tol: @@ -722,12 +781,12 @@ def acos(xl, xu, yl, yu, feasibility_tol): if yl <= -inf: lb = yl elif xl <= math.cos(yl) <= xu: - # if xl <= cos(yl) <= xu then yl satisfies the bounds on x, and the lower bound of y cannot be improved + # if xl <= cos(yl) <= xu then yl satisfies the bounds on x, and + # the lower bound of y cannot be improved lb = yl elif math.cos(yl) < xl: - """ - we can only push yl up from its current value to the next lowest value such that xl = cos(y). In other words, - we need to + """we can only push yl up from its current value to the next lowest + value such that xl = cos(y). In other words, we need to min y s.t. @@ -735,19 +794,21 @@ def acos(xl, xu, yl, yu, feasibility_tol): y >= yl globally. + """ - # first find the next minimum of x = cos(y). Minimums occur at y = 2*pi*n - pi for integer n. + # first find the next minimum of x = cos(y). Minimums occur at y + # = 2*pi*n - pi for integer n. i = (yl + pi) / (2 * pi) i1 = math.floor(i) i2 = math.ceil(i) i1 = 2 * pi * i1 - pi i2 = 2 * pi * i2 - pi - # now find the next value of y such that xl = cos(y). This can be computed by a distance from the minimum (i). + # now find the next value of y such that xl = cos(y). This can + # be computed by a distance from the minimum (i). y_tmp = math.acos(xl) # this will give me a value between 0 and pi - dist = ( - pi - y_tmp - ) # this is the distance between the minimum of the sin function and a value that - # satisfies xl = sin(y) + dist = pi - y_tmp + # this is the distance between the minimum of the sin function + # and a value that satisfies xl = sin(y) lb1 = i1 + dist lb2 = i2 + dist if lb1 >= yl - feasibility_tol: From 3d14132f02340a928c63a78c2c23d2358968f140 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 12 Feb 2024 17:42:41 -0700 Subject: [PATCH 0942/1204] Make bool_ private; rename Bool -> BoolFlag so usage doesn't look like a bool --- pyomo/contrib/fbbt/expression_bounds_walker.py | 6 +++--- pyomo/contrib/fbbt/interval.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/fbbt/expression_bounds_walker.py b/pyomo/contrib/fbbt/expression_bounds_walker.py index a32d138c52b..340af94c83e 100644 --- a/pyomo/contrib/fbbt/expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/expression_bounds_walker.py @@ -13,7 +13,7 @@ from math import pi from pyomo.common.collections import ComponentMap from pyomo.contrib.fbbt.interval import ( - Bool, + BoolFlag, eq, ineq, ranged, @@ -81,7 +81,7 @@ def _before_native_numeric(visitor, child): @staticmethod def _before_native_logical(visitor, child): - return False, (Bool(child), Bool(child)) + return False, (BoolFlag(child), BoolFlag(child)) @staticmethod def _before_var(visitor, child): @@ -266,7 +266,7 @@ def unexpected_expression_type(self, visitor, node, *args): if isinstance(node, NumericExpression): ans = -inf, inf elif isinstance(node, BooleanExpression): - ans = Bool(False), Bool(True) + ans = BoolFlag(False), BoolFlag(True) else: super().unexpected_expression_type(visitor, node, *args) logger.warning( diff --git a/pyomo/contrib/fbbt/interval.py b/pyomo/contrib/fbbt/interval.py index 339d547b7d4..8bebe128988 100644 --- a/pyomo/contrib/fbbt/interval.py +++ b/pyomo/contrib/fbbt/interval.py @@ -17,7 +17,7 @@ inf = float('inf') -class bool_(object): +class _bool_flag(object): def __init__(self, val): self._val = val @@ -49,11 +49,11 @@ def __repr__(self): __rpow__ = _op -_true = bool_(True) -_false = bool_(False) +_true = _bool_flag(True) +_false = _bool_flag(False) -def Bool(val): +def BoolFlag(val): return _true if val else _false From dac2f31c38d8be12f629f4fb5e322d564579696b Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 13 Feb 2024 09:32:54 -0500 Subject: [PATCH 0943/1204] fix lbb solve_data bug --- pyomo/contrib/gdpopt/branch_and_bound.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/gdpopt/branch_and_bound.py b/pyomo/contrib/gdpopt/branch_and_bound.py index 26dc2b5f2eb..645e3564a88 100644 --- a/pyomo/contrib/gdpopt/branch_and_bound.py +++ b/pyomo/contrib/gdpopt/branch_and_bound.py @@ -230,12 +230,12 @@ def _solve_gdp(self, model, config): no_feasible_soln = float('inf') self.LB = ( node_data.obj_lb - if solve_data.objective_sense == minimize + if self.objective_sense == minimize else -no_feasible_soln ) self.UB = ( no_feasible_soln - if solve_data.objective_sense == minimize + if self.objective_sense == minimize else -node_data.obj_lb ) config.logger.info( From e893381c55a29c6b1c4a2deaece4808651638417 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 13 Feb 2024 08:36:48 -0700 Subject: [PATCH 0944/1204] Updating OnlineDocs examples to reflext anonymous sets --- doc/OnlineDocs/src/data/table2.txt | 9 ++-- doc/OnlineDocs/src/data/table3.txt | 9 ++-- doc/OnlineDocs/src/data/table3.ul.txt | 9 ++-- .../src/dataportal/param_initialization.txt | 14 ++---- .../src/dataportal/set_initialization.txt | 9 ++-- doc/OnlineDocs/src/kernel/examples.txt | 45 +++++-------------- 6 files changed, 26 insertions(+), 69 deletions(-) diff --git a/doc/OnlineDocs/src/data/table2.txt b/doc/OnlineDocs/src/data/table2.txt index 60eb55aab4a..a710b6b6042 100644 --- a/doc/OnlineDocs/src/data/table2.txt +++ b/doc/OnlineDocs/src/data/table2.txt @@ -1,13 +1,10 @@ -3 Set Declarations +2 Set Declarations A : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {'A1', 'A2', 'A3'} B : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {'B1', 'B2', 'B3'} - N_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*B : 9 : {('A1', 'B1'), ('A1', 'B2'), ('A1', 'B3'), ('A2', 'B1'), ('A2', 'B2'), ('A2', 'B3'), ('A3', 'B1'), ('A3', 'B2'), ('A3', 'B3')} 2 Param Declarations M : Size=3, Index=A, Domain=Any, Default=None, Mutable=False @@ -15,10 +12,10 @@ A1 : 4.3 A2 : 4.4 A3 : 4.5 - N : Size=3, Index=N_index, Domain=Any, Default=None, Mutable=False + N : Size=3, Index=A*B, Domain=Any, Default=None, Mutable=False Key : Value ('A1', 'B1') : 5.3 ('A2', 'B2') : 5.4 ('A3', 'B3') : 5.5 -5 Declarations: A B M N_index N +4 Declarations: A B M N diff --git a/doc/OnlineDocs/src/data/table3.txt b/doc/OnlineDocs/src/data/table3.txt index cb5e63b30d4..c0c61cd5a5b 100644 --- a/doc/OnlineDocs/src/data/table3.txt +++ b/doc/OnlineDocs/src/data/table3.txt @@ -1,13 +1,10 @@ -4 Set Declarations +3 Set Declarations A : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {'A1', 'A2', 'A3'} B : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {'B1', 'B2', 'B3'} - N_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*B : 9 : {('A1', 'B1'), ('A1', 'B2'), ('A1', 'B3'), ('A2', 'B1'), ('A2', 'B2'), ('A2', 'B3'), ('A3', 'B1'), ('A3', 'B2'), ('A3', 'B3')} Z : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 2 : Any : 3 : {('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')} @@ -18,10 +15,10 @@ A1 : 4.3 A2 : 4.4 A3 : 4.5 - N : Size=3, Index=N_index, Domain=Any, Default=None, Mutable=False + N : Size=3, Index=A*B, Domain=Any, Default=None, Mutable=False Key : Value ('A1', 'B1') : 5.3 ('A2', 'B2') : 5.4 ('A3', 'B3') : 5.5 -6 Declarations: A B Z M N_index N +5 Declarations: A B Z M N diff --git a/doc/OnlineDocs/src/data/table3.ul.txt b/doc/OnlineDocs/src/data/table3.ul.txt index cb5e63b30d4..c0c61cd5a5b 100644 --- a/doc/OnlineDocs/src/data/table3.ul.txt +++ b/doc/OnlineDocs/src/data/table3.ul.txt @@ -1,13 +1,10 @@ -4 Set Declarations +3 Set Declarations A : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {'A1', 'A2', 'A3'} B : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {'B1', 'B2', 'B3'} - N_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*B : 9 : {('A1', 'B1'), ('A1', 'B2'), ('A1', 'B3'), ('A2', 'B1'), ('A2', 'B2'), ('A2', 'B3'), ('A3', 'B1'), ('A3', 'B2'), ('A3', 'B3')} Z : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 2 : Any : 3 : {('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3')} @@ -18,10 +15,10 @@ A1 : 4.3 A2 : 4.4 A3 : 4.5 - N : Size=3, Index=N_index, Domain=Any, Default=None, Mutable=False + N : Size=3, Index=A*B, Domain=Any, Default=None, Mutable=False Key : Value ('A1', 'B1') : 5.3 ('A2', 'B2') : 5.4 ('A3', 'B3') : 5.5 -6 Declarations: A B Z M N_index N +5 Declarations: A B Z M N diff --git a/doc/OnlineDocs/src/dataportal/param_initialization.txt b/doc/OnlineDocs/src/dataportal/param_initialization.txt index fec8a06a84a..49ea105f120 100644 --- a/doc/OnlineDocs/src/dataportal/param_initialization.txt +++ b/doc/OnlineDocs/src/dataportal/param_initialization.txt @@ -1,24 +1,16 @@ -2 Set Declarations - b_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {1, 2, 3} - c_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {1, 2, 3} - 3 Param Declarations a : Size=1, Index=None, Domain=Any, Default=None, Mutable=False Key : Value None : 1.1 - b : Size=3, Index=b_index, Domain=Any, Default=None, Mutable=False + b : Size=3, Index={1, 2, 3}, Domain=Any, Default=None, Mutable=False Key : Value 1 : 1 2 : 2 3 : 3 - c : Size=3, Index=c_index, Domain=Any, Default=None, Mutable=False + c : Size=3, Index={1, 2, 3}, Domain=Any, Default=None, Mutable=False Key : Value 1 : 1 2 : 2 3 : 3 -5 Declarations: a b_index b c_index c +3 Declarations: a b c diff --git a/doc/OnlineDocs/src/dataportal/set_initialization.txt b/doc/OnlineDocs/src/dataportal/set_initialization.txt index c6be448eba9..3c2960ce4ef 100644 --- a/doc/OnlineDocs/src/dataportal/set_initialization.txt +++ b/doc/OnlineDocs/src/dataportal/set_initialization.txt @@ -1,6 +1,6 @@ WARNING: Initializing ordered Set B with a fundamentally unordered data source (type: set). This WILL potentially lead to nondeterministic behavior in Pyomo -9 Set Declarations +8 Set Declarations A : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {2, 3, 5} @@ -22,13 +22,10 @@ WARNING: Initializing ordered Set B with a fundamentally unordered data source G : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {2, 3, 5} - H : Size=3, Index=H_index, Ordered=Insertion + H : Size=3, Index={2, 3, 4}, Ordered=Insertion Key : Dimen : Domain : Size : Members 2 : 1 : Any : 3 : {1, 3, 5} 3 : 1 : Any : 3 : {2, 4, 6} 4 : 1 : Any : 3 : {3, 5, 7} - H_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {2, 3, 4} -9 Declarations: A B C D E F G H_index H +8 Declarations: A B C D E F G H diff --git a/doc/OnlineDocs/src/kernel/examples.txt b/doc/OnlineDocs/src/kernel/examples.txt index e85c64efd86..8ba072d28b1 100644 --- a/doc/OnlineDocs/src/kernel/examples.txt +++ b/doc/OnlineDocs/src/kernel/examples.txt @@ -1,22 +1,7 @@ -6 Set Declarations - cd_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : s*q : 6 : {(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3)} - cl_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {1, 2, 3} - ol_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {1, 2, 3} +1 Set Declarations s : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 2 : {1, 2} - sd_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 2 : {1, 2} - vl_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {1, 2, 3} 1 RangeSet Declarations q : Dimen=1, Size=3, Bounds=(1, 3) @@ -43,7 +28,7 @@ Key : Lower : Value : Upper : Fixed : Stale : Domain 1 : None : None : 9 : False : True : Reals 2 : None : None : 9 : False : True : Reals - vl : Size=3, Index=vl_index + vl : Size=3, Index={1, 2, 3} Key : Lower : Value : Upper : Fixed : Stale : Domain 1 : 1 : None : None : False : True : Reals 2 : 2 : None : None : False : True : Reals @@ -66,7 +51,7 @@ Key : Active : Sense : Expression 1 : True : minimize : - vd[1] 2 : True : minimize : - vd[2] - ol : Size=3, Index=ol_index, Active=True + ol : Size=3, Index={1, 2, 3}, Active=True Key : Active : Sense : Expression 1 : True : minimize : - vl[1] 2 : True : minimize : - vl[2] @@ -76,7 +61,7 @@ c : Size=1, Index=None, Active=True Key : Lower : Body : Upper : Active None : -Inf : vd[1] + vd[2] : 9.0 : True - cd : Size=6, Index=cd_index, Active=True + cd : Size=6, Index=s*q, Active=True Key : Lower : Body : Upper : Active (1, 1) : 1.0 : vd[1] : 1.0 : True (1, 2) : 2.0 : vd[1] : 2.0 : True @@ -84,14 +69,14 @@ (2, 1) : 1.0 : vd[2] : 1.0 : True (2, 2) : 2.0 : vd[2] : 2.0 : True (2, 3) : 3.0 : vd[2] : 3.0 : True - cl : Size=3, Index=cl_index, Active=True + cl : Size=3, Index={1, 2, 3}, Active=True Key : Lower : Body : Upper : Active 1 : -5.0 : vl[1] - v : 5.0 : True 2 : -5.0 : vl[2] - v : 5.0 : True 3 : -5.0 : vl[3] - v : 5.0 : True 3 SOSConstraint Declarations - sd : Size=2 Index= sd_index + sd : Size=2 Index= OrderedScalarSet 1 Type=1 Weight : Variable @@ -119,16 +104,8 @@ b : Size=1, Index=None, Active=True 0 Declarations: pw : Size=1, Index=None, Active=True - 2 Set Declarations - SOS2_constraint_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {1, 2, 3} - SOS2_y_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 4 : {0, 1, 2, 3} - 1 Var Declarations - SOS2_y : Size=4, Index=pw.SOS2_y_index + SOS2_y : Size=4, Index={0, 1, 2, 3} Key : Lower : Value : Upper : Fixed : Stale : Domain 0 : 0 : None : None : False : True : NonNegativeReals 1 : 0 : None : None : False : True : NonNegativeReals @@ -136,7 +113,7 @@ 3 : 0 : None : None : False : True : NonNegativeReals 1 Constraint Declarations - SOS2_constraint : Size=3, Index=pw.SOS2_constraint_index, Active=True + SOS2_constraint : Size=3, Index={1, 2, 3}, Active=True Key : Lower : Body : Upper : Active 1 : 0.0 : v - (pw.SOS2_y[0] + 2*pw.SOS2_y[1] + 3*pw.SOS2_y[2] + 4*pw.SOS2_y[3]) : 0.0 : True 2 : 0.0 : f - (pw.SOS2_y[0] + 2*pw.SOS2_y[1] + pw.SOS2_y[2] + 2*pw.SOS2_y[3]) : 0.0 : True @@ -151,13 +128,13 @@ 3 : pw.SOS2_y[2] 4 : pw.SOS2_y[3] - 5 Declarations: SOS2_y_index SOS2_y SOS2_constraint_index SOS2_constraint SOS2_sosconstraint + 3 Declarations: SOS2_y SOS2_constraint SOS2_sosconstraint 1 Suffix Declarations dual : Direction=IMPORT, Datatype=FLOAT Key : Value -27 Declarations: b s q p pd v vd vl_index vl c cd_index cd cl_index cl e ed o od ol_index ol sos1 sos2 sd_index sd dual f pw +22 Declarations: b s q p pd v vd vl c cd cl e ed o od ol sos1 sos2 sd dual f pw : block(active=True, ctype=IBlock) - b: block(active=True, ctype=IBlock) - p: parameter(active=True, value=0) @@ -231,4 +208,4 @@ - pw.c[2]: linear_constraint(active=True, expr=pw.v[0] + pw.v[1] + pw.v[2] + pw.v[3] == 1) - pw.s: sos(active=True, level=2, entries=['(pw.v[0],1)', '(pw.v[1],2)', '(pw.v[2],3)', '(pw.v[3],4)']) Memory: 1.9 KB -Memory: 9.5 KB +Memory: 9.7 KB From e8ba13e43dc16ca829b8ecda15f1dc7ce8635b19 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 13 Feb 2024 09:12:24 -0700 Subject: [PATCH 0945/1204] Minor update to tests --- pyomo/repn/tests/test_util.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyomo/repn/tests/test_util.py b/pyomo/repn/tests/test_util.py index cce10e58334..ac3f7e62791 100644 --- a/pyomo/repn/tests/test_util.py +++ b/pyomo/repn/tests/test_util.py @@ -699,6 +699,7 @@ def test_ExitNodeDispatcher_registration(self): self.assertEqual(end[node.__class__, 3, 4, 5, 6](None, node, *node.args), 6) self.assertEqual(len(end), 7) + # We don't cache etypes with more than 3 arguments self.assertNotIn((SumExpression, 3, 4, 5, 6), end) class NewProductExpression(ProductExpression): @@ -718,6 +719,7 @@ class UnknownExpression(NumericExpression): ): end[node.__class__](None, node, *node.args) self.assertEqual(len(end), 9) + self.assertIn(UnknownExpression, end) node = UnknownExpression((6, 7)) with self.assertRaisesRegex( @@ -725,6 +727,8 @@ class UnknownExpression(NumericExpression): ): end[node.__class__, 6, 7](None, node, *node.args) self.assertEqual(len(end), 10) + self.assertIn((UnknownExpression, 6, 7), end) + def test_BeforeChildDispatcher_registration(self): class BeforeChildDispatcherTester(BeforeChildDispatcher): From 94d131fca8aba4ec45271f51dcd12722025e97a5 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 13 Feb 2024 09:16:15 -0700 Subject: [PATCH 0946/1204] NFC: apply black --- pyomo/repn/tests/test_util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/repn/tests/test_util.py b/pyomo/repn/tests/test_util.py index ac3f7e62791..c4902a7064d 100644 --- a/pyomo/repn/tests/test_util.py +++ b/pyomo/repn/tests/test_util.py @@ -729,7 +729,6 @@ class UnknownExpression(NumericExpression): self.assertEqual(len(end), 10) self.assertIn((UnknownExpression, 6, 7), end) - def test_BeforeChildDispatcher_registration(self): class BeforeChildDispatcherTester(BeforeChildDispatcher): @staticmethod From 6408d44daa677dc2a62b6216b4efecd162341655 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 13 Feb 2024 09:27:04 -0700 Subject: [PATCH 0947/1204] Updating an additional baseline (it is only tested with pyutilib) --- .../src/dataportal/dataportal_tab.txt | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/doc/OnlineDocs/src/dataportal/dataportal_tab.txt b/doc/OnlineDocs/src/dataportal/dataportal_tab.txt index 2e507971157..a23c63d90c9 100644 --- a/doc/OnlineDocs/src/dataportal/dataportal_tab.txt +++ b/doc/OnlineDocs/src/dataportal/dataportal_tab.txt @@ -85,19 +85,16 @@ A3 : 4.5 2 Declarations: A w -3 Set Declarations +2 Set Declarations A : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {'A1', 'A2', 'A3'} I : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 4 : {'I1', 'I2', 'I3', 'I4'} - u_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : I*A : 12 : {('I1', 'A1'), ('I1', 'A2'), ('I1', 'A3'), ('I2', 'A1'), ('I2', 'A2'), ('I2', 'A3'), ('I3', 'A1'), ('I3', 'A2'), ('I3', 'A3'), ('I4', 'A1'), ('I4', 'A2'), ('I4', 'A3')} 1 Param Declarations - u : Size=12, Index=u_index, Domain=Any, Default=None, Mutable=False + u : Size=12, Index=I*A, Domain=Any, Default=None, Mutable=False Key : Value ('I1', 'A1') : 1.3 ('I1', 'A2') : 2.3 @@ -112,20 +109,17 @@ ('I4', 'A2') : 2.6 ('I4', 'A3') : 3.6 -4 Declarations: A I u_index u -3 Set Declarations +3 Declarations: A I u +2 Set Declarations A : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 3 : {'A1', 'A2', 'A3'} I : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 4 : {'I1', 'I2', 'I3', 'I4'} - t_index : Size=1, Index=None, Ordered=True - Key : Dimen : Domain : Size : Members - None : 2 : A*I : 12 : {('A1', 'I1'), ('A1', 'I2'), ('A1', 'I3'), ('A1', 'I4'), ('A2', 'I1'), ('A2', 'I2'), ('A2', 'I3'), ('A2', 'I4'), ('A3', 'I1'), ('A3', 'I2'), ('A3', 'I3'), ('A3', 'I4')} 1 Param Declarations - t : Size=12, Index=t_index, Domain=Any, Default=None, Mutable=False + t : Size=12, Index=A*I, Domain=Any, Default=None, Mutable=False Key : Value ('A1', 'I1') : 1.3 ('A1', 'I2') : 1.4 @@ -140,7 +134,7 @@ ('A3', 'I3') : 3.5 ('A3', 'I4') : 3.6 -4 Declarations: A I t_index t +3 Declarations: A I t 1 Set Declarations A : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members @@ -185,13 +179,9 @@ None : 1 : Any : 3 : {'A1', 'A2', 'A3'} 1 Declarations: A -1 Set Declarations - y_index : Size=1, Index=None, Ordered=Insertion - Key : Dimen : Domain : Size : Members - None : 1 : Any : 3 : {'A1', 'A2', 'A3'} 2 Param Declarations - y : Size=3, Index=y_index, Domain=Any, Default=None, Mutable=False + y : Size=3, Index={A1, A2, A3}, Domain=Any, Default=None, Mutable=False Key : Value A1 : 3.3 A2 : 3.4 @@ -200,7 +190,7 @@ Key : Value None : 1.1 -3 Declarations: z y_index y +2 Declarations: z y ['A1', 'A2', 'A3'] 1.1 A1 3.3 From 5cc5e621694b05360fd93fbe98835a4c963e08f0 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 13 Feb 2024 10:44:57 -0700 Subject: [PATCH 0948/1204] Black --- pyomo/gdp/tests/test_mbigm.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pyomo/gdp/tests/test_mbigm.py b/pyomo/gdp/tests/test_mbigm.py index 33e8781ac63..51230bab075 100644 --- a/pyomo/gdp/tests/test_mbigm.py +++ b/pyomo/gdp/tests/test_mbigm.py @@ -360,8 +360,9 @@ def test_local_var_suffix_ignored(self): m.d1.LocalVars[m.d1] = m.y mbigm = TransformationFactory('gdp.mbigm') - mbigm.apply_to(m, reduce_bound_constraints=True, - only_mbigm_bound_constraints=True) + mbigm.apply_to( + m, reduce_bound_constraints=True, only_mbigm_bound_constraints=True + ) cons = mbigm.get_transformed_constraints(m.d1.x1_bounds) self.check_pretty_bound_constraints( @@ -382,9 +383,11 @@ def test_local_var_suffix_ignored(self): cons = mbigm.get_transformed_constraints(m.d1.another_thing) self.assertEqual(len(cons), 2) self.check_pretty_bound_constraints( - cons[0], m.y, {m.d1: 3, m.d2: 2, m.d3: 2}, lb=True) + cons[0], m.y, {m.d1: 3, m.d2: 2, m.d3: 2}, lb=True + ) self.check_pretty_bound_constraints( - cons[1], m.y, {m.d1: 3, m.d2: 5, m.d3: 5}, lb=False) + cons[1], m.y, {m.d1: 3, m.d2: 5, m.d3: 5}, lb=False + ) def test_pickle_transformed_model(self): m = self.make_model() From a77a19b5302544aad90b457307f1a5f650583402 Mon Sep 17 00:00:00 2001 From: Zedong Date: Tue, 13 Feb 2024 13:24:13 -0500 Subject: [PATCH 0949/1204] Update pyomo/contrib/mindtpy/util.py Co-authored-by: Bethany Nicholson --- pyomo/contrib/mindtpy/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index 6061da0f0d9..575544eed5c 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -1024,6 +1024,6 @@ def set_var_valid_value( var.set_value(0) else: raise ValueError( - "copy_var_list_values failed with variable {}, value = {} and rounded value = {}" + "set_var_valid_value failed with variable {}, value = {} and rounded value = {}" "".format(var.name, var_val, rounded_val) ) From a0997d824d9079cc3621b4cb77e2d5933f16804c Mon Sep 17 00:00:00 2001 From: Zedong Date: Tue, 13 Feb 2024 13:24:28 -0500 Subject: [PATCH 0950/1204] Update pyomo/contrib/mindtpy/util.py Co-authored-by: Bethany Nicholson --- pyomo/contrib/mindtpy/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index 575544eed5c..fa6aec7f08f 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -944,7 +944,7 @@ def copy_var_list_values( Sets to zero for NonNegativeReals if necessary from_list : list - The variables that provides the values to copy from. + The variables that provide the values to copy from. to_list : list The variables that need to set value. config : ConfigBlock From a5aa8273eb78273d22591eae6b4b8111ee9908ca Mon Sep 17 00:00:00 2001 From: Sakshi <73687517+Sakshi21299@users.noreply.github.com> Date: Tue, 13 Feb 2024 13:28:24 -0500 Subject: [PATCH 0951/1204] change function doc --- pyomo/contrib/incidence_analysis/interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 5bf7ec71e09..595bb42dc41 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -934,10 +934,10 @@ def plot(self, variables=None, constraints=None, title=None, show=True): fig.show() def add_edge(self, variable, constraint): - """Adds an edge between node0 and node1 in the incidence graph + """Adds an edge between variable and constraint in the incidence graph Parameters - --------- + ---------- variable: VarData A variable in the graph constraint: ConstraintData From 6c9fa3ee64bbaeb9d8bce565a857b584b886a401 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 13 Feb 2024 13:34:35 -0500 Subject: [PATCH 0952/1204] update the differentiate.Modes --- pyomo/contrib/mindtpy/util.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index fa6aec7f08f..69c7ca5030a 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -57,10 +57,7 @@ def calc_jacobians(constraint_list, differentiate_mode): # Map nonlinear_constraint --> Map( # variable --> jacobian of constraint w.r.t. variable) jacobians = ComponentMap() - if differentiate_mode == 'reverse_symbolic': - mode = EXPR.differentiate.Modes.reverse_symbolic - elif differentiate_mode == 'sympy': - mode = EXPR.differentiate.Modes.sympy + mode = EXPR.differentiate.Modes(differentiate_mode) for c in constraint_list: vars_in_constr = list(EXPR.identify_variables(c.body)) jac_list = EXPR.differentiate(c.body, wrt_list=vars_in_constr, mode=mode) From 0d39b5d0f20e35c88755dd6557dca63eb1fbf473 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 13 Feb 2024 13:43:35 -0700 Subject: [PATCH 0953/1204] Update NLv2 to only raise exception on empty models in the legacy API --- pyomo/repn/plugins/nl_writer.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 2d5eae151b0..cd570a8a0e1 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -346,6 +346,17 @@ def __call__(self, model, filename, solver_capability, io_options): row_fname ) as ROWFILE, _open(col_fname) as COLFILE: info = self.write(model, FILE, ROWFILE, COLFILE, config=config) + if not info.variables: + # This exception is included for compatibility with the + # original NL writer v1. + os.remove(filename) + os.remove(row_filename) + os.remove(col_filename) + raise ValueError( + "No variables appear in the Pyomo model constraints or" + " objective. This is not supported by the NL file interface" + ) + # Historically, the NL writer communicated the external function # libraries back to the ASL interface through the PYOMO_AMPLFUNC # environment variable. @@ -854,13 +865,6 @@ def write(self, model): con_vars = con_vars_linear | con_vars_nonlinear all_vars = con_vars | obj_vars n_vars = len(all_vars) - if n_vars < 1: - # TODO: Remove this. This exception is included for - # compatibility with the original NL writer v1. - raise ValueError( - "No variables appear in the Pyomo model constraints or" - " objective. This is not supported by the NL file interface" - ) continuous_vars = set() binary_vars = set() From 9764b84541859c797f7790e3b8475a9e7e7abe24 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 13 Feb 2024 15:35:19 -0700 Subject: [PATCH 0954/1204] bugfix: fix symbol name --- pyomo/repn/plugins/nl_writer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index cd570a8a0e1..e12c1f47eb1 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -350,8 +350,8 @@ def __call__(self, model, filename, solver_capability, io_options): # This exception is included for compatibility with the # original NL writer v1. os.remove(filename) - os.remove(row_filename) - os.remove(col_filename) + os.remove(row_fname) + os.remove(col_fname) raise ValueError( "No variables appear in the Pyomo model constraints or" " objective. This is not supported by the NL file interface" From 1462403273125e3cda69720460a0e36240d4c73c Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 13 Feb 2024 15:37:49 -0700 Subject: [PATCH 0955/1204] add guard for symbolic_solver_labels=False --- pyomo/repn/plugins/nl_writer.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index e12c1f47eb1..cda4ee011d3 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -350,8 +350,9 @@ def __call__(self, model, filename, solver_capability, io_options): # This exception is included for compatibility with the # original NL writer v1. os.remove(filename) - os.remove(row_fname) - os.remove(col_fname) + if config.symbolic_solver_labels: + os.remove(row_fname) + os.remove(col_fname) raise ValueError( "No variables appear in the Pyomo model constraints or" " objective. This is not supported by the NL file interface" From 4e8ef42c2534de210adb77105df831b82609c483 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 14 Feb 2024 10:55:21 -0700 Subject: [PATCH 0956/1204] First draft of private data on Blocks --- pyomo/core/base/block.py | 12 ++++++++++++ pyomo/core/tests/unit/test_block.py | 15 +++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 89e872ebbe5..d10754082bd 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -550,6 +550,7 @@ def __init__(self, component): super(_BlockData, self).__setattr__('_ctypes', {}) super(_BlockData, self).__setattr__('_decl', {}) super(_BlockData, self).__setattr__('_decl_order', []) + self._private_data_dict = None def __getattr__(self, val): if val in ModelComponentFactory: @@ -2241,6 +2242,17 @@ def display(self, filename=None, ostream=None, prefix=""): for key in sorted(self): _BlockData.display(self[key], filename, ostream, prefix) + @property + def _private_data(self): + if self._private_data_dict is None: + self._private_data_dict = {} + return self._private_data_dict + + def private_data(self, scope): + if scope not in self._private_data: + self._private_data[scope] = {} + return self._private_data[scope] + class ScalarBlock(_BlockData, Block): def __init__(self, *args, **kwds): diff --git a/pyomo/core/tests/unit/test_block.py b/pyomo/core/tests/unit/test_block.py index f68850d9421..803237b1588 100644 --- a/pyomo/core/tests/unit/test_block.py +++ b/pyomo/core/tests/unit/test_block.py @@ -3407,6 +3407,21 @@ def test_deduplicate_component_data_iterindex(self): ], ) + def test_private_data(self): + m = ConcreteModel() + m.b = Block() + m.b.b = Block([1, 2]) + + mfe = m.private_data('my_scope') + self.assertIsInstance(mfe, dict) + mfe2 = m.private_data('another_scope') + self.assertIsInstance(mfe2, dict) + self.assertEqual(len(m._private_data), 2) + + mfe = m.b.private_data('my_scope') + self.assertIsInstance(mfe, dict) + + if __name__ == "__main__": unittest.main() From 5aa9670c1af9f2e7eee517a0f48cacf840b31822 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 14 Feb 2024 11:01:28 -0700 Subject: [PATCH 0957/1204] Adding copyright statement --- pyomo/gdp/plugins/binary_multiplication.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pyomo/gdp/plugins/binary_multiplication.py b/pyomo/gdp/plugins/binary_multiplication.py index dfdc87ded19..4089ee13a32 100644 --- a/pyomo/gdp/plugins/binary_multiplication.py +++ b/pyomo/gdp/plugins/binary_multiplication.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 .gdp_to_mip_transformation import GDP_to_MIP_Transformation from pyomo.common.config import ConfigDict, ConfigValue from pyomo.core.base import TransformationFactory From 2c8a0a950839ed69c7ee5cd6ba46d22e57a4dab2 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 14 Feb 2024 11:02:34 -0700 Subject: [PATCH 0958/1204] Employing the enter key --- pyomo/gdp/plugins/binary_multiplication.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyomo/gdp/plugins/binary_multiplication.py b/pyomo/gdp/plugins/binary_multiplication.py index 4089ee13a32..f919ee34434 100644 --- a/pyomo/gdp/plugins/binary_multiplication.py +++ b/pyomo/gdp/plugins/binary_multiplication.py @@ -23,7 +23,9 @@ @TransformationFactory.register( 'gdp.binary_multiplication', - doc="Reformulate the GDP as an MINLP by multiplying f(x) <= 0 by y to get f(x) * y <= 0 where y is the binary corresponding to the Boolean indicator var of the Disjunct containing f(x) <= 0.", + doc="Reformulate the GDP as an MINLP by multiplying f(x) <= 0 by y to get " + "f(x) * y <= 0 where y is the binary corresponding to the Boolean indicator " + "var of the Disjunct containing f(x) <= 0.", ) class GDPBinaryMultiplicationTransformation(GDP_to_MIP_Transformation): CONFIG = ConfigDict("gdp.binary_multiplication") From 42067948a923f20bdde323e24e71bf001d1807b5 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 14 Feb 2024 11:16:04 -0700 Subject: [PATCH 0959/1204] Simplifying logic with mapping transformed constraints --- pyomo/gdp/plugins/binary_multiplication.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/pyomo/gdp/plugins/binary_multiplication.py b/pyomo/gdp/plugins/binary_multiplication.py index f919ee34434..5afb661aaa8 100644 --- a/pyomo/gdp/plugins/binary_multiplication.py +++ b/pyomo/gdp/plugins/binary_multiplication.py @@ -157,30 +157,21 @@ def _add_constraint_expressions( # over the constraint indices, but I don't think it matters a lot.) unique = len(newConstraint) name = c.local_name + "_%s" % unique + transformed = constraintMap['transformedConstraints'][c] = [] lb, ub = c.lower, c.upper if (c.equality or lb is ub) and lb is not None: # equality newConstraint.add((name, i, 'eq'), (c.body - lb) * indicator_var == 0) - constraintMap['transformedConstraints'][c] = [newConstraint[name, i, 'eq']] + transformed.append(newConstraint[name, i, 'eq']) constraintMap['srcConstraints'][newConstraint[name, i, 'eq']] = c else: # inequality if lb is not None: newConstraint.add((name, i, 'lb'), 0 <= (c.body - lb) * indicator_var) - constraintMap['transformedConstraints'][c] = [ - newConstraint[name, i, 'lb'] - ] + transformed.append(newConstraint[name, i, 'lb']) constraintMap['srcConstraints'][newConstraint[name, i, 'lb']] = c if ub is not None: newConstraint.add((name, i, 'ub'), (c.body - ub) * indicator_var <= 0) - transformed = constraintMap['transformedConstraints'].get(c) - if transformed is not None: - constraintMap['transformedConstraints'][c].append( - newConstraint[name, i, 'ub'] - ) - else: - constraintMap['transformedConstraints'][c] = [ - newConstraint[name, i, 'ub'] - ] + transformed.append(newConstraint[name, i, 'ub']) constraintMap['srcConstraints'][newConstraint[name, i, 'ub']] = c From 2a3b5731b6b73f8c1ba3b7831c1cdfc9c0a988f2 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 14 Feb 2024 12:34:58 -0700 Subject: [PATCH 0960/1204] Unit tests for config, results, and util complete --- pyomo/contrib/solver/config.py | 6 +- pyomo/contrib/solver/results.py | 103 ++++++++++++------ pyomo/contrib/solver/sol_reader.py | 8 +- pyomo/contrib/solver/solution.py | 6 +- .../contrib/solver/tests/unit/test_config.py | 61 ++++++++++- .../contrib/solver/tests/unit/test_results.py | 6 +- .../solver/tests/unit/test_solution.py | 18 ++- pyomo/contrib/solver/tests/unit/test_util.py | 32 +++++- 8 files changed, 187 insertions(+), 53 deletions(-) diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index d5921c526b0..2a1a129d1ac 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -63,7 +63,8 @@ def __init__( ConfigValue( domain=str, default=None, - description="The directory in which generated files should be saved. This replaced the `keepfiles` option.", + description="The directory in which generated files should be saved. " + "This replaced the `keepfiles` option.", ), ) self.load_solutions: bool = self.declare( @@ -79,7 +80,8 @@ def __init__( ConfigValue( domain=bool, default=True, - description="If False, the `solve` method will continue processing even if the returned result is nonoptimal.", + description="If False, the `solve` method will continue processing " + "even if the returned result is nonoptimal.", ), ) self.symbolic_solver_labels: bool = self.declare( diff --git a/pyomo/contrib/solver/results.py b/pyomo/contrib/solver/results.py index 1fa9d653d01..5ed6de44430 100644 --- a/pyomo/contrib/solver/results.py +++ b/pyomo/contrib/solver/results.py @@ -22,18 +22,12 @@ NonNegativeFloat, ADVANCED_OPTION, ) -from pyomo.common.errors import PyomoException from pyomo.opt.results.solution import SolutionStatus as LegacySolutionStatus from pyomo.opt.results.solver import ( TerminationCondition as LegacyTerminationCondition, SolverStatus as LegacySolverStatus, ) - - -class SolverResultsError(PyomoException): - """ - General exception to catch solver system errors - """ +from pyomo.common.timing import HierarchicalTimer class TerminationCondition(enum.Enum): @@ -167,12 +161,16 @@ class Results(ConfigDict): iteration_count: int The total number of iterations. timing_info: ConfigDict - A ConfigDict containing three pieces of information: - start_time: UTC timestamp of when run was initiated + A ConfigDict containing two pieces of information: + start_timestamp: UTC timestamp of when run was initiated wall_time: elapsed wall clock time for entire process - solver_wall_time: elapsed wall clock time for solve call + timer: a HierarchicalTimer object containing timing data about the solve extra_info: ConfigDict A ConfigDict to store extra information such as solver messages. + solver_configuration: ConfigDict + A copy of the SolverConfig ConfigDict, for later inspection/reproducibility. + solver_log: str + (ADVANCED OPTION) Any solver log messages. """ def __init__( @@ -191,41 +189,85 @@ def __init__( visibility=visibility, ) - self.solution_loader = self.declare('solution_loader', ConfigValue()) + self.solution_loader = self.declare( + 'solution_loader', + ConfigValue( + description="Object for loading the solution back into the model." + ), + ) self.termination_condition: TerminationCondition = self.declare( 'termination_condition', ConfigValue( - domain=In(TerminationCondition), default=TerminationCondition.unknown + domain=In(TerminationCondition), + default=TerminationCondition.unknown, + description="The reason the solver exited. This is a member of the " + "TerminationCondition enum.", ), ) self.solution_status: SolutionStatus = self.declare( 'solution_status', - ConfigValue(domain=In(SolutionStatus), default=SolutionStatus.noSolution), + ConfigValue( + domain=In(SolutionStatus), + default=SolutionStatus.noSolution, + description="The result of the solve call. This is a member of " + "the SolutionStatus enum.", + ), ) self.incumbent_objective: Optional[float] = self.declare( - 'incumbent_objective', ConfigValue(domain=float, default=None) + 'incumbent_objective', + ConfigValue( + domain=float, + default=None, + description="If a feasible solution was found, this is the objective " + "value of the best solution found. If no feasible solution was found, this is None.", + ), ) self.objective_bound: Optional[float] = self.declare( - 'objective_bound', ConfigValue(domain=float, default=None) + 'objective_bound', + ConfigValue( + domain=float, + default=None, + description="The best objective bound found. For minimization problems, " + "this is the lower bound. For maximization problems, this is the " + "upper bound. For solvers that do not provide an objective bound, " + "this should be -inf (minimization) or inf (maximization)", + ), ) self.solver_name: Optional[str] = self.declare( - 'solver_name', ConfigValue(domain=str) + 'solver_name', + ConfigValue(domain=str, description="The name of the solver in use."), ) self.solver_version: Optional[Tuple[int, ...]] = self.declare( - 'solver_version', ConfigValue(domain=tuple) + 'solver_version', + ConfigValue( + domain=tuple, + description="A tuple representing the version of the solver in use.", + ), ) self.iteration_count: Optional[int] = self.declare( - 'iteration_count', ConfigValue(domain=NonNegativeInt, default=None) + 'iteration_count', + ConfigValue( + domain=NonNegativeInt, + default=None, + description="The total number of iterations.", + ), ) self.timing_info: ConfigDict = self.declare( 'timing_info', ConfigDict(implicit=True) ) self.timing_info.start_timestamp: datetime = self.timing_info.declare( - 'start_timestamp', ConfigValue(domain=Datetime) + 'start_timestamp', + ConfigValue( + domain=Datetime, description="UTC timestamp of when run was initiated." + ), ) self.timing_info.wall_time: Optional[float] = self.timing_info.declare( - 'wall_time', ConfigValue(domain=NonNegativeFloat) + 'wall_time', + ConfigValue( + domain=NonNegativeFloat, + description="Elapsed wall clock time for entire process.", + ), ) self.extra_info: ConfigDict = self.declare( 'extra_info', ConfigDict(implicit=True) @@ -233,13 +275,18 @@ def __init__( self.solver_configuration: ConfigDict = self.declare( 'solver_configuration', ConfigValue( - description="A copy of the config object used in the solve", + description="A copy of the config object used in the solve call.", visibility=ADVANCED_OPTION, ), ) self.solver_log: str = self.declare( 'solver_log', - ConfigValue(domain=str, default=None, visibility=ADVANCED_OPTION), + ConfigValue( + domain=str, + default=None, + visibility=ADVANCED_OPTION, + description="Any solver log messages.", + ), ) def display( @@ -248,18 +295,6 @@ def display( return super().display(content_filter, indent_spacing, ostream, visibility) -class ResultsReader: - pass - - -def parse_yaml(): - pass - - -def parse_json(): - pass - - # Everything below here preserves backwards compatibility legacy_termination_condition_map = { diff --git a/pyomo/contrib/solver/sol_reader.py b/pyomo/contrib/solver/sol_reader.py index 68654a4e9d7..3af30e1826b 100644 --- a/pyomo/contrib/solver/sol_reader.py +++ b/pyomo/contrib/solver/sol_reader.py @@ -15,7 +15,7 @@ from pyomo.common.errors import DeveloperError from pyomo.repn.plugins.nl_writer import NLWriterInfo -from .results import Results, SolverResultsError, SolutionStatus, TerminationCondition +from .results import Results, SolutionStatus, TerminationCondition class SolFileData: @@ -69,7 +69,7 @@ def parse_sol_file( line = sol_file.readline() model_objects.append(int(line)) else: - raise SolverResultsError("ERROR READING `sol` FILE. No 'Options' line found.") + raise Exception("ERROR READING `sol` FILE. No 'Options' line found.") # Identify the total number of variables and constraints number_of_cons = model_objects[number_of_options + 1] number_of_vars = model_objects[number_of_options + 3] @@ -85,12 +85,12 @@ def parse_sol_file( if line and ('objno' in line): exit_code_line = line.split() if len(exit_code_line) != 3: - raise SolverResultsError( + raise Exception( f"ERROR READING `sol` FILE. Expected two numbers in `objno` line; received {line}." ) exit_code = [int(exit_code_line[1]), int(exit_code_line[2])] else: - raise SolverResultsError( + raise Exception( f"ERROR READING `sol` FILE. Expected `objno`; received {line}." ) result.extra_info.solver_message = message.strip().replace('\n', '; ') diff --git a/pyomo/contrib/solver/solution.py b/pyomo/contrib/solver/solution.py index 33a3b1c939c..beb53cf979a 100644 --- a/pyomo/contrib/solver/solution.py +++ b/pyomo/contrib/solver/solution.py @@ -23,6 +23,11 @@ class SolutionLoaderBase(abc.ABC): + """ + Base class for all future SolutionLoader classes. + + Intent of this class and its children is to load the solution back into the model. + """ def load_vars( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None ) -> NoReturn: @@ -58,7 +63,6 @@ def get_primals( primals: ComponentMap Maps variables to solution values """ - pass def get_duals( self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None diff --git a/pyomo/contrib/solver/tests/unit/test_config.py b/pyomo/contrib/solver/tests/unit/test_config.py index 4a7cc250623..f28dd5fcedf 100644 --- a/pyomo/contrib/solver/tests/unit/test_config.py +++ b/pyomo/contrib/solver/tests/unit/test_config.py @@ -10,7 +10,12 @@ # ___________________________________________________________________________ from pyomo.common import unittest -from pyomo.contrib.solver.config import SolverConfig, BranchAndBoundConfig +from pyomo.contrib.solver.config import ( + SolverConfig, + BranchAndBoundConfig, + AutoUpdateConfig, + PersistentSolverConfig, +) class TestSolverConfig(unittest.TestCase): @@ -59,3 +64,57 @@ def test_interface_custom_instantiation(self): self.assertIsInstance(config.time_limit, float) config.rel_gap = 2.5 self.assertEqual(config.rel_gap, 2.5) + + +class TestAutoUpdateConfig(unittest.TestCase): + def test_interface_default_instantiation(self): + config = AutoUpdateConfig() + self.assertTrue(config.check_for_new_or_removed_constraints) + self.assertTrue(config.check_for_new_or_removed_vars) + self.assertTrue(config.check_for_new_or_removed_params) + self.assertTrue(config.check_for_new_objective) + self.assertTrue(config.update_constraints) + self.assertTrue(config.update_vars) + self.assertTrue(config.update_named_expressions) + self.assertTrue(config.update_objective) + self.assertTrue(config.update_objective) + self.assertTrue(config.treat_fixed_vars_as_params) + + def test_interface_custom_instantiation(self): + config = AutoUpdateConfig(description="A description") + config.check_for_new_objective = False + self.assertEqual(config._description, "A description") + self.assertTrue(config.check_for_new_or_removed_constraints) + self.assertFalse(config.check_for_new_objective) + + +class TestPersistentSolverConfig(unittest.TestCase): + def test_interface_default_instantiation(self): + config = PersistentSolverConfig() + self.assertIsNone(config._description) + self.assertEqual(config._visibility, 0) + self.assertFalse(config.tee) + self.assertTrue(config.load_solutions) + self.assertTrue(config.raise_exception_on_nonoptimal_result) + self.assertFalse(config.symbolic_solver_labels) + self.assertIsNone(config.timer) + self.assertIsNone(config.threads) + self.assertIsNone(config.time_limit) + self.assertTrue(config.auto_updates.check_for_new_or_removed_constraints) + self.assertTrue(config.auto_updates.check_for_new_or_removed_vars) + self.assertTrue(config.auto_updates.check_for_new_or_removed_params) + self.assertTrue(config.auto_updates.check_for_new_objective) + self.assertTrue(config.auto_updates.update_constraints) + self.assertTrue(config.auto_updates.update_vars) + self.assertTrue(config.auto_updates.update_named_expressions) + self.assertTrue(config.auto_updates.update_objective) + self.assertTrue(config.auto_updates.update_objective) + self.assertTrue(config.auto_updates.treat_fixed_vars_as_params) + + def test_interface_custom_instantiation(self): + config = PersistentSolverConfig(description="A description") + config.tee = True + config.auto_updates.check_for_new_objective = False + self.assertTrue(config.tee) + self.assertEqual(config._description, "A description") + self.assertFalse(config.auto_updates.check_for_new_objective) diff --git a/pyomo/contrib/solver/tests/unit/test_results.py b/pyomo/contrib/solver/tests/unit/test_results.py index 23c2c32f819..caef82129ec 100644 --- a/pyomo/contrib/solver/tests/unit/test_results.py +++ b/pyomo/contrib/solver/tests/unit/test_results.py @@ -69,7 +69,7 @@ def test_codes(self): class TestResults(unittest.TestCase): - def test_declared_items(self): + def test_member_list(self): res = results.Results() expected_declared = { 'extra_info', @@ -88,7 +88,7 @@ def test_declared_items(self): actual_declared = res._declared self.assertEqual(expected_declared, actual_declared) - def test_uninitialized(self): + def test_default_initialization(self): res = results.Results() self.assertIsNone(res.incumbent_objective) self.assertIsNone(res.objective_bound) @@ -118,7 +118,7 @@ def test_uninitialized(self): ): res.solution_loader.get_reduced_costs() - def test_results(self): + def test_generated_results(self): m = pyo.ConcreteModel() m.x = ScalarVar() m.y = ScalarVar() diff --git a/pyomo/contrib/solver/tests/unit/test_solution.py b/pyomo/contrib/solver/tests/unit/test_solution.py index 1ecba45b32a..67ce2556317 100644 --- a/pyomo/contrib/solver/tests/unit/test_solution.py +++ b/pyomo/contrib/solver/tests/unit/test_solution.py @@ -10,22 +10,30 @@ # ___________________________________________________________________________ from pyomo.common import unittest -from pyomo.contrib.solver import solution +from pyomo.contrib.solver.solution import SolutionLoaderBase, PersistentSolutionLoader -class TestPersistentSolverBase(unittest.TestCase): +class TestSolutionLoaderBase(unittest.TestCase): def test_abstract_member_list(self): expected_list = ['get_primals'] - member_list = list(solution.SolutionLoaderBase.__abstractmethods__) + member_list = list(SolutionLoaderBase.__abstractmethods__) self.assertEqual(sorted(expected_list), sorted(member_list)) @unittest.mock.patch.multiple( - solution.SolutionLoaderBase, __abstractmethods__=set() + SolutionLoaderBase, __abstractmethods__=set() ) def test_solution_loader_base(self): - self.instance = solution.SolutionLoaderBase() + self.instance = SolutionLoaderBase() self.assertEqual(self.instance.get_primals(), None) with self.assertRaises(NotImplementedError): self.instance.get_duals() with self.assertRaises(NotImplementedError): self.instance.get_reduced_costs() + + +class TestPersistentSolutionLoader(unittest.TestCase): + def test_abstract_member_list(self): + # We expect no abstract members at this point because it's a real-life + # instantiation of SolutionLoaderBase + member_list = list(PersistentSolutionLoader('ipopt').__abstractmethods__) + self.assertEqual(member_list, []) diff --git a/pyomo/contrib/solver/tests/unit/test_util.py b/pyomo/contrib/solver/tests/unit/test_util.py index 8a8a0221362..ab8a778067f 100644 --- a/pyomo/contrib/solver/tests/unit/test_util.py +++ b/pyomo/contrib/solver/tests/unit/test_util.py @@ -102,15 +102,41 @@ def test_check_optimal_termination_condition_legacy_interface(self): results = SolverResults() results.solver.status = SolverStatus.ok results.solver.termination_condition = LegacyTerminationCondition.optimal + # Both items satisfied self.assertTrue(check_optimal_termination(results)) + # Termination condition not satisfied results.solver.termination_condition = LegacyTerminationCondition.unknown self.assertFalse(check_optimal_termination(results)) + # Both not satisfied results.solver.termination_condition = SolverStatus.aborted self.assertFalse(check_optimal_termination(results)) - # TODO: Left off here; need to make these tests def test_assert_optimal_termination_new_interface(self): - pass + results = Results() + results.solution_status = SolutionStatus.optimal + results.termination_condition = ( + TerminationCondition.convergenceCriteriaSatisfied + ) + assert_optimal_termination(results) + # Termination condition not satisfied + results.termination_condition = TerminationCondition.iterationLimit + with self.assertRaises(RuntimeError): + assert_optimal_termination(results) + # Both not satisfied + results.solution_status = SolutionStatus.noSolution + with self.assertRaises(RuntimeError): + assert_optimal_termination(results) def test_assert_optimal_termination_legacy_interface(self): - pass + results = SolverResults() + results.solver.status = SolverStatus.ok + results.solver.termination_condition = LegacyTerminationCondition.optimal + assert_optimal_termination(results) + # Termination condition not satisfied + results.solver.termination_condition = LegacyTerminationCondition.unknown + with self.assertRaises(RuntimeError): + assert_optimal_termination(results) + # Both not satisfied + results.solver.termination_condition = SolverStatus.aborted + with self.assertRaises(RuntimeError): + assert_optimal_termination(results) From d4e56e81cb5d8ca354aad72b2da4cc2261f2ca05 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 14 Feb 2024 12:38:41 -0700 Subject: [PATCH 0961/1204] Apply new version of black --- pyomo/contrib/solver/ipopt.py | 16 ++++++++-------- pyomo/contrib/solver/sol_reader.py | 4 +--- pyomo/contrib/solver/solution.py | 1 + pyomo/contrib/solver/tests/unit/test_solution.py | 4 +--- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 5eb877f0867..4c4b932381d 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -89,15 +89,15 @@ def __init__( implicit_domain=implicit_domain, visibility=visibility, ) - self.timing_info.no_function_solve_time: Optional[ - float - ] = self.timing_info.declare( - 'no_function_solve_time', ConfigValue(domain=NonNegativeFloat) + self.timing_info.no_function_solve_time: Optional[float] = ( + self.timing_info.declare( + 'no_function_solve_time', ConfigValue(domain=NonNegativeFloat) + ) ) - self.timing_info.function_solve_time: Optional[ - float - ] = self.timing_info.declare( - 'function_solve_time', ConfigValue(domain=NonNegativeFloat) + self.timing_info.function_solve_time: Optional[float] = ( + self.timing_info.declare( + 'function_solve_time', ConfigValue(domain=NonNegativeFloat) + ) ) diff --git a/pyomo/contrib/solver/sol_reader.py b/pyomo/contrib/solver/sol_reader.py index f78d9fb3115..b9b33272fd6 100644 --- a/pyomo/contrib/solver/sol_reader.py +++ b/pyomo/contrib/solver/sol_reader.py @@ -90,9 +90,7 @@ def parse_sol_file( ) exit_code = [int(exit_code_line[1]), int(exit_code_line[2])] else: - raise Exception( - f"ERROR READING `sol` FILE. Expected `objno`; received {line}." - ) + raise Exception(f"ERROR READING `sol` FILE. Expected `objno`; received {line}.") result.extra_info.solver_message = message.strip().replace('\n', '; ') exit_code_message = '' if (exit_code[1] >= 0) and (exit_code[1] <= 99): diff --git a/pyomo/contrib/solver/solution.py b/pyomo/contrib/solver/solution.py index beb53cf979a..ca19e4df0e9 100644 --- a/pyomo/contrib/solver/solution.py +++ b/pyomo/contrib/solver/solution.py @@ -28,6 +28,7 @@ class SolutionLoaderBase(abc.ABC): Intent of this class and its children is to load the solution back into the model. """ + def load_vars( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None ) -> NoReturn: diff --git a/pyomo/contrib/solver/tests/unit/test_solution.py b/pyomo/contrib/solver/tests/unit/test_solution.py index 67ce2556317..877be34d29b 100644 --- a/pyomo/contrib/solver/tests/unit/test_solution.py +++ b/pyomo/contrib/solver/tests/unit/test_solution.py @@ -19,9 +19,7 @@ def test_abstract_member_list(self): member_list = list(SolutionLoaderBase.__abstractmethods__) self.assertEqual(sorted(expected_list), sorted(member_list)) - @unittest.mock.patch.multiple( - SolutionLoaderBase, __abstractmethods__=set() - ) + @unittest.mock.patch.multiple(SolutionLoaderBase, __abstractmethods__=set()) def test_solution_loader_base(self): self.instance = SolutionLoaderBase() self.assertEqual(self.instance.get_primals(), None) From 51dccea8e8835b5150abc68ef29429413a733554 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 14 Feb 2024 13:28:36 -0700 Subject: [PATCH 0962/1204] Add unit tests for solution module --- pyomo/contrib/solver/sol_reader.py | 53 +++++++++---------- .../solver/tests/unit/test_solution.py | 45 ++++++++++++++++ 2 files changed, 71 insertions(+), 27 deletions(-) diff --git a/pyomo/contrib/solver/sol_reader.py b/pyomo/contrib/solver/sol_reader.py index b9b33272fd6..ed4fe4865c2 100644 --- a/pyomo/contrib/solver/sol_reader.py +++ b/pyomo/contrib/solver/sol_reader.py @@ -13,7 +13,7 @@ from typing import Tuple, Dict, Any, List import io -from pyomo.common.errors import DeveloperError +from pyomo.common.errors import DeveloperError, PyomoException from pyomo.repn.plugins.nl_writer import NLWriterInfo from .results import Results, SolutionStatus, TerminationCondition @@ -26,6 +26,7 @@ def __init__(self) -> None: self.con_suffixes: Dict[str, Dict[Any]] = dict() self.obj_suffixes: Dict[str, Dict[int, Any]] = dict() self.problem_suffixes: Dict[str, List[Any]] = dict() + self.other: List(str) = list() def parse_sol_file( @@ -69,7 +70,7 @@ def parse_sol_file( line = sol_file.readline() model_objects.append(int(line)) else: - raise Exception("ERROR READING `sol` FILE. No 'Options' line found.") + raise PyomoException("ERROR READING `sol` FILE. No 'Options' line found.") # Identify the total number of variables and constraints number_of_cons = model_objects[number_of_options + 1] number_of_vars = model_objects[number_of_options + 3] @@ -85,12 +86,14 @@ def parse_sol_file( if line and ('objno' in line): exit_code_line = line.split() if len(exit_code_line) != 3: - raise Exception( + raise PyomoException( f"ERROR READING `sol` FILE. Expected two numbers in `objno` line; received {line}." ) exit_code = [int(exit_code_line[1]), int(exit_code_line[2])] else: - raise Exception(f"ERROR READING `sol` FILE. Expected `objno`; received {line}.") + raise PyomoException( + f"ERROR READING `sol` FILE. Expected `objno`; received {line}." + ) result.extra_info.solver_message = message.strip().replace('\n', '; ') exit_code_message = '' if (exit_code[1] >= 0) and (exit_code[1] <= 99): @@ -103,8 +106,6 @@ def parse_sol_file( elif (exit_code[1] >= 200) and (exit_code[1] <= 299): exit_code_message = "INFEASIBLE SOLUTION: constraints cannot be satisfied!" result.solution_status = SolutionStatus.infeasible - # TODO: this is solver dependent - # But this was the way in the previous version - and has been fine thus far? result.termination_condition = TerminationCondition.locallyInfeasible elif (exit_code[1] >= 300) and (exit_code[1] <= 399): exit_code_message = ( @@ -117,8 +118,6 @@ def parse_sol_file( "EXCEEDED MAXIMUM NUMBER OF ITERATIONS: the solver " "was stopped by a limit that you set!" ) - # TODO: this is solver dependent - # But this was the way in the previous version - and has been fine thus far? result.solution_status = SolutionStatus.infeasible result.termination_condition = ( TerminationCondition.iterationLimit @@ -158,47 +157,47 @@ def parse_sol_file( line = sol_file.readline() result.extra_info.solver_message += remaining break - unmasked_kind = int(line[1]) - kind = unmasked_kind & 3 # 0-var, 1-con, 2-obj, 3-prob + read_data_type = int(line[1]) + data_type = read_data_type & 3 # 0-var, 1-con, 2-obj, 3-prob convert_function = int - if (unmasked_kind & 4) == 4: + if (read_data_type & 4) == 4: convert_function = float - nvalues = int(line[2]) - # namelen = int(line[3]) - # tablen = int(line[4]) - tabline = int(line[5]) + number_of_entries = int(line[2]) + # The third entry is name length, and it is length+1. This is unnecessary + # except for data validation. + # The fourth entry is table "length", e.g., memory size. + number_of_string_lines = int(line[5]) suffix_name = sol_file.readline().strip() - # ignore translation of the table number to string value for now, - # this information can be obtained from the solver documentation - for n in range(tabline): - sol_file.readline() - if kind == 0: # Var + # Add any of arbitrary string lines to the "other" list + for line in range(number_of_string_lines): + sol_data.other.append(sol_file.readline()) + if data_type == 0: # Var sol_data.var_suffixes[suffix_name] = dict() - for cnt in range(nvalues): + for cnt in range(number_of_entries): suf_line = sol_file.readline().split() var_ndx = int(suf_line[0]) sol_data.var_suffixes[suffix_name][var_ndx] = convert_function( suf_line[1] ) - elif kind == 1: # Con + elif data_type == 1: # Con sol_data.con_suffixes[suffix_name] = dict() - for cnt in range(nvalues): + for cnt in range(number_of_entries): suf_line = sol_file.readline().split() con_ndx = int(suf_line[0]) sol_data.con_suffixes[suffix_name][con_ndx] = convert_function( suf_line[1] ) - elif kind == 2: # Obj + elif data_type == 2: # Obj sol_data.obj_suffixes[suffix_name] = dict() - for cnt in range(nvalues): + for cnt in range(number_of_entries): suf_line = sol_file.readline().split() obj_ndx = int(suf_line[0]) sol_data.obj_suffixes[suffix_name][obj_ndx] = convert_function( suf_line[1] ) - elif kind == 3: # Prob + elif data_type == 3: # Prob sol_data.problem_suffixes[suffix_name] = list() - for cnt in range(nvalues): + for cnt in range(number_of_entries): suf_line = sol_file.readline().split() sol_data.problem_suffixes[suffix_name].append( convert_function(suf_line[1]) diff --git a/pyomo/contrib/solver/tests/unit/test_solution.py b/pyomo/contrib/solver/tests/unit/test_solution.py index 877be34d29b..bbcc85bdac8 100644 --- a/pyomo/contrib/solver/tests/unit/test_solution.py +++ b/pyomo/contrib/solver/tests/unit/test_solution.py @@ -19,6 +19,15 @@ def test_abstract_member_list(self): member_list = list(SolutionLoaderBase.__abstractmethods__) self.assertEqual(sorted(expected_list), sorted(member_list)) + def test_member_list(self): + expected_list = ['load_vars', 'get_primals', 'get_duals', 'get_reduced_costs'] + method_list = [ + method + for method in dir(SolutionLoaderBase) + if method.startswith('_') is False + ] + self.assertEqual(sorted(expected_list), sorted(method_list)) + @unittest.mock.patch.multiple(SolutionLoaderBase, __abstractmethods__=set()) def test_solution_loader_base(self): self.instance = SolutionLoaderBase() @@ -29,9 +38,45 @@ def test_solution_loader_base(self): self.instance.get_reduced_costs() +class TestSolSolutionLoader(unittest.TestCase): + # I am currently unsure how to test this further because it relies heavily on + # SolFileData and NLWriterInfo + def test_member_list(self): + expected_list = ['load_vars', 'get_primals', 'get_duals', 'get_reduced_costs'] + method_list = [ + method + for method in dir(SolutionLoaderBase) + if method.startswith('_') is False + ] + self.assertEqual(sorted(expected_list), sorted(method_list)) + + class TestPersistentSolutionLoader(unittest.TestCase): def test_abstract_member_list(self): # We expect no abstract members at this point because it's a real-life # instantiation of SolutionLoaderBase member_list = list(PersistentSolutionLoader('ipopt').__abstractmethods__) self.assertEqual(member_list, []) + + def test_member_list(self): + expected_list = [ + 'load_vars', + 'get_primals', + 'get_duals', + 'get_reduced_costs', + 'invalidate', + ] + method_list = [ + method + for method in dir(PersistentSolutionLoader) + if method.startswith('_') is False + ] + self.assertEqual(sorted(expected_list), sorted(method_list)) + + def test_default_initialization(self): + # Realistically, a solver object should be passed into this. + # However, it works with a string. It'll just error loudly if you + # try to run get_primals, etc. + self.instance = PersistentSolutionLoader('ipopt') + self.assertTrue(self.instance._valid) + self.assertEqual(self.instance._solver, 'ipopt') From 571cc0860be6460363d92f08bca690fbbee4989d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 14 Feb 2024 13:56:41 -0700 Subject: [PATCH 0963/1204] Add 'name' to SolverBase --- pyomo/contrib/solver/base.py | 7 +++++++ pyomo/contrib/solver/tests/unit/test_base.py | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 42524296d74..046a83fb7ec 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -54,6 +54,13 @@ class SolverBase(abc.ABC): CONFIG = SolverConfig() def __init__(self, **kwds) -> None: + # We allow the user and/or developer to name the solver something else, + # if they really desire. Otherwise it defaults to the class name (all lowercase) + if "name" in kwds: + self.name = kwds["name"] + kwds.pop('name') + else: + self.name = type(self).__name__.lower() self.config = self.CONFIG(value=kwds) # diff --git a/pyomo/contrib/solver/tests/unit/test_base.py b/pyomo/contrib/solver/tests/unit/test_base.py index 00e38d9ac59..cda1631d921 100644 --- a/pyomo/contrib/solver/tests/unit/test_base.py +++ b/pyomo/contrib/solver/tests/unit/test_base.py @@ -41,6 +41,7 @@ def test_init(self): self.instance = base.SolverBase() self.assertFalse(self.instance.is_persistent()) self.assertEqual(self.instance.version(), None) + self.assertEqual(self.instance.name, 'solverbase') self.assertEqual(self.instance.CONFIG, self.instance.config) self.assertEqual(self.instance.solve(None), None) self.assertEqual(self.instance.available(), None) @@ -50,6 +51,7 @@ def test_context_manager(self): with base.SolverBase() as self.instance: self.assertFalse(self.instance.is_persistent()) self.assertEqual(self.instance.version(), None) + self.assertEqual(self.instance.name, 'solverbase') self.assertEqual(self.instance.CONFIG, self.instance.config) self.assertEqual(self.instance.solve(None), None) self.assertEqual(self.instance.available(), None) @@ -69,6 +71,11 @@ def test_solver_availability(self): self.instance.Availability.__bool__(self.instance.Availability) ) + @unittest.mock.patch.multiple(base.SolverBase, __abstractmethods__=set()) + def test_custom_solver_name(self): + self.instance = base.SolverBase(name='my_unique_name') + self.assertEqual(self.instance.name, 'my_unique_name') + class TestPersistentSolverBase(unittest.TestCase): def test_abstract_member_list(self): From b19c75aa02ba2249cc92f17612087a3df5c6e3dc Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 14 Feb 2024 14:08:21 -0700 Subject: [PATCH 0964/1204] Update documentation (which is still slim but is a reasonable start) --- .../developer_reference/solvers.rst | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index 10e7e829463..fa24d69a211 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -9,8 +9,19 @@ Pyomo offers interfaces into multiple solvers, both commercial and open source. Interface Implementation ------------------------ -TBD: How to add a new interface; the pieces. +All new interfaces should be built upon one of two classes (currently): +``pyomo.contrib.solver.base.SolverBase`` or ``pyomo.contrib.solver.base.PersistentSolverBase``. +All solvers should have the following: + +.. autoclass:: pyomo.contrib.solver.base.SolverBase + :members: + +Persistent solvers should also include: + +.. autoclass:: pyomo.contrib.solver.base.PersistentSolverBase + :show-inheritance: + :members: Results ------- @@ -56,4 +67,10 @@ returned solver messages or logs for more information. Solution -------- -TBD: How to load/parse a solution. +Solutions can be loaded back into a model using a ``SolutionLoader``. A specific +loader should be written for each unique case. Several have already been +implemented. For example, for ``ipopt``: + +.. autoclass:: pyomo.contrib.solver.solution.SolSolutionLoader + :show-inheritance: + :members: From 9ed93fe11fb2f8ee060614fb3b1fc639d746052b Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 14 Feb 2024 14:10:45 -0700 Subject: [PATCH 0965/1204] Update docs to point to new gurobi interface --- pyomo/contrib/solver/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 046a83fb7ec..96b87924bf6 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -176,7 +176,7 @@ class PersistentSolverBase(SolverBase): methods from the direct solver base and adds those methods that are necessary for persistent solvers. - Example usage can be seen in solvers within APPSI. + Example usage can be seen in the GUROBI solver. """ def is_persistent(self): From 398493c32e2b193f76842a30fc58b52a352c7df7 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 14 Feb 2024 14:24:21 -0700 Subject: [PATCH 0966/1204] solver refactor: update tests --- pyomo/contrib/solver/tests/solvers/test_solvers.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyomo/contrib/solver/tests/solvers/test_solvers.py b/pyomo/contrib/solver/tests/solvers/test_solvers.py index 658aaf41b13..6b798f9bafd 100644 --- a/pyomo/contrib/solver/tests/solvers/test_solvers.py +++ b/pyomo/contrib/solver/tests/solvers/test_solvers.py @@ -139,6 +139,11 @@ def test_reduced_costs(self, name: str, opt_class: Type[SolverBase]): rc = res.solution_loader.get_reduced_costs() self.assertAlmostEqual(rc[m.x], 3) self.assertAlmostEqual(rc[m.y], 4) + m.obj.expr *= -1 + res = opt.solve(m) + rc = res.solution_loader.get_reduced_costs() + self.assertAlmostEqual(rc[m.x], -3) + self.assertAlmostEqual(rc[m.y], -4) @parameterized.expand(input=_load_tests(all_solvers)) def test_reduced_costs2(self, name: str, opt_class: Type[SolverBase]): From 9fcb2366e82e515ea718057e4aa6645308943a88 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 14 Feb 2024 15:23:02 -0700 Subject: [PATCH 0967/1204] Set up structure for sol_reader tests --- pyomo/contrib/solver/ipopt.py | 2 +- pyomo/contrib/solver/sol_reader.py | 2 +- pyomo/contrib/solver/solution.py | 2 +- .../solver/tests/unit/sol_files/bad_objno.sol | 22 + .../tests/unit/sol_files/bad_objnoline.sol | 22 + .../tests/unit/sol_files/bad_options.sol | 22 + .../tests/unit/sol_files/conopt_optimal.sol | 22 + .../tests/unit/sol_files/depr_solver.sol | 67 +++ .../unit/sol_files/iis_no_variable_values.sol | 34 ++ .../tests/unit/sol_files/infeasible1.sol | 491 ++++++++++++++++++ .../tests/unit/sol_files/infeasible2.sol | 13 + pyomo/contrib/solver/tests/unit/test_base.py | 14 +- .../solver/tests/unit/test_sol_reader.py | 51 ++ 13 files changed, 754 insertions(+), 10 deletions(-) create mode 100644 pyomo/contrib/solver/tests/unit/sol_files/bad_objno.sol create mode 100644 pyomo/contrib/solver/tests/unit/sol_files/bad_objnoline.sol create mode 100644 pyomo/contrib/solver/tests/unit/sol_files/bad_options.sol create mode 100644 pyomo/contrib/solver/tests/unit/sol_files/conopt_optimal.sol create mode 100644 pyomo/contrib/solver/tests/unit/sol_files/depr_solver.sol create mode 100644 pyomo/contrib/solver/tests/unit/sol_files/iis_no_variable_values.sol create mode 100644 pyomo/contrib/solver/tests/unit/sol_files/infeasible1.sol create mode 100644 pyomo/contrib/solver/tests/unit/sol_files/infeasible2.sol create mode 100644 pyomo/contrib/solver/tests/unit/test_sol_reader.py diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 4c4b932381d..f70cbb5f194 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -28,7 +28,7 @@ from pyomo.contrib.solver.config import SolverConfig from pyomo.contrib.solver.factory import SolverFactory from pyomo.contrib.solver.results import Results, TerminationCondition, SolutionStatus -from .sol_reader import parse_sol_file +from pyomo.contrib.solver.sol_reader import parse_sol_file from pyomo.contrib.solver.solution import SolSolutionLoader, SolutionLoader from pyomo.common.tee import TeeStream from pyomo.common.log import LogStream diff --git a/pyomo/contrib/solver/sol_reader.py b/pyomo/contrib/solver/sol_reader.py index ed4fe4865c2..c4497516de2 100644 --- a/pyomo/contrib/solver/sol_reader.py +++ b/pyomo/contrib/solver/sol_reader.py @@ -15,7 +15,7 @@ from pyomo.common.errors import DeveloperError, PyomoException from pyomo.repn.plugins.nl_writer import NLWriterInfo -from .results import Results, SolutionStatus, TerminationCondition +from pyomo.contrib.solver.results import Results, SolutionStatus, TerminationCondition class SolFileData: diff --git a/pyomo/contrib/solver/solution.py b/pyomo/contrib/solver/solution.py index ca19e4df0e9..d4069b5b5a1 100644 --- a/pyomo/contrib/solver/solution.py +++ b/pyomo/contrib/solver/solution.py @@ -16,7 +16,7 @@ from pyomo.core.base.var import _GeneralVarData from pyomo.common.collections import ComponentMap from pyomo.core.staleflag import StaleFlagManager -from .sol_reader import SolFileData +from pyomo.contrib.solver.sol_reader import SolFileData from pyomo.repn.plugins.nl_writer import NLWriterInfo from pyomo.core.expr.numvalue import value from pyomo.core.expr.visitor import replace_expressions diff --git a/pyomo/contrib/solver/tests/unit/sol_files/bad_objno.sol b/pyomo/contrib/solver/tests/unit/sol_files/bad_objno.sol new file mode 100644 index 00000000000..a7eccfca388 --- /dev/null +++ b/pyomo/contrib/solver/tests/unit/sol_files/bad_objno.sol @@ -0,0 +1,22 @@ +CONOPT 3.17A: Optimal; objective 1 +4 iterations; evals: nf = 2, ng = 0, nc = 2, nJ = 0, nH = 0, nHv = 0 + +Options +3 +1 +1 +0 +1 +1 +1 +1 +1 +1 +Xobjno 0 0 +suffix 0 1 8 0 0 +sstatus +0 1 +suffix 1 1 8 0 0 +sstatus +0 3 + diff --git a/pyomo/contrib/solver/tests/unit/sol_files/bad_objnoline.sol b/pyomo/contrib/solver/tests/unit/sol_files/bad_objnoline.sol new file mode 100644 index 00000000000..6abcacbb3c4 --- /dev/null +++ b/pyomo/contrib/solver/tests/unit/sol_files/bad_objnoline.sol @@ -0,0 +1,22 @@ +CONOPT 3.17A: Optimal; objective 1 +4 iterations; evals: nf = 2, ng = 0, nc = 2, nJ = 0, nH = 0, nHv = 0 + +Options +3 +1 +1 +0 +1 +1 +1 +1 +1 +1 +objno 0 0 1 +suffix 0 1 8 0 0 +sstatus +0 1 +suffix 1 1 8 0 0 +sstatus +0 3 + diff --git a/pyomo/contrib/solver/tests/unit/sol_files/bad_options.sol b/pyomo/contrib/solver/tests/unit/sol_files/bad_options.sol new file mode 100644 index 00000000000..f59a2ffd3b4 --- /dev/null +++ b/pyomo/contrib/solver/tests/unit/sol_files/bad_options.sol @@ -0,0 +1,22 @@ +CONOPT 3.17A: Optimal; objective 1 +4 iterations; evals: nf = 2, ng = 0, nc = 2, nJ = 0, nH = 0, nHv = 0 + +OXptions +3 +1 +1 +0 +1 +1 +1 +1 +1 +1 +objno 0 0 +suffix 0 1 8 0 0 +sstatus +0 1 +suffix 1 1 8 0 0 +sstatus +0 3 + diff --git a/pyomo/contrib/solver/tests/unit/sol_files/conopt_optimal.sol b/pyomo/contrib/solver/tests/unit/sol_files/conopt_optimal.sol new file mode 100644 index 00000000000..4ff14b50bc7 --- /dev/null +++ b/pyomo/contrib/solver/tests/unit/sol_files/conopt_optimal.sol @@ -0,0 +1,22 @@ +CONOPT 3.17A: Optimal; objective 1 +4 iterations; evals: nf = 2, ng = 0, nc = 2, nJ = 0, nH = 0, nHv = 0 + +Options +3 +1 +1 +0 +1 +1 +1 +1 +1 +1 +objno 0 0 +suffix 0 1 8 0 0 +sstatus +0 1 +suffix 1 1 8 0 0 +sstatus +0 3 + diff --git a/pyomo/contrib/solver/tests/unit/sol_files/depr_solver.sol b/pyomo/contrib/solver/tests/unit/sol_files/depr_solver.sol new file mode 100644 index 00000000000..01ceb566334 --- /dev/null +++ b/pyomo/contrib/solver/tests/unit/sol_files/depr_solver.sol @@ -0,0 +1,67 @@ +PICO Solver: final f = 88.200000 + +Options +3 +0 +0 +0 +24 +24 +32 +32 +0 +0 +0.12599999999999997 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +46.666666666666664 +0 +0 +0 +0 +0 +0 +933.3333333333336 +10000 +10000 +10000 +10000 +0 +100 +0 +100 +0 +100 +0 +100 +46.666666666666664 +53.333333333333336 +0 +100 +0 +100 +0 +100 diff --git a/pyomo/contrib/solver/tests/unit/sol_files/iis_no_variable_values.sol b/pyomo/contrib/solver/tests/unit/sol_files/iis_no_variable_values.sol new file mode 100644 index 00000000000..641a3162a8f --- /dev/null +++ b/pyomo/contrib/solver/tests/unit/sol_files/iis_no_variable_values.sol @@ -0,0 +1,34 @@ +CPLEX 12.8.0.0: integer infeasible. +0 MIP simplex iterations +0 branch-and-bound nodes +Returning an IIS of 2 variables and 1 constraints. +No basis. + +Options +3 +1 +1 +0 +1 +0 +2 +0 +objno 0 220 +suffix 0 2 4 181 11 +iis + +0 non not in the iis +1 low at lower bound +2 fix fixed +3 upp at upper bound +4 mem member +5 pmem possible member +6 plow possibly at lower bound +7 pupp possibly at upper bound +8 bug + +0 1 +1 1 +suffix 1 1 4 0 0 +iis +0 4 diff --git a/pyomo/contrib/solver/tests/unit/sol_files/infeasible1.sol b/pyomo/contrib/solver/tests/unit/sol_files/infeasible1.sol new file mode 100644 index 00000000000..9e7c47f2091 --- /dev/null +++ b/pyomo/contrib/solver/tests/unit/sol_files/infeasible1.sol @@ -0,0 +1,491 @@ + +Ipopt 3.12: Converged to a locally infeasible point. Problem may be infeasible. + +Options +3 +1 +1 +0 +242 +242 +86 +86 +-3.5031247438024307e-14 +-3.5234584915901186e-14 +-3.5172095867741636e-14 +-3.530546013164763e-14 +-3.5172095867741636e-14 +-3.5305460131648396e-14 +-2.366093398247632e-13 +-2.3660933995816667e-13 +-2.366093403160036e-13 +-2.366093402111279e-13 +-2.366093403160036e-13 +-2.366093402111279e-13 +-3.230618014133495e-14 +-3.229008861611988e-14 +-3.2372291959738883e-14 +-3.233107904711923e-14 +-3.2372291959738883e-14 +-3.233107904711986e-14 +-2.366093402825742e-13 +-2.3660934046399004e-13 +-2.366093408240676e-13 +-2.3660934074259244e-13 +-2.366093408240676e-13 +-2.3660934074259244e-13 +-3.5337260190603076e-15 +-3.5384985959538063e-15 +-3.5360752870197467e-15 +-3.5401103667524204e-15 +-3.5360752870197475e-15 +-3.540110366752954e-15 +-1.1241014244910024e-13 +-7.229408362081387e-14 +-1.1241014257725814e-13 +-7.229408365067014e-14 +-1.1241014257725814e-13 +-7.229408365067014e-14 +-0.045045044618550245 +-2.2503048100082865e-13 +-0.04504504461894986 +-2.3019280438209537e-13 +-2.4246742873024166e-13 +-2.3089017630512727e-13 +-2.303517676239642e-13 +-2.3258460904987257e-13 +-2.2657149778091163e-13 +-2.3561210481068387e-13 +-2.260257681221233e-13 +-2.4196851090379605e-13 +-2.2609595226592818e-13 +-0.04504504461900244 +-2.249595193064585e-13 +-0.04504504461913233 +-2.2215413967954347e-13 +-0.045045044619133334 +1.4720100770836167e-13 +0.5405405354313707 +-1.1746366725687393e-13 +-8.181817954545458e-14 +3.3628105937413004e-10 +2.5420446367682183e-10 +-4.068865957494519e-10 +-3.3083656247909664e-10 +2.0162505532975142e-10 +1.3899803000287233e-10 +1.9264257030343367e-10 +1.5784707460270425e-10 +4.0453655296452274e-10 +1.8623815108786813e-10 +4.023012427968502e-10 +2.2427204843237042e-10 +4.285852894154949e-10 +2.7438151967949997e-10 +4.990725722952413e-10 +3.24233733037425e-10 +6.365790489375267e-10 +1.8786461752037693e-10 +9.36934851555115e-10 +1.9328729420874646e-10 +2.1302900967163764e-09 +1.9184434624295806e-10 +1.839058810801874e-10 +3.1045038304739125e-08 +2.033627397720737e-10 +1.965179362792721e-09 +3.9014568630621037e-10 +9.629991995490913e-10 +3.8529492862465446e-10 +6.543016210883198e-10 +3.1023232285992586e-10 +5.203524431666233e-10 +2.443053484937026e-10 +4.814394103716646e-10 +1.9839047821553417e-10 +2.29157081595439e-10 +1.6697733108860693e-10 +2.2885043298472609e-10 +1.4439699240241691e-10 +2.231817349184844e-10 +7.996844380007978e-07 +7.95878555840714e-07 +-6.161782990947841e-09 +-6.174783045271923e-09 +-6.180473110458713e-09 +-6.1838001759594465e-09 +-6.180473110458713e-09 +-6.183800175957144e-09 +-1.3264604647361279e-14 +-1.3437580361963064e-14 +-1.381614108205247e-14 +-1.3724139850276759e-14 +-1.381614108205247e-14 +-1.3724139850276584e-14 +-1.3264604647361279e-14 +-1.3437580361963064e-14 +-1.381614108205247e-14 +-1.3724139850276759e-14 +-1.381614108205247e-14 +-1.3724139850276584e-14 +-1.3264604647357383e-14 +-1.3264604647357383e-14 +-1.258629585661237e-14 +-1.2586303131773045e-14 +-1.2586307639008801e-14 +-1.2586311120145482e-14 +-1.2586314285443517e-14 +-1.258631748040718e-14 +-1.2586321221671653e-14 +-1.2741959563395428e-14 +-1.2741955464025058e-14 +-1.2741952925774324e-14 +-1.2741950138083889e-14 +-1.2741945491635486e-14 +-1.274193825746462e-14 +-1.3437580361959015e-14 +-1.3437580361959015e-14 +-1.3437580361959015e-14 +-1.3816141082048241e-14 +-1.3816141082048241e-14 +-1.3081851406508949e-14 +-1.308185926540242e-14 +-1.3081864134282786e-14 +-1.3081867894733614e-14 +-1.308187131400409e-14 +-1.308187476532053e-14 +-1.3081878806771144e-14 +-1.2999353684840647e-14 +-1.299934941829921e-14 +-1.2999346776539415e-14 +-1.2999343875167873e-14 +-1.2999339039238868e-14 +-1.2999331510061096e-14 +-1.3724139850272537e-14 +-1.3724139850272537e-14 +-1.3724139850272537e-14 +-1.3816141082048243e-14 +-1.3816141082048243e-14 +-1.3081851406508949e-14 +-1.3081859265402422e-14 +-1.3081864134282784e-14 +-1.3081867894733614e-14 +-1.308187131400409e-14 +-1.308187476532053e-14 +-1.3081878806771145e-14 +-1.299935368484049e-14 +-1.2999349418299049e-14 +-1.2999346776539257e-14 +-1.2999343875167712e-14 +-1.299933903923871e-14 +-1.2999331510060935e-14 +-1.3724139850272359e-14 +-1.3724139850272359e-14 +-1.3724139850272359e-14 +-0.39647376852165084 +-0.4455844823264693 +-0.3964737698727394 +-0.4455844904349083 +-0.04058112126213324 +-2.37392784926522e-13 +-0.04058112126182639 +-2.3739125313713354e-13 +-2.3738581599973924e-13 +-2.3739030469186293e-13 +-2.373886019673396e-13 +-2.3738926304868226e-13 +-2.3739032800906814e-13 +-2.373875268840388e-13 +-2.3739166112281285e-13 +-2.373848238523691e-13 +-2.3739287329689576e-13 +-0.04058112126709927 +-2.3739409684312144e-13 +-0.04058112126734901 +-2.3739552961585984e-13 +-0.040581121263560345 +-7.976233462779415e-11 +-8.149038165921345e-11 +-8.149038165921345e-11 +-8.022671984428942e-11 +-8.112229180405433e-11 +-8.112229180405698e-11 +-1.1362727144888948e-10 +-4.545363318183219e-10 +-1.5766054471383136e-10 +-999.9999999987843 +2.0239864420785628e-10 +3.6952311802810024e-10 +2.123373938372435e-10 +2.804864327332228e-10 +1.346149969721881e-10 +2.2070281853153174e-10 +1.3486437441647496e-10 +1.837701666832909e-10 +1.3214731344936636e-10 +1.59848684557641e-10 +1.2663217798563007e-10 +1.4670685236091518e-10 +1.2005152713943525e-10 +2.1846147211317584e-10 +1.1320656639453056e-10 +2.1155957764572616e-10 +1.0602947953081767e-10 +2.1331568061293854e-10 +2.2406981587244565e-10 +1.0144323269437438e-10 +2.0067712609010725e-10 +1.0647572138657723e-10 +1.3628795523686926e-10 +1.1283736217061156e-10 +1.3689006597815967e-10 +1.1944117806753888e-10 +1.4976540231691364e-10 +1.2533138246033542e-10 +1.7219937613078787e-10 +1.2782000199367948e-10 +2.0576625901474408e-10 +1.8061506448741275e-10 +2.5564782647515365e-10 +1.8080595589290967e-10 +3.3611540082361537e-10 +1.8450853640157845e-10 +-999.9999999992634 +500.00000267889834 +3700.000036997707 +3700.00003699796 +3700.000036997707 +3700.00003699796 +3700.000036977598 +3700.000036977598 +11.65620349374497 +11.697892989049905 +11.723721175743378 +11.743669409189184 +11.761807757832353 +11.780116092441125 +11.801554922843986 +11.760485435103986 +11.737564481489017 +11.723372263570411 +11.70778533743834 +11.68180544764916 +11.64135667458445 +3700.000036977598 +3700.000036977598 +3700.000036977598 +0.3151184672323908 +0.32392866804605874 +0.34244076638380455 +0.33803566597697493 +0.34244076638380455 +0.3380356659769663 +0.27110063090377123 +0.2699297687440479 +0.2929786728909554 +0.29344480424126584 +0.28838393432428394 +0.2893992806145764 +0.2710728789062779 +0.26993404119945896 +0.2934152392453943 +0.29361001971947676 +0.2884212793214469 +0.28944447549328195 +0.2710728789062779 +0.2699340411994531 +0.29341523924539437 +0.29361001971947087 +0.28842127932144684 +0.2894444754932388 +0.5508615869879336 +0.15398873818985254 +0.6718832432569866 +0.17589826345513584 +0.5247189958883286 +0.18810973351399282 +0.6259675738420305 +0.20533542867213556 +0.7121098490801165 +0.23131269225729922 +0.7821527320463884 +0.28037348913556315 +0.8428067559035302 +0.5838840489481971 +0.8970272395501521 +0.6703093152878702 +0.94267886174376 +0.7738465562949745 +0.8177198430399907 +0.9786900926762641 +0.6704296542151029 +0.9210489338249574 +0.3564282839324347 +0.8691777702202935 +0.2593618184144545 +0.8137154539828636 +0.21644752420062746 +0.7494805564573437 +0.1955192721716388 +0.6636009115148781 +0.1816326651938952 +0.7714724374833359 +0.16783059150769936 +0.6720038647474075 +0.15295832306009652 +0.5820927246947017 +0 +5.999999940000606 +3.2342062150876796 +9.747775650827162 +objno 0 200 +suffix 4 60 13 0 0 +ipopt_zU_out +22 -1.327369555645263e-09 +23 -1.3446671271054377e-09 +24 -1.382523199114386e-09 +25 -1.373323075936809e-09 +26 -1.382523199114386e-09 +27 -1.3733230759367915e-09 +28 -1.2472104315043693e-09 +29 -1.2452101972496192e-09 +30 -1.2858040647227637e-09 +31 -1.2866523403876923e-09 +32 -1.2775019286011434e-09 +33 -1.2793272952136163e-09 +34 -1.2471629472231613e-09 +35 -1.2452174844060395e-09 +36 -1.2865985041388369e-09 +37 -1.2869532717202986e-09 +38 -1.2775689743171436e-09 +39 -1.2794086668147935e-09 +40 -1.2471629472231613e-09 +41 -1.2452174844060298e-09 +42 -1.2865985041388369e-09 +43 -1.2869532717202878e-09 +44 -1.2775689743171434e-09 +45 -1.2794086668147155e-09 +46 -2.0240773556752306e-09 +47 -1.0745612255836558e-09 +48 -2.770632290509263e-09 +49 -1.103129453565228e-09 +50 -1.9127440056903688e-09 +51 -1.1197213910483093e-09 +52 -2.430513566198766e-09 +53 -1.1439932412498466e-09 +54 -3.1577699873109563e-09 +55 -1.182653712929702e-09 +56 -4.173065268467735e-09 +57 -1.2632815552706913e-09 +58 -5.783269227344645e-09 +59 -2.1847056932251413e-09 +60 -8.828459262787896e-09 +61 -2.7574054223382863e-09 +62 -1.5860201572267072e-08 +63 -4.019796745114287e-09 +64 -4.987327799213503e-09 +65 -4.128677327837785e-08 +66 -2.7584122571707027e-09 +67 -1.1514963264478648e-08 +68 -1.4125712376227499e-09 +69 -6.9490543282105264e-09 +70 -1.2274426584743552e-09 +71 -4.880119585077116e-09 +72 -1.160216995366489e-09 +73 -3.628823630675873e-09 +74 -1.13003440308759e-09 +75 -2.7024178093492304e-09 +76 -1.1108592195439713e-09 +77 -3.978035995523888e-09 +78 -1.0924348929579286e-09 +79 -2.7716511991201962e-09 +80 -1.073254036073809e-09 +81 -2.175341139896496e-09 +suffix 4 86 13 0 0 +ipopt_zL_out +0 2.457002432427315e-13 +1 2.457002432427147e-13 +2 2.457002432427315e-13 +3 2.457002432427147e-13 +4 2.457002432440668e-13 +5 2.457002432440668e-13 +6 7.799202448711829e-11 +7 7.771407288173584e-11 +8 7.754286328443318e-11 +9 7.741114609420585e-11 +10 7.72917673061454e-11 +11 7.717164255304123e-11 +12 7.703145172513595e-11 +13 7.730045781990877e-11 +14 7.7451409084917e-11 +15 7.754517112285163e-11 +16 7.76484093372809e-11 +17 7.782109643810629e-11 +18 7.809149171545744e-11 +19 2.457002432440668e-13 +20 2.457002432440668e-13 +21 2.457002432440668e-13 +22 2.88491781594494e-09 +23 2.806453922602062e-09 +24 2.6547390725285084e-09 +25 2.6893342144319893e-09 +26 2.6547390725285084e-09 +27 2.6893342144320575e-09 +28 3.3533336782625715e-09 +29 3.367879281546927e-09 +30 3.1029251008167857e-09 +31 3.0979961649984553e-09 +32 3.152363115331538e-09 +33 3.1413031705213295e-09 +34 3.353676987058653e-09 +35 3.3678259755079893e-09 +36 3.0983083240635833e-09 +37 3.096252910785026e-09 +38 3.1519549450665203e-09 +39 3.1408126764021113e-09 +40 3.353676987058653e-09 +41 3.367825975508062e-09 +42 3.0983083240635824e-09 +43 3.0962529107850877e-09 +44 3.151954945066521e-09 +45 3.140812676402579e-09 +46 1.6503072927322882e-09 +47 5.903619062223097e-09 +48 1.3530489183372102e-09 +49 5.168276510428202e-09 +50 1.7325290303934247e-09 +51 4.8327689212818915e-09 +52 1.4522971044995076e-09 +53 4.4273454737645e-09 +54 1.276616097383978e-09 +55 3.930138360770138e-09 +56 1.1622933223262232e-09 +57 3.242428123819113e-09 +58 1.0786469044524248e-09 +59 1.556971619947646e-09 +60 1.0134484872637181e-09 +61 1.356225961423535e-09 +62 9.643698375125132e-10 +63 1.174768939146355e-09 +64 1.1117388275802617e-09 +65 9.288986889801197e-10 +66 1.3559825252250914e-09 +67 9.870172368223874e-10 +68 2.55055764727633e-09 +69 1.0459205566343963e-09 +70 3.5051068618760334e-09 +71 1.1172098225860037e-09 +72 4.2000521577056155e-09 +73 1.212961283078632e-09 +74 4.649622902405193e-09 +75 1.3699361786951016e-09 +76 5.005106744564875e-09 +77 1.1783841562800436e-09 +78 5.416717299785639e-09 +79 1.3528060526165563e-09 +80 5.943389257560972e-09 +81 1.561763024323873e-09 +82 500.00000026951534 +83 1.515151527777625e-10 +84 2.8108595681091103e-10 +85 9.326135918021712e-11 diff --git a/pyomo/contrib/solver/tests/unit/sol_files/infeasible2.sol b/pyomo/contrib/solver/tests/unit/sol_files/infeasible2.sol new file mode 100644 index 00000000000..6fddb053745 --- /dev/null +++ b/pyomo/contrib/solver/tests/unit/sol_files/infeasible2.sol @@ -0,0 +1,13 @@ + + Couenne (C:\Users\SASCHA~1\AppData\Local\Temp\tmpvcmknhw0.pyomo.nl May 18 2015): Infeasible + +Options +3 +0 +1 +0 +242 +0 +86 +0 +objno 0 220 diff --git a/pyomo/contrib/solver/tests/unit/test_base.py b/pyomo/contrib/solver/tests/unit/test_base.py index cda1631d921..59a80ba270b 100644 --- a/pyomo/contrib/solver/tests/unit/test_base.py +++ b/pyomo/contrib/solver/tests/unit/test_base.py @@ -189,7 +189,7 @@ def test_class_method_list(self): def test_context_manager(self): with base.LegacySolverWrapper() as instance: - with self.assertRaises(AttributeError) as context: + with self.assertRaises(AttributeError): instance.available() def test_map_config(self): @@ -209,14 +209,14 @@ def test_map_config(self): self.assertFalse(instance.config.load_solutions) self.assertEqual(instance.config.time_limit, 20) # Report timing shouldn't be created because it no longer exists - with self.assertRaises(AttributeError) as context: + with self.assertRaises(AttributeError): print(instance.config.report_timing) # Keepfiles should not be created because we did not declare keepfiles on # the original config - with self.assertRaises(AttributeError) as context: + with self.assertRaises(AttributeError): print(instance.config.keepfiles) # We haven't implemented solver_io, suffixes, or logfile - with self.assertRaises(NotImplementedError) as context: + with self.assertRaises(NotImplementedError): instance._map_config( False, False, @@ -231,7 +231,7 @@ def test_map_config(self): None, None, ) - with self.assertRaises(NotImplementedError) as context: + with self.assertRaises(NotImplementedError): instance._map_config( False, False, @@ -246,7 +246,7 @@ def test_map_config(self): None, None, ) - with self.assertRaises(NotImplementedError) as context: + with self.assertRaises(NotImplementedError): instance._map_config( False, False, @@ -266,7 +266,7 @@ def test_map_config(self): False, False, False, 20, False, False, None, None, None, True, None, None ) self.assertEqual(instance.config.working_dir, os.getcwd()) - with self.assertRaises(AttributeError) as context: + with self.assertRaises(AttributeError): print(instance.config.keepfiles) def test_map_results(self): diff --git a/pyomo/contrib/solver/tests/unit/test_sol_reader.py b/pyomo/contrib/solver/tests/unit/test_sol_reader.py new file mode 100644 index 00000000000..0ab94dfc4ac --- /dev/null +++ b/pyomo/contrib/solver/tests/unit/test_sol_reader.py @@ -0,0 +1,51 @@ +# ___________________________________________________________________________ +# +# 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.common import unittest +from pyomo.common.fileutils import this_file_dir +from pyomo.common.tempfiles import TempfileManager +from pyomo.contrib.solver.sol_reader import parse_sol_file, SolFileData + +currdir = this_file_dir() + + +class TestSolFileData(unittest.TestCase): + def test_default_instantiation(self): + instance = SolFileData() + self.assertIsInstance(instance.primals, list) + self.assertIsInstance(instance.duals, list) + self.assertIsInstance(instance.var_suffixes, dict) + self.assertIsInstance(instance.con_suffixes, dict) + self.assertIsInstance(instance.obj_suffixes, dict) + self.assertIsInstance(instance.problem_suffixes, dict) + self.assertIsInstance(instance.other, list) + + +class TestSolParser(unittest.TestCase): + # I am not sure how to write these tests best since the sol parser requires + # not only a file but also the nl_info and results objects. + def setUp(self): + TempfileManager.push() + + def tearDown(self): + TempfileManager.pop(remove=True) + + def test_default_behavior(self): + pass + + def test_custom_behavior(self): + pass + + def test_infeasible1(self): + pass + + def test_infeasible2(self): + pass From f4355927cfe9592e1a88ce351fd5b77e8c35d080 Mon Sep 17 00:00:00 2001 From: Sakshi <73687517+Sakshi21299@users.noreply.github.com> Date: Wed, 14 Feb 2024 17:38:24 -0500 Subject: [PATCH 0968/1204] Update pyomo/contrib/incidence_analysis/tests/test_interface.py Co-authored-by: Bethany Nicholson --- pyomo/contrib/incidence_analysis/tests/test_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/incidence_analysis/tests/test_interface.py b/pyomo/contrib/incidence_analysis/tests/test_interface.py index 01c15c9c84d..2908a4dd04a 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_interface.py +++ b/pyomo/contrib/incidence_analysis/tests/test_interface.py @@ -1802,7 +1802,7 @@ def test_add_edge(self): igraph = IncidenceGraphInterface(m, linear_only=False) n_edges_original = igraph.n_edges - # Test edge is added between previously unconnectes nodes + # Test edge is added between previously unconnected nodes igraph.add_edge(m.x[1], m.eq3) n_edges_new = igraph.n_edges assert ComponentSet(igraph.get_adjacent_to(m.eq3)) == ComponentSet(m.x[:]) From dbb92306b29882ee94f481349ffb128ab7eee37c Mon Sep 17 00:00:00 2001 From: Sakshi <73687517+Sakshi21299@users.noreply.github.com> Date: Wed, 14 Feb 2024 17:40:44 -0500 Subject: [PATCH 0969/1204] fix typo --- pyomo/contrib/incidence_analysis/tests/test_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/incidence_analysis/tests/test_interface.py b/pyomo/contrib/incidence_analysis/tests/test_interface.py index 2908a4dd04a..3ec37d5531a 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_interface.py +++ b/pyomo/contrib/incidence_analysis/tests/test_interface.py @@ -1836,7 +1836,7 @@ def test_var_elim(self): m.eq4 = pyo.Constraint(expr=m.x[1] == 5 * m.x[2]) igraph = IncidenceGraphInterface(m) - # Eliminate x[1] usinf eq4 + # Eliminate x[1] using eq4 for adj_con in igraph.get_adjacent_to(m.x[1]): for adj_var in igraph.get_adjacent_to(m.eq4): igraph.add_edge(adj_var, adj_con) From 273fd72d1587093b67eca832d9606f78b3a88b1b Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 14 Feb 2024 15:50:17 -0700 Subject: [PATCH 0970/1204] Add init file --- pyomo/contrib/solver/tests/unit/sol_files/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 pyomo/contrib/solver/tests/unit/sol_files/__init__.py diff --git a/pyomo/contrib/solver/tests/unit/sol_files/__init__.py b/pyomo/contrib/solver/tests/unit/sol_files/__init__.py new file mode 100644 index 00000000000..d93cfd77b3c --- /dev/null +++ b/pyomo/contrib/solver/tests/unit/sol_files/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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 b0ecba2421219e46679c47578d82ce67bd15db04 Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 14 Feb 2024 20:10:29 -0500 Subject: [PATCH 0971/1204] Update name and base class of solver arg exception --- pyomo/contrib/pyros/config.py | 8 ++++---- pyomo/contrib/pyros/tests/test_config.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index 3256a333fdc..798e68b157f 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -15,7 +15,7 @@ InEnum, Path, ) -from pyomo.common.errors import ApplicationError +from pyomo.common.errors import ApplicationError, PyomoException from pyomo.core.base import Var, _VarData from pyomo.core.base.param import Param, _ParamData from pyomo.opt import SolverFactory @@ -303,7 +303,7 @@ def domain_name(self): ) -class NotSolverResolvable(Exception): +class SolverNotResolvable(PyomoException): """ Exception type for failure to cast an object to a Pyomo solver. """ @@ -382,7 +382,7 @@ def __call__(self, obj, require_available=None, solver_desc=None): Raises ------ - NotSolverResolvable + SolverNotResolvable If `obj` cannot be cast to a Pyomo solver because it is neither a str nor a Pyomo solver type. ApplicationError @@ -405,7 +405,7 @@ def __call__(self, obj, require_available=None, solver_desc=None): elif self.is_solver_type(obj): solver = obj else: - raise NotSolverResolvable( + raise SolverNotResolvable( f"Cannot cast object `{obj!r}` to a Pyomo optimizer for use as " f"{solver_desc}, as the object is neither a str nor a " f"Pyomo Solver type (got type {type(obj).__name__})." diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index 3113afaac89..eaed462a9b3 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -15,7 +15,7 @@ InputDataStandardizer, mutable_param_validator, LoggerType, - NotSolverResolvable, + SolverNotResolvable, PathLikeOrNone, PositiveIntOrMinusOne, pyros_config, @@ -397,7 +397,7 @@ def test_solver_resolvable_invalid_type(self): r"Cannot cast object `2` to a Pyomo optimizer.*" r"local solver.*got type int.*" ) - with self.assertRaisesRegex(NotSolverResolvable, exc_str): + with self.assertRaisesRegex(SolverNotResolvable, exc_str): standardizer_func(invalid_object) def test_solver_resolvable_unavailable_solver(self): @@ -542,7 +542,7 @@ def test_solver_iterable_invalid_list(self): r"Cannot cast object `2` to a Pyomo optimizer.*" r"backup solver.*index 1.*got type int.*" ) - with self.assertRaisesRegex(NotSolverResolvable, exc_str): + with self.assertRaisesRegex(SolverNotResolvable, exc_str): standardizer_func(invalid_object) From f52db9d2efe9fdc1317183ad8c9e9f347d706bab Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 15 Feb 2024 07:47:40 -0700 Subject: [PATCH 0972/1204] Update base member unit test - better checking logic --- pyomo/contrib/solver/tests/unit/test_base.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyomo/contrib/solver/tests/unit/test_base.py b/pyomo/contrib/solver/tests/unit/test_base.py index 59a80ba270b..b8d5c79fc0f 100644 --- a/pyomo/contrib/solver/tests/unit/test_base.py +++ b/pyomo/contrib/solver/tests/unit/test_base.py @@ -104,7 +104,6 @@ def test_class_method_list(self): expected_list = [ 'Availability', 'CONFIG', - '_abc_impl', '_get_duals', '_get_primals', '_get_reduced_costs', @@ -129,7 +128,7 @@ def test_class_method_list(self): method_list = [ method for method in dir(base.PersistentSolverBase) - if method.startswith('__') is False + if (method.startswith('__') or method.startswith('_abc')) is False ] self.assertEqual(sorted(expected_list), sorted(method_list)) From b99221cfaf58d1bada29f3e8d059c1ca4a6a1585 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 15 Feb 2024 08:32:43 -0700 Subject: [PATCH 0973/1204] Address missing coverage for several modules --- pyomo/common/config.py | 2 ++ pyomo/common/tests/test_config.py | 18 +++++++++++++++ .../contrib/solver/tests/unit/test_results.py | 22 +++++++++++++++++++ .../solver/tests/unit/test_solution.py | 6 +++++ 4 files changed, 48 insertions(+) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index 657765fdc02..4adb0299f0e 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -211,6 +211,8 @@ def Datetime(val): This domain will return the original object, assuming it is of the right type. """ + if val is None: + return val if not isinstance(val, datetime.datetime): raise ValueError(f"Expected datetime object, but received {type(val)}.") return val diff --git a/pyomo/common/tests/test_config.py b/pyomo/common/tests/test_config.py index e2a8c0fb591..ac23e4c54d3 100644 --- a/pyomo/common/tests/test_config.py +++ b/pyomo/common/tests/test_config.py @@ -25,6 +25,7 @@ # ___________________________________________________________________________ import argparse +import datetime import enum import os import os.path @@ -47,6 +48,7 @@ def yaml_load(arg): ConfigDict, ConfigValue, ConfigList, + Datetime, MarkImmutable, ImmutableConfigValue, Bool, @@ -738,6 +740,22 @@ def _rule(key, val): } ) + def test_Datetime(self): + c = ConfigDict() + c.declare('a', ConfigValue(domain=Datetime, default=None)) + self.assertEqual(c.get('a').domain_name(), 'Datetime') + + self.assertEqual(c.a, None) + c.a = datetime.datetime(2022, 1, 1) + self.assertEqual(c.a, datetime.datetime(2022, 1, 1)) + + with self.assertRaises(ValueError): + c.a = 5 + with self.assertRaises(ValueError): + c.a = 'Hello' + with self.assertRaises(ValueError): + c.a = False + class TestImmutableConfigValue(unittest.TestCase): def test_immutable_config_value(self): diff --git a/pyomo/contrib/solver/tests/unit/test_results.py b/pyomo/contrib/solver/tests/unit/test_results.py index caef82129ec..4672903cb43 100644 --- a/pyomo/contrib/solver/tests/unit/test_results.py +++ b/pyomo/contrib/solver/tests/unit/test_results.py @@ -9,6 +9,9 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +import sys +from io import StringIO + from pyomo.common import unittest from pyomo.common.config import ConfigDict from pyomo.contrib.solver import results @@ -118,6 +121,25 @@ def test_default_initialization(self): ): res.solution_loader.get_reduced_costs() + def test_display(self): + res = results.Results() + stream = StringIO() + res.display(ostream=stream) + expected_print = """solution_loader: None +termination_condition: TerminationCondition.unknown +solution_status: SolutionStatus.noSolution +incumbent_objective: None +objective_bound: None +solver_name: None +solver_version: None +iteration_count: None +timing_info: + start_timestamp: None + wall_time: None +extra_info: +""" + self.assertEqual(expected_print, stream.getvalue()) + def test_generated_results(self): m = pyo.ConcreteModel() m.x = ScalarVar() diff --git a/pyomo/contrib/solver/tests/unit/test_solution.py b/pyomo/contrib/solver/tests/unit/test_solution.py index bbcc85bdac8..7a18344d4cb 100644 --- a/pyomo/contrib/solver/tests/unit/test_solution.py +++ b/pyomo/contrib/solver/tests/unit/test_solution.py @@ -80,3 +80,9 @@ def test_default_initialization(self): self.instance = PersistentSolutionLoader('ipopt') self.assertTrue(self.instance._valid) self.assertEqual(self.instance._solver, 'ipopt') + + def test_invalid(self): + self.instance = PersistentSolutionLoader('ipopt') + self.instance.invalidate() + with self.assertRaises(RuntimeError): + self.instance.get_primals() From a45e0854746ab4d54b69a8160bab8386ff3b07e0 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 15 Feb 2024 08:38:43 -0700 Subject: [PATCH 0974/1204] Add copyright statements; clean up unused imports --- pyomo/contrib/solver/gurobi.py | 26 +++++++++++++------ .../tests/solvers/test_gurobi_persistent.py | 15 ++++++++--- .../solver/tests/solvers/test_solvers.py | 12 ++++++++- .../contrib/solver/tests/unit/test_results.py | 1 - 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/pyomo/contrib/solver/gurobi.py b/pyomo/contrib/solver/gurobi.py index 50d241e1e88..919e7ae3995 100644 --- a/pyomo/contrib/solver/gurobi.py +++ b/pyomo/contrib/solver/gurobi.py @@ -1,7 +1,18 @@ +# ___________________________________________________________________________ +# +# 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 collections.abc import Iterable import logging import math -from typing import List, Dict, Optional +from typing import List, Optional from pyomo.common.collections import ComponentSet, ComponentMap, OrderedSet from pyomo.common.log import LogStream from pyomo.common.dependencies import attempt_import @@ -9,10 +20,10 @@ from pyomo.common.tee import capture_output, TeeStream from pyomo.common.timing import HierarchicalTimer from pyomo.common.shutdown import python_is_shutting_down -from pyomo.common.config import ConfigValue, NonNegativeInt +from pyomo.common.config import ConfigValue from pyomo.core.kernel.objective import minimize, maximize from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler -from pyomo.core.base.var import Var, _GeneralVarData +from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.sos import _SOSConstraintData from pyomo.core.base.param import _ParamData @@ -28,7 +39,6 @@ import sys import datetime import io -from pyomo.contrib.solver.factory import SolverFactory logger = logging.getLogger(__name__) @@ -1137,9 +1147,9 @@ def set_linear_constraint_attr(self, con, attr, val): """ if attr in {'Sense', 'RHS', 'ConstrName'}: raise ValueError( - 'Linear constraint attr {0} cannot be set with' + 'Linear constraint attr {0} cannot be set with'.format(attr) + ' the set_linear_constraint_attr method. Please use' - + ' the remove_constraint and add_constraint methods.'.format(attr) + + ' the remove_constraint and add_constraint methods.' ) self._pyomo_con_to_solver_con_map[con].setAttr(attr, val) self._needs_updated = True @@ -1166,9 +1176,9 @@ def set_var_attr(self, var, attr, val): """ if attr in {'LB', 'UB', 'VType', 'VarName'}: raise ValueError( - 'Var attr {0} cannot be set with' + 'Var attr {0} cannot be set with'.format(attr) + ' the set_var_attr method. Please use' - + ' the update_var method.'.format(attr) + + ' the update_var method.' ) if attr == 'Obj': raise ValueError( diff --git a/pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py b/pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py index f53088506f9..d4c0078a0df 100644 --- a/pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py +++ b/pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py @@ -1,9 +1,18 @@ -from pyomo.common.errors import PyomoException +# ___________________________________________________________________________ +# +# 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.common.unittest as unittest import pyomo.environ as pe from pyomo.contrib.solver.gurobi import Gurobi -from pyomo.contrib.solver.results import TerminationCondition, SolutionStatus -from pyomo.core.expr.numeric_expr import LinearExpression +from pyomo.contrib.solver.results import SolutionStatus from pyomo.core.expr.taylor_series import taylor_series_expansion diff --git a/pyomo/contrib/solver/tests/solvers/test_solvers.py b/pyomo/contrib/solver/tests/solvers/test_solvers.py index 6b798f9bafd..36f3596e890 100644 --- a/pyomo/contrib/solver/tests/solvers/test_solvers.py +++ b/pyomo/contrib/solver/tests/solvers/test_solvers.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pe from pyomo.common.dependencies import attempt_import import pyomo.common.unittest as unittest @@ -10,7 +21,6 @@ from pyomo.contrib.solver.gurobi import Gurobi from typing import Type from pyomo.core.expr.numeric_expr import LinearExpression -import os import math numpy, numpy_available = attempt_import('numpy') diff --git a/pyomo/contrib/solver/tests/unit/test_results.py b/pyomo/contrib/solver/tests/unit/test_results.py index 4672903cb43..8e16a1384ee 100644 --- a/pyomo/contrib/solver/tests/unit/test_results.py +++ b/pyomo/contrib/solver/tests/unit/test_results.py @@ -9,7 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -import sys from io import StringIO from pyomo.common import unittest From 6d21b71c6c95b448ff4cb16ff4d8b646611becc7 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Thu, 15 Feb 2024 08:52:17 -0700 Subject: [PATCH 0975/1204] updating docs --- doc/OnlineDocs/conf.py | 1 + .../developer_reference/solvers.rst | 35 +++++++++++-------- pyomo/contrib/solver/base.py | 29 +++++++-------- pyomo/contrib/solver/results.py | 4 +-- 4 files changed, 38 insertions(+), 31 deletions(-) diff --git a/doc/OnlineDocs/conf.py b/doc/OnlineDocs/conf.py index ef6510daedf..88f84ec8b37 100644 --- a/doc/OnlineDocs/conf.py +++ b/doc/OnlineDocs/conf.py @@ -72,6 +72,7 @@ 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx_copybutton', + 'enum_tools.autoenum', #'sphinx.ext.githubpages', ] diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index fa24d69a211..45d8e55daf9 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -10,7 +10,8 @@ Interface Implementation ------------------------ All new interfaces should be built upon one of two classes (currently): -``pyomo.contrib.solver.base.SolverBase`` or ``pyomo.contrib.solver.base.PersistentSolverBase``. +:class:`SolverBase` or +:class:`PersistentSolverBase`. All solvers should have the following: @@ -26,9 +27,11 @@ Persistent solvers should also include: Results ------- -Every solver, at the end of a ``solve`` call, will return a ``Results`` object. -This object is a :py:class:`pyomo.common.config.ConfigDict`, which can be manipulated similar -to a standard ``dict`` in Python. +Every solver, at the end of a +:meth:`solve` call, will +return a :class:`Results` +object. This object is a :py:class:`pyomo.common.config.ConfigDict`, +which can be manipulated similar to a standard ``dict`` in Python. .. autoclass:: pyomo.contrib.solver.results.Results :show-inheritance: @@ -40,28 +43,29 @@ Termination Conditions ^^^^^^^^^^^^^^^^^^^^^^ Pyomo offers a standard set of termination conditions to map to solver -returns. The intent of ``TerminationCondition`` is to notify the user of why -the solver exited. The user is expected to inspect the ``Results`` object or any -returned solver messages or logs for more information. - - +returns. The intent of +:class:`TerminationCondition` +is to notify the user of why the solver exited. The user is expected +to inspect the :class:`Results` +object or any returned solver messages or logs for more information. .. autoclass:: pyomo.contrib.solver.results.TerminationCondition :show-inheritance: - :noindex: Solution Status ^^^^^^^^^^^^^^^ -Pyomo offers a standard set of solution statuses to map to solver output. The -intent of ``SolutionStatus`` is to notify the user of what the solver returned -at a high level. The user is expected to inspect the ``Results`` object or any +Pyomo offers a standard set of solution statuses to map to solver +output. The intent of +:class:`SolutionStatus` +is to notify the user of what the solver returned at a high level. The +user is expected to inspect the +:class:`Results` object or any returned solver messages or logs for more information. .. autoclass:: pyomo.contrib.solver.results.SolutionStatus :show-inheritance: - :noindex: Solution @@ -71,6 +75,7 @@ Solutions can be loaded back into a model using a ``SolutionLoader``. A specific loader should be written for each unique case. Several have already been implemented. For example, for ``ipopt``: -.. autoclass:: pyomo.contrib.solver.solution.SolSolutionLoader +.. autoclass:: pyomo.contrib.solver.ipopt.ipoptSolutionLoader :show-inheritance: :members: + :inherited-members: diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 96b87924bf6..aad8d10ec63 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -40,15 +40,18 @@ class SolverBase(abc.ABC): """ - Base class upon which direct solver interfaces can be built. - - This base class contains the required methods for all direct solvers: - - available: Determines whether the solver is able to be run, combining - both whether it can be found on the system and if the license is valid. - - config: The configuration method for solver objects. + This base class defines the methods required for all solvers: + - available: Determines whether the solver is able to be run, + combining both whether it can be found on the system and if the license is valid. - solve: The main method of every solver - version: The version of the solver - - is_persistent: Set to false for all direct solvers. + - is_persistent: Set to false for all non-persistent solvers. + + Additionally, solvers should have a :attr:`config` attribute that + inherits from one of :class:`SolverConfig`, + :class:`BranchAndBoundConfig`, + :class:`PersistentSolverConfig`, or + :class:`PersistentBranchAndBoundConfig`. """ CONFIG = SolverConfig() @@ -104,7 +107,7 @@ def __str__(self): @abc.abstractmethod def solve( - self, model: _BlockData, timer: HierarchicalTimer = None, **kwargs + self, model: _BlockData, **kwargs ) -> Results: """ Solve a Pyomo model. @@ -113,15 +116,13 @@ def solve( ---------- model: _BlockData The Pyomo model to be solved - timer: HierarchicalTimer - An option timer for reporting timing **kwargs Additional keyword arguments (including solver_options - passthrough options; delivered directly to the solver (with no validation)) Returns ------- - results: Results + results: :class:`Results` A results object """ @@ -144,7 +145,7 @@ def available(self): Returns ------- - available: Solver.Availability + available: SolverBase.Availability An enum that indicates "how available" the solver is. Note that the enum can be cast to bool, which will be True if the solver is runable at all and False @@ -173,10 +174,10 @@ def is_persistent(self): class PersistentSolverBase(SolverBase): """ Base class upon which persistent solvers can be built. This inherits the - methods from the direct solver base and adds those methods that are necessary + methods from the solver base class and adds those methods that are necessary for persistent solvers. - Example usage can be seen in the GUROBI solver. + Example usage can be seen in the Gurobi interface. """ def is_persistent(self): diff --git a/pyomo/contrib/solver/results.py b/pyomo/contrib/solver/results.py index 5ed6de44430..e80bad126a1 100644 --- a/pyomo/contrib/solver/results.py +++ b/pyomo/contrib/solver/results.py @@ -139,10 +139,10 @@ class Results(ConfigDict): ---------- solution_loader: SolutionLoaderBase Object for loading the solution back into the model. - termination_condition: TerminationCondition + termination_condition: :class:`TerminationCondition` The reason the solver exited. This is a member of the TerminationCondition enum. - solution_status: SolutionStatus + solution_status: :class:`SolutionStatus` The result of the solve call. This is a member of the SolutionStatus enum. incumbent_objective: float From 69905e5ddfa4a02f2ce19ec0537a06c3a4711ade Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 15 Feb 2024 09:47:56 -0700 Subject: [PATCH 0976/1204] Only using infeasible termination condition from trusted solvers, adding tests --- pyomo/gdp/plugins/multiple_bigm.py | 94 +++++++++++++----------------- pyomo/gdp/tests/test_mbigm.py | 39 ++++++++++++- 2 files changed, 79 insertions(+), 54 deletions(-) diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index 1cd9af2911b..bdab6363c3a 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -59,6 +59,8 @@ logger = logging.getLogger('pyomo.gdp.mbigm') +_trusted_solvers = {'gurobi', 'cplex', 'cbc', 'glpk', 'scip', 'xpress_direct', + 'mosek_direct', 'baron', 'apsi_highs'} @TransformationFactory.register( 'gdp.mbigm', @@ -623,68 +625,25 @@ def _calculate_missing_M_values( self.used_args[constraint, other_disjunct] = (lower_M, upper_M) else: (lower_M, upper_M) = (None, None) + unsuccessful_solve_msg = ( + "Unsuccessful solve to calculate M value to " + "relax constraint '%s' on Disjunct '%s' when " + "Disjunct '%s' is selected." + % (constraint.name, disjunct.name, other_disjunct.name)) if constraint.lower is not None and lower_M is None: # last resort: calculate if lower_M is None: scratch.obj.expr = constraint.body - constraint.lower scratch.obj.sense = minimize - results = self._config.solver.solve( - other_disjunct, load_solutions=False - ) - if ( - results.solver.termination_condition - is TerminationCondition.infeasible - ): - logger.debug( - "Disjunct '%s' is infeasible, deactivating." - % other_disjunct.name - ) - other_disjunct.deactivate() - lower_M = 0 - elif ( - results.solver.termination_condition - is not TerminationCondition.optimal - ): - raise GDP_Error( - "Unsuccessful solve to calculate M value to " - "relax constraint '%s' on Disjunct '%s' when " - "Disjunct '%s' is selected." - % (constraint.name, disjunct.name, other_disjunct.name) - ) - else: - other_disjunct.solutions.load_from(results) - lower_M = value(scratch.obj.expr) + lower_M = self._solve_disjunct_for_M(other_disjunct, scratch, + unsuccessful_solve_msg) if constraint.upper is not None and upper_M is None: # last resort: calculate if upper_M is None: scratch.obj.expr = constraint.body - constraint.upper scratch.obj.sense = maximize - results = self._config.solver.solve( - other_disjunct, load_solutions=False - ) - if ( - results.solver.termination_condition - is TerminationCondition.infeasible - ): - logger.debug( - "Disjunct '%s' is infeasible, deactivating." - % other_disjunct.name - ) - other_disjunct.deactivate() - upper_M = 0 - elif ( - results.solver.termination_condition - is not TerminationCondition.optimal - ): - raise GDP_Error( - "Unsuccessful solve to calculate M value to " - "relax constraint '%s' on Disjunct '%s' when " - "Disjunct '%s' is selected." - % (constraint.name, disjunct.name, other_disjunct.name) - ) - else: - other_disjunct.solutions.load_from(results) - upper_M = value(scratch.obj.expr) + upper_M = self._solve_disjunct_for_M(other_disjunct, scratch, + unsuccessful_solve_msg) arg_Ms[constraint, other_disjunct] = (lower_M, upper_M) transBlock._mbm_values[constraint, other_disjunct] = (lower_M, upper_M) @@ -694,6 +653,37 @@ def _calculate_missing_M_values( return arg_Ms + def _solve_disjunct_for_M(self, other_disjunct, scratch_block, + unsuccessful_solve_msg): + solver = self._config.solver + solver_trusted = solver.name in _trusted_solvers + results = solver.solve(other_disjunct, load_solutions=False) + if (results.solver.termination_condition is + TerminationCondition.infeasible ): + if solver_trusted: + logger.debug( + "Disjunct '%s' is infeasible, deactivating." + % other_disjunct.name + ) + other_disjunct.deactivate() + M = 0 + else: + # This is a solver that might report + # 'infeasible' for local infeasibility, so we + # can't deactivate with confidence. To be + # conservative, we'll just complain about + # it. Post-solver-rewrite we will want to change + # this so that we check for 'proven_infeasible' + # and then we can abandon this hack + raise GDP_Error(unsuccessful_solve_msg) + elif (results.solver.termination_condition is not + TerminationCondition.optimal): + raise GDP_Error(unsuccessful_solve_msg) + else: + other_disjunct.solutions.load_from(results) + M = value(scratch_block.obj.expr) + return M + def _warn_for_active_suffix(self, suffix, disjunct, active_disjuncts, Ms): if suffix.local_name == 'BigM': logger.debug( diff --git a/pyomo/gdp/tests/test_mbigm.py b/pyomo/gdp/tests/test_mbigm.py index 51230bab075..d65f6e350dd 100644 --- a/pyomo/gdp/tests/test_mbigm.py +++ b/pyomo/gdp/tests/test_mbigm.py @@ -992,8 +992,7 @@ def test_two_term_indexed_disjunction(self): class EdgeCases(unittest.TestCase): - @unittest.skipUnless(gurobi_available, "Gurobi is not available") - def test_calculate_Ms_infeasible_Disjunct(self): + def make_infeasible_disjunct_model(self): m = ConcreteModel() m.x = Var(bounds=(1, 12)) m.y = Var(bounds=(19, 22)) @@ -1004,7 +1003,11 @@ def test_calculate_Ms_infeasible_Disjunct(self): [m.x == m.y - 9], # x in interval [10, 12] ] ) + return m + @unittest.skipUnless(gurobi_available, "Gurobi is not available") + def test_calculate_Ms_infeasible_Disjunct(self): + m = self.make_infeasible_disjunct_model() out = StringIO() mbm = TransformationFactory('gdp.mbigm') with LoggingIntercept(out, 'pyomo.gdp.mbigm', logging.DEBUG): @@ -1050,3 +1053,35 @@ def test_calculate_Ms_infeasible_Disjunct(self): <= 0.0 * m.disjunction_disjuncts[0].binary_indicator_var - 12.0 * m.disjunction_disjuncts[1].binary_indicator_var, ) + + @unittest.skipUnless(SolverFactory('ipopt').available(exception_flag=False), + "Ipopt is not available") + def test_calculate_Ms_infeasible_Disjunct_local_solver(self): + m = self.make_infeasible_disjunct_model() + with self.assertRaisesRegex( + GDP_Error, + r"Unsuccessful solve to calculate M value to " + r"relax constraint 'disjunction_disjuncts\[1\].constraint\[1\]' " + r"on Disjunct 'disjunction_disjuncts\[1\]' when " + r"Disjunct 'disjunction_disjuncts\[0\]' is selected."): + TransformationFactory('gdp.mbigm').apply_to( + m, solver=SolverFactory('ipopt'), + reduce_bound_constraints=False) + + @unittest.skipUnless(gurobi_available, "Gurobi is not available") + def test_politely_ignore_BigM_Suffix(self): + m = self.make_infeasible_disjunct_model() + m.disjunction.disjuncts[0].deactivate() + m.disjunction.disjuncts[1].BigM = Suffix(direction=Suffix.LOCAL) + out = StringIO() + with LoggingIntercept(out, 'pyomo.gdp.mbigm', logging.DEBUG): + TransformationFactory('gdp.mbigm').apply_to( + m, reduce_bound_constraints=False) + warnings = out.getvalue() + self.assertIn( + r"Found active 'BigM' Suffix on 'disjunction_disjuncts[1]'. " + r"The multiple bigM transformation does not currently " + r"support specifying M's with Suffixes and is ignoring " + r"this Suffix.", + warnings, + ) From 877c0aa57bcfc02e559a1736bfda97fb60ba08e7 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 15 Feb 2024 09:48:16 -0700 Subject: [PATCH 0977/1204] black --- pyomo/gdp/plugins/multiple_bigm.py | 41 +++++++++++++++++++----------- pyomo/gdp/tests/test_mbigm.py | 29 +++++++++++---------- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index bdab6363c3a..803d2e80807 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -59,8 +59,18 @@ logger = logging.getLogger('pyomo.gdp.mbigm') -_trusted_solvers = {'gurobi', 'cplex', 'cbc', 'glpk', 'scip', 'xpress_direct', - 'mosek_direct', 'baron', 'apsi_highs'} +_trusted_solvers = { + 'gurobi', + 'cplex', + 'cbc', + 'glpk', + 'scip', + 'xpress_direct', + 'mosek_direct', + 'baron', + 'apsi_highs', +} + @TransformationFactory.register( 'gdp.mbigm', @@ -629,21 +639,24 @@ def _calculate_missing_M_values( "Unsuccessful solve to calculate M value to " "relax constraint '%s' on Disjunct '%s' when " "Disjunct '%s' is selected." - % (constraint.name, disjunct.name, other_disjunct.name)) + % (constraint.name, disjunct.name, other_disjunct.name) + ) if constraint.lower is not None and lower_M is None: # last resort: calculate if lower_M is None: scratch.obj.expr = constraint.body - constraint.lower scratch.obj.sense = minimize - lower_M = self._solve_disjunct_for_M(other_disjunct, scratch, - unsuccessful_solve_msg) + lower_M = self._solve_disjunct_for_M( + other_disjunct, scratch, unsuccessful_solve_msg + ) if constraint.upper is not None and upper_M is None: # last resort: calculate if upper_M is None: scratch.obj.expr = constraint.body - constraint.upper scratch.obj.sense = maximize - upper_M = self._solve_disjunct_for_M(other_disjunct, scratch, - unsuccessful_solve_msg) + upper_M = self._solve_disjunct_for_M( + other_disjunct, scratch, unsuccessful_solve_msg + ) arg_Ms[constraint, other_disjunct] = (lower_M, upper_M) transBlock._mbm_values[constraint, other_disjunct] = (lower_M, upper_M) @@ -653,17 +666,16 @@ def _calculate_missing_M_values( return arg_Ms - def _solve_disjunct_for_M(self, other_disjunct, scratch_block, - unsuccessful_solve_msg): + def _solve_disjunct_for_M( + self, other_disjunct, scratch_block, unsuccessful_solve_msg + ): solver = self._config.solver solver_trusted = solver.name in _trusted_solvers results = solver.solve(other_disjunct, load_solutions=False) - if (results.solver.termination_condition is - TerminationCondition.infeasible ): + if results.solver.termination_condition is TerminationCondition.infeasible: if solver_trusted: logger.debug( - "Disjunct '%s' is infeasible, deactivating." - % other_disjunct.name + "Disjunct '%s' is infeasible, deactivating." % other_disjunct.name ) other_disjunct.deactivate() M = 0 @@ -676,8 +688,7 @@ def _solve_disjunct_for_M(self, other_disjunct, scratch_block, # this so that we check for 'proven_infeasible' # and then we can abandon this hack raise GDP_Error(unsuccessful_solve_msg) - elif (results.solver.termination_condition is not - TerminationCondition.optimal): + elif results.solver.termination_condition is not TerminationCondition.optimal: raise GDP_Error(unsuccessful_solve_msg) else: other_disjunct.solutions.load_from(results) diff --git a/pyomo/gdp/tests/test_mbigm.py b/pyomo/gdp/tests/test_mbigm.py index d65f6e350dd..dc395c87576 100644 --- a/pyomo/gdp/tests/test_mbigm.py +++ b/pyomo/gdp/tests/test_mbigm.py @@ -1054,19 +1054,21 @@ def test_calculate_Ms_infeasible_Disjunct(self): - 12.0 * m.disjunction_disjuncts[1].binary_indicator_var, ) - @unittest.skipUnless(SolverFactory('ipopt').available(exception_flag=False), - "Ipopt is not available") + @unittest.skipUnless( + SolverFactory('ipopt').available(exception_flag=False), "Ipopt is not available" + ) def test_calculate_Ms_infeasible_Disjunct_local_solver(self): m = self.make_infeasible_disjunct_model() with self.assertRaisesRegex( - GDP_Error, - r"Unsuccessful solve to calculate M value to " - r"relax constraint 'disjunction_disjuncts\[1\].constraint\[1\]' " - r"on Disjunct 'disjunction_disjuncts\[1\]' when " - r"Disjunct 'disjunction_disjuncts\[0\]' is selected."): + GDP_Error, + r"Unsuccessful solve to calculate M value to " + r"relax constraint 'disjunction_disjuncts\[1\].constraint\[1\]' " + r"on Disjunct 'disjunction_disjuncts\[1\]' when " + r"Disjunct 'disjunction_disjuncts\[0\]' is selected.", + ): TransformationFactory('gdp.mbigm').apply_to( - m, solver=SolverFactory('ipopt'), - reduce_bound_constraints=False) + m, solver=SolverFactory('ipopt'), reduce_bound_constraints=False + ) @unittest.skipUnless(gurobi_available, "Gurobi is not available") def test_politely_ignore_BigM_Suffix(self): @@ -1076,12 +1078,13 @@ def test_politely_ignore_BigM_Suffix(self): out = StringIO() with LoggingIntercept(out, 'pyomo.gdp.mbigm', logging.DEBUG): TransformationFactory('gdp.mbigm').apply_to( - m, reduce_bound_constraints=False) + m, reduce_bound_constraints=False + ) warnings = out.getvalue() self.assertIn( - r"Found active 'BigM' Suffix on 'disjunction_disjuncts[1]'. " - r"The multiple bigM transformation does not currently " - r"support specifying M's with Suffixes and is ignoring " + r"Found active 'BigM' Suffix on 'disjunction_disjuncts[1]'. " + r"The multiple bigM transformation does not currently " + r"support specifying M's with Suffixes and is ignoring " r"this Suffix.", warnings, ) From 6c3739e6bd2c52a6ed3daaf3c14c92346ed31c5e Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 15 Feb 2024 09:53:56 -0700 Subject: [PATCH 0978/1204] Update docstrings; fix one test to account for pypy differences --- pyomo/contrib/solver/config.py | 33 ++++++++++++++----- .../contrib/solver/tests/unit/test_results.py | 5 ++- pyomo/contrib/solver/util.py | 2 +- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index 2a1a129d1ac..e38f903e1ac 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -23,7 +23,7 @@ class SolverConfig(ConfigDict): """ - Base config values for all solver interfaces + Base config for all direct solver interfaces """ def __init__( @@ -118,13 +118,14 @@ def __init__( class BranchAndBoundConfig(SolverConfig): """ + Base config for all direct MIP solver interfaces + Attributes ---------- - mip_gap: float - Solver will terminate if the mip gap is less than mip_gap - relax_integrality: bool - If True, all integer variables will be relaxed to continuous - variables before solving + rel_gap: float + The relative value of the gap in relation to the best bound + abs_gap: float + The absolute value of the difference between the incumbent and best bound """ def __init__( @@ -144,10 +145,20 @@ def __init__( ) self.rel_gap: Optional[float] = self.declare( - 'rel_gap', ConfigValue(domain=NonNegativeFloat) + 'rel_gap', + ConfigValue( + domain=NonNegativeFloat, + description="Optional termination condition; the relative value of the " + "gap in relation to the best bound", + ), ) self.abs_gap: Optional[float] = self.declare( - 'abs_gap', ConfigValue(domain=NonNegativeFloat) + 'abs_gap', + ConfigValue( + domain=NonNegativeFloat, + description="Optional termination condition; the absolute value of the " + "difference between the incumbent and best bound", + ), ) @@ -315,6 +326,9 @@ def __init__( class PersistentSolverConfig(SolverConfig): + """ + Base config for all persistent solver interfaces + """ def __init__( self, description=None, @@ -337,6 +351,9 @@ def __init__( class PersistentBranchAndBoundConfig(BranchAndBoundConfig): + """ + Base config for all persistent MIP solver interfaces + """ def __init__( self, description=None, diff --git a/pyomo/contrib/solver/tests/unit/test_results.py b/pyomo/contrib/solver/tests/unit/test_results.py index 8e16a1384ee..7b9de32bc00 100644 --- a/pyomo/contrib/solver/tests/unit/test_results.py +++ b/pyomo/contrib/solver/tests/unit/test_results.py @@ -137,7 +137,10 @@ def test_display(self): wall_time: None extra_info: """ - self.assertEqual(expected_print, stream.getvalue()) + out = stream.getvalue() + if 'null' in out: + out = out.replace('null', 'None') + self.assertEqual(expected_print, out) def test_generated_results(self): m = pyo.ConcreteModel() diff --git a/pyomo/contrib/solver/util.py b/pyomo/contrib/solver/util.py index c4d13ae31d2..af856eab7e2 100644 --- a/pyomo/contrib/solver/util.py +++ b/pyomo/contrib/solver/util.py @@ -16,7 +16,7 @@ import pyomo.core.expr as EXPR from pyomo.core.base.constraint import _GeneralConstraintData, Constraint from pyomo.core.base.sos import _SOSConstraintData, SOSConstraint -from pyomo.core.base.var import _GeneralVarData, Var +from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.param import _ParamData, Param from pyomo.core.base.objective import Objective, _GeneralObjectiveData from pyomo.common.collections import ComponentMap From 6da161c6d558dbe05180b7787edf39ea808a5b9c Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 15 Feb 2024 09:58:40 -0700 Subject: [PATCH 0979/1204] Adding a test for unrecognized suffixes --- pyomo/gdp/plugins/multiple_bigm.py | 4 ++-- pyomo/gdp/tests/test_mbigm.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index 803d2e80807..c3964dd84f3 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -709,8 +709,8 @@ def _warn_for_active_suffix(self, suffix, disjunct, active_disjuncts, Ms): else: raise GDP_Error( "Found active Suffix '{0}' on Disjunct '{1}'. " - "The multiple bigM transformation does not currently " - "support Suffixes.".format(suffix.name, disjunct.name) + "The multiple bigM transformation does not " + "support this Suffix.".format(suffix.name, disjunct.name) ) # These are all functions to retrieve transformed components from diff --git a/pyomo/gdp/tests/test_mbigm.py b/pyomo/gdp/tests/test_mbigm.py index dc395c87576..521d975652b 100644 --- a/pyomo/gdp/tests/test_mbigm.py +++ b/pyomo/gdp/tests/test_mbigm.py @@ -1088,3 +1088,19 @@ def test_politely_ignore_BigM_Suffix(self): r"this Suffix.", warnings, ) + + @unittest.skipUnless(gurobi_available, "Gurobi is not available") + def test_complain_for_unrecognized_Suffix(self): + m = self.make_infeasible_disjunct_model() + m.disjunction.disjuncts[0].deactivate() + m.disjunction.disjuncts[1].HiThere = Suffix(direction=Suffix.LOCAL) + out = StringIO() + with self.assertRaisesRegex( + GDP_Error, + r"Found active Suffix 'disjunction_disjuncts\[1\].HiThere' " + r"on Disjunct 'disjunction_disjuncts\[1\]'. The multiple bigM " + r"transformation does not support this Suffix.", + ): + TransformationFactory('gdp.mbigm').apply_to( + m, reduce_bound_constraints=False + ) From 220dd134dfdaf232561ea37efd45a64ef95e11fc Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 15 Feb 2024 10:02:37 -0700 Subject: [PATCH 0980/1204] Black and its empty lines --- pyomo/contrib/solver/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index e38f903e1ac..a1133f93ae4 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -329,6 +329,7 @@ class PersistentSolverConfig(SolverConfig): """ Base config for all persistent solver interfaces """ + def __init__( self, description=None, @@ -354,6 +355,7 @@ class PersistentBranchAndBoundConfig(BranchAndBoundConfig): """ Base config for all persistent MIP solver interfaces """ + def __init__( self, description=None, From e4fe3a5e37283039f8e49cff2e8e1c8940f71678 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 15 Feb 2024 10:03:12 -0700 Subject: [PATCH 0981/1204] Fixing a typo --- pyomo/gdp/plugins/multiple_bigm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index c3964dd84f3..5e23a706361 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -68,7 +68,7 @@ 'xpress_direct', 'mosek_direct', 'baron', - 'apsi_highs', + 'appsi_highs', } From 95f005ab9b6bfa006120401d7b9f53f599a5c292 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 15 Feb 2024 10:27:03 -0700 Subject: [PATCH 0982/1204] Actually putting private_data on _BlockData (whoops), adding a kind of silly test --- pyomo/core/base/block.py | 22 +++++++++++----------- pyomo/core/tests/unit/test_block.py | 5 ++++- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index d10754082bd..73ac2ebf397 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -2029,6 +2029,17 @@ def _create_objects_for_deepcopy(self, memo, component_list): comp._create_objects_for_deepcopy(memo, component_list) return _ans + @property + def _private_data(self): + if self._private_data_dict is None: + self._private_data_dict = {} + return self._private_data_dict + + def private_data(self, scope): + if scope not in self._private_data: + self._private_data[scope] = {} + return self._private_data[scope] + @ModelComponentFactory.register( "A component that contains one or more model components." @@ -2242,17 +2253,6 @@ def display(self, filename=None, ostream=None, prefix=""): for key in sorted(self): _BlockData.display(self[key], filename, ostream, prefix) - @property - def _private_data(self): - if self._private_data_dict is None: - self._private_data_dict = {} - return self._private_data_dict - - def private_data(self, scope): - if scope not in self._private_data: - self._private_data[scope] = {} - return self._private_data[scope] - class ScalarBlock(_BlockData, Block): def __init__(self, *args, **kwds): diff --git a/pyomo/core/tests/unit/test_block.py b/pyomo/core/tests/unit/test_block.py index 803237b1588..93333d9f764 100644 --- a/pyomo/core/tests/unit/test_block.py +++ b/pyomo/core/tests/unit/test_block.py @@ -3420,7 +3420,10 @@ def test_private_data(self): mfe = m.b.private_data('my_scope') self.assertIsInstance(mfe, dict) - + mfe1 = m.b.b[1].private_data('no mice here') + self.assertIsInstance(mfe1, dict) + mfe2 = m.b.b[2].private_data('no mice here') + self.assertIsInstance(mfe2, dict) if __name__ == "__main__": From cc54ae506818d2fe3dbeb728b2ff3bba2ffa973f Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 15 Feb 2024 11:50:37 -0700 Subject: [PATCH 0983/1204] Making _private_data the actual dict and doing away with the property --- pyomo/core/base/block.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 73ac2ebf397..f0e0e60350f 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -550,7 +550,7 @@ def __init__(self, component): super(_BlockData, self).__setattr__('_ctypes', {}) super(_BlockData, self).__setattr__('_decl', {}) super(_BlockData, self).__setattr__('_decl_order', []) - self._private_data_dict = None + self._private_data = None def __getattr__(self, val): if val in ModelComponentFactory: @@ -2029,13 +2029,9 @@ def _create_objects_for_deepcopy(self, memo, component_list): comp._create_objects_for_deepcopy(memo, component_list) return _ans - @property - def _private_data(self): - if self._private_data_dict is None: - self._private_data_dict = {} - return self._private_data_dict - def private_data(self, scope): + if self._private_data is None: + self._private_data = {} if scope not in self._private_data: self._private_data[scope] = {} return self._private_data[scope] From 73977aa5b8f3dd501644a928d60d0a7c352491af Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 15 Feb 2024 12:18:03 -0700 Subject: [PATCH 0984/1204] Ensuring private_data really is private by limiting the possible keys to substrings of the caller's scope --- pyomo/core/base/block.py | 14 +++++++-- pyomo/core/tests/unit/test_block.py | 46 ++++++++++++++++++++++------- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index f0e0e60350f..ea12295b7bd 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -29,7 +29,7 @@ import textwrap from contextlib import contextmanager -from inspect import isclass +from inspect import isclass, currentframe from itertools import filterfalse, chain from operator import itemgetter, attrgetter from io import StringIO @@ -2029,7 +2029,17 @@ def _create_objects_for_deepcopy(self, memo, component_list): comp._create_objects_for_deepcopy(memo, component_list) return _ans - def private_data(self, scope): + def private_data(self, scope=None): + mod = currentframe().f_back.f_globals['__name__'] + if scope is None: + scope = mod + elif not mod.startswith(scope): + raise ValueError( + "All keys in the 'private_data' dictionary must " + "be substrings of the caller's module name. " + "Received '%s' when calling private_data on Block " + "'%s'." % (scope, self.name) + ) if self._private_data is None: self._private_data = {} if scope not in self._private_data: diff --git a/pyomo/core/tests/unit/test_block.py b/pyomo/core/tests/unit/test_block.py index 93333d9f764..eb9c449af21 100644 --- a/pyomo/core/tests/unit/test_block.py +++ b/pyomo/core/tests/unit/test_block.py @@ -3412,18 +3412,44 @@ def test_private_data(self): m.b = Block() m.b.b = Block([1, 2]) - mfe = m.private_data('my_scope') + mfe = m.private_data() self.assertIsInstance(mfe, dict) - mfe2 = m.private_data('another_scope') - self.assertIsInstance(mfe2, dict) - self.assertEqual(len(m._private_data), 2) + self.assertEqual(len(mfe), 0) + self.assertEqual(len(m._private_data), 1) + self.assertIn('pyomo.core.tests.unit.test_block', m._private_data) + self.assertIs(mfe, m._private_data['pyomo.core.tests.unit.test_block']) - mfe = m.b.private_data('my_scope') - self.assertIsInstance(mfe, dict) - mfe1 = m.b.b[1].private_data('no mice here') - self.assertIsInstance(mfe1, dict) - mfe2 = m.b.b[2].private_data('no mice here') - self.assertIsInstance(mfe2, dict) + with self.assertRaisesRegex( + ValueError, + "All keys in the 'private_data' dictionary must " + "be substrings of the caller's module name. " + "Received 'no mice here' when calling private_data on Block " + "'b'.", + ): + mfe2 = m.b.private_data('no mice here') + + mfe3 = m.b.b[1].private_data('pyomo.core.tests') + self.assertIsInstance(mfe3, dict) + self.assertEqual(len(mfe3), 0) + self.assertIsInstance(m.b.b[1]._private_data, dict) + self.assertEqual(len(m.b.b[1]._private_data), 1) + self.assertIn('pyomo.core.tests', m.b.b[1]._private_data) + self.assertIs(mfe3, m.b.b[1]._private_data['pyomo.core.tests']) + mfe3['there are cookies'] = 'but no mice' + + mfe4 = m.b.b[1].private_data('pyomo.core.tests') + self.assertIs(mfe4, mfe3) + + # mfe2 = m.private_data('another_scope') + # self.assertIsInstance(mfe2, dict) + # self.assertEqual(len(m._private_data), 2) + + # mfe = m.b.private_data('my_scope') + # self.assertIsInstance(mfe, dict) + # mfe1 = m.b.b[1].private_data('no mice here') + # self.assertIsInstance(mfe1, dict) + # mfe2 = m.b.b[2].private_data('no mice here') + # self.assertIsInstance(mfe2, dict) if __name__ == "__main__": From 170acb83c80e6f452217ab39c590ab656d1753fc Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 15 Feb 2024 12:22:20 -0700 Subject: [PATCH 0985/1204] Whoops, removing comments --- pyomo/core/tests/unit/test_block.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pyomo/core/tests/unit/test_block.py b/pyomo/core/tests/unit/test_block.py index eb9c449af21..0ffdb537ac1 100644 --- a/pyomo/core/tests/unit/test_block.py +++ b/pyomo/core/tests/unit/test_block.py @@ -3440,17 +3440,6 @@ def test_private_data(self): mfe4 = m.b.b[1].private_data('pyomo.core.tests') self.assertIs(mfe4, mfe3) - # mfe2 = m.private_data('another_scope') - # self.assertIsInstance(mfe2, dict) - # self.assertEqual(len(m._private_data), 2) - - # mfe = m.b.private_data('my_scope') - # self.assertIsInstance(mfe, dict) - # mfe1 = m.b.b[1].private_data('no mice here') - # self.assertIsInstance(mfe1, dict) - # mfe2 = m.b.b[2].private_data('no mice here') - # self.assertIsInstance(mfe2, dict) - if __name__ == "__main__": unittest.main() From 7edd9db575bf8e89e8b537fb34baf4e551276e09 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 15 Feb 2024 12:48:51 -0700 Subject: [PATCH 0986/1204] Update documentation to include examples for usage --- .../developer_reference/solvers.rst | 74 ++++++++++++++++++- pyomo/contrib/solver/base.py | 8 -- 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index fa24d69a211..ad0ade94f41 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -1,11 +1,81 @@ -Solver Interfaces -================= +Future Solver Interface Changes +=============================== Pyomo offers interfaces into multiple solvers, both commercial and open source. +To support better capabilities for solver interfaces, the Pyomo team is actively +redesigning the existing interfaces to make them more maintainable and intuitive +for use. Redesigned interfaces can be found in ``pyomo.contrib.solver``. .. currentmodule:: pyomo.contrib.solver +New Interface Usage +------------------- + +The new interfaces have two modes: backwards compatible and future capability. +To use the backwards compatible version, simply use the ``SolverFactory`` +as usual and replace the solver name with the new version. Currently, the new +versions available are: + +.. list-table:: Available Redesigned Solvers + :widths: 25 25 + :header-rows: 1 + + * - Solver + - ``SolverFactory`` Name + * - ipopt + - ``ipopt_v2`` + * - GUROBI + - ``gurobi_v2`` + +Backwards Compatible Mode +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: python + + import pyomo.environ as pyo + from pyomo.contrib.solver.util import assert_optimal_termination + + model = pyo.ConcreteModel() + model.x = pyo.Var(initialize=1.5) + model.y = pyo.Var(initialize=1.5) + + def rosenbrock(model): + return (1.0 - model.x) ** 2 + 100.0 * (model.y - model.x**2) ** 2 + + model.obj = pyo.Objective(rule=rosenbrock, sense=pyo.minimize) + + status = pyo.SolverFactory('ipopt_v2').solve(model) + assert_optimal_termination(status) + model.pprint() + +Future Capability Mode +^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: python + + import pyomo.environ as pyo + from pyomo.contrib.solver.util import assert_optimal_termination + from pyomo.contrib.solver.ipopt import ipopt + + model = pyo.ConcreteModel() + model.x = pyo.Var(initialize=1.5) + model.y = pyo.Var(initialize=1.5) + + def rosenbrock(model): + return (1.0 - model.x) ** 2 + 100.0 * (model.y - model.x**2) ** 2 + + model.obj = pyo.Objective(rule=rosenbrock, sense=pyo.minimize) + + opt = ipopt() + status = opt.solve(model) + assert_optimal_termination(status) + # Displays important results information; only available in future capability mode + status.display() + model.pprint() + + + Interface Implementation ------------------------ diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 96b87924bf6..d69fecc5837 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -41,14 +41,6 @@ class SolverBase(abc.ABC): """ Base class upon which direct solver interfaces can be built. - - This base class contains the required methods for all direct solvers: - - available: Determines whether the solver is able to be run, combining - both whether it can be found on the system and if the license is valid. - - config: The configuration method for solver objects. - - solve: The main method of every solver - - version: The version of the solver - - is_persistent: Set to false for all direct solvers. """ CONFIG = SolverConfig() From 843c0f416b591cfc33a92a290428b3ab7522fe00 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 15 Feb 2024 13:17:50 -0700 Subject: [PATCH 0987/1204] Add copyright statement to all source files --- doc/OnlineDocs/conf.py | 11 + .../kernel/examples/aml_example.py | 11 + .../kernel/examples/conic.py | 11 + .../kernel/examples/kernel_containers.py | 11 + .../kernel/examples/kernel_example.py | 11 + .../kernel/examples/kernel_solving.py | 11 + .../kernel/examples/kernel_subclassing.py | 11 + .../kernel/examples/transformer.py | 11 + .../modeling_extensions/__init__.py | 10 + doc/OnlineDocs/src/data/ABCD1.py | 11 + doc/OnlineDocs/src/data/ABCD2.py | 11 + doc/OnlineDocs/src/data/ABCD3.py | 11 + doc/OnlineDocs/src/data/ABCD4.py | 11 + doc/OnlineDocs/src/data/ABCD5.py | 11 + doc/OnlineDocs/src/data/ABCD6.py | 11 + doc/OnlineDocs/src/data/ABCD7.py | 11 + doc/OnlineDocs/src/data/ABCD8.py | 11 + doc/OnlineDocs/src/data/ABCD9.py | 11 + doc/OnlineDocs/src/data/diet1.py | 11 + doc/OnlineDocs/src/data/ex.py | 11 + doc/OnlineDocs/src/data/import1.tab.py | 11 + doc/OnlineDocs/src/data/import2.tab.py | 11 + doc/OnlineDocs/src/data/import3.tab.py | 11 + doc/OnlineDocs/src/data/import4.tab.py | 11 + doc/OnlineDocs/src/data/import5.tab.py | 11 + doc/OnlineDocs/src/data/import6.tab.py | 11 + doc/OnlineDocs/src/data/import7.tab.py | 11 + doc/OnlineDocs/src/data/import8.tab.py | 11 + doc/OnlineDocs/src/data/param1.py | 11 + doc/OnlineDocs/src/data/param2.py | 11 + doc/OnlineDocs/src/data/param2a.py | 11 + doc/OnlineDocs/src/data/param3.py | 11 + doc/OnlineDocs/src/data/param3a.py | 11 + doc/OnlineDocs/src/data/param3b.py | 11 + doc/OnlineDocs/src/data/param3c.py | 11 + doc/OnlineDocs/src/data/param4.py | 11 + doc/OnlineDocs/src/data/param5.py | 11 + doc/OnlineDocs/src/data/param5a.py | 11 + doc/OnlineDocs/src/data/param6.py | 11 + doc/OnlineDocs/src/data/param6a.py | 11 + doc/OnlineDocs/src/data/param7a.py | 11 + doc/OnlineDocs/src/data/param7b.py | 11 + doc/OnlineDocs/src/data/param8a.py | 11 + doc/OnlineDocs/src/data/set1.py | 11 + doc/OnlineDocs/src/data/set2.py | 11 + doc/OnlineDocs/src/data/set2a.py | 11 + doc/OnlineDocs/src/data/set3.py | 11 + doc/OnlineDocs/src/data/set4.py | 11 + doc/OnlineDocs/src/data/set5.py | 11 + doc/OnlineDocs/src/data/table0.py | 11 + doc/OnlineDocs/src/data/table0.ul.py | 11 + doc/OnlineDocs/src/data/table1.py | 11 + doc/OnlineDocs/src/data/table2.py | 11 + doc/OnlineDocs/src/data/table3.py | 11 + doc/OnlineDocs/src/data/table3.ul.py | 11 + doc/OnlineDocs/src/data/table4.py | 11 + doc/OnlineDocs/src/data/table4.ul.py | 11 + doc/OnlineDocs/src/data/table5.py | 11 + doc/OnlineDocs/src/data/table6.py | 11 + doc/OnlineDocs/src/data/table7.py | 11 + .../src/dataportal/dataportal_tab.py | 11 + .../src/dataportal/param_initialization.py | 11 + .../src/dataportal/set_initialization.py | 11 + doc/OnlineDocs/src/expr/design.py | 11 + doc/OnlineDocs/src/expr/index.py | 11 + doc/OnlineDocs/src/expr/managing.py | 11 + doc/OnlineDocs/src/expr/overview.py | 11 + doc/OnlineDocs/src/expr/performance.py | 11 + doc/OnlineDocs/src/expr/quicksum.py | 11 + .../src/scripting/AbstractSuffixes.py | 11 + doc/OnlineDocs/src/scripting/Isinglebuild.py | 11 + doc/OnlineDocs/src/scripting/NodesIn_init.py | 12 + doc/OnlineDocs/src/scripting/Z_init.py | 12 + doc/OnlineDocs/src/scripting/abstract2.py | 11 + .../src/scripting/abstract2piece.py | 11 + .../src/scripting/abstract2piecebuild.py | 11 + .../src/scripting/block_iter_example.py | 11 + doc/OnlineDocs/src/scripting/concrete1.py | 11 + doc/OnlineDocs/src/scripting/doubleA.py | 12 + doc/OnlineDocs/src/scripting/driveabs2.py | 11 + doc/OnlineDocs/src/scripting/driveconc1.py | 11 + doc/OnlineDocs/src/scripting/iterative1.py | 11 + doc/OnlineDocs/src/scripting/iterative2.py | 11 + doc/OnlineDocs/src/scripting/noiteration1.py | 11 + doc/OnlineDocs/src/scripting/parallel.py | 11 + .../src/scripting/spy4Constraints.py | 11 + .../src/scripting/spy4Expressions.py | 11 + .../src/scripting/spy4PyomoCommand.py | 11 + doc/OnlineDocs/src/scripting/spy4Variables.py | 11 + doc/OnlineDocs/src/scripting/spy4scripts.py | 11 + doc/OnlineDocs/src/strip_examples.py | 11 + examples/dae/car_example.py | 11 + examples/dae/disease_DAE.py | 11 + examples/dae/run_disease.py | 11 + examples/dae/run_stochpdegas_automatic.py | 11 + examples/dae/simulator_dae_example.py | 11 + .../dae/simulator_dae_multindex_example.py | 11 + examples/dae/simulator_ode_example.py | 11 + .../dae/simulator_ode_multindex_example.py | 11 + examples/dae/stochpdegas_automatic.py | 11 + examples/doc/samples/__init__.py | 11 + .../samples/case_studies/deer/DeerProblem.py | 11 + .../samples/case_studies/diet/DietProblem.py | 11 + .../disease_est/DiseaseEstimation.py | 11 + .../samples/case_studies/max_flow/MaxFlow.py | 11 + .../case_studies/network_flow/networkFlow1.py | 11 + .../samples/case_studies/rosen/Rosenbrock.py | 11 + .../transportation/transportation.py | 11 + .../comparisons/cutstock/cutstock_cplex.py | 11 + .../comparisons/cutstock/cutstock_grb.py | 11 + .../comparisons/cutstock/cutstock_lpsolve.py | 11 + .../comparisons/cutstock/cutstock_pulpor.py | 11 + .../comparisons/cutstock/cutstock_pyomo.py | 11 + .../comparisons/cutstock/cutstock_util.py | 12 + .../samples/comparisons/sched/pyomo/sched.py | 11 + examples/doc/samples/scripts/__init__.py | 11 + examples/doc/samples/scripts/s1/knapsack.py | 11 + examples/doc/samples/scripts/s1/script.py | 11 + examples/doc/samples/scripts/s2/knapsack.py | 11 + examples/doc/samples/scripts/s2/script.py | 11 + examples/doc/samples/update.py | 11 + examples/gdp/batchProcessing.py | 11 + examples/gdp/circles/circles.py | 11 + .../constrained_layout/cons_layout_model.py | 11 + .../gdp/eight_process/eight_proc_logical.py | 11 + .../gdp/eight_process/eight_proc_model.py | 11 + .../eight_process/eight_proc_verbose_model.py | 11 + examples/gdp/farm_layout/farm_layout.py | 11 + examples/gdp/medTermPurchasing_Literal.py | 11 + examples/gdp/nine_process/small_process.py | 11 + examples/gdp/simple1.py | 11 + examples/gdp/simple2.py | 11 + examples/gdp/simple3.py | 11 + examples/gdp/small_lit/basic_step.py | 11 + examples/gdp/small_lit/contracts_problem.py | 11 + examples/gdp/small_lit/ex1_Lee.py | 11 + examples/gdp/small_lit/ex_633_trespalacios.py | 11 + examples/gdp/small_lit/nonconvex_HEN.py | 11 + examples/gdp/stickies.py | 11 + examples/gdp/strip_packing/stripPacking.py | 11 + .../gdp/strip_packing/strip_packing_8rect.py | 11 + .../strip_packing/strip_packing_concrete.py | 11 + examples/gdp/two_rxn_lee/two_rxn_model.py | 11 + examples/kernel/blocks.py | 11 + examples/kernel/conic.py | 11 + examples/kernel/constraints.py | 11 + examples/kernel/containers.py | 11 + examples/kernel/expressions.py | 11 + examples/kernel/mosek/geometric1.py | 11 + examples/kernel/mosek/geometric2.py | 11 + .../kernel/mosek/maximum_volume_cuboid.py | 11 + examples/kernel/mosek/power1.py | 11 + examples/kernel/mosek/semidefinite.py | 11 + examples/kernel/objectives.py | 11 + examples/kernel/parameters.py | 11 + examples/kernel/piecewise_functions.py | 11 + examples/kernel/piecewise_nd_functions.py | 11 + examples/kernel/special_ordered_sets.py | 11 + examples/kernel/suffixes.py | 11 + examples/kernel/variables.py | 11 + examples/mpec/bard1.py | 11 + examples/mpec/scholtes4.py | 11 + .../dae/run_stochpdegas1_automatic.py | 11 + .../performance/dae/stochpdegas1_automatic.py | 11 + examples/performance/jump/clnlbeam.py | 11 + examples/performance/jump/facility.py | 11 + examples/performance/jump/lqcp.py | 11 + examples/performance/jump/opf_66200bus.py | 11 + examples/performance/jump/opf_6620bus.py | 11 + examples/performance/jump/opf_662bus.py | 11 + examples/performance/misc/bilinear1_100.py | 11 + examples/performance/misc/bilinear1_100000.py | 11 + examples/performance/misc/bilinear2_100.py | 11 + examples/performance/misc/bilinear2_100000.py | 11 + examples/performance/misc/diag1_100.py | 11 + examples/performance/misc/diag1_100000.py | 11 + examples/performance/misc/diag2_100.py | 11 + examples/performance/misc/diag2_100000.py | 11 + examples/performance/misc/set1.py | 11 + examples/performance/misc/sparse1.py | 11 + examples/pyomo/concrete/rosen.py | 11 + examples/pyomo/concrete/sodacan.py | 11 + examples/pyomo/concrete/sodacan_fig.py | 11 + examples/pyomo/concrete/sp.py | 11 + examples/pyomo/concrete/sp_data.py | 11 + examples/pyomo/p-median/decorated_pmedian.py | 11 + examples/pyomobook/__init__.py | 10 + .../pyomobook/abstract-ch/AbstHLinScript.py | 11 + examples/pyomobook/abstract-ch/AbstractH.py | 11 + .../pyomobook/abstract-ch/AbstractHLinear.py | 11 + examples/pyomobook/abstract-ch/abstract5.py | 11 + examples/pyomobook/abstract-ch/abstract6.py | 11 + examples/pyomobook/abstract-ch/abstract7.py | 11 + .../pyomobook/abstract-ch/buildactions.py | 11 + examples/pyomobook/abstract-ch/concrete1.py | 11 + examples/pyomobook/abstract-ch/concrete2.py | 11 + examples/pyomobook/abstract-ch/diet1.py | 11 + examples/pyomobook/abstract-ch/ex.py | 11 + examples/pyomobook/abstract-ch/param1.py | 11 + examples/pyomobook/abstract-ch/param2.py | 11 + examples/pyomobook/abstract-ch/param2a.py | 11 + examples/pyomobook/abstract-ch/param3.py | 11 + examples/pyomobook/abstract-ch/param3a.py | 11 + examples/pyomobook/abstract-ch/param3b.py | 11 + examples/pyomobook/abstract-ch/param3c.py | 11 + examples/pyomobook/abstract-ch/param4.py | 11 + examples/pyomobook/abstract-ch/param5.py | 11 + examples/pyomobook/abstract-ch/param5a.py | 11 + examples/pyomobook/abstract-ch/param6.py | 11 + examples/pyomobook/abstract-ch/param6a.py | 11 + examples/pyomobook/abstract-ch/param7a.py | 11 + examples/pyomobook/abstract-ch/param7b.py | 11 + examples/pyomobook/abstract-ch/param8a.py | 11 + .../pyomobook/abstract-ch/postprocess_fn.py | 11 + examples/pyomobook/abstract-ch/set1.py | 11 + examples/pyomobook/abstract-ch/set2.py | 11 + examples/pyomobook/abstract-ch/set2a.py | 11 + examples/pyomobook/abstract-ch/set3.py | 11 + examples/pyomobook/abstract-ch/set4.py | 11 + examples/pyomobook/abstract-ch/set5.py | 11 + examples/pyomobook/abstract-ch/wl_abstract.py | 11 + .../abstract-ch/wl_abstract_script.py | 11 + examples/pyomobook/blocks-ch/blocks_gen.py | 11 + examples/pyomobook/blocks-ch/blocks_intro.py | 11 + .../pyomobook/blocks-ch/blocks_lotsizing.py | 11 + examples/pyomobook/blocks-ch/lotsizing.py | 11 + .../pyomobook/blocks-ch/lotsizing_no_time.py | 11 + .../blocks-ch/lotsizing_uncertain.py | 11 + examples/pyomobook/dae-ch/dae_tester_model.py | 11 + .../pyomobook/dae-ch/plot_path_constraint.py | 12 + .../pyomobook/dae-ch/run_path_constraint.py | 11 + .../dae-ch/run_path_constraint_tester.py | 11 + examples/pyomobook/gdp-ch/gdp_uc.py | 11 + examples/pyomobook/gdp-ch/scont.py | 11 + examples/pyomobook/gdp-ch/scont2.py | 11 + examples/pyomobook/gdp-ch/scont_script.py | 11 + examples/pyomobook/gdp-ch/verify_scont.py | 11 + examples/pyomobook/intro-ch/abstract5.py | 11 + .../pyomobook/intro-ch/coloring_concrete.py | 11 + examples/pyomobook/intro-ch/concrete1.py | 11 + .../pyomobook/intro-ch/concrete1_generic.py | 11 + examples/pyomobook/intro-ch/mydata.py | 11 + examples/pyomobook/mpec-ch/ex1a.py | 11 + examples/pyomobook/mpec-ch/ex1b.py | 11 + examples/pyomobook/mpec-ch/ex1c.py | 11 + examples/pyomobook/mpec-ch/ex1d.py | 11 + examples/pyomobook/mpec-ch/ex1e.py | 11 + examples/pyomobook/mpec-ch/ex2.py | 11 + examples/pyomobook/mpec-ch/munson1.py | 11 + examples/pyomobook/mpec-ch/ralph1.py | 11 + .../nonlinear-ch/deer/DeerProblem.py | 11 + .../disease_est/disease_estimation.py | 11 + .../multimodal/multimodal_init1.py | 11 + .../multimodal/multimodal_init2.py | 11 + .../react_design/ReactorDesign.py | 11 + .../react_design/ReactorDesignTable.py | 11 + .../nonlinear-ch/rosen/rosenbrock.py | 11 + .../optimization-ch/ConcHLinScript.py | 11 + .../pyomobook/optimization-ch/ConcreteH.py | 11 + .../optimization-ch/ConcreteHLinear.py | 11 + .../optimization-ch/IC_model_dict.py | 11 + .../overview-ch/var_obj_con_snippet.py | 11 + examples/pyomobook/overview-ch/wl_abstract.py | 11 + .../overview-ch/wl_abstract_script.py | 11 + examples/pyomobook/overview-ch/wl_concrete.py | 11 + .../overview-ch/wl_concrete_script.py | 11 + examples/pyomobook/overview-ch/wl_excel.py | 11 + examples/pyomobook/overview-ch/wl_list.py | 11 + examples/pyomobook/overview-ch/wl_mutable.py | 11 + .../pyomobook/overview-ch/wl_mutable_excel.py | 11 + examples/pyomobook/overview-ch/wl_scalar.py | 11 + .../pyomobook/performance-ch/SparseSets.py | 11 + examples/pyomobook/performance-ch/lin_expr.py | 11 + .../pyomobook/performance-ch/persistent.py | 11 + examples/pyomobook/performance-ch/wl.py | 11 + .../pyomo-components-ch/con_declaration.py | 11 + .../pyomobook/pyomo-components-ch/examples.py | 11 + .../pyomo-components-ch/expr_declaration.py | 11 + .../pyomo-components-ch/obj_declaration.py | 11 + .../pyomo-components-ch/param_declaration.py | 11 + .../param_initialization.py | 11 + .../pyomo-components-ch/param_misc.py | 11 + .../pyomo-components-ch/param_validation.py | 11 + .../pyomobook/pyomo-components-ch/rangeset.py | 11 + .../pyomo-components-ch/set_declaration.py | 11 + .../pyomo-components-ch/set_initialization.py | 11 + .../pyomobook/pyomo-components-ch/set_misc.py | 11 + .../pyomo-components-ch/set_options.py | 11 + .../pyomo-components-ch/set_validation.py | 11 + .../pyomo-components-ch/suffix_declaration.py | 11 + .../pyomo-components-ch/var_declaration.py | 11 + examples/pyomobook/python-ch/BadIndent.py | 11 + examples/pyomobook/python-ch/LineExample.py | 11 + examples/pyomobook/python-ch/class.py | 61 +- examples/pyomobook/python-ch/ctob.py | 11 + examples/pyomobook/python-ch/example.py | 11 + examples/pyomobook/python-ch/example2.py | 11 + examples/pyomobook/python-ch/functions.py | 59 +- examples/pyomobook/python-ch/iterate.py | 47 +- .../pyomobook/python-ch/pythonconditional.py | 11 + examples/pyomobook/scripts-ch/attributes.py | 11 + examples/pyomobook/scripts-ch/prob_mod_ex.py | 11 + .../pyomobook/scripts-ch/sudoku/sudoku.py | 11 + .../pyomobook/scripts-ch/sudoku/sudoku_run.py | 11 + .../pyomobook/scripts-ch/value_expression.py | 11 + .../pyomobook/scripts-ch/warehouse_cuts.py | 11 + .../scripts-ch/warehouse_load_solutions.py | 11 + .../pyomobook/scripts-ch/warehouse_model.py | 11 + .../pyomobook/scripts-ch/warehouse_print.py | 11 + .../pyomobook/scripts-ch/warehouse_script.py | 11 + .../scripts-ch/warehouse_solver_options.py | 11 + examples/pyomobook/strip_examples.py | 11 + pyomo/common/multithread.py | 11 + pyomo/common/shutdown.py | 11 + pyomo/common/tests/import_ex.py | 12 + pyomo/common/tests/test_multithread.py | 11 + pyomo/contrib/__init__.py | 10 + pyomo/contrib/ampl_function_demo/__init__.py | 10 + .../ampl_function_demo/tests/__init__.py | 10 + pyomo/contrib/appsi/__init__.py | 11 + pyomo/contrib/appsi/base.py | 11 + pyomo/contrib/appsi/cmodel/src/common.cpp | 12 + pyomo/contrib/appsi/cmodel/src/common.hpp | 12 + pyomo/contrib/appsi/cmodel/src/expression.cpp | 3952 +++++++++-------- pyomo/contrib/appsi/cmodel/src/expression.hpp | 1588 +++---- pyomo/contrib/appsi/cmodel/src/fbbt_model.cpp | 12 + pyomo/contrib/appsi/cmodel/src/fbbt_model.hpp | 12 + pyomo/contrib/appsi/cmodel/src/interval.cpp | 12 + pyomo/contrib/appsi/cmodel/src/interval.hpp | 12 + pyomo/contrib/appsi/cmodel/src/lp_writer.cpp | 12 + pyomo/contrib/appsi/cmodel/src/lp_writer.hpp | 12 + pyomo/contrib/appsi/cmodel/src/model_base.cpp | 12 + pyomo/contrib/appsi/cmodel/src/model_base.hpp | 12 + pyomo/contrib/appsi/cmodel/src/nl_writer.cpp | 12 + pyomo/contrib/appsi/cmodel/src/nl_writer.hpp | 12 + pyomo/contrib/appsi/cmodel/tests/__init__.py | 10 + .../contrib/appsi/cmodel/tests/test_import.py | 11 + pyomo/contrib/appsi/examples/__init__.py | 10 + .../contrib/appsi/examples/getting_started.py | 11 + .../contrib/appsi/examples/tests/__init__.py | 10 + .../appsi/examples/tests/test_examples.py | 11 + pyomo/contrib/appsi/fbbt.py | 11 + pyomo/contrib/appsi/plugins.py | 11 + pyomo/contrib/appsi/solvers/__init__.py | 11 + pyomo/contrib/appsi/solvers/cbc.py | 11 + pyomo/contrib/appsi/solvers/cplex.py | 11 + pyomo/contrib/appsi/solvers/gurobi.py | 11 + pyomo/contrib/appsi/solvers/highs.py | 11 + pyomo/contrib/appsi/solvers/ipopt.py | 11 + pyomo/contrib/appsi/solvers/tests/__init__.py | 10 + .../solvers/tests/test_gurobi_persistent.py | 11 + .../solvers/tests/test_highs_persistent.py | 11 + .../solvers/tests/test_ipopt_persistent.py | 11 + .../solvers/tests/test_persistent_solvers.py | 11 + .../solvers/tests/test_wntr_persistent.py | 11 + pyomo/contrib/appsi/solvers/wntr.py | 11 + pyomo/contrib/appsi/tests/__init__.py | 10 + pyomo/contrib/appsi/tests/test_base.py | 11 + pyomo/contrib/appsi/tests/test_fbbt.py | 11 + pyomo/contrib/appsi/tests/test_interval.py | 11 + pyomo/contrib/appsi/utils/__init__.py | 11 + .../utils/collect_vars_and_named_exprs.py | 11 + pyomo/contrib/appsi/utils/get_objective.py | 11 + pyomo/contrib/appsi/utils/tests/__init__.py | 10 + .../test_collect_vars_and_named_exprs.py | 11 + pyomo/contrib/appsi/writers/__init__.py | 11 + pyomo/contrib/appsi/writers/config.py | 12 + pyomo/contrib/appsi/writers/lp_writer.py | 11 + pyomo/contrib/appsi/writers/nl_writer.py | 11 + pyomo/contrib/appsi/writers/tests/__init__.py | 10 + .../appsi/writers/tests/test_nl_writer.py | 11 + pyomo/contrib/benders/__init__.py | 10 + pyomo/contrib/benders/examples/__init__.py | 10 + pyomo/contrib/benders/tests/__init__.py | 10 + pyomo/contrib/community_detection/__init__.py | 10 + .../community_detection/community_graph.py | 11 + .../contrib/community_detection/detection.py | 11 + .../contrib/community_detection/event_log.py | 11 + pyomo/contrib/community_detection/plugins.py | 12 + .../community_detection/tests/__init__.py | 10 + pyomo/contrib/cp/__init__.py | 11 + pyomo/contrib/cp/repn/__init__.py | 10 + pyomo/contrib/cp/scheduling_expr/__init__.py | 11 +- pyomo/contrib/cp/tests/__init__.py | 10 + pyomo/contrib/cp/transform/__init__.py | 10 + pyomo/contrib/doe/examples/__init__.py | 10 + pyomo/contrib/doe/tests/__init__.py | 10 + pyomo/contrib/example/__init__.py | 11 + pyomo/contrib/example/bar.py | 11 + pyomo/contrib/example/foo.py | 11 + pyomo/contrib/example/plugins/__init__.py | 11 + pyomo/contrib/example/plugins/ex_plugin.py | 11 + pyomo/contrib/example/tests/__init__.py | 11 + pyomo/contrib/fbbt/__init__.py | 10 + pyomo/contrib/fbbt/tests/__init__.py | 10 + pyomo/contrib/fbbt/tests/test_interval.py | 11 + pyomo/contrib/fme/__init__.py | 10 + pyomo/contrib/fme/tests/__init__.py | 10 + pyomo/contrib/gdp_bounds/__init__.py | 11 + pyomo/contrib/gdp_bounds/info.py | 11 + pyomo/contrib/gdp_bounds/tests/__init__.py | 10 + .../gdp_bounds/tests/test_gdp_bounds.py | 11 + pyomo/contrib/gdpopt/__init__.py | 11 + pyomo/contrib/gdpopt/tests/__init__.py | 10 + pyomo/contrib/iis/__init__.py | 11 + pyomo/contrib/iis/iis.py | 11 + pyomo/contrib/iis/tests/__init__.py | 10 + pyomo/contrib/iis/tests/test_iis.py | 11 + pyomo/contrib/incidence_analysis/__init__.py | 11 + .../incidence_analysis/common/__init__.py | 10 + .../common/tests/__init__.py | 10 + .../incidence_analysis/tests/__init__.py | 10 + pyomo/contrib/interior_point/__init__.py | 10 + .../interior_point/examples/__init__.py | 10 + .../contrib/interior_point/linalg/__init__.py | 10 + .../linalg/base_linear_solver_interface.py | 11 + .../interior_point/linalg/ma27_interface.py | 11 + .../interior_point/linalg/scipy_interface.py | 11 + .../interior_point/linalg/tests/__init__.py | 10 + .../linalg/tests/test_linear_solvers.py | 11 + .../linalg/tests/test_realloc.py | 11 + .../contrib/interior_point/tests/__init__.py | 10 + .../interior_point/tests/test_realloc.py | 11 + pyomo/contrib/latex_printer/__init__.py | 11 + pyomo/contrib/latex_printer/latex_printer.py | 11 + pyomo/contrib/latex_printer/tests/__init__.py | 11 +- .../latex_printer/tests/test_latex_printer.py | 11 + .../tests/test_latex_printer_vartypes.py | 11 + pyomo/contrib/mindtpy/__init__.py | 11 + pyomo/contrib/mindtpy/config_options.py | 11 + pyomo/contrib/mindtpy/tests/MINLP4_simple.py | 11 + pyomo/contrib/mindtpy/tests/MINLP5_simple.py | 11 + .../mindtpy/tests/MINLP_simple_grey_box.py | 11 + pyomo/contrib/mindtpy/tests/__init__.py | 10 + .../tests/constraint_qualification_example.py | 11 + .../mindtpy/tests/eight_process_problem.py | 11 + .../mindtpy/tests/feasibility_pump1.py | 11 + .../mindtpy/tests/feasibility_pump2.py | 11 + pyomo/contrib/mindtpy/tests/from_proposal.py | 11 + pyomo/contrib/mindtpy/tests/nonconvex1.py | 11 + pyomo/contrib/mindtpy/tests/nonconvex2.py | 11 + pyomo/contrib/mindtpy/tests/nonconvex3.py | 11 + pyomo/contrib/mindtpy/tests/nonconvex4.py | 11 + .../contrib/mindtpy/tests/test_mindtpy_ECP.py | 11 + .../mindtpy/tests/test_mindtpy_feas_pump.py | 11 + .../mindtpy/tests/test_mindtpy_global.py | 11 + .../tests/test_mindtpy_global_lp_nlp.py | 11 + .../tests/test_mindtpy_regularization.py | 11 + .../tests/test_mindtpy_solution_pool.py | 11 + pyomo/contrib/mpc/data/tests/__init__.py | 10 + pyomo/contrib/mpc/examples/__init__.py | 10 + pyomo/contrib/mpc/examples/cstr/__init__.py | 10 + .../mpc/examples/cstr/tests/__init__.py | 10 + .../contrib/mpc/interfaces/tests/__init__.py | 10 + pyomo/contrib/mpc/modeling/tests/__init__.py | 10 + pyomo/contrib/multistart/__init__.py | 10 + pyomo/contrib/multistart/high_conf_stop.py | 11 + pyomo/contrib/multistart/plugins.py | 12 + pyomo/contrib/multistart/reinit.py | 11 + pyomo/contrib/multistart/test_multi.py | 11 + pyomo/contrib/parmest/utils/create_ef.py | 11 + pyomo/contrib/parmest/utils/scenario_tree.py | 11 + pyomo/contrib/piecewise/__init__.py | 11 + pyomo/contrib/piecewise/tests/__init__.py | 10 + pyomo/contrib/piecewise/transform/__init__.py | 10 + pyomo/contrib/preprocessing/__init__.py | 11 + .../contrib/preprocessing/plugins/__init__.py | 12 + .../plugins/constraint_tightener.py | 11 + .../preprocessing/plugins/int_to_binary.py | 11 + pyomo/contrib/preprocessing/tests/__init__.py | 10 + .../tests/test_bounds_to_vars_xfrm.py | 11 + .../tests/test_constraint_tightener.py | 11 + .../test_deactivate_trivial_constraints.py | 11 + .../tests/test_detect_fixed_vars.py | 11 + .../tests/test_equality_propagate.py | 11 + .../preprocessing/tests/test_init_vars.py | 11 + .../preprocessing/tests/test_strip_bounds.py | 11 + .../tests/test_var_aggregator.py | 11 + .../tests/test_zero_sum_propagate.py | 11 + .../tests/test_zero_term_removal.py | 11 + pyomo/contrib/preprocessing/util.py | 11 + .../pynumero/algorithms/solvers/__init__.py | 10 + .../algorithms/solvers/tests/__init__.py | 10 + .../pynumero/examples/callback/__init__.py | 10 + .../examples/callback/cyipopt_callback.py | 11 + .../callback/cyipopt_callback_halt.py | 11 + .../callback/cyipopt_functor_callback.py | 11 + .../examples/callback/reactor_design.py | 11 + .../examples/external_grey_box/__init__.py | 10 + .../external_grey_box/param_est/__init__.py | 10 + .../param_est/generate_data.py | 11 + .../external_grey_box/param_est/models.py | 11 + .../param_est/perform_estimation.py | 11 + .../react_example/__init__.py | 10 + .../pynumero/examples/mumps_example.py | 11 + .../pynumero/examples/parallel_matvec.py | 11 + .../pynumero/examples/parallel_vector_ops.py | 11 + pyomo/contrib/pynumero/examples/sqp.py | 11 + .../pynumero/examples/tests/__init__.py | 10 + .../pynumero/examples/tests/test_examples.py | 11 + .../examples/tests/test_mpi_examples.py | 11 + .../pynumero/interfaces/nlp_projections.py | 11 + .../tests/external_grey_box_models.py | 11 + pyomo/contrib/pynumero/linalg/base.py | 11 + .../contrib/pynumero/linalg/ma27_interface.py | 11 + .../contrib/pynumero/linalg/ma57_interface.py | 11 + .../pynumero/linalg/scipy_interface.py | 11 + .../contrib/pynumero/linalg/tests/__init__.py | 10 + .../linalg/tests/test_linear_solvers.py | 11 + pyomo/contrib/pynumero/src/ma27Interface.cpp | 12 + pyomo/contrib/pynumero/src/ma57Interface.cpp | 12 + .../pynumero/src/tests/simple_test.cpp | 12 + pyomo/contrib/pynumero/tests/__init__.py | 10 + pyomo/contrib/pyros/__init__.py | 11 + pyomo/contrib/pyros/master_problem_methods.py | 11 + .../contrib/pyros/pyros_algorithm_methods.py | 11 + .../pyros/separation_problem_methods.py | 11 + pyomo/contrib/pyros/solve_data.py | 11 + pyomo/contrib/pyros/tests/__init__.py | 10 + pyomo/contrib/pyros/tests/test_grcs.py | 11 + pyomo/contrib/pyros/uncertainty_sets.py | 11 + pyomo/contrib/pyros/util.py | 11 + pyomo/contrib/satsolver/__init__.py | 10 + pyomo/contrib/satsolver/satsolver.py | 11 + pyomo/contrib/sensitivity_toolbox/__init__.py | 11 + .../sensitivity_toolbox/examples/__init__.py | 11 + .../examples/rooney_biegler.py | 11 + pyomo/contrib/sensitivity_toolbox/k_aug.py | 11 + pyomo/contrib/sensitivity_toolbox/sens.py | 11 + .../sensitivity_toolbox/tests/__init__.py | 11 + .../tests/test_k_aug_interface.py | 11 + .../sensitivity_toolbox/tests/test_sens.py | 11 + .../tests/test_sens_unit.py | 11 + pyomo/contrib/viewer/__init__.py | 11 +- pyomo/contrib/viewer/tests/__init__.py | 10 + pyomo/core/expr/calculus/__init__.py | 10 + pyomo/core/expr/taylor_series.py | 11 + .../plugins/transform/logical_to_linear.py | 11 + .../tests/unit/test_logical_constraint.py | 11 + pyomo/core/tests/unit/test_sos_v2.py | 11 + pyomo/dae/simulator.py | 11 + pyomo/gdp/tests/models.py | 11 + pyomo/gdp/tests/test_reclassify.py | 11 + pyomo/repn/tests/ampl/__init__.py | 10 + pyomo/solvers/plugins/solvers/XPRESS.py | 11 + .../tests/checks/test_MOSEKPersistent.py | 11 + pyomo/solvers/tests/checks/test_gurobi.py | 11 + .../tests/checks/test_gurobi_direct.py | 11 + .../tests/checks/test_xpress_persistent.py | 11 + pyomo/solvers/tests/mip/test_scip_log_data.py | 11 + pyomo/util/__init__.py | 10 + pyomo/util/diagnostics.py | 11 + pyomo/util/tests/__init__.py | 10 + scripts/performance/compare_components.py | 11 + scripts/performance/expr_perf.py | 11 + scripts/performance/simple.py | 11 + 556 files changed, 8901 insertions(+), 2828 deletions(-) diff --git a/doc/OnlineDocs/conf.py b/doc/OnlineDocs/conf.py index ef6510daedf..24f8d26c9e8 100644 --- a/doc/OnlineDocs/conf.py +++ b/doc/OnlineDocs/conf.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + #!/usr/bin/env python3 # -*- coding: utf-8 -*- # diff --git a/doc/OnlineDocs/library_reference/kernel/examples/aml_example.py b/doc/OnlineDocs/library_reference/kernel/examples/aml_example.py index 146048a6046..564764071b7 100644 --- a/doc/OnlineDocs/library_reference/kernel/examples/aml_example.py +++ b/doc/OnlineDocs/library_reference/kernel/examples/aml_example.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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_Syntax import pyomo.environ as aml diff --git a/doc/OnlineDocs/library_reference/kernel/examples/conic.py b/doc/OnlineDocs/library_reference/kernel/examples/conic.py index 9282bc67f9a..866377ed641 100644 --- a/doc/OnlineDocs/library_reference/kernel/examples/conic.py +++ b/doc/OnlineDocs/library_reference/kernel/examples/conic.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # @Class import pyomo.kernel as pmo diff --git a/doc/OnlineDocs/library_reference/kernel/examples/kernel_containers.py b/doc/OnlineDocs/library_reference/kernel/examples/kernel_containers.py index f2a4ec25ac5..9b33ed71e1d 100644 --- a/doc/OnlineDocs/library_reference/kernel/examples/kernel_containers.py +++ b/doc/OnlineDocs/library_reference/kernel/examples/kernel_containers.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.kernel # @all diff --git a/doc/OnlineDocs/library_reference/kernel/examples/kernel_example.py b/doc/OnlineDocs/library_reference/kernel/examples/kernel_example.py index 1caf064bb2a..6ee766e3055 100644 --- a/doc/OnlineDocs/library_reference/kernel/examples/kernel_example.py +++ b/doc/OnlineDocs/library_reference/kernel/examples/kernel_example.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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_Syntax import pyomo.kernel as pmo diff --git a/doc/OnlineDocs/library_reference/kernel/examples/kernel_solving.py b/doc/OnlineDocs/library_reference/kernel/examples/kernel_solving.py index 5a8eed9fd89..30b588f89b8 100644 --- a/doc/OnlineDocs/library_reference/kernel/examples/kernel_solving.py +++ b/doc/OnlineDocs/library_reference/kernel/examples/kernel_solving.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.kernel as pmo model = pmo.block() diff --git a/doc/OnlineDocs/library_reference/kernel/examples/kernel_subclassing.py b/doc/OnlineDocs/library_reference/kernel/examples/kernel_subclassing.py index c21c6dc890b..a603050e828 100644 --- a/doc/OnlineDocs/library_reference/kernel/examples/kernel_subclassing.py +++ b/doc/OnlineDocs/library_reference/kernel/examples/kernel_subclassing.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.kernel diff --git a/doc/OnlineDocs/library_reference/kernel/examples/transformer.py b/doc/OnlineDocs/library_reference/kernel/examples/transformer.py index 66893008cf9..0df239c61ad 100644 --- a/doc/OnlineDocs/library_reference/kernel/examples/transformer.py +++ b/doc/OnlineDocs/library_reference/kernel/examples/transformer.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import pyomo.kernel diff --git a/doc/OnlineDocs/modeling_extensions/__init__.py b/doc/OnlineDocs/modeling_extensions/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/doc/OnlineDocs/modeling_extensions/__init__.py +++ b/doc/OnlineDocs/modeling_extensions/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/doc/OnlineDocs/src/data/ABCD1.py b/doc/OnlineDocs/src/data/ABCD1.py index 32600b226e1..6d34bec756e 100644 --- a/doc/OnlineDocs/src/data/ABCD1.py +++ b/doc/OnlineDocs/src/data/ABCD1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/ABCD2.py b/doc/OnlineDocs/src/data/ABCD2.py index 65a46415368..beadd71916d 100644 --- a/doc/OnlineDocs/src/data/ABCD2.py +++ b/doc/OnlineDocs/src/data/ABCD2.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/ABCD3.py b/doc/OnlineDocs/src/data/ABCD3.py index 48797ced5bb..1a3e826c6a9 100644 --- a/doc/OnlineDocs/src/data/ABCD3.py +++ b/doc/OnlineDocs/src/data/ABCD3.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/ABCD4.py b/doc/OnlineDocs/src/data/ABCD4.py index 20f6a21c011..59055cadf71 100644 --- a/doc/OnlineDocs/src/data/ABCD4.py +++ b/doc/OnlineDocs/src/data/ABCD4.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/ABCD5.py b/doc/OnlineDocs/src/data/ABCD5.py index 58461af056b..051e2c9ba9e 100644 --- a/doc/OnlineDocs/src/data/ABCD5.py +++ b/doc/OnlineDocs/src/data/ABCD5.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/ABCD6.py b/doc/OnlineDocs/src/data/ABCD6.py index 961408dbc7e..8c239eb5332 100644 --- a/doc/OnlineDocs/src/data/ABCD6.py +++ b/doc/OnlineDocs/src/data/ABCD6.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/ABCD7.py b/doc/OnlineDocs/src/data/ABCD7.py index a97e764fa5a..a52c27af234 100644 --- a/doc/OnlineDocs/src/data/ABCD7.py +++ b/doc/OnlineDocs/src/data/ABCD7.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * import pyomo.common import sys diff --git a/doc/OnlineDocs/src/data/ABCD8.py b/doc/OnlineDocs/src/data/ABCD8.py index 9bcd950c681..f979f373a18 100644 --- a/doc/OnlineDocs/src/data/ABCD8.py +++ b/doc/OnlineDocs/src/data/ABCD8.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * import pyomo.common import sys diff --git a/doc/OnlineDocs/src/data/ABCD9.py b/doc/OnlineDocs/src/data/ABCD9.py index 29fcb6426db..aaa1e7c908d 100644 --- a/doc/OnlineDocs/src/data/ABCD9.py +++ b/doc/OnlineDocs/src/data/ABCD9.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * import pyomo.common import sys diff --git a/doc/OnlineDocs/src/data/diet1.py b/doc/OnlineDocs/src/data/diet1.py index ef0d8096350..9201edb8c4c 100644 --- a/doc/OnlineDocs/src/data/diet1.py +++ b/doc/OnlineDocs/src/data/diet1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # diet1.py from pyomo.environ import * diff --git a/doc/OnlineDocs/src/data/ex.py b/doc/OnlineDocs/src/data/ex.py index 8c9473f2852..3fd91b623b2 100644 --- a/doc/OnlineDocs/src/data/ex.py +++ b/doc/OnlineDocs/src/data/ex.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/import1.tab.py b/doc/OnlineDocs/src/data/import1.tab.py index c9164ab73ec..ade8edcc2a3 100644 --- a/doc/OnlineDocs/src/data/import1.tab.py +++ b/doc/OnlineDocs/src/data/import1.tab.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/import2.tab.py b/doc/OnlineDocs/src/data/import2.tab.py index d03f053d090..6491c1ec30e 100644 --- a/doc/OnlineDocs/src/data/import2.tab.py +++ b/doc/OnlineDocs/src/data/import2.tab.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/import3.tab.py b/doc/OnlineDocs/src/data/import3.tab.py index e86557677ee..ec57c018b00 100644 --- a/doc/OnlineDocs/src/data/import3.tab.py +++ b/doc/OnlineDocs/src/data/import3.tab.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/import4.tab.py b/doc/OnlineDocs/src/data/import4.tab.py index 93df9c761ab..b48278bd28d 100644 --- a/doc/OnlineDocs/src/data/import4.tab.py +++ b/doc/OnlineDocs/src/data/import4.tab.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/import5.tab.py b/doc/OnlineDocs/src/data/import5.tab.py index 1d20476a16f..9604c328c64 100644 --- a/doc/OnlineDocs/src/data/import5.tab.py +++ b/doc/OnlineDocs/src/data/import5.tab.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/import6.tab.py b/doc/OnlineDocs/src/data/import6.tab.py index 8a1ab232f86..a1c269a0abf 100644 --- a/doc/OnlineDocs/src/data/import6.tab.py +++ b/doc/OnlineDocs/src/data/import6.tab.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/import7.tab.py b/doc/OnlineDocs/src/data/import7.tab.py index 747d884be31..f4b60cb42d9 100644 --- a/doc/OnlineDocs/src/data/import7.tab.py +++ b/doc/OnlineDocs/src/data/import7.tab.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/import8.tab.py b/doc/OnlineDocs/src/data/import8.tab.py index b7866d7a3e5..d1d2e6f8160 100644 --- a/doc/OnlineDocs/src/data/import8.tab.py +++ b/doc/OnlineDocs/src/data/import8.tab.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/param1.py b/doc/OnlineDocs/src/data/param1.py index c4bc8de5acc..e606b7f6b4f 100644 --- a/doc/OnlineDocs/src/data/param1.py +++ b/doc/OnlineDocs/src/data/param1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/param2.py b/doc/OnlineDocs/src/data/param2.py index f46f05ceebc..725a6002ede 100644 --- a/doc/OnlineDocs/src/data/param2.py +++ b/doc/OnlineDocs/src/data/param2.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/param2a.py b/doc/OnlineDocs/src/data/param2a.py index 4557f63d841..29416e2dcbc 100644 --- a/doc/OnlineDocs/src/data/param2a.py +++ b/doc/OnlineDocs/src/data/param2a.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/param3.py b/doc/OnlineDocs/src/data/param3.py index 149155ce67d..0cc4df57511 100644 --- a/doc/OnlineDocs/src/data/param3.py +++ b/doc/OnlineDocs/src/data/param3.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/param3a.py b/doc/OnlineDocs/src/data/param3a.py index 0e99cad0c7a..42204de468f 100644 --- a/doc/OnlineDocs/src/data/param3a.py +++ b/doc/OnlineDocs/src/data/param3a.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/param3b.py b/doc/OnlineDocs/src/data/param3b.py index deda175ea12..9f0375d7b87 100644 --- a/doc/OnlineDocs/src/data/param3b.py +++ b/doc/OnlineDocs/src/data/param3b.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/param3c.py b/doc/OnlineDocs/src/data/param3c.py index 4056dc8107d..9efac11553e 100644 --- a/doc/OnlineDocs/src/data/param3c.py +++ b/doc/OnlineDocs/src/data/param3c.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/param4.py b/doc/OnlineDocs/src/data/param4.py index 1190dae8dec..ab184e65ed3 100644 --- a/doc/OnlineDocs/src/data/param4.py +++ b/doc/OnlineDocs/src/data/param4.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/param5.py b/doc/OnlineDocs/src/data/param5.py index 69f6cc46552..f842e48995a 100644 --- a/doc/OnlineDocs/src/data/param5.py +++ b/doc/OnlineDocs/src/data/param5.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/param5a.py b/doc/OnlineDocs/src/data/param5a.py index 303b92f9f2e..f65de59ca78 100644 --- a/doc/OnlineDocs/src/data/param5a.py +++ b/doc/OnlineDocs/src/data/param5a.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/param6.py b/doc/OnlineDocs/src/data/param6.py index c3e4b25d144..54cb350298b 100644 --- a/doc/OnlineDocs/src/data/param6.py +++ b/doc/OnlineDocs/src/data/param6.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/param6a.py b/doc/OnlineDocs/src/data/param6a.py index 07e8280cc18..7aabe7ec929 100644 --- a/doc/OnlineDocs/src/data/param6a.py +++ b/doc/OnlineDocs/src/data/param6a.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/param7a.py b/doc/OnlineDocs/src/data/param7a.py index 3bb68b3f3b7..8d0c49210b5 100644 --- a/doc/OnlineDocs/src/data/param7a.py +++ b/doc/OnlineDocs/src/data/param7a.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/param7b.py b/doc/OnlineDocs/src/data/param7b.py index 6e5c857851f..8481083c31c 100644 --- a/doc/OnlineDocs/src/data/param7b.py +++ b/doc/OnlineDocs/src/data/param7b.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/param8a.py b/doc/OnlineDocs/src/data/param8a.py index 57c9b08ca43..59ddba34091 100644 --- a/doc/OnlineDocs/src/data/param8a.py +++ b/doc/OnlineDocs/src/data/param8a.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/set1.py b/doc/OnlineDocs/src/data/set1.py index 5248e9d5dc9..e1d8a09c394 100644 --- a/doc/OnlineDocs/src/data/set1.py +++ b/doc/OnlineDocs/src/data/set1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/set2.py b/doc/OnlineDocs/src/data/set2.py index 82772f48e46..8e2f4b756d9 100644 --- a/doc/OnlineDocs/src/data/set2.py +++ b/doc/OnlineDocs/src/data/set2.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/set2a.py b/doc/OnlineDocs/src/data/set2a.py index edf28757f96..6358178d109 100644 --- a/doc/OnlineDocs/src/data/set2a.py +++ b/doc/OnlineDocs/src/data/set2a.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/set3.py b/doc/OnlineDocs/src/data/set3.py index d58e0c0dd43..dced69c2375 100644 --- a/doc/OnlineDocs/src/data/set3.py +++ b/doc/OnlineDocs/src/data/set3.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/set4.py b/doc/OnlineDocs/src/data/set4.py index 29548519571..887d12ebf05 100644 --- a/doc/OnlineDocs/src/data/set4.py +++ b/doc/OnlineDocs/src/data/set4.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/set5.py b/doc/OnlineDocs/src/data/set5.py index 35acd4e4317..8fb5ced0a3f 100644 --- a/doc/OnlineDocs/src/data/set5.py +++ b/doc/OnlineDocs/src/data/set5.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/table0.py b/doc/OnlineDocs/src/data/table0.py index af7f634bd34..10ace6ea37b 100644 --- a/doc/OnlineDocs/src/data/table0.py +++ b/doc/OnlineDocs/src/data/table0.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/table0.ul.py b/doc/OnlineDocs/src/data/table0.ul.py index 213407b071c..79dc2933667 100644 --- a/doc/OnlineDocs/src/data/table0.ul.py +++ b/doc/OnlineDocs/src/data/table0.ul.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/table1.py b/doc/OnlineDocs/src/data/table1.py index 1f86508c60a..d69c35b4860 100644 --- a/doc/OnlineDocs/src/data/table1.py +++ b/doc/OnlineDocs/src/data/table1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/table2.py b/doc/OnlineDocs/src/data/table2.py index d7708b9277f..5b4718f60ad 100644 --- a/doc/OnlineDocs/src/data/table2.py +++ b/doc/OnlineDocs/src/data/table2.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/table3.py b/doc/OnlineDocs/src/data/table3.py index fa871a4f79c..efa438eddd0 100644 --- a/doc/OnlineDocs/src/data/table3.py +++ b/doc/OnlineDocs/src/data/table3.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/table3.ul.py b/doc/OnlineDocs/src/data/table3.ul.py index 713d36b9f3a..ace661ac91c 100644 --- a/doc/OnlineDocs/src/data/table3.ul.py +++ b/doc/OnlineDocs/src/data/table3.ul.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/table4.py b/doc/OnlineDocs/src/data/table4.py index 1af9fe47a44..b571d8ec1e4 100644 --- a/doc/OnlineDocs/src/data/table4.py +++ b/doc/OnlineDocs/src/data/table4.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/table4.ul.py b/doc/OnlineDocs/src/data/table4.ul.py index 2acf8e21ca8..1b4f192130b 100644 --- a/doc/OnlineDocs/src/data/table4.ul.py +++ b/doc/OnlineDocs/src/data/table4.ul.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/table5.py b/doc/OnlineDocs/src/data/table5.py index 2fe3d08fe91..25269bb0bde 100644 --- a/doc/OnlineDocs/src/data/table5.py +++ b/doc/OnlineDocs/src/data/table5.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/table6.py b/doc/OnlineDocs/src/data/table6.py index fcbc2f10860..1e2201cb6d6 100644 --- a/doc/OnlineDocs/src/data/table6.py +++ b/doc/OnlineDocs/src/data/table6.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/data/table7.py b/doc/OnlineDocs/src/data/table7.py index f8f8e769b2e..e5507c546ea 100644 --- a/doc/OnlineDocs/src/data/table7.py +++ b/doc/OnlineDocs/src/data/table7.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/dataportal/dataportal_tab.py b/doc/OnlineDocs/src/dataportal/dataportal_tab.py index d1a75196c99..d6b679078e6 100644 --- a/doc/OnlineDocs/src/dataportal/dataportal_tab.py +++ b/doc/OnlineDocs/src/dataportal/dataportal_tab.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * # -------------------------------------------------- diff --git a/doc/OnlineDocs/src/dataportal/param_initialization.py b/doc/OnlineDocs/src/dataportal/param_initialization.py index 5567b01f284..71c54b4a9d9 100644 --- a/doc/OnlineDocs/src/dataportal/param_initialization.py +++ b/doc/OnlineDocs/src/dataportal/param_initialization.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * import numpy diff --git a/doc/OnlineDocs/src/dataportal/set_initialization.py b/doc/OnlineDocs/src/dataportal/set_initialization.py index aa7b426fa82..a086473fb1c 100644 --- a/doc/OnlineDocs/src/dataportal/set_initialization.py +++ b/doc/OnlineDocs/src/dataportal/set_initialization.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * import numpy diff --git a/doc/OnlineDocs/src/expr/design.py b/doc/OnlineDocs/src/expr/design.py index b122a5f2bf3..a5401a3c554 100644 --- a/doc/OnlineDocs/src/expr/design.py +++ b/doc/OnlineDocs/src/expr/design.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * # --------------------------------------------- diff --git a/doc/OnlineDocs/src/expr/index.py b/doc/OnlineDocs/src/expr/index.py index 9c9c79bf7be..65291c0ff6f 100644 --- a/doc/OnlineDocs/src/expr/index.py +++ b/doc/OnlineDocs/src/expr/index.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * # --------------------------------------------- diff --git a/doc/OnlineDocs/src/expr/managing.py b/doc/OnlineDocs/src/expr/managing.py index 0a59c13bc1b..48bb005943e 100644 --- a/doc/OnlineDocs/src/expr/managing.py +++ b/doc/OnlineDocs/src/expr/managing.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * from math import isclose import math diff --git a/doc/OnlineDocs/src/expr/overview.py b/doc/OnlineDocs/src/expr/overview.py index 6207a4c4288..32a5f569115 100644 --- a/doc/OnlineDocs/src/expr/overview.py +++ b/doc/OnlineDocs/src/expr/overview.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * # --------------------------------------------- diff --git a/doc/OnlineDocs/src/expr/performance.py b/doc/OnlineDocs/src/expr/performance.py index 53ac5bb4f9e..59514718cb4 100644 --- a/doc/OnlineDocs/src/expr/performance.py +++ b/doc/OnlineDocs/src/expr/performance.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * # --------------------------------------------- diff --git a/doc/OnlineDocs/src/expr/quicksum.py b/doc/OnlineDocs/src/expr/quicksum.py index a1ad9660664..6b4d70bd961 100644 --- a/doc/OnlineDocs/src/expr/quicksum.py +++ b/doc/OnlineDocs/src/expr/quicksum.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * from pyomo.repn import generate_standard_repn import time diff --git a/doc/OnlineDocs/src/scripting/AbstractSuffixes.py b/doc/OnlineDocs/src/scripting/AbstractSuffixes.py index 20a4cc20581..24162d7bc8f 100644 --- a/doc/OnlineDocs/src/scripting/AbstractSuffixes.py +++ b/doc/OnlineDocs/src/scripting/AbstractSuffixes.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/doc/OnlineDocs/src/scripting/Isinglebuild.py b/doc/OnlineDocs/src/scripting/Isinglebuild.py index 00f79c9a750..b31e692d198 100644 --- a/doc/OnlineDocs/src/scripting/Isinglebuild.py +++ b/doc/OnlineDocs/src/scripting/Isinglebuild.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # Isinglebuild.py # NodesIn and NodesOut are created by a build action using the Arcs from pyomo.environ import * diff --git a/doc/OnlineDocs/src/scripting/NodesIn_init.py b/doc/OnlineDocs/src/scripting/NodesIn_init.py index 4a90029baa3..60268ef3183 100644 --- a/doc/OnlineDocs/src/scripting/NodesIn_init.py +++ b/doc/OnlineDocs/src/scripting/NodesIn_init.py @@ -1,3 +1,15 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + + def NodesIn_init(model, node): retval = [] for i, j in model.Arcs: diff --git a/doc/OnlineDocs/src/scripting/Z_init.py b/doc/OnlineDocs/src/scripting/Z_init.py index 426de6f7d08..ab441eef101 100644 --- a/doc/OnlineDocs/src/scripting/Z_init.py +++ b/doc/OnlineDocs/src/scripting/Z_init.py @@ -1,3 +1,15 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + + def Z_init(model, i): if i > 10: return Set.End diff --git a/doc/OnlineDocs/src/scripting/abstract2.py b/doc/OnlineDocs/src/scripting/abstract2.py index 1e14d1d1898..1f7c35508db 100644 --- a/doc/OnlineDocs/src/scripting/abstract2.py +++ b/doc/OnlineDocs/src/scripting/abstract2.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # abstract2.py diff --git a/doc/OnlineDocs/src/scripting/abstract2piece.py b/doc/OnlineDocs/src/scripting/abstract2piece.py index 225ec0d1a64..8b58184e5e4 100644 --- a/doc/OnlineDocs/src/scripting/abstract2piece.py +++ b/doc/OnlineDocs/src/scripting/abstract2piece.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # abstract2piece.py # Similar to abstract2.py, but the objective is now c times x to the fourth power diff --git a/doc/OnlineDocs/src/scripting/abstract2piecebuild.py b/doc/OnlineDocs/src/scripting/abstract2piecebuild.py index 1f00cdb0265..694ee2b0336 100644 --- a/doc/OnlineDocs/src/scripting/abstract2piecebuild.py +++ b/doc/OnlineDocs/src/scripting/abstract2piecebuild.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # abstract2piecebuild.py # Similar to abstract2piece.py, but the breakpoints are created using a build action diff --git a/doc/OnlineDocs/src/scripting/block_iter_example.py b/doc/OnlineDocs/src/scripting/block_iter_example.py index 680e0d1728b..27d5a0e1819 100644 --- a/doc/OnlineDocs/src/scripting/block_iter_example.py +++ b/doc/OnlineDocs/src/scripting/block_iter_example.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # written by jds, adapted for doc by dlw from pyomo.environ import * diff --git a/doc/OnlineDocs/src/scripting/concrete1.py b/doc/OnlineDocs/src/scripting/concrete1.py index 2cd1a1f722c..31986c21ded 100644 --- a/doc/OnlineDocs/src/scripting/concrete1.py +++ b/doc/OnlineDocs/src/scripting/concrete1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = ConcreteModel() diff --git a/doc/OnlineDocs/src/scripting/doubleA.py b/doc/OnlineDocs/src/scripting/doubleA.py index 12a07944db3..2f65266d685 100644 --- a/doc/OnlineDocs/src/scripting/doubleA.py +++ b/doc/OnlineDocs/src/scripting/doubleA.py @@ -1,3 +1,15 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + + def doubleA_init(model): return (i * 2 for i in model.A) diff --git a/doc/OnlineDocs/src/scripting/driveabs2.py b/doc/OnlineDocs/src/scripting/driveabs2.py index 45862195a57..87056f0fcb6 100644 --- a/doc/OnlineDocs/src/scripting/driveabs2.py +++ b/doc/OnlineDocs/src/scripting/driveabs2.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # driveabs2.py import pyomo.environ as pyo diff --git a/doc/OnlineDocs/src/scripting/driveconc1.py b/doc/OnlineDocs/src/scripting/driveconc1.py index 95b0f42806d..2f7ece65a30 100644 --- a/doc/OnlineDocs/src/scripting/driveconc1.py +++ b/doc/OnlineDocs/src/scripting/driveconc1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # driveconc1.py import pyomo.environ as pyo diff --git a/doc/OnlineDocs/src/scripting/iterative1.py b/doc/OnlineDocs/src/scripting/iterative1.py index 61b0fd3828e..8e91ea0d516 100644 --- a/doc/OnlineDocs/src/scripting/iterative1.py +++ b/doc/OnlineDocs/src/scripting/iterative1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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_symbols_for_pyomo # iterative1.py import pyomo.environ as pyo diff --git a/doc/OnlineDocs/src/scripting/iterative2.py b/doc/OnlineDocs/src/scripting/iterative2.py index e559a2c8400..558e7427441 100644 --- a/doc/OnlineDocs/src/scripting/iterative2.py +++ b/doc/OnlineDocs/src/scripting/iterative2.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # iterative2.py import pyomo.environ as pyo diff --git a/doc/OnlineDocs/src/scripting/noiteration1.py b/doc/OnlineDocs/src/scripting/noiteration1.py index be9fb529855..079b99365da 100644 --- a/doc/OnlineDocs/src/scripting/noiteration1.py +++ b/doc/OnlineDocs/src/scripting/noiteration1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # noiteration1.py import pyomo.environ as pyo diff --git a/doc/OnlineDocs/src/scripting/parallel.py b/doc/OnlineDocs/src/scripting/parallel.py index cf9b55d9605..ead3e1d674b 100644 --- a/doc/OnlineDocs/src/scripting/parallel.py +++ b/doc/OnlineDocs/src/scripting/parallel.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # parallel.py # run with mpirun -np 2 python -m mpi4py parallel.py import pyomo.environ as pyo diff --git a/doc/OnlineDocs/src/scripting/spy4Constraints.py b/doc/OnlineDocs/src/scripting/spy4Constraints.py index f0033bbc33e..f0f43672602 100644 --- a/doc/OnlineDocs/src/scripting/spy4Constraints.py +++ b/doc/OnlineDocs/src/scripting/spy4Constraints.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """ David L. Woodruff and Mingye Yang, Spring 2018 Code snippets for Constraints.rst in testable form diff --git a/doc/OnlineDocs/src/scripting/spy4Expressions.py b/doc/OnlineDocs/src/scripting/spy4Expressions.py index 0e8a50c78b3..415481203a5 100644 --- a/doc/OnlineDocs/src/scripting/spy4Expressions.py +++ b/doc/OnlineDocs/src/scripting/spy4Expressions.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """ David L. Woodruff and Mingye Yang, Spring 2018 Code snippets for Expressions.rst in testable form diff --git a/doc/OnlineDocs/src/scripting/spy4PyomoCommand.py b/doc/OnlineDocs/src/scripting/spy4PyomoCommand.py index f655b812076..66dcb5e36b4 100644 --- a/doc/OnlineDocs/src/scripting/spy4PyomoCommand.py +++ b/doc/OnlineDocs/src/scripting/spy4PyomoCommand.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """ David L. Woodruff and Mingye Yang, Spring 2018 Code snippets for PyomoCommand.rst in testable form diff --git a/doc/OnlineDocs/src/scripting/spy4Variables.py b/doc/OnlineDocs/src/scripting/spy4Variables.py index c4e2ff612f1..1dcdfc58a10 100644 --- a/doc/OnlineDocs/src/scripting/spy4Variables.py +++ b/doc/OnlineDocs/src/scripting/spy4Variables.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """ David L. Woodruff and Mingye Yang, Spring 2018 Code snippets for Variables.rst in testable form diff --git a/doc/OnlineDocs/src/scripting/spy4scripts.py b/doc/OnlineDocs/src/scripting/spy4scripts.py index 48ba923d09c..d35030241fc 100644 --- a/doc/OnlineDocs/src/scripting/spy4scripts.py +++ b/doc/OnlineDocs/src/scripting/spy4scripts.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + ###NOTE: as of May 16, this will not even come close to running. DLW ### and it is "wrong" in a lot of places. ### Someone should edit this file, then delete these comment lines. DLW may 16 diff --git a/doc/OnlineDocs/src/strip_examples.py b/doc/OnlineDocs/src/strip_examples.py index 045af6b87cc..dc742c1f53b 100644 --- a/doc/OnlineDocs/src/strip_examples.py +++ b/doc/OnlineDocs/src/strip_examples.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # # This script finds all *.py files in the current and subdirectories. # It processes these files to find blocks that start/end with "# @" diff --git a/examples/dae/car_example.py b/examples/dae/car_example.py index a157159cf6c..f632e83f62a 100644 --- a/examples/dae/car_example.py +++ b/examples/dae/car_example.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # Ampl Car Example # # Shows how to convert a minimize final time optimal control problem diff --git a/examples/dae/disease_DAE.py b/examples/dae/disease_DAE.py index 59e598aa504..319e7276d83 100644 --- a/examples/dae/disease_DAE.py +++ b/examples/dae/disease_DAE.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + ### # SIR disease model using radau collocation ### diff --git a/examples/dae/run_disease.py b/examples/dae/run_disease.py index 139046d434e..04457dfc890 100644 --- a/examples/dae/run_disease.py +++ b/examples/dae/run_disease.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * from pyomo.dae import * from disease_DAE import model diff --git a/examples/dae/run_stochpdegas_automatic.py b/examples/dae/run_stochpdegas_automatic.py index dd710588406..92f95b9d828 100644 --- a/examples/dae/run_stochpdegas_automatic.py +++ b/examples/dae/run_stochpdegas_automatic.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 time from pyomo.environ import * diff --git a/examples/dae/simulator_dae_example.py b/examples/dae/simulator_dae_example.py index ef6484be6c6..81fd3af816d 100644 --- a/examples/dae/simulator_dae_example.py +++ b/examples/dae/simulator_dae_example.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # # Batch reactor example from Biegler book on Nonlinear Programming Chapter 9 # diff --git a/examples/dae/simulator_dae_multindex_example.py b/examples/dae/simulator_dae_multindex_example.py index d1a97fec79f..bc17fd41643 100644 --- a/examples/dae/simulator_dae_multindex_example.py +++ b/examples/dae/simulator_dae_multindex_example.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # # Batch reactor example from Biegler book on Nonlinear Programming Chapter 9 # diff --git a/examples/dae/simulator_ode_example.py b/examples/dae/simulator_ode_example.py index bf600cf163e..ae30071f4ef 100644 --- a/examples/dae/simulator_ode_example.py +++ b/examples/dae/simulator_ode_example.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # # Example from Scipy odeint examples # diff --git a/examples/dae/simulator_ode_multindex_example.py b/examples/dae/simulator_ode_multindex_example.py index fa2623f4cc2..e02eabef076 100644 --- a/examples/dae/simulator_ode_multindex_example.py +++ b/examples/dae/simulator_ode_multindex_example.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # # Example from Scipy odeint examples # diff --git a/examples/dae/stochpdegas_automatic.py b/examples/dae/stochpdegas_automatic.py index fdde099a396..e846d045ddc 100644 --- a/examples/dae/stochpdegas_automatic.py +++ b/examples/dae/stochpdegas_automatic.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # stochastic pde model for natural gas network # victor m. zavala / 2013 diff --git a/examples/doc/samples/__init__.py b/examples/doc/samples/__init__.py index 3115f06ef53..c967348cb68 100644 --- a/examples/doc/samples/__init__.py +++ b/examples/doc/samples/__init__.py @@ -1 +1,12 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # Dummy file for pytest diff --git a/examples/doc/samples/case_studies/deer/DeerProblem.py b/examples/doc/samples/case_studies/deer/DeerProblem.py index 0b6b7252aaa..dfdc987ade4 100644 --- a/examples/doc/samples/case_studies/deer/DeerProblem.py +++ b/examples/doc/samples/case_studies/deer/DeerProblem.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core import * # diff --git a/examples/doc/samples/case_studies/diet/DietProblem.py b/examples/doc/samples/case_studies/diet/DietProblem.py index f070201c28e..462deee03a5 100644 --- a/examples/doc/samples/case_studies/diet/DietProblem.py +++ b/examples/doc/samples/case_studies/diet/DietProblem.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core import * model = AbstractModel() diff --git a/examples/doc/samples/case_studies/disease_est/DiseaseEstimation.py b/examples/doc/samples/case_studies/disease_est/DiseaseEstimation.py index c685a6ee67f..d8f3f94bcc5 100644 --- a/examples/doc/samples/case_studies/disease_est/DiseaseEstimation.py +++ b/examples/doc/samples/case_studies/disease_est/DiseaseEstimation.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core import * model = AbstractModel() diff --git a/examples/doc/samples/case_studies/max_flow/MaxFlow.py b/examples/doc/samples/case_studies/max_flow/MaxFlow.py index c6eb42ccf7d..36d42dfd3e3 100644 --- a/examples/doc/samples/case_studies/max_flow/MaxFlow.py +++ b/examples/doc/samples/case_studies/max_flow/MaxFlow.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core import * model = AbstractModel() diff --git a/examples/doc/samples/case_studies/network_flow/networkFlow1.py b/examples/doc/samples/case_studies/network_flow/networkFlow1.py index adfaab4476b..a1d05ccbd20 100644 --- a/examples/doc/samples/case_studies/network_flow/networkFlow1.py +++ b/examples/doc/samples/case_studies/network_flow/networkFlow1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core import * model = AbstractModel() diff --git a/examples/doc/samples/case_studies/rosen/Rosenbrock.py b/examples/doc/samples/case_studies/rosen/Rosenbrock.py index 9677cea95dd..f70fa30199c 100644 --- a/examples/doc/samples/case_studies/rosen/Rosenbrock.py +++ b/examples/doc/samples/case_studies/rosen/Rosenbrock.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # @intro: from pyomo.core import * diff --git a/examples/doc/samples/case_studies/transportation/transportation.py b/examples/doc/samples/case_studies/transportation/transportation.py index 26fcb5f0b66..620e6c3fa1d 100644 --- a/examples/doc/samples/case_studies/transportation/transportation.py +++ b/examples/doc/samples/case_studies/transportation/transportation.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core import * model = AbstractModel() diff --git a/examples/doc/samples/comparisons/cutstock/cutstock_cplex.py b/examples/doc/samples/comparisons/cutstock/cutstock_cplex.py index 796c39810f8..e61f82b388d 100644 --- a/examples/doc/samples/comparisons/cutstock/cutstock_cplex.py +++ b/examples/doc/samples/comparisons/cutstock/cutstock_cplex.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 cplex from cutstock_util import * from cplex.exceptions import CplexSolverError diff --git a/examples/doc/samples/comparisons/cutstock/cutstock_grb.py b/examples/doc/samples/comparisons/cutstock/cutstock_grb.py index 4fa4556fc96..9940c729b6e 100644 --- a/examples/doc/samples/comparisons/cutstock/cutstock_grb.py +++ b/examples/doc/samples/comparisons/cutstock/cutstock_grb.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 gurobipy import * from cutstock_util import * diff --git a/examples/doc/samples/comparisons/cutstock/cutstock_lpsolve.py b/examples/doc/samples/comparisons/cutstock/cutstock_lpsolve.py index 658ee006c30..5bc7aea2116 100644 --- a/examples/doc/samples/comparisons/cutstock/cutstock_lpsolve.py +++ b/examples/doc/samples/comparisons/cutstock/cutstock_lpsolve.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 lpsolve55 import * from cutstock_util import * diff --git a/examples/doc/samples/comparisons/cutstock/cutstock_pulpor.py b/examples/doc/samples/comparisons/cutstock/cutstock_pulpor.py index 2f2506ba3d6..3f366e9b3e2 100644 --- a/examples/doc/samples/comparisons/cutstock/cutstock_pulpor.py +++ b/examples/doc/samples/comparisons/cutstock/cutstock_pulpor.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 pulp import * from cutstock_util import * diff --git a/examples/doc/samples/comparisons/cutstock/cutstock_pyomo.py b/examples/doc/samples/comparisons/cutstock/cutstock_pyomo.py index a67ebdd0675..c87b93c37a5 100644 --- a/examples/doc/samples/comparisons/cutstock/cutstock_pyomo.py +++ b/examples/doc/samples/comparisons/cutstock/cutstock_pyomo.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core import * import pyomo.opt from cutstock_util import * diff --git a/examples/doc/samples/comparisons/cutstock/cutstock_util.py b/examples/doc/samples/comparisons/cutstock/cutstock_util.py index 1cd8c61922f..3fde234a0f9 100644 --- a/examples/doc/samples/comparisons/cutstock/cutstock_util.py +++ b/examples/doc/samples/comparisons/cutstock/cutstock_util.py @@ -1,3 +1,15 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + + def getCutCount(): cutCount = 0 fout1 = open('WidthDemand.csv', 'r') diff --git a/examples/doc/samples/comparisons/sched/pyomo/sched.py b/examples/doc/samples/comparisons/sched/pyomo/sched.py index 627bc083fbe..2c03bebb421 100644 --- a/examples/doc/samples/comparisons/sched/pyomo/sched.py +++ b/examples/doc/samples/comparisons/sched/pyomo/sched.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core import * model = AbstractModel() diff --git a/examples/doc/samples/scripts/__init__.py b/examples/doc/samples/scripts/__init__.py index 3115f06ef53..c967348cb68 100644 --- a/examples/doc/samples/scripts/__init__.py +++ b/examples/doc/samples/scripts/__init__.py @@ -1 +1,12 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # Dummy file for pytest diff --git a/examples/doc/samples/scripts/s1/knapsack.py b/examples/doc/samples/scripts/s1/knapsack.py index 642e0faaaed..2965d76650c 100644 --- a/examples/doc/samples/scripts/s1/knapsack.py +++ b/examples/doc/samples/scripts/s1/knapsack.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core import * diff --git a/examples/doc/samples/scripts/s1/script.py b/examples/doc/samples/scripts/s1/script.py index 02b6b406922..b5d60af6182 100644 --- a/examples/doc/samples/scripts/s1/script.py +++ b/examples/doc/samples/scripts/s1/script.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core import * import pyomo.opt import pyomo.environ diff --git a/examples/doc/samples/scripts/s2/knapsack.py b/examples/doc/samples/scripts/s2/knapsack.py index a7d693f5d35..66e55188871 100644 --- a/examples/doc/samples/scripts/s2/knapsack.py +++ b/examples/doc/samples/scripts/s2/knapsack.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core import * diff --git a/examples/doc/samples/scripts/s2/script.py b/examples/doc/samples/scripts/s2/script.py index 88de1dec680..481ae7b26bb 100644 --- a/examples/doc/samples/scripts/s2/script.py +++ b/examples/doc/samples/scripts/s2/script.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core import * import pyomo.opt import pyomo.environ diff --git a/examples/doc/samples/update.py b/examples/doc/samples/update.py index 9eae2f4b694..ab2195d1f32 100644 --- a/examples/doc/samples/update.py +++ b/examples/doc/samples/update.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + #!/usr/bin/env python # # This is a Python script that regenerates the top-level TRAC.txt file, which diff --git a/examples/gdp/batchProcessing.py b/examples/gdp/batchProcessing.py index f0980dd5034..4f3fb02df25 100644 --- a/examples/gdp/batchProcessing.py +++ b/examples/gdp/batchProcessing.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * from pyomo.gdp import * diff --git a/examples/gdp/circles/circles.py b/examples/gdp/circles/circles.py index ae905998403..3a7846f1441 100644 --- a/examples/gdp/circles/circles.py +++ b/examples/gdp/circles/circles.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """ The "circles" GDP example problem originating in Lee and Grossman (2000). The goal is to choose a point to minimize a convex quadratic function over a set of diff --git a/examples/gdp/constrained_layout/cons_layout_model.py b/examples/gdp/constrained_layout/cons_layout_model.py index 245aa2df58e..9f9169ede22 100644 --- a/examples/gdp/constrained_layout/cons_layout_model.py +++ b/examples/gdp/constrained_layout/cons_layout_model.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """2-D constrained layout example. Example based on: https://www.minlp.org/library/problem/index.php?i=107&lib=GDP diff --git a/examples/gdp/eight_process/eight_proc_logical.py b/examples/gdp/eight_process/eight_proc_logical.py index 60f7acee876..23827a52d71 100644 --- a/examples/gdp/eight_process/eight_proc_logical.py +++ b/examples/gdp/eight_process/eight_proc_logical.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Disjunctive re-implementation of eight-process problem. Re-implementation of Duran example 3 superstructure synthesis problem in Pyomo diff --git a/examples/gdp/eight_process/eight_proc_model.py b/examples/gdp/eight_process/eight_proc_model.py index 840b6911d83..d333405e469 100644 --- a/examples/gdp/eight_process/eight_proc_model.py +++ b/examples/gdp/eight_process/eight_proc_model.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Disjunctive re-implementation of eight-process problem. Re-implementation of Duran example 3 superstructure synthesis problem in Pyomo diff --git a/examples/gdp/eight_process/eight_proc_verbose_model.py b/examples/gdp/eight_process/eight_proc_verbose_model.py index cae584d4127..fc748cce20f 100644 --- a/examples/gdp/eight_process/eight_proc_verbose_model.py +++ b/examples/gdp/eight_process/eight_proc_verbose_model.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Disjunctive re-implementation of eight-process problem. This is the more verbose formulation of the same problem given in diff --git a/examples/gdp/farm_layout/farm_layout.py b/examples/gdp/farm_layout/farm_layout.py index 411e2de3242..87043bc4ff5 100644 --- a/examples/gdp/farm_layout/farm_layout.py +++ b/examples/gdp/farm_layout/farm_layout.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """ Farm layout example from Sawaya (2006). The goal is to determine optimal placements and dimensions for farm plots of specified areas to minimize the perimeter of a minimal enclosing fence. This is a GDP problem with diff --git a/examples/gdp/medTermPurchasing_Literal.py b/examples/gdp/medTermPurchasing_Literal.py index c9b27920396..14ec25d750c 100755 --- a/examples/gdp/medTermPurchasing_Literal.py +++ b/examples/gdp/medTermPurchasing_Literal.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * from pyomo.gdp import * diff --git a/examples/gdp/nine_process/small_process.py b/examples/gdp/nine_process/small_process.py index 2758069f316..adc7098d991 100644 --- a/examples/gdp/nine_process/small_process.py +++ b/examples/gdp/nine_process/small_process.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Small process synthesis-inspired toy GDP example. """ diff --git a/examples/gdp/simple1.py b/examples/gdp/simple1.py index f7c77b111f0..323943cd552 100644 --- a/examples/gdp/simple1.py +++ b/examples/gdp/simple1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # Example: modeling a complementarity condition as a # disjunction # diff --git a/examples/gdp/simple2.py b/examples/gdp/simple2.py index 6bcc7bbf747..9c13872100c 100644 --- a/examples/gdp/simple2.py +++ b/examples/gdp/simple2.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # Example: modeling a complementarity condition as a # disjunction # diff --git a/examples/gdp/simple3.py b/examples/gdp/simple3.py index 6b3d6ec46c4..bbe9745d193 100644 --- a/examples/gdp/simple3.py +++ b/examples/gdp/simple3.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # Example: modeling a complementarity condition as a # disjunction # diff --git a/examples/gdp/small_lit/basic_step.py b/examples/gdp/small_lit/basic_step.py index 48ef52d9ba0..fd466dfc1f4 100644 --- a/examples/gdp/small_lit/basic_step.py +++ b/examples/gdp/small_lit/basic_step.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """ Example from Section 3.2 in paper of Pseudo Basic Steps Ref: diff --git a/examples/gdp/small_lit/contracts_problem.py b/examples/gdp/small_lit/contracts_problem.py index 500fe15cb2a..9d1254688b2 100644 --- a/examples/gdp/small_lit/contracts_problem.py +++ b/examples/gdp/small_lit/contracts_problem.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """ Example from 'Lagrangean Relaxation of the Hull-Reformulation of Linear \ Generalized Disjunctive Programs and its use in Disjunctive Branch \ and Bound' Page 25 f. diff --git a/examples/gdp/small_lit/ex1_Lee.py b/examples/gdp/small_lit/ex1_Lee.py index ddd2e1c3d2f..05bd1bd1bc0 100644 --- a/examples/gdp/small_lit/ex1_Lee.py +++ b/examples/gdp/small_lit/ex1_Lee.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Simple example of nonlinear problem modeled with GDP framework. Taken from Example 1 of the paper "New Algorithms for Nonlinear Generalized Disjunctive Programming" by Lee and Grossmann diff --git a/examples/gdp/small_lit/ex_633_trespalacios.py b/examples/gdp/small_lit/ex_633_trespalacios.py index 61b7294e3ba..499294be2ae 100644 --- a/examples/gdp/small_lit/ex_633_trespalacios.py +++ b/examples/gdp/small_lit/ex_633_trespalacios.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Analytical example from Section 6.3.3 of F. Trespalacions Ph.D. Thesis (2015) Analytical example for a nonconvex GDP with 2 disjunctions, each with 2 disjuncts. diff --git a/examples/gdp/small_lit/nonconvex_HEN.py b/examples/gdp/small_lit/nonconvex_HEN.py index 99e2c4f15e2..bdec0e2823a 100644 --- a/examples/gdp/small_lit/nonconvex_HEN.py +++ b/examples/gdp/small_lit/nonconvex_HEN.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """ Example from 'Systematic Modeling of Discrete-Continuous Optimization \ Models through Generalized Disjunctive Programming' Ignacio E. Grossmann and Francisco Trespalacios, 2013 diff --git a/examples/gdp/stickies.py b/examples/gdp/stickies.py index 75beb911415..154a9cbc0cd 100644 --- a/examples/gdp/stickies.py +++ b/examples/gdp/stickies.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 os from pyomo.common.fileutils import this_file_dir diff --git a/examples/gdp/strip_packing/stripPacking.py b/examples/gdp/strip_packing/stripPacking.py index 0e8902c5ee4..fb2ed3f91fd 100644 --- a/examples/gdp/strip_packing/stripPacking.py +++ b/examples/gdp/strip_packing/stripPacking.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core import * from pyomo.gdp import * diff --git a/examples/gdp/strip_packing/strip_packing_8rect.py b/examples/gdp/strip_packing/strip_packing_8rect.py index e1350dbc39e..f03b3f798f9 100644 --- a/examples/gdp/strip_packing/strip_packing_8rect.py +++ b/examples/gdp/strip_packing/strip_packing_8rect.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Strip packing example from MINLP.org library. Strip-packing example from http://minlp.org/library/lib.php?lib=GDP This model packs a set of rectangles without rotation or overlap within a diff --git a/examples/gdp/strip_packing/strip_packing_concrete.py b/examples/gdp/strip_packing/strip_packing_concrete.py index 1313d75561c..d5ace9632fd 100644 --- a/examples/gdp/strip_packing/strip_packing_concrete.py +++ b/examples/gdp/strip_packing/strip_packing_concrete.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Strip packing example from MINLP.org library. Strip-packing example from http://minlp.org/library/lib.php?lib=GDP diff --git a/examples/gdp/two_rxn_lee/two_rxn_model.py b/examples/gdp/two_rxn_lee/two_rxn_model.py index 2e5f1734130..4f9471b583a 100644 --- a/examples/gdp/two_rxn_lee/two_rxn_model.py +++ b/examples/gdp/two_rxn_lee/two_rxn_model.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Two reactor model from literature. See README.md.""" from pyomo.core import ConcreteModel, Constraint, Objective, Param, Var, maximize diff --git a/examples/kernel/blocks.py b/examples/kernel/blocks.py index 7036981dcc8..b19108ffb44 100644 --- a/examples/kernel/blocks.py +++ b/examples/kernel/blocks.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.kernel as pmo # diff --git a/examples/kernel/conic.py b/examples/kernel/conic.py index a2a787794a4..86e5a95580c 100644 --- a/examples/kernel/conic.py +++ b/examples/kernel/conic.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.kernel as pmo # diff --git a/examples/kernel/constraints.py b/examples/kernel/constraints.py index 6495ad12f63..e5bf9797987 100644 --- a/examples/kernel/constraints.py +++ b/examples/kernel/constraints.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.kernel as pmo v = pmo.variable() diff --git a/examples/kernel/containers.py b/examples/kernel/containers.py index 9b525e87af6..44c65bfbda8 100644 --- a/examples/kernel/containers.py +++ b/examples/kernel/containers.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.kernel as pmo # diff --git a/examples/kernel/expressions.py b/examples/kernel/expressions.py index 1756e5d3fd4..2f27239f26e 100644 --- a/examples/kernel/expressions.py +++ b/examples/kernel/expressions.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.kernel as pmo v = pmo.variable(value=2) diff --git a/examples/kernel/mosek/geometric1.py b/examples/kernel/mosek/geometric1.py index b5ec59541c4..9cd492f36cf 100644 --- a/examples/kernel/mosek/geometric1.py +++ b/examples/kernel/mosek/geometric1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # Source: https://docs.mosek.com/9.0/pythonapi/tutorial-gp-shared.html import pyomo.kernel as pmo diff --git a/examples/kernel/mosek/geometric2.py b/examples/kernel/mosek/geometric2.py index 84825c0a39b..2f75f721dc4 100644 --- a/examples/kernel/mosek/geometric2.py +++ b/examples/kernel/mosek/geometric2.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # Source: https://docs.mosek.com/modeling-cookbook/expo.html # (first example in Section 5.3.1) diff --git a/examples/kernel/mosek/maximum_volume_cuboid.py b/examples/kernel/mosek/maximum_volume_cuboid.py index 92e210cf400..661adc3bf5a 100644 --- a/examples/kernel/mosek/maximum_volume_cuboid.py +++ b/examples/kernel/mosek/maximum_volume_cuboid.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 scipy.spatial import ConvexHull from mpl_toolkits.mplot3d import Axes3D from mpl_toolkits.mplot3d.art3d import Poly3DCollection diff --git a/examples/kernel/mosek/power1.py b/examples/kernel/mosek/power1.py index d7a12c1ce54..b8a306e7cdf 100644 --- a/examples/kernel/mosek/power1.py +++ b/examples/kernel/mosek/power1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # Source: https://docs.mosek.com/9.0/pythonapi/tutorial-pow-shared.html import pyomo.kernel as pmo diff --git a/examples/kernel/mosek/semidefinite.py b/examples/kernel/mosek/semidefinite.py index 44ab7c95a68..177662205f8 100644 --- a/examples/kernel/mosek/semidefinite.py +++ b/examples/kernel/mosek/semidefinite.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # Source: https://docs.mosek.com/latest/pythonfusion/tutorial-sdo-shared.html#doc-tutorial-sdo # This examples illustrates SDP formulations in Pyomo using diff --git a/examples/kernel/objectives.py b/examples/kernel/objectives.py index 7d87671ef8d..bbb0c704211 100644 --- a/examples/kernel/objectives.py +++ b/examples/kernel/objectives.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.kernel as pmo v = pmo.variable(value=2) diff --git a/examples/kernel/parameters.py b/examples/kernel/parameters.py index 55b230add6b..a8bce6ca6af 100644 --- a/examples/kernel/parameters.py +++ b/examples/kernel/parameters.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.kernel as pmo # diff --git a/examples/kernel/piecewise_functions.py b/examples/kernel/piecewise_functions.py index 528d4c16791..f372227fcb4 100644 --- a/examples/kernel/piecewise_functions.py +++ b/examples/kernel/piecewise_functions.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.kernel as pmo # diff --git a/examples/kernel/piecewise_nd_functions.py b/examples/kernel/piecewise_nd_functions.py index 847bb5f4a84..78739e3825c 100644 --- a/examples/kernel/piecewise_nd_functions.py +++ b/examples/kernel/piecewise_nd_functions.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 random import sys diff --git a/examples/kernel/special_ordered_sets.py b/examples/kernel/special_ordered_sets.py index 9526a551c12..53328923a60 100644 --- a/examples/kernel/special_ordered_sets.py +++ b/examples/kernel/special_ordered_sets.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.kernel as pmo v1 = pmo.variable() diff --git a/examples/kernel/suffixes.py b/examples/kernel/suffixes.py index 39caa5b8652..029dd046f26 100644 --- a/examples/kernel/suffixes.py +++ b/examples/kernel/suffixes.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.kernel as pmo # diff --git a/examples/kernel/variables.py b/examples/kernel/variables.py index 7ab571245a1..b2dd0ae8dff 100644 --- a/examples/kernel/variables.py +++ b/examples/kernel/variables.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.kernel as pmo # diff --git a/examples/mpec/bard1.py b/examples/mpec/bard1.py index dbe666a7004..4a6f7ab6642 100644 --- a/examples/mpec/bard1.py +++ b/examples/mpec/bard1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # bard1.py QQR2-MN-8-5 # Original Pyomo coding by William Hart # Adapted from AMPL coding by Sven Leyffer diff --git a/examples/mpec/scholtes4.py b/examples/mpec/scholtes4.py index 904729780cf..93cdb8fa6fe 100644 --- a/examples/mpec/scholtes4.py +++ b/examples/mpec/scholtes4.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # scholtes4.py LQR2-MN-3-2 # Original Pyomo coding by William Hart # Adapted from AMPL coding by Sven Leyffer diff --git a/examples/performance/dae/run_stochpdegas1_automatic.py b/examples/performance/dae/run_stochpdegas1_automatic.py index 993e22c7c86..5eacc8992d1 100644 --- a/examples/performance/dae/run_stochpdegas1_automatic.py +++ b/examples/performance/dae/run_stochpdegas1_automatic.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 time from pyomo.environ import * diff --git a/examples/performance/dae/stochpdegas1_automatic.py b/examples/performance/dae/stochpdegas1_automatic.py index 905ec9a5330..962ed266148 100644 --- a/examples/performance/dae/stochpdegas1_automatic.py +++ b/examples/performance/dae/stochpdegas1_automatic.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # stochastic pde model for natural gas network # victor m. zavala / 2013 diff --git a/examples/performance/jump/clnlbeam.py b/examples/performance/jump/clnlbeam.py index d2ceda790ec..18948c1b549 100644 --- a/examples/performance/jump/clnlbeam.py +++ b/examples/performance/jump/clnlbeam.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = AbstractModel() diff --git a/examples/performance/jump/facility.py b/examples/performance/jump/facility.py index 6832e8d32ac..b67f21bd048 100644 --- a/examples/performance/jump/facility.py +++ b/examples/performance/jump/facility.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core import * model = AbstractModel() diff --git a/examples/performance/jump/lqcp.py b/examples/performance/jump/lqcp.py index bb3e66b36f5..b4da6b62a5e 100644 --- a/examples/performance/jump/lqcp.py +++ b/examples/performance/jump/lqcp.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core import * model = ConcreteModel() diff --git a/examples/performance/jump/opf_66200bus.py b/examples/performance/jump/opf_66200bus.py index f3e1822fbfb..022eb938fb0 100644 --- a/examples/performance/jump/opf_66200bus.py +++ b/examples/performance/jump/opf_66200bus.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * diff --git a/examples/performance/jump/opf_6620bus.py b/examples/performance/jump/opf_6620bus.py index 64348ae931e..0e139cd8c5f 100644 --- a/examples/performance/jump/opf_6620bus.py +++ b/examples/performance/jump/opf_6620bus.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * diff --git a/examples/performance/jump/opf_662bus.py b/examples/performance/jump/opf_662bus.py index 6ff97c577e3..5270c573236 100644 --- a/examples/performance/jump/opf_662bus.py +++ b/examples/performance/jump/opf_662bus.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * diff --git a/examples/performance/misc/bilinear1_100.py b/examples/performance/misc/bilinear1_100.py index e68fbba6283..527cf5d7ccc 100644 --- a/examples/performance/misc/bilinear1_100.py +++ b/examples/performance/misc/bilinear1_100.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * diff --git a/examples/performance/misc/bilinear1_100000.py b/examples/performance/misc/bilinear1_100000.py index 924d7233d24..9fdef98c059 100644 --- a/examples/performance/misc/bilinear1_100000.py +++ b/examples/performance/misc/bilinear1_100000.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * diff --git a/examples/performance/misc/bilinear2_100.py b/examples/performance/misc/bilinear2_100.py index 4dd9f9ead57..77b8737339d 100644 --- a/examples/performance/misc/bilinear2_100.py +++ b/examples/performance/misc/bilinear2_100.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * diff --git a/examples/performance/misc/bilinear2_100000.py b/examples/performance/misc/bilinear2_100000.py index 90eeaf82271..7bf224f8b47 100644 --- a/examples/performance/misc/bilinear2_100000.py +++ b/examples/performance/misc/bilinear2_100000.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * diff --git a/examples/performance/misc/diag1_100.py b/examples/performance/misc/diag1_100.py index e47a9179974..e92fc50201f 100644 --- a/examples/performance/misc/diag1_100.py +++ b/examples/performance/misc/diag1_100.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * diff --git a/examples/performance/misc/diag1_100000.py b/examples/performance/misc/diag1_100000.py index a110c0d9d67..2bdfe99e749 100644 --- a/examples/performance/misc/diag1_100000.py +++ b/examples/performance/misc/diag1_100000.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * diff --git a/examples/performance/misc/diag2_100.py b/examples/performance/misc/diag2_100.py index fe820e8590b..fe005eb74f1 100644 --- a/examples/performance/misc/diag2_100.py +++ b/examples/performance/misc/diag2_100.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * diff --git a/examples/performance/misc/diag2_100000.py b/examples/performance/misc/diag2_100000.py index 38563de57b9..eca192b9679 100644 --- a/examples/performance/misc/diag2_100000.py +++ b/examples/performance/misc/diag2_100000.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * diff --git a/examples/performance/misc/set1.py b/examples/performance/misc/set1.py index 53227a3ee73..abf656ee350 100644 --- a/examples/performance/misc/set1.py +++ b/examples/performance/misc/set1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * model = ConcreteModel() diff --git a/examples/performance/misc/sparse1.py b/examples/performance/misc/sparse1.py index 264862760f9..0858f374248 100644 --- a/examples/performance/misc/sparse1.py +++ b/examples/performance/misc/sparse1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # # This is a performance test that we cannot easily execute right now # diff --git a/examples/pyomo/concrete/rosen.py b/examples/pyomo/concrete/rosen.py index a8e8a175127..a8eb89081e8 100644 --- a/examples/pyomo/concrete/rosen.py +++ b/examples/pyomo/concrete/rosen.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # rosen.py from pyomo.environ import * diff --git a/examples/pyomo/concrete/sodacan.py b/examples/pyomo/concrete/sodacan.py index 3c0cfd3aab2..fddc8d5aa95 100644 --- a/examples/pyomo/concrete/sodacan.py +++ b/examples/pyomo/concrete/sodacan.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # sodacan.py from pyomo.environ import * from math import pi diff --git a/examples/pyomo/concrete/sodacan_fig.py b/examples/pyomo/concrete/sodacan_fig.py index bf9ae476b4c..ab33f522dfe 100644 --- a/examples/pyomo/concrete/sodacan_fig.py +++ b/examples/pyomo/concrete/sodacan_fig.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 mpl_toolkits.mplot3d import Axes3D from matplotlib import cm from matplotlib.ticker import LinearLocator, FormatStrFormatter diff --git a/examples/pyomo/concrete/sp.py b/examples/pyomo/concrete/sp.py index edc2d68b170..3a1b8aeef5a 100644 --- a/examples/pyomo/concrete/sp.py +++ b/examples/pyomo/concrete/sp.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # sp.py from pyomo.environ import * from sp_data import * # define c, b, h, and d diff --git a/examples/pyomo/concrete/sp_data.py b/examples/pyomo/concrete/sp_data.py index 58210126819..d65ae5a1d83 100644 --- a/examples/pyomo/concrete/sp_data.py +++ b/examples/pyomo/concrete/sp_data.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + c = 1.0 b = 1.5 h = 0.1 diff --git a/examples/pyomo/p-median/decorated_pmedian.py b/examples/pyomo/p-median/decorated_pmedian.py index 90345daf78d..be4cc5994be 100644 --- a/examples/pyomo/p-median/decorated_pmedian.py +++ b/examples/pyomo/p-median/decorated_pmedian.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * import random diff --git a/examples/pyomobook/__init__.py b/examples/pyomobook/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/examples/pyomobook/__init__.py +++ b/examples/pyomobook/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/examples/pyomobook/abstract-ch/AbstHLinScript.py b/examples/pyomobook/abstract-ch/AbstHLinScript.py index adf700bfd5c..48946e0fb3d 100644 --- a/examples/pyomobook/abstract-ch/AbstHLinScript.py +++ b/examples/pyomobook/abstract-ch/AbstHLinScript.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # AbstHLinScript.py - Script for a simple linear version of (H) import pyomo.environ as pyo diff --git a/examples/pyomobook/abstract-ch/AbstractH.py b/examples/pyomobook/abstract-ch/AbstractH.py index da9f0a4931c..cda8b489f28 100644 --- a/examples/pyomobook/abstract-ch/AbstractH.py +++ b/examples/pyomobook/abstract-ch/AbstractH.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # AbstractH.py - Implement model (H) import pyomo.environ as pyo diff --git a/examples/pyomobook/abstract-ch/AbstractHLinear.py b/examples/pyomobook/abstract-ch/AbstractHLinear.py index 575487d3e95..78ac4813709 100644 --- a/examples/pyomobook/abstract-ch/AbstractHLinear.py +++ b/examples/pyomobook/abstract-ch/AbstractHLinear.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # AbstractHLinear.py - A simple linear version of (H) import pyomo.environ as pyo diff --git a/examples/pyomobook/abstract-ch/abstract5.py b/examples/pyomobook/abstract-ch/abstract5.py index 3a06256dff8..20abd31dbd6 100644 --- a/examples/pyomobook/abstract-ch/abstract5.py +++ b/examples/pyomobook/abstract-ch/abstract5.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # abstract5.py import pyomo.environ as pyo diff --git a/examples/pyomobook/abstract-ch/abstract6.py b/examples/pyomobook/abstract-ch/abstract6.py index d11a4652f64..fdbbed88d25 100644 --- a/examples/pyomobook/abstract-ch/abstract6.py +++ b/examples/pyomobook/abstract-ch/abstract6.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # abstract6.py import pyomo.environ as pyo diff --git a/examples/pyomobook/abstract-ch/abstract7.py b/examples/pyomobook/abstract-ch/abstract7.py index 2fd5d467d3e..21d264d53e1 100644 --- a/examples/pyomobook/abstract-ch/abstract7.py +++ b/examples/pyomobook/abstract-ch/abstract7.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # abstract7.py import pyomo.environ as pyo import pickle diff --git a/examples/pyomobook/abstract-ch/buildactions.py b/examples/pyomobook/abstract-ch/buildactions.py index ad918e2b5f2..a64de052176 100644 --- a/examples/pyomobook/abstract-ch/buildactions.py +++ b/examples/pyomobook/abstract-ch/buildactions.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # buildactions.py: Warehouse location problem showing build actions import pyomo.environ as pyo diff --git a/examples/pyomobook/abstract-ch/concrete1.py b/examples/pyomobook/abstract-ch/concrete1.py index 0ad41c79ea3..d2d6d09ac4f 100644 --- a/examples/pyomobook/abstract-ch/concrete1.py +++ b/examples/pyomobook/abstract-ch/concrete1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.ConcreteModel() diff --git a/examples/pyomobook/abstract-ch/concrete2.py b/examples/pyomobook/abstract-ch/concrete2.py index 6aee434d556..d0500df53fa 100644 --- a/examples/pyomobook/abstract-ch/concrete2.py +++ b/examples/pyomobook/abstract-ch/concrete2.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.ConcreteModel() diff --git a/examples/pyomobook/abstract-ch/diet1.py b/examples/pyomobook/abstract-ch/diet1.py index eb8b071cdb5..319bdec5144 100644 --- a/examples/pyomobook/abstract-ch/diet1.py +++ b/examples/pyomobook/abstract-ch/diet1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # diet1.py import pyomo.environ as pyo diff --git a/examples/pyomobook/abstract-ch/ex.py b/examples/pyomobook/abstract-ch/ex.py index 88005b7dc0c..2309f3330a0 100644 --- a/examples/pyomobook/abstract-ch/ex.py +++ b/examples/pyomobook/abstract-ch/ex.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/param1.py b/examples/pyomobook/abstract-ch/param1.py index fc9fac99ff4..f5f34838215 100644 --- a/examples/pyomobook/abstract-ch/param1.py +++ b/examples/pyomobook/abstract-ch/param1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/param2.py b/examples/pyomobook/abstract-ch/param2.py index d51cbeffe84..ac3b5b8bd27 100644 --- a/examples/pyomobook/abstract-ch/param2.py +++ b/examples/pyomobook/abstract-ch/param2.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/param2a.py b/examples/pyomobook/abstract-ch/param2a.py index fe928eb4197..59f455bc290 100644 --- a/examples/pyomobook/abstract-ch/param2a.py +++ b/examples/pyomobook/abstract-ch/param2a.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/param3.py b/examples/pyomobook/abstract-ch/param3.py index 64efba5c5ad..5c3462f2e64 100644 --- a/examples/pyomobook/abstract-ch/param3.py +++ b/examples/pyomobook/abstract-ch/param3.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/param3a.py b/examples/pyomobook/abstract-ch/param3a.py index 857d96f8318..25b575e3266 100644 --- a/examples/pyomobook/abstract-ch/param3a.py +++ b/examples/pyomobook/abstract-ch/param3a.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/param3b.py b/examples/pyomobook/abstract-ch/param3b.py index 655694c33dd..a4ad2d4ffc5 100644 --- a/examples/pyomobook/abstract-ch/param3b.py +++ b/examples/pyomobook/abstract-ch/param3b.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/param3c.py b/examples/pyomobook/abstract-ch/param3c.py index 7d58b8b6a39..96e2f4e88a4 100644 --- a/examples/pyomobook/abstract-ch/param3c.py +++ b/examples/pyomobook/abstract-ch/param3c.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/param4.py b/examples/pyomobook/abstract-ch/param4.py index c902b9034ad..4f427f44ee6 100644 --- a/examples/pyomobook/abstract-ch/param4.py +++ b/examples/pyomobook/abstract-ch/param4.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/param5.py b/examples/pyomobook/abstract-ch/param5.py index 488e1debda8..6cdb46db30d 100644 --- a/examples/pyomobook/abstract-ch/param5.py +++ b/examples/pyomobook/abstract-ch/param5.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/param5a.py b/examples/pyomobook/abstract-ch/param5a.py index 7e814b917cc..cd0187dabb1 100644 --- a/examples/pyomobook/abstract-ch/param5a.py +++ b/examples/pyomobook/abstract-ch/param5a.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/param6.py b/examples/pyomobook/abstract-ch/param6.py index d9c49a548b2..e4cbb40f984 100644 --- a/examples/pyomobook/abstract-ch/param6.py +++ b/examples/pyomobook/abstract-ch/param6.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/param6a.py b/examples/pyomobook/abstract-ch/param6a.py index e9aca384ee6..c2995ee864c 100644 --- a/examples/pyomobook/abstract-ch/param6a.py +++ b/examples/pyomobook/abstract-ch/param6a.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/param7a.py b/examples/pyomobook/abstract-ch/param7a.py index 2a18cceabf6..3ed9163daec 100644 --- a/examples/pyomobook/abstract-ch/param7a.py +++ b/examples/pyomobook/abstract-ch/param7a.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/param7b.py b/examples/pyomobook/abstract-ch/param7b.py index acf02ddd62f..59f5e28f979 100644 --- a/examples/pyomobook/abstract-ch/param7b.py +++ b/examples/pyomobook/abstract-ch/param7b.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/param8a.py b/examples/pyomobook/abstract-ch/param8a.py index e68378961ed..2e57f9c3bb7 100644 --- a/examples/pyomobook/abstract-ch/param8a.py +++ b/examples/pyomobook/abstract-ch/param8a.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/postprocess_fn.py b/examples/pyomobook/abstract-ch/postprocess_fn.py index f96a5b4dac1..b54c11b5a0e 100644 --- a/examples/pyomobook/abstract-ch/postprocess_fn.py +++ b/examples/pyomobook/abstract-ch/postprocess_fn.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 csv diff --git a/examples/pyomobook/abstract-ch/set1.py b/examples/pyomobook/abstract-ch/set1.py index ee281bd10bd..6c549f61c49 100644 --- a/examples/pyomobook/abstract-ch/set1.py +++ b/examples/pyomobook/abstract-ch/set1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/set2.py b/examples/pyomobook/abstract-ch/set2.py index 27af609cead..bd7f98d5174 100644 --- a/examples/pyomobook/abstract-ch/set2.py +++ b/examples/pyomobook/abstract-ch/set2.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/set2a.py b/examples/pyomobook/abstract-ch/set2a.py index bf8f06dd7a8..e6960396dd7 100644 --- a/examples/pyomobook/abstract-ch/set2a.py +++ b/examples/pyomobook/abstract-ch/set2a.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/set3.py b/examples/pyomobook/abstract-ch/set3.py index 7661963d19d..4a3a27aa342 100644 --- a/examples/pyomobook/abstract-ch/set3.py +++ b/examples/pyomobook/abstract-ch/set3.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/set4.py b/examples/pyomobook/abstract-ch/set4.py index c9125dad657..7d782cb268e 100644 --- a/examples/pyomobook/abstract-ch/set4.py +++ b/examples/pyomobook/abstract-ch/set4.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/set5.py b/examples/pyomobook/abstract-ch/set5.py index 9f79870d3ff..7478316897a 100644 --- a/examples/pyomobook/abstract-ch/set5.py +++ b/examples/pyomobook/abstract-ch/set5.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/abstract-ch/wl_abstract.py b/examples/pyomobook/abstract-ch/wl_abstract.py index f35a5327bfb..361729a1eff 100644 --- a/examples/pyomobook/abstract-ch/wl_abstract.py +++ b/examples/pyomobook/abstract-ch/wl_abstract.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # wl_abstract.py: AbstractModel version of warehouse location determination problem import pyomo.environ as pyo diff --git a/examples/pyomobook/abstract-ch/wl_abstract_script.py b/examples/pyomobook/abstract-ch/wl_abstract_script.py index 0b042405714..b70c6dbb8d2 100644 --- a/examples/pyomobook/abstract-ch/wl_abstract_script.py +++ b/examples/pyomobook/abstract-ch/wl_abstract_script.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # wl_abstract_script.py: Scripting using an AbstractModel import pyomo.environ as pyo diff --git a/examples/pyomobook/blocks-ch/blocks_gen.py b/examples/pyomobook/blocks-ch/blocks_gen.py index 109e881cad5..7a74986ed81 100644 --- a/examples/pyomobook/blocks-ch/blocks_gen.py +++ b/examples/pyomobook/blocks-ch/blocks_gen.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo time = range(5) diff --git a/examples/pyomobook/blocks-ch/blocks_intro.py b/examples/pyomobook/blocks-ch/blocks_intro.py index ad3ceaa4349..3160c29b385 100644 --- a/examples/pyomobook/blocks-ch/blocks_intro.py +++ b/examples/pyomobook/blocks-ch/blocks_intro.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo # @hierarchy: diff --git a/examples/pyomobook/blocks-ch/blocks_lotsizing.py b/examples/pyomobook/blocks-ch/blocks_lotsizing.py index fe0717d8c7c..897ba9a4e5c 100644 --- a/examples/pyomobook/blocks-ch/blocks_lotsizing.py +++ b/examples/pyomobook/blocks-ch/blocks_lotsizing.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.ConcreteModel() diff --git a/examples/pyomobook/blocks-ch/lotsizing.py b/examples/pyomobook/blocks-ch/lotsizing.py index 47ea265246e..766c1892111 100644 --- a/examples/pyomobook/blocks-ch/lotsizing.py +++ b/examples/pyomobook/blocks-ch/lotsizing.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.ConcreteModel() diff --git a/examples/pyomobook/blocks-ch/lotsizing_no_time.py b/examples/pyomobook/blocks-ch/lotsizing_no_time.py index 901467a0cbb..e0fa69922c1 100644 --- a/examples/pyomobook/blocks-ch/lotsizing_no_time.py +++ b/examples/pyomobook/blocks-ch/lotsizing_no_time.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.ConcreteModel() diff --git a/examples/pyomobook/blocks-ch/lotsizing_uncertain.py b/examples/pyomobook/blocks-ch/lotsizing_uncertain.py index 6d16de7e3a7..9870d195841 100644 --- a/examples/pyomobook/blocks-ch/lotsizing_uncertain.py +++ b/examples/pyomobook/blocks-ch/lotsizing_uncertain.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.ConcreteModel() diff --git a/examples/pyomobook/dae-ch/dae_tester_model.py b/examples/pyomobook/dae-ch/dae_tester_model.py index 9e0da9f4a62..00d51e8e05d 100644 --- a/examples/pyomobook/dae-ch/dae_tester_model.py +++ b/examples/pyomobook/dae-ch/dae_tester_model.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # This is a file for testing miscellaneous code snippets from the DAE chapter import pyomo.environ as pyo import pyomo.dae as dae diff --git a/examples/pyomobook/dae-ch/plot_path_constraint.py b/examples/pyomobook/dae-ch/plot_path_constraint.py index 4c04bc1b6b6..d1af5c617ff 100644 --- a/examples/pyomobook/dae-ch/plot_path_constraint.py +++ b/examples/pyomobook/dae-ch/plot_path_constraint.py @@ -1,3 +1,15 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + + # @plot_path: def plotter(subplot, x, *y, **kwds): plt.subplot(subplot) diff --git a/examples/pyomobook/dae-ch/run_path_constraint.py b/examples/pyomobook/dae-ch/run_path_constraint.py index b819d6a7127..d4345e9e424 100644 --- a/examples/pyomobook/dae-ch/run_path_constraint.py +++ b/examples/pyomobook/dae-ch/run_path_constraint.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo from pyomo.dae import * from path_constraint import m diff --git a/examples/pyomobook/dae-ch/run_path_constraint_tester.py b/examples/pyomobook/dae-ch/run_path_constraint_tester.py index bbcd83f5da5..d71c5126609 100644 --- a/examples/pyomobook/dae-ch/run_path_constraint_tester.py +++ b/examples/pyomobook/dae-ch/run_path_constraint_tester.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common.tee import capture_output from six import StringIO diff --git a/examples/pyomobook/gdp-ch/gdp_uc.py b/examples/pyomobook/gdp-ch/gdp_uc.py index 2495ed9bef1..9f2562efad0 100644 --- a/examples/pyomobook/gdp-ch/gdp_uc.py +++ b/examples/pyomobook/gdp-ch/gdp_uc.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # gdp_uc.py import pyomo.environ as pyo from pyomo.gdp import * diff --git a/examples/pyomobook/gdp-ch/scont.py b/examples/pyomobook/gdp-ch/scont.py index 76597326700..99beb042728 100644 --- a/examples/pyomobook/gdp-ch/scont.py +++ b/examples/pyomobook/gdp-ch/scont.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # scont.py import pyomo.environ as pyo from pyomo.gdp import Disjunct, Disjunction diff --git a/examples/pyomobook/gdp-ch/scont2.py b/examples/pyomobook/gdp-ch/scont2.py index 94e510b358a..cf392441487 100644 --- a/examples/pyomobook/gdp-ch/scont2.py +++ b/examples/pyomobook/gdp-ch/scont2.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo import scont diff --git a/examples/pyomobook/gdp-ch/scont_script.py b/examples/pyomobook/gdp-ch/scont_script.py index 22c9b88ad0c..fee14bedaac 100644 --- a/examples/pyomobook/gdp-ch/scont_script.py +++ b/examples/pyomobook/gdp-ch/scont_script.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo import scont diff --git a/examples/pyomobook/gdp-ch/verify_scont.py b/examples/pyomobook/gdp-ch/verify_scont.py index db44024fe66..a0acd3cf376 100644 --- a/examples/pyomobook/gdp-ch/verify_scont.py +++ b/examples/pyomobook/gdp-ch/verify_scont.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 os diff --git a/examples/pyomobook/intro-ch/abstract5.py b/examples/pyomobook/intro-ch/abstract5.py index 2184ed7b3aa..b273d49b2ea 100644 --- a/examples/pyomobook/intro-ch/abstract5.py +++ b/examples/pyomobook/intro-ch/abstract5.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/intro-ch/coloring_concrete.py b/examples/pyomobook/intro-ch/coloring_concrete.py index 107a31668c4..5b4baca99af 100644 --- a/examples/pyomobook/intro-ch/coloring_concrete.py +++ b/examples/pyomobook/intro-ch/coloring_concrete.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # # Graph coloring example adapted from # diff --git a/examples/pyomobook/intro-ch/concrete1.py b/examples/pyomobook/intro-ch/concrete1.py index a39ca1d41cd..c7aea6ff0b6 100644 --- a/examples/pyomobook/intro-ch/concrete1.py +++ b/examples/pyomobook/intro-ch/concrete1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.ConcreteModel() diff --git a/examples/pyomobook/intro-ch/concrete1_generic.py b/examples/pyomobook/intro-ch/concrete1_generic.py index de648470469..183eb480fa1 100644 --- a/examples/pyomobook/intro-ch/concrete1_generic.py +++ b/examples/pyomobook/intro-ch/concrete1_generic.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo import mydata diff --git a/examples/pyomobook/intro-ch/mydata.py b/examples/pyomobook/intro-ch/mydata.py index 83aa26bacd9..aaf8ec3d8be 100644 --- a/examples/pyomobook/intro-ch/mydata.py +++ b/examples/pyomobook/intro-ch/mydata.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + N = [1, 2] M = [1, 2] c = {1: 1, 2: 2} diff --git a/examples/pyomobook/mpec-ch/ex1a.py b/examples/pyomobook/mpec-ch/ex1a.py index 30cd2842556..a57e714cd1c 100644 --- a/examples/pyomobook/mpec-ch/ex1a.py +++ b/examples/pyomobook/mpec-ch/ex1a.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ex1a.py import pyomo.environ as pyo from pyomo.mpec import Complementarity, complements diff --git a/examples/pyomobook/mpec-ch/ex1b.py b/examples/pyomobook/mpec-ch/ex1b.py index 9592c81c4f6..37a658f5294 100644 --- a/examples/pyomobook/mpec-ch/ex1b.py +++ b/examples/pyomobook/mpec-ch/ex1b.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ex1b.py import pyomo.environ as pyo from pyomo.mpec import ComplementarityList, complements diff --git a/examples/pyomobook/mpec-ch/ex1c.py b/examples/pyomobook/mpec-ch/ex1c.py index aad9c9b0d47..35c0be9345d 100644 --- a/examples/pyomobook/mpec-ch/ex1c.py +++ b/examples/pyomobook/mpec-ch/ex1c.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ex1c.py import pyomo.environ as pyo from pyomo.mpec import ComplementarityList, complements diff --git a/examples/pyomobook/mpec-ch/ex1d.py b/examples/pyomobook/mpec-ch/ex1d.py index fa5247ff831..05105df265c 100644 --- a/examples/pyomobook/mpec-ch/ex1d.py +++ b/examples/pyomobook/mpec-ch/ex1d.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ex1d.py import pyomo.environ as pyo from pyomo.mpec import Complementarity, complements diff --git a/examples/pyomobook/mpec-ch/ex1e.py b/examples/pyomobook/mpec-ch/ex1e.py index bf714411396..66831a58255 100644 --- a/examples/pyomobook/mpec-ch/ex1e.py +++ b/examples/pyomobook/mpec-ch/ex1e.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ex1e.py import pyomo.environ as pyo from pyomo.mpec import ComplementarityList, complements diff --git a/examples/pyomobook/mpec-ch/ex2.py b/examples/pyomobook/mpec-ch/ex2.py index c192ccc7a34..69d3813432d 100644 --- a/examples/pyomobook/mpec-ch/ex2.py +++ b/examples/pyomobook/mpec-ch/ex2.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ex2.py import pyomo.environ as pyo from pyomo.mpec import * diff --git a/examples/pyomobook/mpec-ch/munson1.py b/examples/pyomobook/mpec-ch/munson1.py index c7d171eb416..1c73c6279af 100644 --- a/examples/pyomobook/mpec-ch/munson1.py +++ b/examples/pyomobook/mpec-ch/munson1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # munson1.py import pyomo.environ as pyo from pyomo.mpec import Complementarity, complements diff --git a/examples/pyomobook/mpec-ch/ralph1.py b/examples/pyomobook/mpec-ch/ralph1.py index 1d44a303b84..38ee803b1f1 100644 --- a/examples/pyomobook/mpec-ch/ralph1.py +++ b/examples/pyomobook/mpec-ch/ralph1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ralph1.py import pyomo.environ as pyo from pyomo.mpec import Complementarity, complements diff --git a/examples/pyomobook/nonlinear-ch/deer/DeerProblem.py b/examples/pyomobook/nonlinear-ch/deer/DeerProblem.py index c076a7f4687..574a92ed0a2 100644 --- a/examples/pyomobook/nonlinear-ch/deer/DeerProblem.py +++ b/examples/pyomobook/nonlinear-ch/deer/DeerProblem.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # DeerProblem.py import pyomo.environ as pyo diff --git a/examples/pyomobook/nonlinear-ch/disease_est/disease_estimation.py b/examples/pyomobook/nonlinear-ch/disease_est/disease_estimation.py index 4eb859dc349..4b805b9cf7f 100644 --- a/examples/pyomobook/nonlinear-ch/disease_est/disease_estimation.py +++ b/examples/pyomobook/nonlinear-ch/disease_est/disease_estimation.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # disease_estimation.py import pyomo.environ as pyo diff --git a/examples/pyomobook/nonlinear-ch/multimodal/multimodal_init1.py b/examples/pyomobook/nonlinear-ch/multimodal/multimodal_init1.py index c435cafc3d5..6cebe59a612 100644 --- a/examples/pyomobook/nonlinear-ch/multimodal/multimodal_init1.py +++ b/examples/pyomobook/nonlinear-ch/multimodal/multimodal_init1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # multimodal_init1.py import pyomo.environ as pyo from math import pi diff --git a/examples/pyomobook/nonlinear-ch/multimodal/multimodal_init2.py b/examples/pyomobook/nonlinear-ch/multimodal/multimodal_init2.py index aa0dbae1e66..a2c9d9c5a60 100644 --- a/examples/pyomobook/nonlinear-ch/multimodal/multimodal_init2.py +++ b/examples/pyomobook/nonlinear-ch/multimodal/multimodal_init2.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo from math import pi diff --git a/examples/pyomobook/nonlinear-ch/react_design/ReactorDesign.py b/examples/pyomobook/nonlinear-ch/react_design/ReactorDesign.py index 90822c153a5..c3115f396ce 100644 --- a/examples/pyomobook/nonlinear-ch/react_design/ReactorDesign.py +++ b/examples/pyomobook/nonlinear-ch/react_design/ReactorDesign.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import pyomo.environ as pyo diff --git a/examples/pyomobook/nonlinear-ch/react_design/ReactorDesignTable.py b/examples/pyomobook/nonlinear-ch/react_design/ReactorDesignTable.py index a242c85fbc2..c748cd7d41e 100644 --- a/examples/pyomobook/nonlinear-ch/react_design/ReactorDesignTable.py +++ b/examples/pyomobook/nonlinear-ch/react_design/ReactorDesignTable.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo from ReactorDesign import create_model diff --git a/examples/pyomobook/nonlinear-ch/rosen/rosenbrock.py b/examples/pyomobook/nonlinear-ch/rosen/rosenbrock.py index e1633e2df69..3d14d15aa93 100644 --- a/examples/pyomobook/nonlinear-ch/rosen/rosenbrock.py +++ b/examples/pyomobook/nonlinear-ch/rosen/rosenbrock.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # rosenbrock.py # A Pyomo model for the Rosenbrock problem import pyomo.environ as pyo diff --git a/examples/pyomobook/optimization-ch/ConcHLinScript.py b/examples/pyomobook/optimization-ch/ConcHLinScript.py index 8481a83afbf..f4f5fac6b6c 100644 --- a/examples/pyomobook/optimization-ch/ConcHLinScript.py +++ b/examples/pyomobook/optimization-ch/ConcHLinScript.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ConcHLinScript.py - Linear (H) as a script import pyomo.environ as pyo diff --git a/examples/pyomobook/optimization-ch/ConcreteH.py b/examples/pyomobook/optimization-ch/ConcreteH.py index 1bf2a9446c1..6cb3f7c5052 100644 --- a/examples/pyomobook/optimization-ch/ConcreteH.py +++ b/examples/pyomobook/optimization-ch/ConcreteH.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ConcreteH.py - Implement a particular instance of (H) # @fct: diff --git a/examples/pyomobook/optimization-ch/ConcreteHLinear.py b/examples/pyomobook/optimization-ch/ConcreteHLinear.py index 0b42d5e2187..3cc7478f1c9 100644 --- a/examples/pyomobook/optimization-ch/ConcreteHLinear.py +++ b/examples/pyomobook/optimization-ch/ConcreteHLinear.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ConcreteHLinear.py - Linear (H) import pyomo.environ as pyo diff --git a/examples/pyomobook/optimization-ch/IC_model_dict.py b/examples/pyomobook/optimization-ch/IC_model_dict.py index 4c54ef83701..a76f19797af 100644 --- a/examples/pyomobook/optimization-ch/IC_model_dict.py +++ b/examples/pyomobook/optimization-ch/IC_model_dict.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # IC_model_dict.py - Implement a particular instance of (H) # @fct: diff --git a/examples/pyomobook/overview-ch/var_obj_con_snippet.py b/examples/pyomobook/overview-ch/var_obj_con_snippet.py index 49bb7c1276b..e979e4b18de 100644 --- a/examples/pyomobook/overview-ch/var_obj_con_snippet.py +++ b/examples/pyomobook/overview-ch/var_obj_con_snippet.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.ConcreteModel() diff --git a/examples/pyomobook/overview-ch/wl_abstract.py b/examples/pyomobook/overview-ch/wl_abstract.py index f35a5327bfb..361729a1eff 100644 --- a/examples/pyomobook/overview-ch/wl_abstract.py +++ b/examples/pyomobook/overview-ch/wl_abstract.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # wl_abstract.py: AbstractModel version of warehouse location determination problem import pyomo.environ as pyo diff --git a/examples/pyomobook/overview-ch/wl_abstract_script.py b/examples/pyomobook/overview-ch/wl_abstract_script.py index 0b042405714..b70c6dbb8d2 100644 --- a/examples/pyomobook/overview-ch/wl_abstract_script.py +++ b/examples/pyomobook/overview-ch/wl_abstract_script.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # wl_abstract_script.py: Scripting using an AbstractModel import pyomo.environ as pyo diff --git a/examples/pyomobook/overview-ch/wl_concrete.py b/examples/pyomobook/overview-ch/wl_concrete.py index 29316304f0a..da32c7ba5bf 100644 --- a/examples/pyomobook/overview-ch/wl_concrete.py +++ b/examples/pyomobook/overview-ch/wl_concrete.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # wl_concrete.py # ConcreteModel version of warehouse location problem import pyomo.environ as pyo diff --git a/examples/pyomobook/overview-ch/wl_concrete_script.py b/examples/pyomobook/overview-ch/wl_concrete_script.py index 278937f5aed..59baa241718 100644 --- a/examples/pyomobook/overview-ch/wl_concrete_script.py +++ b/examples/pyomobook/overview-ch/wl_concrete_script.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # wl_concrete_script.py # Solve an instance of the warehouse location problem diff --git a/examples/pyomobook/overview-ch/wl_excel.py b/examples/pyomobook/overview-ch/wl_excel.py index 1c4ad997225..777412abb23 100644 --- a/examples/pyomobook/overview-ch/wl_excel.py +++ b/examples/pyomobook/overview-ch/wl_excel.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # wl_excel.py: Loading Excel data using Pandas import pandas import pyomo.environ as pyo diff --git a/examples/pyomobook/overview-ch/wl_list.py b/examples/pyomobook/overview-ch/wl_list.py index 64db76be548..375a1c7400e 100644 --- a/examples/pyomobook/overview-ch/wl_list.py +++ b/examples/pyomobook/overview-ch/wl_list.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # wl_list.py: Warehouse location problem using constraint lists import pyomo.environ as pyo diff --git a/examples/pyomobook/overview-ch/wl_mutable.py b/examples/pyomobook/overview-ch/wl_mutable.py index e5c4f5e9dbb..1b65dcc84a1 100644 --- a/examples/pyomobook/overview-ch/wl_mutable.py +++ b/examples/pyomobook/overview-ch/wl_mutable.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # wl_mutable.py: warehouse location problem with mutable param import pyomo.environ as pyo diff --git a/examples/pyomobook/overview-ch/wl_mutable_excel.py b/examples/pyomobook/overview-ch/wl_mutable_excel.py index 0906fbb25b3..52cac31f5f6 100644 --- a/examples/pyomobook/overview-ch/wl_mutable_excel.py +++ b/examples/pyomobook/overview-ch/wl_mutable_excel.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # wl_mutable_excel.py: solve problem with different values for P import pandas import pyomo.environ as pyo diff --git a/examples/pyomobook/overview-ch/wl_scalar.py b/examples/pyomobook/overview-ch/wl_scalar.py index ac10fbe8265..b524f22c82d 100644 --- a/examples/pyomobook/overview-ch/wl_scalar.py +++ b/examples/pyomobook/overview-ch/wl_scalar.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # wl_scalar.py: snippets that show the warehouse location problem implemented as scalar quantities import pyomo.environ as pyo diff --git a/examples/pyomobook/performance-ch/SparseSets.py b/examples/pyomobook/performance-ch/SparseSets.py index 90d097b53aa..913b7587368 100644 --- a/examples/pyomobook/performance-ch/SparseSets.py +++ b/examples/pyomobook/performance-ch/SparseSets.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/performance-ch/lin_expr.py b/examples/pyomobook/performance-ch/lin_expr.py index 75f4e70ec2a..20585d4719b 100644 --- a/examples/pyomobook/performance-ch/lin_expr.py +++ b/examples/pyomobook/performance-ch/lin_expr.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo from pyomo.common.timing import TicTocTimer from pyomo.core.expr.numeric_expr import LinearExpression diff --git a/examples/pyomobook/performance-ch/persistent.py b/examples/pyomobook/performance-ch/persistent.py index 98207909cb6..e468b281579 100644 --- a/examples/pyomobook/performance-ch/persistent.py +++ b/examples/pyomobook/performance-ch/persistent.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # @model: import pyomo.environ as pyo diff --git a/examples/pyomobook/performance-ch/wl.py b/examples/pyomobook/performance-ch/wl.py index 34c8a73f36e..614ffc0fd66 100644 --- a/examples/pyomobook/performance-ch/wl.py +++ b/examples/pyomobook/performance-ch/wl.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # wl.py # define a script to demonstrate performance profiling and improvements # @imports: import pyomo.environ as pyo # import pyomo environment diff --git a/examples/pyomobook/pyomo-components-ch/con_declaration.py b/examples/pyomobook/pyomo-components-ch/con_declaration.py index 7775c1b26a0..b014697fd62 100644 --- a/examples/pyomobook/pyomo-components-ch/con_declaration.py +++ b/examples/pyomobook/pyomo-components-ch/con_declaration.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.ConcreteModel() diff --git a/examples/pyomobook/pyomo-components-ch/examples.py b/examples/pyomobook/pyomo-components-ch/examples.py index 6ba96792e28..5f154c0ecc9 100644 --- a/examples/pyomobook/pyomo-components-ch/examples.py +++ b/examples/pyomobook/pyomo-components-ch/examples.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo print("indexed1") diff --git a/examples/pyomobook/pyomo-components-ch/expr_declaration.py b/examples/pyomobook/pyomo-components-ch/expr_declaration.py index 8974a4d406a..9baff1e4dba 100644 --- a/examples/pyomobook/pyomo-components-ch/expr_declaration.py +++ b/examples/pyomobook/pyomo-components-ch/expr_declaration.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.ConcreteModel() diff --git a/examples/pyomobook/pyomo-components-ch/obj_declaration.py b/examples/pyomobook/pyomo-components-ch/obj_declaration.py index 2c26c2b3363..ac8b56a3a03 100644 --- a/examples/pyomobook/pyomo-components-ch/obj_declaration.py +++ b/examples/pyomobook/pyomo-components-ch/obj_declaration.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.ConcreteModel() diff --git a/examples/pyomobook/pyomo-components-ch/param_declaration.py b/examples/pyomobook/pyomo-components-ch/param_declaration.py index a9d3256abfe..ded0adfcb22 100644 --- a/examples/pyomobook/pyomo-components-ch/param_declaration.py +++ b/examples/pyomobook/pyomo-components-ch/param_declaration.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.ConcreteModel() diff --git a/examples/pyomobook/pyomo-components-ch/param_initialization.py b/examples/pyomobook/pyomo-components-ch/param_initialization.py index 11c257d2c31..e9a90210df5 100644 --- a/examples/pyomobook/pyomo-components-ch/param_initialization.py +++ b/examples/pyomobook/pyomo-components-ch/param_initialization.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.ConcreteModel() diff --git a/examples/pyomobook/pyomo-components-ch/param_misc.py b/examples/pyomobook/pyomo-components-ch/param_misc.py index baf76cc7c03..cc3be7a6ac5 100644 --- a/examples/pyomobook/pyomo-components-ch/param_misc.py +++ b/examples/pyomobook/pyomo-components-ch/param_misc.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo # @mutable1: diff --git a/examples/pyomobook/pyomo-components-ch/param_validation.py b/examples/pyomobook/pyomo-components-ch/param_validation.py index c82657c8d0f..cf540ac8a70 100644 --- a/examples/pyomobook/pyomo-components-ch/param_validation.py +++ b/examples/pyomobook/pyomo-components-ch/param_validation.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/pyomo-components-ch/rangeset.py b/examples/pyomobook/pyomo-components-ch/rangeset.py index d5e1015064c..a5ef4a85017 100644 --- a/examples/pyomobook/pyomo-components-ch/rangeset.py +++ b/examples/pyomobook/pyomo-components-ch/rangeset.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/pyomo-components-ch/set_declaration.py b/examples/pyomobook/pyomo-components-ch/set_declaration.py index 1a507d4f588..a60904ff510 100644 --- a/examples/pyomobook/pyomo-components-ch/set_declaration.py +++ b/examples/pyomobook/pyomo-components-ch/set_declaration.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/pyomo-components-ch/set_initialization.py b/examples/pyomobook/pyomo-components-ch/set_initialization.py index 89dbaa713db..972d65e0499 100644 --- a/examples/pyomobook/pyomo-components-ch/set_initialization.py +++ b/examples/pyomobook/pyomo-components-ch/set_initialization.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.ConcreteModel() diff --git a/examples/pyomobook/pyomo-components-ch/set_misc.py b/examples/pyomobook/pyomo-components-ch/set_misc.py index 9a795b196b8..2bd8297cc80 100644 --- a/examples/pyomobook/pyomo-components-ch/set_misc.py +++ b/examples/pyomobook/pyomo-components-ch/set_misc.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.ConcreteModel() diff --git a/examples/pyomobook/pyomo-components-ch/set_options.py b/examples/pyomobook/pyomo-components-ch/set_options.py index 8d49882de2f..27c47ee95c7 100644 --- a/examples/pyomobook/pyomo-components-ch/set_options.py +++ b/examples/pyomobook/pyomo-components-ch/set_options.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/pyomo-components-ch/set_validation.py b/examples/pyomobook/pyomo-components-ch/set_validation.py index a55dfc9ab7c..3b6b8bee25b 100644 --- a/examples/pyomobook/pyomo-components-ch/set_validation.py +++ b/examples/pyomobook/pyomo-components-ch/set_validation.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.AbstractModel() diff --git a/examples/pyomobook/pyomo-components-ch/suffix_declaration.py b/examples/pyomobook/pyomo-components-ch/suffix_declaration.py index 650669ef5a6..a5c0bc988bb 100644 --- a/examples/pyomobook/pyomo-components-ch/suffix_declaration.py +++ b/examples/pyomobook/pyomo-components-ch/suffix_declaration.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo print('') diff --git a/examples/pyomobook/pyomo-components-ch/var_declaration.py b/examples/pyomobook/pyomo-components-ch/var_declaration.py index 60d3b00756a..b3180f25381 100644 --- a/examples/pyomobook/pyomo-components-ch/var_declaration.py +++ b/examples/pyomobook/pyomo-components-ch/var_declaration.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.ConcreteModel() diff --git a/examples/pyomobook/python-ch/BadIndent.py b/examples/pyomobook/python-ch/BadIndent.py index 6ab545a6f46..63013067468 100644 --- a/examples/pyomobook/python-ch/BadIndent.py +++ b/examples/pyomobook/python-ch/BadIndent.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # This comment is the first line of BadIndent.py, # which will cause Python to give an error message # concerning indentation. diff --git a/examples/pyomobook/python-ch/LineExample.py b/examples/pyomobook/python-ch/LineExample.py index 0109a64167e..320289a2a79 100644 --- a/examples/pyomobook/python-ch/LineExample.py +++ b/examples/pyomobook/python-ch/LineExample.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # This comment is the first line of LineExample.py # all characters on a line after the #-character are # ignored by Python diff --git a/examples/pyomobook/python-ch/class.py b/examples/pyomobook/python-ch/class.py index 562cef07ea7..12eafe23a44 100644 --- a/examples/pyomobook/python-ch/class.py +++ b/examples/pyomobook/python-ch/class.py @@ -1,25 +1,36 @@ -# class.py - - -# @all: -class IntLocker: - sint = None - - def __init__(self, i): - self.set_value(i) - - def set_value(self, i): - if type(i) is not int: - print("Error: %d is not integer." % i) - else: - self.sint = i - - def pprint(self): - print("The Int Locker has " + str(self.sint)) - - -a = IntLocker(3) -a.pprint() # prints: The Int Locker has 3 -a.set_value(5) -a.pprint() # prints: The Int Locker has 5 -# @:all +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + +# class.py + + +# @all: +class IntLocker: + sint = None + + def __init__(self, i): + self.set_value(i) + + def set_value(self, i): + if type(i) is not int: + print("Error: %d is not integer." % i) + else: + self.sint = i + + def pprint(self): + print("The Int Locker has " + str(self.sint)) + + +a = IntLocker(3) +a.pprint() # prints: The Int Locker has 3 +a.set_value(5) +a.pprint() # prints: The Int Locker has 5 +# @:all diff --git a/examples/pyomobook/python-ch/ctob.py b/examples/pyomobook/python-ch/ctob.py index e418d27f103..fe2c474de4d 100644 --- a/examples/pyomobook/python-ch/ctob.py +++ b/examples/pyomobook/python-ch/ctob.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # An example of a silly decorator to change 'c' to 'b' # in the return value of a function. diff --git a/examples/pyomobook/python-ch/example.py b/examples/pyomobook/python-ch/example.py index 0a404add58d..2bab6d4b9fe 100644 --- a/examples/pyomobook/python-ch/example.py +++ b/examples/pyomobook/python-ch/example.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # This is a comment line, which is ignored by Python print("Hello World") diff --git a/examples/pyomobook/python-ch/example2.py b/examples/pyomobook/python-ch/example2.py index da7d14e24ae..0c282eccacd 100644 --- a/examples/pyomobook/python-ch/example2.py +++ b/examples/pyomobook/python-ch/example2.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # A modified example.py program print("Hello World") diff --git a/examples/pyomobook/python-ch/functions.py b/examples/pyomobook/python-ch/functions.py index 7948c5e55df..b23b6dc6bee 100644 --- a/examples/pyomobook/python-ch/functions.py +++ b/examples/pyomobook/python-ch/functions.py @@ -1,24 +1,35 @@ -# functions.py - - -# @all: -def Apply(f, a): - r = [] - for i in range(len(a)): - r.append(f(a[i])) - return r - - -def SqifOdd(x): - # if x is odd, 2*int(x/2) is not x - # due to integer divide of x/2 - if 2 * int(x / 2) == x: - return x - else: - return x * x - - -ShortList = range(4) -B = Apply(SqifOdd, ShortList) -print(B) -# @:all +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + +# functions.py + + +# @all: +def Apply(f, a): + r = [] + for i in range(len(a)): + r.append(f(a[i])) + return r + + +def SqifOdd(x): + # if x is odd, 2*int(x/2) is not x + # due to integer divide of x/2 + if 2 * int(x / 2) == x: + return x + else: + return x * x + + +ShortList = range(4) +B = Apply(SqifOdd, ShortList) +print(B) +# @:all diff --git a/examples/pyomobook/python-ch/iterate.py b/examples/pyomobook/python-ch/iterate.py index 3a3422b2a09..cd8fe697afb 100644 --- a/examples/pyomobook/python-ch/iterate.py +++ b/examples/pyomobook/python-ch/iterate.py @@ -1,18 +1,29 @@ -# iterate.py - -# @all: -D = {'Mary': 231} -D['Bob'] = 123 -D['Alice'] = 331 -D['Ted'] = 987 - -for i in sorted(D): - if i == 'Alice': - continue - if i == 'John': - print("Loop ends. Cleese alert!") - break - print(i + " " + str(D[i])) -else: - print("Cleese is not in the list.") -# @:all +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + +# iterate.py + +# @all: +D = {'Mary': 231} +D['Bob'] = 123 +D['Alice'] = 331 +D['Ted'] = 987 + +for i in sorted(D): + if i == 'Alice': + continue + if i == 'John': + print("Loop ends. Cleese alert!") + break + print(i + " " + str(D[i])) +else: + print("Cleese is not in the list.") +# @:all diff --git a/examples/pyomobook/python-ch/pythonconditional.py b/examples/pyomobook/python-ch/pythonconditional.py index 205428e5ad1..2c48a2db6f4 100644 --- a/examples/pyomobook/python-ch/pythonconditional.py +++ b/examples/pyomobook/python-ch/pythonconditional.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # pythonconditional.py # @all: diff --git a/examples/pyomobook/scripts-ch/attributes.py b/examples/pyomobook/scripts-ch/attributes.py index 643162082b6..fccdb6932da 100644 --- a/examples/pyomobook/scripts-ch/attributes.py +++ b/examples/pyomobook/scripts-ch/attributes.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 json import pyomo.environ as pyo from warehouse_model import create_wl_model diff --git a/examples/pyomobook/scripts-ch/prob_mod_ex.py b/examples/pyomobook/scripts-ch/prob_mod_ex.py index 6d610e9b44a..f94fec5eb8a 100644 --- a/examples/pyomobook/scripts-ch/prob_mod_ex.py +++ b/examples/pyomobook/scripts-ch/prob_mod_ex.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.ConcreteModel() diff --git a/examples/pyomobook/scripts-ch/sudoku/sudoku.py b/examples/pyomobook/scripts-ch/sudoku/sudoku.py index ea0c0044e1d..ac6d1eabf14 100644 --- a/examples/pyomobook/scripts-ch/sudoku/sudoku.py +++ b/examples/pyomobook/scripts-ch/sudoku/sudoku.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo # create a standard python dict for mapping subsquares to diff --git a/examples/pyomobook/scripts-ch/sudoku/sudoku_run.py b/examples/pyomobook/scripts-ch/sudoku/sudoku_run.py index 266362308fa..948c5a59ee8 100644 --- a/examples/pyomobook/scripts-ch/sudoku/sudoku_run.py +++ b/examples/pyomobook/scripts-ch/sudoku/sudoku_run.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.opt import SolverFactory, TerminationCondition from sudoku import create_sudoku_model, print_solution, add_integer_cut diff --git a/examples/pyomobook/scripts-ch/value_expression.py b/examples/pyomobook/scripts-ch/value_expression.py index 51c07500ea8..ca154341b43 100644 --- a/examples/pyomobook/scripts-ch/value_expression.py +++ b/examples/pyomobook/scripts-ch/value_expression.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo model = pyo.ConcreteModel() diff --git a/examples/pyomobook/scripts-ch/warehouse_cuts.py b/examples/pyomobook/scripts-ch/warehouse_cuts.py index c6516e796af..82dabfcb6f8 100644 --- a/examples/pyomobook/scripts-ch/warehouse_cuts.py +++ b/examples/pyomobook/scripts-ch/warehouse_cuts.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 warnings warnings.filterwarnings("ignore") diff --git a/examples/pyomobook/scripts-ch/warehouse_load_solutions.py b/examples/pyomobook/scripts-ch/warehouse_load_solutions.py index 790333a0e64..4d47a8ab916 100644 --- a/examples/pyomobook/scripts-ch/warehouse_load_solutions.py +++ b/examples/pyomobook/scripts-ch/warehouse_load_solutions.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 json import pyomo.environ as pyo from warehouse_model import create_wl_model diff --git a/examples/pyomobook/scripts-ch/warehouse_model.py b/examples/pyomobook/scripts-ch/warehouse_model.py index f5983d3cd89..cb9a43563fb 100644 --- a/examples/pyomobook/scripts-ch/warehouse_model.py +++ b/examples/pyomobook/scripts-ch/warehouse_model.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo diff --git a/examples/pyomobook/scripts-ch/warehouse_print.py b/examples/pyomobook/scripts-ch/warehouse_print.py index e0e2f961345..2353a8d6b44 100644 --- a/examples/pyomobook/scripts-ch/warehouse_print.py +++ b/examples/pyomobook/scripts-ch/warehouse_print.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 json import pyomo.environ as pyo from warehouse_model import create_wl_model diff --git a/examples/pyomobook/scripts-ch/warehouse_script.py b/examples/pyomobook/scripts-ch/warehouse_script.py index f2635a45d3d..37d71b466d2 100644 --- a/examples/pyomobook/scripts-ch/warehouse_script.py +++ b/examples/pyomobook/scripts-ch/warehouse_script.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # @script: import json import pyomo.environ as pyo diff --git a/examples/pyomobook/scripts-ch/warehouse_solver_options.py b/examples/pyomobook/scripts-ch/warehouse_solver_options.py index c8eaf11a0f3..5a482bf3216 100644 --- a/examples/pyomobook/scripts-ch/warehouse_solver_options.py +++ b/examples/pyomobook/scripts-ch/warehouse_solver_options.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # @script: import json import pyomo.environ as pyo diff --git a/examples/pyomobook/strip_examples.py b/examples/pyomobook/strip_examples.py index 0a65eef7c04..68d9e0d99a5 100644 --- a/examples/pyomobook/strip_examples.py +++ b/examples/pyomobook/strip_examples.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 glob import sys import os diff --git a/pyomo/common/multithread.py b/pyomo/common/multithread.py index 415d8aaba7e..f90e7f7c89e 100644 --- a/pyomo/common/multithread.py +++ b/pyomo/common/multithread.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 collections import defaultdict from threading import get_ident, main_thread diff --git a/pyomo/common/shutdown.py b/pyomo/common/shutdown.py index 5054fd21279..984fa8e8a52 100644 --- a/pyomo/common/shutdown.py +++ b/pyomo/common/shutdown.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 atexit diff --git a/pyomo/common/tests/import_ex.py b/pyomo/common/tests/import_ex.py index e19ad956044..d1bf02752eb 100644 --- a/pyomo/common/tests/import_ex.py +++ b/pyomo/common/tests/import_ex.py @@ -1,3 +1,15 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + + def a(): pass diff --git a/pyomo/common/tests/test_multithread.py b/pyomo/common/tests/test_multithread.py index ae1bc48be44..a6c0cac32c7 100644 --- a/pyomo/common/tests/test_multithread.py +++ b/pyomo/common/tests/test_multithread.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 threading import pyomo.common.unittest as unittest from pyomo.common.multithread import * diff --git a/pyomo/contrib/__init__.py b/pyomo/contrib/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/__init__.py +++ b/pyomo/contrib/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/ampl_function_demo/__init__.py b/pyomo/contrib/ampl_function_demo/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/ampl_function_demo/__init__.py +++ b/pyomo/contrib/ampl_function_demo/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/ampl_function_demo/tests/__init__.py b/pyomo/contrib/ampl_function_demo/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/ampl_function_demo/tests/__init__.py +++ b/pyomo/contrib/ampl_function_demo/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/appsi/__init__.py b/pyomo/contrib/appsi/__init__.py index df3ba212448..305231001c4 100644 --- a/pyomo/contrib/appsi/__init__.py +++ b/pyomo/contrib/appsi/__init__.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 . import base from . import solvers from . import writers diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index e6186eeedd2..a34bbdb5e1f 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 abc import enum from typing import ( diff --git a/pyomo/contrib/appsi/cmodel/src/common.cpp b/pyomo/contrib/appsi/cmodel/src/common.cpp index 255a0a3a70f..e9f1398fa2f 100644 --- a/pyomo/contrib/appsi/cmodel/src/common.cpp +++ b/pyomo/contrib/appsi/cmodel/src/common.cpp @@ -1,3 +1,15 @@ +/**___________________________________________________________________________ + * + * 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. + * ___________________________________________________________________________ +**/ + #include "common.hpp" double inf; diff --git a/pyomo/contrib/appsi/cmodel/src/common.hpp b/pyomo/contrib/appsi/cmodel/src/common.hpp index 36afd549116..9a025e031ae 100644 --- a/pyomo/contrib/appsi/cmodel/src/common.hpp +++ b/pyomo/contrib/appsi/cmodel/src/common.hpp @@ -1,3 +1,15 @@ +/**___________________________________________________________________________ + * + * 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. + * ___________________________________________________________________________ +**/ + #include #include diff --git a/pyomo/contrib/appsi/cmodel/src/expression.cpp b/pyomo/contrib/appsi/cmodel/src/expression.cpp index 1923d3a1894..f9e6b5c326a 100644 --- a/pyomo/contrib/appsi/cmodel/src/expression.cpp +++ b/pyomo/contrib/appsi/cmodel/src/expression.cpp @@ -1,1970 +1,1982 @@ -#include "expression.hpp" - -bool Leaf::is_leaf() { return true; } - -bool Var::is_variable_type() { return true; } - -bool Param::is_param_type() { return true; } - -bool Constant::is_constant_type() { return true; } - -bool Expression::is_expression_type() { return true; } - -double Leaf::evaluate() { return value; } - -double Var::get_lb() { - if (fixed) - return value; - else - return std::max(lb->evaluate(), domain_lb); -} - -double Var::get_ub() { - if (fixed) - return value; - else - return std::min(ub->evaluate(), domain_ub); -} - -Domain Var::get_domain() { return domain; } - -bool Operator::is_operator_type() { return true; } - -std::vector> Expression::get_operators() { - std::vector> res(n_operators); - for (unsigned int i = 0; i < n_operators; ++i) { - res[i] = operators[i]; - } - return res; -} - -double Leaf::get_value_from_array(double *val_array) { return value; } - -double Expression::get_value_from_array(double *val_array) { - return val_array[n_operators - 1]; -} - -double Operator::get_value_from_array(double *val_array) { - return val_array[index]; -} - -void MultiplyOperator::evaluate(double *values) { - values[index] = operand1->get_value_from_array(values) * - operand2->get_value_from_array(values); -} - -void ExternalOperator::evaluate(double *values) { - // It would be nice to implement this, but it will take some more work. - // This would require dynamic linking to the external function. - throw std::runtime_error("cannot evaluate ExternalOperator yet"); -} - -void LinearOperator::evaluate(double *values) { - values[index] = constant->evaluate(); - for (unsigned int i = 0; i < nterms; ++i) { - values[index] += coefficients[i]->evaluate() * variables[i]->evaluate(); - } -} - -void SumOperator::evaluate(double *values) { - values[index] = 0.0; - for (unsigned int i = 0; i < nargs; ++i) { - values[index] += operands[i]->get_value_from_array(values); - } -} - -void DivideOperator::evaluate(double *values) { - values[index] = operand1->get_value_from_array(values) / - operand2->get_value_from_array(values); -} - -void PowerOperator::evaluate(double *values) { - values[index] = std::pow(operand1->get_value_from_array(values), - operand2->get_value_from_array(values)); -} - -void NegationOperator::evaluate(double *values) { - values[index] = -operand->get_value_from_array(values); -} - -void ExpOperator::evaluate(double *values) { - values[index] = std::exp(operand->get_value_from_array(values)); -} - -void LogOperator::evaluate(double *values) { - values[index] = std::log(operand->get_value_from_array(values)); -} - -void AbsOperator::evaluate(double *values) { - values[index] = std::fabs(operand->get_value_from_array(values)); -} - -void SqrtOperator::evaluate(double *values) { - values[index] = std::pow(operand->get_value_from_array(values), 0.5); -} - -void Log10Operator::evaluate(double *values) { - values[index] = std::log10(operand->get_value_from_array(values)); -} - -void SinOperator::evaluate(double *values) { - values[index] = std::sin(operand->get_value_from_array(values)); -} - -void CosOperator::evaluate(double *values) { - values[index] = std::cos(operand->get_value_from_array(values)); -} - -void TanOperator::evaluate(double *values) { - values[index] = std::tan(operand->get_value_from_array(values)); -} - -void AsinOperator::evaluate(double *values) { - values[index] = std::asin(operand->get_value_from_array(values)); -} - -void AcosOperator::evaluate(double *values) { - values[index] = std::acos(operand->get_value_from_array(values)); -} - -void AtanOperator::evaluate(double *values) { - values[index] = std::atan(operand->get_value_from_array(values)); -} - -double Expression::evaluate() { - double *values = new double[n_operators]; - for (unsigned int i = 0; i < n_operators; ++i) { - operators[i]->index = i; - operators[i]->evaluate(values); - } - double res = get_value_from_array(values); - delete[] values; - return res; -} - -void UnaryOperator::identify_variables( - std::set> &var_set, - std::shared_ptr>> var_vec) { - if (operand->is_variable_type()) { - if (var_set.count(operand) == 0) { - var_vec->push_back(std::dynamic_pointer_cast(operand)); - var_set.insert(operand); - } - } -} - -void BinaryOperator::identify_variables( - std::set> &var_set, - std::shared_ptr>> var_vec) { - if (operand1->is_variable_type()) { - if (var_set.count(operand1) == 0) { - var_vec->push_back(std::dynamic_pointer_cast(operand1)); - var_set.insert(operand1); - } - } - if (operand2->is_variable_type()) { - if (var_set.count(operand2) == 0) { - var_vec->push_back(std::dynamic_pointer_cast(operand2)); - var_set.insert(operand2); - } - } -} - -void ExternalOperator::identify_variables( - std::set> &var_set, - std::shared_ptr>> var_vec) { - for (unsigned int i = 0; i < nargs; ++i) { - if (operands[i]->is_variable_type()) { - if (var_set.count(operands[i]) == 0) { - var_vec->push_back(std::dynamic_pointer_cast(operands[i])); - var_set.insert(operands[i]); - } - } - } -} - -void LinearOperator::identify_variables( - std::set> &var_set, - std::shared_ptr>> var_vec) { - for (unsigned int i = 0; i < nterms; ++i) { - if (var_set.count(variables[i]) == 0) { - var_vec->push_back(std::dynamic_pointer_cast(variables[i])); - var_set.insert(variables[i]); - } - } -} - -void SumOperator::identify_variables( - std::set> &var_set, - std::shared_ptr>> var_vec) { - for (unsigned int i = 0; i < nargs; ++i) { - if (operands[i]->is_variable_type()) { - if (var_set.count(operands[i]) == 0) { - var_vec->push_back(std::dynamic_pointer_cast(operands[i])); - var_set.insert(operands[i]); - } - } - } -} - -std::shared_ptr>> -Expression::identify_variables() { - std::set> var_set; - std::shared_ptr>> res = - std::make_shared>>(var_set.size()); - for (unsigned int i = 0; i < n_operators; ++i) { - operators[i]->identify_variables(var_set, res); - } - return res; -} - -std::shared_ptr>> Var::identify_variables() { - std::shared_ptr>> res = - std::make_shared>>(); - res->push_back(shared_from_this()); - return res; -} - -std::shared_ptr>> -Constant::identify_variables() { - std::shared_ptr>> res = - std::make_shared>>(); - return res; -} - -std::shared_ptr>> Param::identify_variables() { - std::shared_ptr>> res = - std::make_shared>>(); - return res; -} - -std::shared_ptr>> -Expression::identify_external_operators() { - std::set> external_set; - for (unsigned int i = 0; i < n_operators; ++i) { - if (operators[i]->is_external_operator()) { - external_set.insert(operators[i]); - } - } - std::shared_ptr>> res = - std::make_shared>>( - external_set.size()); - int ndx = 0; - for (std::shared_ptr n : external_set) { - (*res)[ndx] = std::dynamic_pointer_cast(n); - ndx += 1; - } - return res; -} - -std::shared_ptr>> -Var::identify_external_operators() { - std::shared_ptr>> res = - std::make_shared>>(); - return res; -} - -std::shared_ptr>> -Constant::identify_external_operators() { - std::shared_ptr>> res = - std::make_shared>>(); - return res; -} - -std::shared_ptr>> -Param::identify_external_operators() { - std::shared_ptr>> res = - std::make_shared>>(); - return res; -} - -int Var::get_degree_from_array(int *degree_array) { return 1; } - -int Param::get_degree_from_array(int *degree_array) { return 0; } - -int Constant::get_degree_from_array(int *degree_array) { return 0; } - -int Expression::get_degree_from_array(int *degree_array) { - return degree_array[n_operators - 1]; -} - -int Operator::get_degree_from_array(int *degree_array) { - return degree_array[index]; -} - -void LinearOperator::propagate_degree_forward(int *degrees, double *values) { - degrees[index] = 1; -} - -void SumOperator::propagate_degree_forward(int *degrees, double *values) { - int deg = 0; - int _deg; - for (unsigned int i = 0; i < nargs; ++i) { - _deg = operands[i]->get_degree_from_array(degrees); - if (_deg > deg) { - deg = _deg; - } - } - degrees[index] = deg; -} - -void MultiplyOperator::propagate_degree_forward(int *degrees, double *values) { - degrees[index] = operand1->get_degree_from_array(degrees) + - operand2->get_degree_from_array(degrees); -} - -void ExternalOperator::propagate_degree_forward(int *degrees, double *values) { - // External functions are always considered nonlinear - // Anything larger than 2 is nonlinear - degrees[index] = 3; -} - -void DivideOperator::propagate_degree_forward(int *degrees, double *values) { - // anything larger than 2 is nonlinear - degrees[index] = std::max(operand1->get_degree_from_array(degrees), - 3 * (operand2->get_degree_from_array(degrees))); -} - -void PowerOperator::propagate_degree_forward(int *degrees, double *values) { - if (operand2->get_degree_from_array(degrees) != 0) { - degrees[index] = 3; - } else { - double val2 = operand2->get_value_from_array(values); - double intpart; - if (std::modf(val2, &intpart) == 0.0) { - degrees[index] = operand1->get_degree_from_array(degrees) * (int)val2; - } else { - degrees[index] = 3; - } - } -} - -void NegationOperator::propagate_degree_forward(int *degrees, double *values) { - degrees[index] = operand->get_degree_from_array(degrees); -} - -void UnaryOperator::propagate_degree_forward(int *degrees, double *values) { - if (operand->get_degree_from_array(degrees) == 0) { - degrees[index] = 0; - } else { - degrees[index] = 3; - } -} - -std::string Var::__str__() { return name; } - -std::string Param::__str__() { return name; } - -std::string Constant::__str__() { return std::to_string(value); } - -std::string Expression::__str__() { - std::string *string_array = new std::string[n_operators]; - std::shared_ptr oper; - for (unsigned int i = 0; i < n_operators; ++i) { - oper = operators[i]; - oper->index = i; - oper->print(string_array); - } - std::string res = string_array[n_operators - 1]; - delete[] string_array; - return res; -} - -std::string Leaf::get_string_from_array(std::string *string_array) { - return __str__(); -} - -std::string Expression::get_string_from_array(std::string *string_array) { - return string_array[n_operators - 1]; -} - -std::string Operator::get_string_from_array(std::string *string_array) { - return string_array[index]; -} - -void MultiplyOperator::print(std::string *string_array) { - string_array[index] = - ("(" + operand1->get_string_from_array(string_array) + "*" + - operand2->get_string_from_array(string_array) + ")"); -} - -void ExternalOperator::print(std::string *string_array) { - std::string res = function_name + "("; - for (unsigned int i = 0; i < (nargs - 1); ++i) { - res += operands[i]->get_string_from_array(string_array); - res += ", "; - } - res += operands[nargs - 1]->get_string_from_array(string_array); - res += ")"; - string_array[index] = res; -} - -void DivideOperator::print(std::string *string_array) { - string_array[index] = - ("(" + operand1->get_string_from_array(string_array) + "/" + - operand2->get_string_from_array(string_array) + ")"); -} - -void PowerOperator::print(std::string *string_array) { - string_array[index] = - ("(" + operand1->get_string_from_array(string_array) + "**" + - operand2->get_string_from_array(string_array) + ")"); -} - -void NegationOperator::print(std::string *string_array) { - string_array[index] = - ("(-" + operand->get_string_from_array(string_array) + ")"); -} - -void ExpOperator::print(std::string *string_array) { - string_array[index] = - ("exp(" + operand->get_string_from_array(string_array) + ")"); -} - -void LogOperator::print(std::string *string_array) { - string_array[index] = - ("log(" + operand->get_string_from_array(string_array) + ")"); -} - -void AbsOperator::print(std::string *string_array) { - string_array[index] = - ("abs(" + operand->get_string_from_array(string_array) + ")"); -} - -void SqrtOperator::print(std::string *string_array) { - string_array[index] = - ("sqrt(" + operand->get_string_from_array(string_array) + ")"); -} - -void Log10Operator::print(std::string *string_array) { - string_array[index] = - ("log10(" + operand->get_string_from_array(string_array) + ")"); -} - -void SinOperator::print(std::string *string_array) { - string_array[index] = - ("sin(" + operand->get_string_from_array(string_array) + ")"); -} - -void CosOperator::print(std::string *string_array) { - string_array[index] = - ("cos(" + operand->get_string_from_array(string_array) + ")"); -} - -void TanOperator::print(std::string *string_array) { - string_array[index] = - ("tan(" + operand->get_string_from_array(string_array) + ")"); -} - -void AsinOperator::print(std::string *string_array) { - string_array[index] = - ("asin(" + operand->get_string_from_array(string_array) + ")"); -} - -void AcosOperator::print(std::string *string_array) { - string_array[index] = - ("acos(" + operand->get_string_from_array(string_array) + ")"); -} - -void AtanOperator::print(std::string *string_array) { - string_array[index] = - ("atan(" + operand->get_string_from_array(string_array) + ")"); -} - -void LinearOperator::print(std::string *string_array) { - std::string res = "(" + constant->__str__(); - for (unsigned int i = 0; i < nterms; ++i) { - res += " + " + coefficients[i]->__str__() + "*" + variables[i]->__str__(); - } - res += ")"; - string_array[index] = res; -} - -void SumOperator::print(std::string *string_array) { - std::string res = "(" + operands[0]->get_string_from_array(string_array); - for (unsigned int i = 1; i < nargs; ++i) { - res += " + " + operands[i]->get_string_from_array(string_array); - } - res += ")"; - string_array[index] = res; -} - -std::shared_ptr>> -Leaf::get_prefix_notation() { - std::shared_ptr>> res = - std::make_shared>>(); - res->push_back(shared_from_this()); - return res; -} - -std::shared_ptr>> -Expression::get_prefix_notation() { - std::shared_ptr>> res = - std::make_shared>>(); - std::shared_ptr>> stack = - std::make_shared>>(); - std::shared_ptr node; - stack->push_back(operators[n_operators - 1]); - while (stack->size() > 0) { - node = stack->back(); - stack->pop_back(); - res->push_back(node); - node->fill_prefix_notation_stack(stack); - } - - return res; -} - -void BinaryOperator::fill_prefix_notation_stack( - std::shared_ptr>> stack) { - stack->push_back(operand2); - stack->push_back(operand1); -} - -void UnaryOperator::fill_prefix_notation_stack( - std::shared_ptr>> stack) { - stack->push_back(operand); -} - -void SumOperator::fill_prefix_notation_stack( - std::shared_ptr>> stack) { - int ndx = nargs - 1; - while (ndx >= 0) { - stack->push_back(operands[ndx]); - ndx -= 1; - } -} - -void LinearOperator::fill_prefix_notation_stack( - std::shared_ptr>> stack) { - ; // This is treated as a leaf in this context; write_nl_string will take care - // of it -} - -void ExternalOperator::fill_prefix_notation_stack( - std::shared_ptr>> stack) { - int i = nargs - 1; - while (i >= 0) { - stack->push_back(operands[i]); - i -= 1; - } -} - -void Var::write_nl_string(std::ofstream &f) { f << "v" << index << "\n"; } - -void Param::write_nl_string(std::ofstream &f) { f << "n" << value << "\n"; } - -void Constant::write_nl_string(std::ofstream &f) { f << "n" << value << "\n"; } - -void Expression::write_nl_string(std::ofstream &f) { - std::shared_ptr>> prefix_notation = - get_prefix_notation(); - for (std::shared_ptr &node : *(prefix_notation)) { - node->write_nl_string(f); - } -} - -void MultiplyOperator::write_nl_string(std::ofstream &f) { f << "o2\n"; } - -void ExternalOperator::write_nl_string(std::ofstream &f) { - f << "f" << external_function_index << " " << nargs << "\n"; -} - -void SumOperator::write_nl_string(std::ofstream &f) { - if (nargs == 2) { - f << "o0\n"; - } else { - f << "o54\n"; - f << nargs << "\n"; - } -} - -void LinearOperator::write_nl_string(std::ofstream &f) { - bool has_const = - (!constant->is_constant_type()) || (constant->evaluate() != 0); - unsigned int n_sum_args = nterms + (has_const ? 1 : 0); - if (n_sum_args == 2) { - f << "o0\n"; - } else { - f << "o54\n"; - f << n_sum_args << "\n"; - } - if (has_const) - f << "n" << constant->evaluate() << "\n"; - for (unsigned int ndx = 0; ndx < nterms; ++ndx) { - f << "o2\n"; - f << "n" << coefficients[ndx]->evaluate() << "\n"; - variables[ndx]->write_nl_string(f); - } -} - -void DivideOperator::write_nl_string(std::ofstream &f) { f << "o3\n"; } - -void PowerOperator::write_nl_string(std::ofstream &f) { f << "o5\n"; } - -void NegationOperator::write_nl_string(std::ofstream &f) { f << "o16\n"; } - -void ExpOperator::write_nl_string(std::ofstream &f) { f << "o44\n"; } - -void LogOperator::write_nl_string(std::ofstream &f) { f << "o43\n"; } - -void AbsOperator::write_nl_string(std::ofstream &f) { f << "o15\n"; } - -void SqrtOperator::write_nl_string(std::ofstream &f) { f << "o39\n"; } - -void Log10Operator::write_nl_string(std::ofstream &f) { f << "o42\n"; } - -void SinOperator::write_nl_string(std::ofstream &f) { f << "o41\n"; } - -void CosOperator::write_nl_string(std::ofstream &f) { f << "o46\n"; } - -void TanOperator::write_nl_string(std::ofstream &f) { f << "o38\n"; } - -void AsinOperator::write_nl_string(std::ofstream &f) { f << "o51\n"; } - -void AcosOperator::write_nl_string(std::ofstream &f) { f << "o53\n"; } - -void AtanOperator::write_nl_string(std::ofstream &f) { f << "o49\n"; } - -bool BinaryOperator::is_binary_operator() { return true; } - -bool UnaryOperator::is_unary_operator() { return true; } - -bool LinearOperator::is_linear_operator() { return true; } - -bool SumOperator::is_sum_operator() { return true; } - -bool MultiplyOperator::is_multiply_operator() { return true; } - -bool DivideOperator::is_divide_operator() { return true; } - -bool PowerOperator::is_power_operator() { return true; } - -bool NegationOperator::is_negation_operator() { return true; } - -bool ExpOperator::is_exp_operator() { return true; } - -bool LogOperator::is_log_operator() { return true; } - -bool AbsOperator::is_abs_operator() { return true; } - -bool SqrtOperator::is_sqrt_operator() { return true; } - -bool ExternalOperator::is_external_operator() { return true; } - -void Leaf::fill_expression(std::shared_ptr *oper_array, - int &oper_ndx) { - ; -} - -void Expression::fill_expression(std::shared_ptr *oper_array, - int &oper_ndx) { - throw std::runtime_error("This should not happen"); -} - -void BinaryOperator::fill_expression(std::shared_ptr *oper_array, - int &oper_ndx) { - oper_ndx -= 1; - oper_array[oper_ndx] = shared_from_this(); - // The order does not actually matter here. It - // will just be easier to debug this way. - operand2->fill_expression(oper_array, oper_ndx); - operand1->fill_expression(oper_array, oper_ndx); -} - -void UnaryOperator::fill_expression(std::shared_ptr *oper_array, - int &oper_ndx) { - oper_ndx -= 1; - oper_array[oper_ndx] = shared_from_this(); - operand->fill_expression(oper_array, oper_ndx); -} - -void LinearOperator::fill_expression(std::shared_ptr *oper_array, - int &oper_ndx) { - oper_ndx -= 1; - oper_array[oper_ndx] = shared_from_this(); -} - -void SumOperator::fill_expression(std::shared_ptr *oper_array, - int &oper_ndx) { - oper_ndx -= 1; - oper_array[oper_ndx] = shared_from_this(); - // The order does not actually matter here. It - // will just be easier to debug this way. - int arg_ndx = nargs - 1; - while (arg_ndx >= 0) { - operands[arg_ndx]->fill_expression(oper_array, oper_ndx); - arg_ndx -= 1; - } -} - -void ExternalOperator::fill_expression(std::shared_ptr *oper_array, - int &oper_ndx) { - oper_ndx -= 1; - oper_array[oper_ndx] = shared_from_this(); - // The order does not actually matter here. It - // will just be easier to debug this way. - int arg_ndx = nargs - 1; - while (arg_ndx >= 0) { - operands[arg_ndx]->fill_expression(oper_array, oper_ndx); - arg_ndx -= 1; - } -} - -double Leaf::get_lb_from_array(double *lbs) { return value; } - -double Leaf::get_ub_from_array(double *ubs) { return value; } - -double Var::get_lb_from_array(double *lbs) { return get_lb(); } - -double Var::get_ub_from_array(double *ubs) { return get_ub(); } - -double Expression::get_lb_from_array(double *lbs) { - return lbs[n_operators - 1]; -} - -double Expression::get_ub_from_array(double *ubs) { - return ubs[n_operators - 1]; -} - -double Operator::get_lb_from_array(double *lbs) { return lbs[index]; } - -double Operator::get_ub_from_array(double *ubs) { return ubs[index]; } - -void Leaf::set_bounds_in_array(double new_lb, double new_ub, double *lbs, - double *ubs, double feasibility_tol, - double integer_tol, double improvement_tol, - std::set> &improved_vars) { - if (new_lb < value - feasibility_tol || new_lb > value + feasibility_tol) { - throw InfeasibleConstraintException( - "Infeasible constraint; bounds computed on parameter or constant " - "disagree with the value of the parameter or constant\n value: " + - std::to_string(value) + "\n computed LB: " + std::to_string(new_lb) + - "\n computed UB: " + std::to_string(new_ub)); - } - - if (new_ub < value - feasibility_tol || new_ub > value + feasibility_tol) { - throw InfeasibleConstraintException( - "Infeasible constraint; bounds computed on parameter or constant " - "disagree with the value of the parameter or constant\n value: " + - std::to_string(value) + "\n computed LB: " + std::to_string(new_lb) + - "\n computed UB: " + std::to_string(new_ub)); - } -} - -void Var::set_bounds_in_array(double new_lb, double new_ub, double *lbs, - double *ubs, double feasibility_tol, - double integer_tol, double improvement_tol, - std::set> &improved_vars) { - if (new_lb > new_ub) { - if (new_lb - feasibility_tol > new_ub) - throw InfeasibleConstraintException( - "Infeasible constraint; The computed lower bound for a variable is " - "larger than the computed upper bound.\n computed LB: " + - std::to_string(new_lb) + - "\n computed UB: " + std::to_string(new_ub)); - else { - new_lb -= feasibility_tol; - new_ub += feasibility_tol; - } - } - if (new_lb >= inf) - throw InfeasibleConstraintException( - "Infeasible constraint; The compute lower bound for " + name + - " is inf"); - if (new_ub <= -inf) - throw InfeasibleConstraintException( - "Infeasible constraint; The computed upper bound for " + name + - " is -inf"); - - if (domain == integers || domain == binary) { - if (new_lb > -inf) { - double lb_floor = floor(new_lb); - double lb_ceil = ceil(new_lb - integer_tol); - if (lb_floor > lb_ceil) - new_lb = lb_floor; - else - new_lb = lb_ceil; - } - if (new_ub < inf) { - double ub_ceil = ceil(new_ub); - double ub_floor = floor(new_ub + integer_tol); - if (ub_ceil < ub_floor) - new_ub = ub_ceil; - else - new_ub = ub_floor; - } - } - - double current_lb = get_lb(); - double current_ub = get_ub(); - - if (new_lb > current_lb + improvement_tol || - new_ub < current_ub - improvement_tol) - improved_vars.insert(shared_from_this()); - - if (new_lb > current_lb) { - if (lb->is_leaf()) - std::dynamic_pointer_cast(lb)->value = new_lb; - else - throw py::value_error( - "variable bounds cannot be expressions when performing FBBT"); - } - - if (new_ub < current_ub) { - if (ub->is_leaf()) - std::dynamic_pointer_cast(ub)->value = new_ub; - else - throw py::value_error( - "variable bounds cannot be expressions when performing FBBT"); - } -} - -void Expression::set_bounds_in_array( - double new_lb, double new_ub, double *lbs, double *ubs, - double feasibility_tol, double integer_tol, double improvement_tol, - std::set> &improved_vars) { - lbs[n_operators - 1] = new_lb; - ubs[n_operators - 1] = new_ub; -} - -void Operator::set_bounds_in_array( - double new_lb, double new_ub, double *lbs, double *ubs, - double feasibility_tol, double integer_tol, double improvement_tol, - std::set> &improved_vars) { - lbs[index] = new_lb; - ubs[index] = new_ub; -} - -void Expression::propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) { - for (unsigned int ndx = 0; ndx < n_operators; ++ndx) { - operators[ndx]->index = ndx; - operators[ndx]->propagate_bounds_forward(lbs, ubs, feasibility_tol, - integer_tol); - } -} - -void Expression::propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, std::set> &improved_vars) { - int ndx = n_operators - 1; - while (ndx >= 0) { - operators[ndx]->propagate_bounds_backward( - lbs, ubs, feasibility_tol, integer_tol, improvement_tol, improved_vars); - ndx -= 1; - } -} - -void Operator::propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) { - lbs[index] = -inf; - ubs[index] = inf; -} - -void Operator::propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, std::set> &improved_vars) { - ; -} - -void MultiplyOperator::propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) { - if (operand1 == operand2) { - interval_power(operand1->get_lb_from_array(lbs), - operand1->get_ub_from_array(ubs), 2, 2, &lbs[index], - &ubs[index], feasibility_tol); - } else { - interval_mul(operand1->get_lb_from_array(lbs), - operand1->get_ub_from_array(ubs), - operand2->get_lb_from_array(lbs), - operand2->get_ub_from_array(ubs), &lbs[index], &ubs[index]); - } -} - -void MultiplyOperator::propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, std::set> &improved_vars) { - double xl = operand1->get_lb_from_array(lbs); - double xu = operand1->get_ub_from_array(ubs); - double yl = operand2->get_lb_from_array(lbs); - double yu = operand2->get_ub_from_array(ubs); - double lb = get_lb_from_array(lbs); - double ub = get_ub_from_array(ubs); - - double new_xl, new_xu, new_yl, new_yu; - - if (operand1 == operand2) { - _inverse_power1(lb, ub, 2, 2, xl, xu, &new_xl, &new_xu, feasibility_tol); - new_yl = new_xl; - new_yu = new_xu; - } else { - interval_div(lb, ub, yl, yu, &new_xl, &new_xu, feasibility_tol); - interval_div(lb, ub, xl, xu, &new_yl, &new_yu, feasibility_tol); - } - - if (new_xl > xl) - xl = new_xl; - if (new_xu < xu) - xu = new_xu; - operand1->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, - improvement_tol, improved_vars); - - if (new_yl > yl) - yl = new_yl; - if (new_yu < yu) - yu = new_yu; - operand2->set_bounds_in_array(yl, yu, lbs, ubs, feasibility_tol, integer_tol, - improvement_tol, improved_vars); -} - -void SumOperator::propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) { - double lb = operands[0]->get_lb_from_array(lbs); - double ub = operands[0]->get_ub_from_array(ubs); - double tmp_lb; - double tmp_ub; - - for (unsigned int ndx = 1; ndx < nargs; ++ndx) { - interval_add(lb, ub, operands[ndx]->get_lb_from_array(lbs), - operands[ndx]->get_ub_from_array(ubs), &tmp_lb, &tmp_ub); - lb = tmp_lb; - ub = tmp_ub; - } - - lbs[index] = lb; - ubs[index] = ub; -} - -void SumOperator::propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, std::set> &improved_vars) { - double *accumulated_lbs = new double[nargs]; - double *accumulated_ubs = new double[nargs]; - - accumulated_lbs[0] = operands[0]->get_lb_from_array(lbs); - accumulated_ubs[0] = operands[0]->get_ub_from_array(ubs); - for (unsigned int ndx = 1; ndx < nargs; ++ndx) { - interval_add(accumulated_lbs[ndx - 1], accumulated_ubs[ndx - 1], - operands[ndx]->get_lb_from_array(lbs), - operands[ndx]->get_ub_from_array(ubs), &accumulated_lbs[ndx], - &accumulated_ubs[ndx]); - } - - double new_sum_lb = get_lb_from_array(lbs); - double new_sum_ub = get_ub_from_array(ubs); - - if (new_sum_lb > accumulated_lbs[nargs - 1]) - accumulated_lbs[nargs - 1] = new_sum_lb; - if (new_sum_ub < accumulated_ubs[nargs - 1]) - accumulated_ubs[nargs - 1] = new_sum_ub; - - double lb0, ub0, lb1, ub1, lb2, ub2, _lb1, _ub1, _lb2, _ub2; - - int ndx = nargs - 1; - while (ndx >= 1) { - lb0 = accumulated_lbs[ndx]; - ub0 = accumulated_ubs[ndx]; - lb1 = accumulated_lbs[ndx - 1]; - ub1 = accumulated_ubs[ndx - 1]; - lb2 = operands[ndx]->get_lb_from_array(lbs); - ub2 = operands[ndx]->get_ub_from_array(ubs); - interval_sub(lb0, ub0, lb2, ub2, &_lb1, &_ub1); - interval_sub(lb0, ub0, lb1, ub1, &_lb2, &_ub2); - if (_lb1 > lb1) - lb1 = _lb1; - if (_ub1 < ub1) - ub1 = _ub1; - if (_lb2 > lb2) - lb2 = _lb2; - if (_ub2 < ub2) - ub2 = _ub2; - accumulated_lbs[ndx - 1] = lb1; - accumulated_ubs[ndx - 1] = ub1; - operands[ndx]->set_bounds_in_array(lb2, ub2, lbs, ubs, feasibility_tol, - integer_tol, improvement_tol, - improved_vars); - ndx -= 1; - } - - // take care of ndx = 0 - lb1 = operands[0]->get_lb_from_array(lbs); - ub1 = operands[0]->get_ub_from_array(ubs); - _lb1 = accumulated_lbs[0]; - _ub1 = accumulated_ubs[0]; - if (_lb1 > lb1) - lb1 = _lb1; - if (_ub1 < ub1) - ub1 = _ub1; - operands[0]->set_bounds_in_array(lb1, ub1, lbs, ubs, feasibility_tol, - integer_tol, improvement_tol, improved_vars); - - delete[] accumulated_lbs; - delete[] accumulated_ubs; -} - -void LinearOperator::propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) { - double lb = constant->evaluate(); - double ub = lb; - double tmp_lb; - double tmp_ub; - double coef; - - for (unsigned int ndx = 0; ndx < nterms; ++ndx) { - coef = coefficients[ndx]->evaluate(); - interval_mul(coef, coef, variables[ndx]->get_lb(), variables[ndx]->get_ub(), - &tmp_lb, &tmp_ub); - interval_add(lb, ub, tmp_lb, tmp_ub, &lb, &ub); - } - - lbs[index] = lb; - ubs[index] = ub; -} - -void LinearOperator::propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, std::set> &improved_vars) { - double *accumulated_lbs = new double[nterms + 1]; - double *accumulated_ubs = new double[nterms + 1]; - - double coef; - - accumulated_lbs[0] = constant->evaluate(); - accumulated_ubs[0] = constant->evaluate(); - for (unsigned int ndx = 0; ndx < nterms; ++ndx) { - coef = coefficients[ndx]->evaluate(); - interval_mul(coef, coef, variables[ndx]->get_lb(), variables[ndx]->get_ub(), - &accumulated_lbs[ndx + 1], &accumulated_ubs[ndx + 1]); - interval_add(accumulated_lbs[ndx], accumulated_ubs[ndx], - accumulated_lbs[ndx + 1], accumulated_ubs[ndx + 1], - &accumulated_lbs[ndx + 1], &accumulated_ubs[ndx + 1]); - } - - double new_sum_lb = get_lb_from_array(lbs); - double new_sum_ub = get_ub_from_array(ubs); - - if (new_sum_lb > accumulated_lbs[nterms]) - accumulated_lbs[nterms] = new_sum_lb; - if (new_sum_ub < accumulated_ubs[nterms]) - accumulated_ubs[nterms] = new_sum_ub; - - double lb0, ub0, lb1, ub1, lb2, ub2, _lb1, _ub1, _lb2, _ub2, new_v_lb, - new_v_ub; - - int ndx = nterms - 1; - while (ndx >= 0) { - lb0 = accumulated_lbs[ndx + 1]; - ub0 = accumulated_ubs[ndx + 1]; - lb1 = accumulated_lbs[ndx]; - ub1 = accumulated_ubs[ndx]; - coef = coefficients[ndx]->evaluate(); - interval_mul(coef, coef, variables[ndx]->get_lb(), variables[ndx]->get_ub(), - &lb2, &ub2); - interval_sub(lb0, ub0, lb2, ub2, &_lb1, &_ub1); - interval_sub(lb0, ub0, lb1, ub1, &_lb2, &_ub2); - if (_lb1 > lb1) - lb1 = _lb1; - if (_ub1 < ub1) - ub1 = _ub1; - if (_lb2 > lb2) - lb2 = _lb2; - if (_ub2 < ub2) - ub2 = _ub2; - accumulated_lbs[ndx] = lb1; - accumulated_ubs[ndx] = ub1; - interval_div(lb2, ub2, coef, coef, &new_v_lb, &new_v_ub, feasibility_tol); - variables[ndx]->set_bounds_in_array(new_v_lb, new_v_ub, lbs, ubs, - feasibility_tol, integer_tol, - improvement_tol, improved_vars); - ndx -= 1; - } - - delete[] accumulated_lbs; - delete[] accumulated_ubs; -} - -void DivideOperator::propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) { - interval_div( - operand1->get_lb_from_array(lbs), operand1->get_ub_from_array(ubs), - operand2->get_lb_from_array(lbs), operand2->get_ub_from_array(ubs), - &lbs[index], &ubs[index], feasibility_tol); -} - -void DivideOperator::propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, std::set> &improved_vars) { - double xl = operand1->get_lb_from_array(lbs); - double xu = operand1->get_ub_from_array(ubs); - double yl = operand2->get_lb_from_array(lbs); - double yu = operand2->get_ub_from_array(ubs); - double lb = get_lb_from_array(lbs); - double ub = get_ub_from_array(ubs); - - double new_xl; - double new_xu; - double new_yl; - double new_yu; - - interval_mul(lb, ub, yl, yu, &new_xl, &new_xu); - interval_div(xl, xu, lb, ub, &new_yl, &new_yu, feasibility_tol); - - if (new_xl > xl) - xl = new_xl; - if (new_xu < xu) - xu = new_xu; - operand1->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, - improvement_tol, improved_vars); - - if (new_yl > yl) - yl = new_yl; - if (new_yu < yu) - yu = new_yu; - operand2->set_bounds_in_array(yl, yu, lbs, ubs, feasibility_tol, integer_tol, - improvement_tol, improved_vars); -} - -void NegationOperator::propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) { - interval_sub(0, 0, operand->get_lb_from_array(lbs), - operand->get_ub_from_array(ubs), &lbs[index], &ubs[index]); -} - -void NegationOperator::propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, std::set> &improved_vars) { - double xl = operand->get_lb_from_array(lbs); - double xu = operand->get_ub_from_array(ubs); - double lb = get_lb_from_array(lbs); - double ub = get_ub_from_array(ubs); - - double new_xl; - double new_xu; - - interval_sub(0, 0, lb, ub, &new_xl, &new_xu); - - if (new_xl > xl) - xl = new_xl; - if (new_xu < xu) - xu = new_xu; - operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, - improvement_tol, improved_vars); -} - -void PowerOperator::propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) { - interval_power( - operand1->get_lb_from_array(lbs), operand1->get_ub_from_array(ubs), - operand2->get_lb_from_array(lbs), operand2->get_ub_from_array(ubs), - &lbs[index], &ubs[index], feasibility_tol); -} - -void PowerOperator::propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, std::set> &improved_vars) { - double xl = operand1->get_lb_from_array(lbs); - double xu = operand1->get_ub_from_array(ubs); - double yl = operand2->get_lb_from_array(lbs); - double yu = operand2->get_ub_from_array(ubs); - double lb = get_lb_from_array(lbs); - double ub = get_ub_from_array(ubs); - - double new_xl, new_xu, new_yl, new_yu; - _inverse_power1(lb, ub, yl, yu, xl, xu, &new_xl, &new_xu, feasibility_tol); - if (yl != yu) - _inverse_power2(lb, ub, xl, xu, &new_yl, &new_yu, feasibility_tol); - else { - new_yl = yl; - new_yu = yu; - } - - if (new_xl > xl) - xl = new_xl; - if (new_xu < xu) - xu = new_xu; - operand1->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, - improvement_tol, improved_vars); - - if (new_yl > yl) - yl = new_yl; - if (new_yu < yu) - yu = new_yu; - operand2->set_bounds_in_array(yl, yu, lbs, ubs, feasibility_tol, integer_tol, - improvement_tol, improved_vars); -} - -void SqrtOperator::propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) { - interval_power(operand->get_lb_from_array(lbs), - operand->get_ub_from_array(ubs), 0.5, 0.5, &lbs[index], - &ubs[index], feasibility_tol); -} - -void SqrtOperator::propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, std::set> &improved_vars) { - double xl = operand->get_lb_from_array(lbs); - double xu = operand->get_ub_from_array(ubs); - double yl = 0.5; - double yu = 0.5; - double lb = get_lb_from_array(lbs); - double ub = get_ub_from_array(ubs); - - double new_xl, new_xu; - _inverse_power1(lb, ub, yl, yu, xl, xu, &new_xl, &new_xu, feasibility_tol); - - if (new_xl > xl) - xl = new_xl; - if (new_xu < xu) - xu = new_xu; - operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, - improvement_tol, improved_vars); -} - -void ExpOperator::propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) { - interval_exp(operand->get_lb_from_array(lbs), operand->get_ub_from_array(ubs), - &lbs[index], &ubs[index]); -} - -void ExpOperator::propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, std::set> &improved_vars) { - double xl = operand->get_lb_from_array(lbs); - double xu = operand->get_ub_from_array(ubs); - double lb = get_lb_from_array(lbs); - double ub = get_ub_from_array(ubs); - - double new_xl, new_xu; - interval_log(lb, ub, &new_xl, &new_xu); - - if (new_xl > xl) - xl = new_xl; - if (new_xu < xu) - xu = new_xu; - operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, - improvement_tol, improved_vars); -} - -void LogOperator::propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) { - interval_log(operand->get_lb_from_array(lbs), operand->get_ub_from_array(ubs), - &lbs[index], &ubs[index]); -} - -void LogOperator::propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, std::set> &improved_vars) { - double xl = operand->get_lb_from_array(lbs); - double xu = operand->get_ub_from_array(ubs); - double lb = get_lb_from_array(lbs); - double ub = get_ub_from_array(ubs); - - double new_xl, new_xu; - interval_exp(lb, ub, &new_xl, &new_xu); - - if (new_xl > xl) - xl = new_xl; - if (new_xu < xu) - xu = new_xu; - operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, - improvement_tol, improved_vars); -} - -void AbsOperator::propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) { - interval_abs(operand->get_lb_from_array(lbs), operand->get_ub_from_array(ubs), - &lbs[index], &ubs[index]); -} - -void AbsOperator::propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, std::set> &improved_vars) { - double xl = operand->get_lb_from_array(lbs); - double xu = operand->get_ub_from_array(ubs); - double lb = get_lb_from_array(lbs); - double ub = get_ub_from_array(ubs); - - double new_xl, new_xu; - _inverse_abs(lb, ub, &new_xl, &new_xu); - - if (new_xl > xl) - xl = new_xl; - if (new_xu < xu) - xu = new_xu; - operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, - improvement_tol, improved_vars); -} - -void Log10Operator::propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) { - interval_log10(operand->get_lb_from_array(lbs), - operand->get_ub_from_array(ubs), &lbs[index], &ubs[index]); -} - -void Log10Operator::propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, std::set> &improved_vars) { - double xl = operand->get_lb_from_array(lbs); - double xu = operand->get_ub_from_array(ubs); - double lb = get_lb_from_array(lbs); - double ub = get_ub_from_array(ubs); - - double new_xl, new_xu; - interval_power(10, 10, lb, ub, &new_xl, &new_xu, feasibility_tol); - - if (new_xl > xl) - xl = new_xl; - if (new_xu < xu) - xu = new_xu; - operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, - improvement_tol, improved_vars); -} - -void SinOperator::propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) { - interval_sin(operand->get_lb_from_array(lbs), operand->get_ub_from_array(ubs), - &lbs[index], &ubs[index]); -} - -void SinOperator::propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, std::set> &improved_vars) { - double xl = operand->get_lb_from_array(lbs); - double xu = operand->get_ub_from_array(ubs); - double lb = get_lb_from_array(lbs); - double ub = get_ub_from_array(ubs); - - double new_xl, new_xu; - interval_asin(lb, ub, xl, xu, &new_xl, &new_xu, feasibility_tol); - - if (new_xl > xl) - xl = new_xl; - if (new_xu < xu) - xu = new_xu; - operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, - improvement_tol, improved_vars); -} - -void CosOperator::propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) { - interval_cos(operand->get_lb_from_array(lbs), operand->get_ub_from_array(ubs), - &lbs[index], &ubs[index]); -} - -void CosOperator::propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, std::set> &improved_vars) { - double xl = operand->get_lb_from_array(lbs); - double xu = operand->get_ub_from_array(ubs); - double lb = get_lb_from_array(lbs); - double ub = get_ub_from_array(ubs); - - double new_xl, new_xu; - interval_acos(lb, ub, xl, xu, &new_xl, &new_xu, feasibility_tol); - - if (new_xl > xl) - xl = new_xl; - if (new_xu < xu) - xu = new_xu; - operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, - improvement_tol, improved_vars); -} - -void TanOperator::propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) { - interval_tan(operand->get_lb_from_array(lbs), operand->get_ub_from_array(ubs), - &lbs[index], &ubs[index]); -} - -void TanOperator::propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, std::set> &improved_vars) { - double xl = operand->get_lb_from_array(lbs); - double xu = operand->get_ub_from_array(ubs); - double lb = get_lb_from_array(lbs); - double ub = get_ub_from_array(ubs); - - double new_xl, new_xu; - interval_atan(lb, ub, xl, xu, &new_xl, &new_xu); - - if (new_xl > xl) - xl = new_xl; - if (new_xu < xu) - xu = new_xu; - operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, - improvement_tol, improved_vars); -} - -void AsinOperator::propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) { - interval_asin(operand->get_lb_from_array(lbs), - operand->get_ub_from_array(ubs), -inf, inf, &lbs[index], - &ubs[index], feasibility_tol); -} - -void AsinOperator::propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, std::set> &improved_vars) { - double xl = operand->get_lb_from_array(lbs); - double xu = operand->get_ub_from_array(ubs); - double lb = get_lb_from_array(lbs); - double ub = get_ub_from_array(ubs); - - double new_xl, new_xu; - interval_sin(lb, ub, &new_xl, &new_xu); - - if (new_xl > xl) - xl = new_xl; - if (new_xu < xu) - xu = new_xu; - operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, - improvement_tol, improved_vars); -} - -void AcosOperator::propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) { - interval_acos(operand->get_lb_from_array(lbs), - operand->get_ub_from_array(ubs), -inf, inf, &lbs[index], - &ubs[index], feasibility_tol); -} - -void AcosOperator::propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, std::set> &improved_vars) { - double xl = operand->get_lb_from_array(lbs); - double xu = operand->get_ub_from_array(ubs); - double lb = get_lb_from_array(lbs); - double ub = get_ub_from_array(ubs); - - double new_xl, new_xu; - interval_cos(lb, ub, &new_xl, &new_xu); - - if (new_xl > xl) - xl = new_xl; - if (new_xu < xu) - xu = new_xu; - operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, - improvement_tol, improved_vars); -} - -void AtanOperator::propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) { - interval_atan(operand->get_lb_from_array(lbs), - operand->get_ub_from_array(ubs), -inf, inf, &lbs[index], - &ubs[index]); -} - -void AtanOperator::propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, std::set> &improved_vars) { - double xl = operand->get_lb_from_array(lbs); - double xu = operand->get_ub_from_array(ubs); - double lb = get_lb_from_array(lbs); - double ub = get_ub_from_array(ubs); - - double new_xl, new_xu; - interval_tan(lb, ub, &new_xl, &new_xu); - - if (new_xl > xl) - xl = new_xl; - if (new_xu < xu) - xu = new_xu; - operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, - improvement_tol, improved_vars); -} - -std::vector> create_vars(int n_vars) { - std::vector> res; - for (int i = 0; i < n_vars; ++i) { - res.push_back(std::make_shared()); - } - return res; -} - -std::vector> create_params(int n_params) { - std::vector> res; - for (int i = 0; i < n_params; ++i) { - res.push_back(std::make_shared()); - } - return res; -} - -std::vector> create_constants(int n_constants) { - std::vector> res; - for (int i = 0; i < n_constants; ++i) { - res.push_back(std::make_shared()); - } - return res; -} - -std::shared_ptr -appsi_operator_from_pyomo_expr(py::handle expr, py::handle var_map, - py::handle param_map, - PyomoExprTypes &expr_types) { - std::shared_ptr res; - ExprType tmp_type = - expr_types.expr_type_map[py::type::of(expr)].cast(); - - switch (tmp_type) { - case py_float: { - res = std::make_shared(expr.cast()); - break; - } - case var: { - res = var_map[expr_types.id(expr)].cast>(); - break; - } - case param: { - res = param_map[expr_types.id(expr)].cast>(); - break; - } - case product: { - res = std::make_shared(); - break; - } - case sum: { - res = std::make_shared(expr.attr("nargs")().cast()); - break; - } - case negation: { - res = std::make_shared(); - break; - } - case external_func: { - res = std::make_shared(expr.attr("nargs")().cast()); - std::shared_ptr oper = - std::dynamic_pointer_cast(res); - oper->function_name = - expr.attr("_fcn").attr("_function").cast(); - break; - } - case power: { - res = std::make_shared(); - break; - } - case division: { - res = std::make_shared(); - break; - } - case unary_func: { - std::string function_name = expr.attr("getname")().cast(); - if (function_name == "exp") - res = std::make_shared(); - else if (function_name == "log") - res = std::make_shared(); - else if (function_name == "log10") - res = std::make_shared(); - else if (function_name == "sin") - res = std::make_shared(); - else if (function_name == "cos") - res = std::make_shared(); - else if (function_name == "tan") - res = std::make_shared(); - else if (function_name == "asin") - res = std::make_shared(); - else if (function_name == "acos") - res = std::make_shared(); - else if (function_name == "atan") - res = std::make_shared(); - else if (function_name == "sqrt") - res = std::make_shared(); - else - throw py::value_error("Unrecognized expression type: " + function_name); - break; - } - case linear: { - res = std::make_shared( - expr_types.len(expr.attr("linear_vars")).cast()); - break; - } - case named_expr: { - res = appsi_operator_from_pyomo_expr(expr.attr("expr"), var_map, param_map, - expr_types); - break; - } - case numeric_constant: { - res = std::make_shared(expr.attr("value").cast()); - break; - } - case pyomo_unit: { - res = std::make_shared(1.0); - break; - } - case unary_abs: { - res = std::make_shared(); - break; - } - default: { - throw py::value_error("Unrecognized expression type: " + - expr_types.builtins.attr("str")(py::type::of(expr)) - .cast()); - break; - } - } - return res; -} - -void prep_for_repn_helper(py::handle expr, py::handle named_exprs, - py::handle variables, py::handle fixed_vars, - py::handle external_funcs, - PyomoExprTypes &expr_types) { - ExprType tmp_type = - expr_types.expr_type_map[py::type::of(expr)].cast(); - - switch (tmp_type) { - case py_float: { - break; - } - case var: { - variables[expr_types.id(expr)] = expr; - if (expr.attr("fixed").cast()) { - fixed_vars[expr_types.id(expr)] = expr; - } - break; - } - case param: { - break; - } - case product: { - py::tuple args = expr.attr("_args_"); - for (py::handle arg : args) { - prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, - external_funcs, expr_types); - } - break; - } - case sum: { - py::tuple args = expr.attr("args"); - for (py::handle arg : args) { - prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, - external_funcs, expr_types); - } - break; - } - case negation: { - py::tuple args = expr.attr("_args_"); - for (py::handle arg : args) { - prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, - external_funcs, expr_types); - } - break; - } - case external_func: { - external_funcs[expr_types.id(expr)] = expr; - py::tuple args = expr.attr("args"); - for (py::handle arg : args) { - prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, - external_funcs, expr_types); - } - break; - } - case power: { - py::tuple args = expr.attr("_args_"); - for (py::handle arg : args) { - prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, - external_funcs, expr_types); - } - break; - } - case division: { - py::tuple args = expr.attr("_args_"); - for (py::handle arg : args) { - prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, - external_funcs, expr_types); - } - break; - } - case unary_func: { - py::tuple args = expr.attr("_args_"); - for (py::handle arg : args) { - prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, - external_funcs, expr_types); - } - break; - } - case linear: { - py::list linear_vars = expr.attr("linear_vars"); - py::list linear_coefs = expr.attr("linear_coefs"); - for (py::handle arg : linear_vars) { - prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, - external_funcs, expr_types); - } - for (py::handle arg : linear_coefs) { - prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, - external_funcs, expr_types); - } - prep_for_repn_helper(expr.attr("constant"), named_exprs, variables, - fixed_vars, external_funcs, expr_types); - break; - } - case named_expr: { - named_exprs[expr_types.id(expr)] = expr; - prep_for_repn_helper(expr.attr("expr"), named_exprs, variables, fixed_vars, - external_funcs, expr_types); - break; - } - case numeric_constant: { - break; - } - case pyomo_unit: { - break; - } - case unary_abs: { - py::tuple args = expr.attr("_args_"); - for (py::handle arg : args) { - prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, - external_funcs, expr_types); - } - break; - } - default: { - if (expr_types.builtins.attr("hasattr")(expr, "is_constant").cast()) { - if (expr.attr("is_constant")().cast()) - break; - } - throw py::value_error("Unrecognized expression type: " + - expr_types.builtins.attr("str")(py::type::of(expr)) - .cast()); - break; - } - } -} - -py::tuple prep_for_repn(py::handle expr, PyomoExprTypes &expr_types) { - py::dict named_exprs; - py::dict variables; - py::dict fixed_vars; - py::dict external_funcs; - - prep_for_repn_helper(expr, named_exprs, variables, fixed_vars, external_funcs, - expr_types); - - py::list named_expr_list = named_exprs.attr("values")(); - py::list variable_list = variables.attr("values")(); - py::list fixed_var_list = fixed_vars.attr("values")(); - py::list external_func_list = external_funcs.attr("values")(); - - py::tuple res = py::make_tuple(named_expr_list, variable_list, fixed_var_list, - external_func_list); - return res; -} - -int build_expression_tree(py::handle pyomo_expr, - std::shared_ptr appsi_expr, py::handle var_map, - py::handle param_map, PyomoExprTypes &expr_types) { - int num_nodes = 0; - - if (expr_types.expr_type_map[py::type::of(pyomo_expr)].cast() == - named_expr) - pyomo_expr = pyomo_expr.attr("expr"); - - if (appsi_expr->is_leaf()) { - ; - } else if (appsi_expr->is_binary_operator()) { - num_nodes += 1; - std::shared_ptr oper = - std::dynamic_pointer_cast(appsi_expr); - py::list pyomo_args = pyomo_expr.attr("args"); - oper->operand1 = appsi_operator_from_pyomo_expr(pyomo_args[0], var_map, - param_map, expr_types); - oper->operand2 = appsi_operator_from_pyomo_expr(pyomo_args[1], var_map, - param_map, expr_types); - num_nodes += build_expression_tree(pyomo_args[0], oper->operand1, var_map, - param_map, expr_types); - num_nodes += build_expression_tree(pyomo_args[1], oper->operand2, var_map, - param_map, expr_types); - } else if (appsi_expr->is_unary_operator()) { - num_nodes += 1; - std::shared_ptr oper = - std::dynamic_pointer_cast(appsi_expr); - py::list pyomo_args = pyomo_expr.attr("args"); - oper->operand = appsi_operator_from_pyomo_expr(pyomo_args[0], var_map, - param_map, expr_types); - num_nodes += build_expression_tree(pyomo_args[0], oper->operand, var_map, - param_map, expr_types); - } else if (appsi_expr->is_sum_operator()) { - num_nodes += 1; - std::shared_ptr oper = - std::dynamic_pointer_cast(appsi_expr); - py::list pyomo_args = pyomo_expr.attr("args"); - for (unsigned int arg_ndx = 0; arg_ndx < oper->nargs; ++arg_ndx) { - oper->operands[arg_ndx] = appsi_operator_from_pyomo_expr( - pyomo_args[arg_ndx], var_map, param_map, expr_types); - num_nodes += - build_expression_tree(pyomo_args[arg_ndx], oper->operands[arg_ndx], - var_map, param_map, expr_types); - } - } else if (appsi_expr->is_linear_operator()) { - num_nodes += 1; - std::shared_ptr oper = - std::dynamic_pointer_cast(appsi_expr); - oper->constant = appsi_expr_from_pyomo_expr(pyomo_expr.attr("constant"), - var_map, param_map, expr_types); - py::list pyomo_vars = pyomo_expr.attr("linear_vars"); - py::list pyomo_coefs = pyomo_expr.attr("linear_coefs"); - for (unsigned int arg_ndx = 0; arg_ndx < oper->nterms; ++arg_ndx) { - oper->variables[arg_ndx] = var_map[expr_types.id(pyomo_vars[arg_ndx])] - .cast>(); - oper->coefficients[arg_ndx] = appsi_expr_from_pyomo_expr( - pyomo_coefs[arg_ndx], var_map, param_map, expr_types); - } - } else if (appsi_expr->is_external_operator()) { - num_nodes += 1; - std::shared_ptr oper = - std::dynamic_pointer_cast(appsi_expr); - py::list pyomo_args = pyomo_expr.attr("args"); - for (unsigned int arg_ndx = 0; arg_ndx < oper->nargs; ++arg_ndx) { - oper->operands[arg_ndx] = appsi_operator_from_pyomo_expr( - pyomo_args[arg_ndx], var_map, param_map, expr_types); - num_nodes += - build_expression_tree(pyomo_args[arg_ndx], oper->operands[arg_ndx], - var_map, param_map, expr_types); - } - } else { - throw py::value_error( - "Unrecognized expression type: " + - expr_types.builtins.attr("str")(py::type::of(pyomo_expr)) - .cast()); - } - return num_nodes; -} - -std::shared_ptr -appsi_expr_from_pyomo_expr(py::handle expr, py::handle var_map, - py::handle param_map, PyomoExprTypes &expr_types) { - std::shared_ptr node = - appsi_operator_from_pyomo_expr(expr, var_map, param_map, expr_types); - int num_nodes = - build_expression_tree(expr, node, var_map, param_map, expr_types); - if (num_nodes == 0) { - return std::dynamic_pointer_cast(node); - } else { - std::shared_ptr res = std::make_shared(num_nodes); - node->fill_expression(res->operators, num_nodes); - return res; - } -} - -std::vector> -appsi_exprs_from_pyomo_exprs(py::list expr_list, py::dict var_map, - py::dict param_map) { - PyomoExprTypes expr_types = PyomoExprTypes(); - int num_exprs = expr_types.builtins.attr("len")(expr_list).cast(); - std::vector> res(num_exprs); - - int ndx = 0; - for (py::handle expr : expr_list) { - res[ndx] = appsi_expr_from_pyomo_expr(expr, var_map, param_map, expr_types); - ndx += 1; - } - return res; -} - -void process_pyomo_vars(PyomoExprTypes &expr_types, py::list pyomo_vars, - py::dict var_map, py::dict param_map, - py::dict var_attrs, py::dict rev_var_map, - py::bool_ _set_name, py::handle symbol_map, - py::handle labeler, py::bool_ _update) { - py::tuple v_attrs; - std::shared_ptr cv; - py::handle v_lb; - py::handle v_ub; - py::handle v_val; - py::tuple domain_interval; - py::handle interval_lb; - py::handle interval_ub; - py::handle interval_step; - bool v_fixed; - bool set_name = _set_name.cast(); - bool update = _update.cast(); - double domain_step; - - for (py::handle v : pyomo_vars) { - v_attrs = var_attrs[expr_types.id(v)]; - v_lb = v_attrs[1]; - v_ub = v_attrs[2]; - v_fixed = v_attrs[3].cast(); - domain_interval = v_attrs[4]; - v_val = v_attrs[5]; - - interval_lb = domain_interval[0]; - interval_ub = domain_interval[1]; - interval_step = domain_interval[2]; - domain_step = interval_step.cast(); - - if (update) { - cv = var_map[expr_types.id(v)].cast>(); - } else { - cv = std::make_shared(); - } - - if (!(v_lb.is(py::none()))) { - cv->lb = appsi_expr_from_pyomo_expr(v_lb, var_map, param_map, expr_types); - } else { - cv->lb = std::make_shared(-inf); - } - if (!(v_ub.is(py::none()))) { - cv->ub = appsi_expr_from_pyomo_expr(v_ub, var_map, param_map, expr_types); - } else { - cv->ub = std::make_shared(inf); - } - - if (!(v_val.is(py::none()))) { - cv->value = v_val.cast(); - } - - if (v_fixed) { - cv->fixed = true; - } else { - cv->fixed = false; - } - - if (set_name && !update) { - cv->name = symbol_map.attr("getSymbol")(v, labeler).cast(); - } - - if (interval_lb.is(py::none())) - cv->domain_lb = -inf; - else - cv->domain_lb = interval_lb.cast(); - if (interval_ub.is(py::none())) - cv->domain_ub = inf; - else - cv->domain_ub = interval_ub.cast(); - if (domain_step == 0) - cv->domain = continuous; - else if (domain_step == 1) { - if ((cv->domain_lb == 0) && (cv->domain_ub == 1)) - cv->domain = binary; - else - cv->domain = integers; - } else - throw py::value_error("Unrecognized domain step"); - - if (!update) { - var_map[expr_types.id(v)] = py::cast(cv); - rev_var_map[py::cast(cv)] = v; - } - } -} +/**___________________________________________________________________________ + * + * 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. + * ___________________________________________________________________________ +**/ + +#include "expression.hpp" + +bool Leaf::is_leaf() { return true; } + +bool Var::is_variable_type() { return true; } + +bool Param::is_param_type() { return true; } + +bool Constant::is_constant_type() { return true; } + +bool Expression::is_expression_type() { return true; } + +double Leaf::evaluate() { return value; } + +double Var::get_lb() { + if (fixed) + return value; + else + return std::max(lb->evaluate(), domain_lb); +} + +double Var::get_ub() { + if (fixed) + return value; + else + return std::min(ub->evaluate(), domain_ub); +} + +Domain Var::get_domain() { return domain; } + +bool Operator::is_operator_type() { return true; } + +std::vector> Expression::get_operators() { + std::vector> res(n_operators); + for (unsigned int i = 0; i < n_operators; ++i) { + res[i] = operators[i]; + } + return res; +} + +double Leaf::get_value_from_array(double *val_array) { return value; } + +double Expression::get_value_from_array(double *val_array) { + return val_array[n_operators - 1]; +} + +double Operator::get_value_from_array(double *val_array) { + return val_array[index]; +} + +void MultiplyOperator::evaluate(double *values) { + values[index] = operand1->get_value_from_array(values) * + operand2->get_value_from_array(values); +} + +void ExternalOperator::evaluate(double *values) { + // It would be nice to implement this, but it will take some more work. + // This would require dynamic linking to the external function. + throw std::runtime_error("cannot evaluate ExternalOperator yet"); +} + +void LinearOperator::evaluate(double *values) { + values[index] = constant->evaluate(); + for (unsigned int i = 0; i < nterms; ++i) { + values[index] += coefficients[i]->evaluate() * variables[i]->evaluate(); + } +} + +void SumOperator::evaluate(double *values) { + values[index] = 0.0; + for (unsigned int i = 0; i < nargs; ++i) { + values[index] += operands[i]->get_value_from_array(values); + } +} + +void DivideOperator::evaluate(double *values) { + values[index] = operand1->get_value_from_array(values) / + operand2->get_value_from_array(values); +} + +void PowerOperator::evaluate(double *values) { + values[index] = std::pow(operand1->get_value_from_array(values), + operand2->get_value_from_array(values)); +} + +void NegationOperator::evaluate(double *values) { + values[index] = -operand->get_value_from_array(values); +} + +void ExpOperator::evaluate(double *values) { + values[index] = std::exp(operand->get_value_from_array(values)); +} + +void LogOperator::evaluate(double *values) { + values[index] = std::log(operand->get_value_from_array(values)); +} + +void AbsOperator::evaluate(double *values) { + values[index] = std::fabs(operand->get_value_from_array(values)); +} + +void SqrtOperator::evaluate(double *values) { + values[index] = std::pow(operand->get_value_from_array(values), 0.5); +} + +void Log10Operator::evaluate(double *values) { + values[index] = std::log10(operand->get_value_from_array(values)); +} + +void SinOperator::evaluate(double *values) { + values[index] = std::sin(operand->get_value_from_array(values)); +} + +void CosOperator::evaluate(double *values) { + values[index] = std::cos(operand->get_value_from_array(values)); +} + +void TanOperator::evaluate(double *values) { + values[index] = std::tan(operand->get_value_from_array(values)); +} + +void AsinOperator::evaluate(double *values) { + values[index] = std::asin(operand->get_value_from_array(values)); +} + +void AcosOperator::evaluate(double *values) { + values[index] = std::acos(operand->get_value_from_array(values)); +} + +void AtanOperator::evaluate(double *values) { + values[index] = std::atan(operand->get_value_from_array(values)); +} + +double Expression::evaluate() { + double *values = new double[n_operators]; + for (unsigned int i = 0; i < n_operators; ++i) { + operators[i]->index = i; + operators[i]->evaluate(values); + } + double res = get_value_from_array(values); + delete[] values; + return res; +} + +void UnaryOperator::identify_variables( + std::set> &var_set, + std::shared_ptr>> var_vec) { + if (operand->is_variable_type()) { + if (var_set.count(operand) == 0) { + var_vec->push_back(std::dynamic_pointer_cast(operand)); + var_set.insert(operand); + } + } +} + +void BinaryOperator::identify_variables( + std::set> &var_set, + std::shared_ptr>> var_vec) { + if (operand1->is_variable_type()) { + if (var_set.count(operand1) == 0) { + var_vec->push_back(std::dynamic_pointer_cast(operand1)); + var_set.insert(operand1); + } + } + if (operand2->is_variable_type()) { + if (var_set.count(operand2) == 0) { + var_vec->push_back(std::dynamic_pointer_cast(operand2)); + var_set.insert(operand2); + } + } +} + +void ExternalOperator::identify_variables( + std::set> &var_set, + std::shared_ptr>> var_vec) { + for (unsigned int i = 0; i < nargs; ++i) { + if (operands[i]->is_variable_type()) { + if (var_set.count(operands[i]) == 0) { + var_vec->push_back(std::dynamic_pointer_cast(operands[i])); + var_set.insert(operands[i]); + } + } + } +} + +void LinearOperator::identify_variables( + std::set> &var_set, + std::shared_ptr>> var_vec) { + for (unsigned int i = 0; i < nterms; ++i) { + if (var_set.count(variables[i]) == 0) { + var_vec->push_back(std::dynamic_pointer_cast(variables[i])); + var_set.insert(variables[i]); + } + } +} + +void SumOperator::identify_variables( + std::set> &var_set, + std::shared_ptr>> var_vec) { + for (unsigned int i = 0; i < nargs; ++i) { + if (operands[i]->is_variable_type()) { + if (var_set.count(operands[i]) == 0) { + var_vec->push_back(std::dynamic_pointer_cast(operands[i])); + var_set.insert(operands[i]); + } + } + } +} + +std::shared_ptr>> +Expression::identify_variables() { + std::set> var_set; + std::shared_ptr>> res = + std::make_shared>>(var_set.size()); + for (unsigned int i = 0; i < n_operators; ++i) { + operators[i]->identify_variables(var_set, res); + } + return res; +} + +std::shared_ptr>> Var::identify_variables() { + std::shared_ptr>> res = + std::make_shared>>(); + res->push_back(shared_from_this()); + return res; +} + +std::shared_ptr>> +Constant::identify_variables() { + std::shared_ptr>> res = + std::make_shared>>(); + return res; +} + +std::shared_ptr>> Param::identify_variables() { + std::shared_ptr>> res = + std::make_shared>>(); + return res; +} + +std::shared_ptr>> +Expression::identify_external_operators() { + std::set> external_set; + for (unsigned int i = 0; i < n_operators; ++i) { + if (operators[i]->is_external_operator()) { + external_set.insert(operators[i]); + } + } + std::shared_ptr>> res = + std::make_shared>>( + external_set.size()); + int ndx = 0; + for (std::shared_ptr n : external_set) { + (*res)[ndx] = std::dynamic_pointer_cast(n); + ndx += 1; + } + return res; +} + +std::shared_ptr>> +Var::identify_external_operators() { + std::shared_ptr>> res = + std::make_shared>>(); + return res; +} + +std::shared_ptr>> +Constant::identify_external_operators() { + std::shared_ptr>> res = + std::make_shared>>(); + return res; +} + +std::shared_ptr>> +Param::identify_external_operators() { + std::shared_ptr>> res = + std::make_shared>>(); + return res; +} + +int Var::get_degree_from_array(int *degree_array) { return 1; } + +int Param::get_degree_from_array(int *degree_array) { return 0; } + +int Constant::get_degree_from_array(int *degree_array) { return 0; } + +int Expression::get_degree_from_array(int *degree_array) { + return degree_array[n_operators - 1]; +} + +int Operator::get_degree_from_array(int *degree_array) { + return degree_array[index]; +} + +void LinearOperator::propagate_degree_forward(int *degrees, double *values) { + degrees[index] = 1; +} + +void SumOperator::propagate_degree_forward(int *degrees, double *values) { + int deg = 0; + int _deg; + for (unsigned int i = 0; i < nargs; ++i) { + _deg = operands[i]->get_degree_from_array(degrees); + if (_deg > deg) { + deg = _deg; + } + } + degrees[index] = deg; +} + +void MultiplyOperator::propagate_degree_forward(int *degrees, double *values) { + degrees[index] = operand1->get_degree_from_array(degrees) + + operand2->get_degree_from_array(degrees); +} + +void ExternalOperator::propagate_degree_forward(int *degrees, double *values) { + // External functions are always considered nonlinear + // Anything larger than 2 is nonlinear + degrees[index] = 3; +} + +void DivideOperator::propagate_degree_forward(int *degrees, double *values) { + // anything larger than 2 is nonlinear + degrees[index] = std::max(operand1->get_degree_from_array(degrees), + 3 * (operand2->get_degree_from_array(degrees))); +} + +void PowerOperator::propagate_degree_forward(int *degrees, double *values) { + if (operand2->get_degree_from_array(degrees) != 0) { + degrees[index] = 3; + } else { + double val2 = operand2->get_value_from_array(values); + double intpart; + if (std::modf(val2, &intpart) == 0.0) { + degrees[index] = operand1->get_degree_from_array(degrees) * (int)val2; + } else { + degrees[index] = 3; + } + } +} + +void NegationOperator::propagate_degree_forward(int *degrees, double *values) { + degrees[index] = operand->get_degree_from_array(degrees); +} + +void UnaryOperator::propagate_degree_forward(int *degrees, double *values) { + if (operand->get_degree_from_array(degrees) == 0) { + degrees[index] = 0; + } else { + degrees[index] = 3; + } +} + +std::string Var::__str__() { return name; } + +std::string Param::__str__() { return name; } + +std::string Constant::__str__() { return std::to_string(value); } + +std::string Expression::__str__() { + std::string *string_array = new std::string[n_operators]; + std::shared_ptr oper; + for (unsigned int i = 0; i < n_operators; ++i) { + oper = operators[i]; + oper->index = i; + oper->print(string_array); + } + std::string res = string_array[n_operators - 1]; + delete[] string_array; + return res; +} + +std::string Leaf::get_string_from_array(std::string *string_array) { + return __str__(); +} + +std::string Expression::get_string_from_array(std::string *string_array) { + return string_array[n_operators - 1]; +} + +std::string Operator::get_string_from_array(std::string *string_array) { + return string_array[index]; +} + +void MultiplyOperator::print(std::string *string_array) { + string_array[index] = + ("(" + operand1->get_string_from_array(string_array) + "*" + + operand2->get_string_from_array(string_array) + ")"); +} + +void ExternalOperator::print(std::string *string_array) { + std::string res = function_name + "("; + for (unsigned int i = 0; i < (nargs - 1); ++i) { + res += operands[i]->get_string_from_array(string_array); + res += ", "; + } + res += operands[nargs - 1]->get_string_from_array(string_array); + res += ")"; + string_array[index] = res; +} + +void DivideOperator::print(std::string *string_array) { + string_array[index] = + ("(" + operand1->get_string_from_array(string_array) + "/" + + operand2->get_string_from_array(string_array) + ")"); +} + +void PowerOperator::print(std::string *string_array) { + string_array[index] = + ("(" + operand1->get_string_from_array(string_array) + "**" + + operand2->get_string_from_array(string_array) + ")"); +} + +void NegationOperator::print(std::string *string_array) { + string_array[index] = + ("(-" + operand->get_string_from_array(string_array) + ")"); +} + +void ExpOperator::print(std::string *string_array) { + string_array[index] = + ("exp(" + operand->get_string_from_array(string_array) + ")"); +} + +void LogOperator::print(std::string *string_array) { + string_array[index] = + ("log(" + operand->get_string_from_array(string_array) + ")"); +} + +void AbsOperator::print(std::string *string_array) { + string_array[index] = + ("abs(" + operand->get_string_from_array(string_array) + ")"); +} + +void SqrtOperator::print(std::string *string_array) { + string_array[index] = + ("sqrt(" + operand->get_string_from_array(string_array) + ")"); +} + +void Log10Operator::print(std::string *string_array) { + string_array[index] = + ("log10(" + operand->get_string_from_array(string_array) + ")"); +} + +void SinOperator::print(std::string *string_array) { + string_array[index] = + ("sin(" + operand->get_string_from_array(string_array) + ")"); +} + +void CosOperator::print(std::string *string_array) { + string_array[index] = + ("cos(" + operand->get_string_from_array(string_array) + ")"); +} + +void TanOperator::print(std::string *string_array) { + string_array[index] = + ("tan(" + operand->get_string_from_array(string_array) + ")"); +} + +void AsinOperator::print(std::string *string_array) { + string_array[index] = + ("asin(" + operand->get_string_from_array(string_array) + ")"); +} + +void AcosOperator::print(std::string *string_array) { + string_array[index] = + ("acos(" + operand->get_string_from_array(string_array) + ")"); +} + +void AtanOperator::print(std::string *string_array) { + string_array[index] = + ("atan(" + operand->get_string_from_array(string_array) + ")"); +} + +void LinearOperator::print(std::string *string_array) { + std::string res = "(" + constant->__str__(); + for (unsigned int i = 0; i < nterms; ++i) { + res += " + " + coefficients[i]->__str__() + "*" + variables[i]->__str__(); + } + res += ")"; + string_array[index] = res; +} + +void SumOperator::print(std::string *string_array) { + std::string res = "(" + operands[0]->get_string_from_array(string_array); + for (unsigned int i = 1; i < nargs; ++i) { + res += " + " + operands[i]->get_string_from_array(string_array); + } + res += ")"; + string_array[index] = res; +} + +std::shared_ptr>> +Leaf::get_prefix_notation() { + std::shared_ptr>> res = + std::make_shared>>(); + res->push_back(shared_from_this()); + return res; +} + +std::shared_ptr>> +Expression::get_prefix_notation() { + std::shared_ptr>> res = + std::make_shared>>(); + std::shared_ptr>> stack = + std::make_shared>>(); + std::shared_ptr node; + stack->push_back(operators[n_operators - 1]); + while (stack->size() > 0) { + node = stack->back(); + stack->pop_back(); + res->push_back(node); + node->fill_prefix_notation_stack(stack); + } + + return res; +} + +void BinaryOperator::fill_prefix_notation_stack( + std::shared_ptr>> stack) { + stack->push_back(operand2); + stack->push_back(operand1); +} + +void UnaryOperator::fill_prefix_notation_stack( + std::shared_ptr>> stack) { + stack->push_back(operand); +} + +void SumOperator::fill_prefix_notation_stack( + std::shared_ptr>> stack) { + int ndx = nargs - 1; + while (ndx >= 0) { + stack->push_back(operands[ndx]); + ndx -= 1; + } +} + +void LinearOperator::fill_prefix_notation_stack( + std::shared_ptr>> stack) { + ; // This is treated as a leaf in this context; write_nl_string will take care + // of it +} + +void ExternalOperator::fill_prefix_notation_stack( + std::shared_ptr>> stack) { + int i = nargs - 1; + while (i >= 0) { + stack->push_back(operands[i]); + i -= 1; + } +} + +void Var::write_nl_string(std::ofstream &f) { f << "v" << index << "\n"; } + +void Param::write_nl_string(std::ofstream &f) { f << "n" << value << "\n"; } + +void Constant::write_nl_string(std::ofstream &f) { f << "n" << value << "\n"; } + +void Expression::write_nl_string(std::ofstream &f) { + std::shared_ptr>> prefix_notation = + get_prefix_notation(); + for (std::shared_ptr &node : *(prefix_notation)) { + node->write_nl_string(f); + } +} + +void MultiplyOperator::write_nl_string(std::ofstream &f) { f << "o2\n"; } + +void ExternalOperator::write_nl_string(std::ofstream &f) { + f << "f" << external_function_index << " " << nargs << "\n"; +} + +void SumOperator::write_nl_string(std::ofstream &f) { + if (nargs == 2) { + f << "o0\n"; + } else { + f << "o54\n"; + f << nargs << "\n"; + } +} + +void LinearOperator::write_nl_string(std::ofstream &f) { + bool has_const = + (!constant->is_constant_type()) || (constant->evaluate() != 0); + unsigned int n_sum_args = nterms + (has_const ? 1 : 0); + if (n_sum_args == 2) { + f << "o0\n"; + } else { + f << "o54\n"; + f << n_sum_args << "\n"; + } + if (has_const) + f << "n" << constant->evaluate() << "\n"; + for (unsigned int ndx = 0; ndx < nterms; ++ndx) { + f << "o2\n"; + f << "n" << coefficients[ndx]->evaluate() << "\n"; + variables[ndx]->write_nl_string(f); + } +} + +void DivideOperator::write_nl_string(std::ofstream &f) { f << "o3\n"; } + +void PowerOperator::write_nl_string(std::ofstream &f) { f << "o5\n"; } + +void NegationOperator::write_nl_string(std::ofstream &f) { f << "o16\n"; } + +void ExpOperator::write_nl_string(std::ofstream &f) { f << "o44\n"; } + +void LogOperator::write_nl_string(std::ofstream &f) { f << "o43\n"; } + +void AbsOperator::write_nl_string(std::ofstream &f) { f << "o15\n"; } + +void SqrtOperator::write_nl_string(std::ofstream &f) { f << "o39\n"; } + +void Log10Operator::write_nl_string(std::ofstream &f) { f << "o42\n"; } + +void SinOperator::write_nl_string(std::ofstream &f) { f << "o41\n"; } + +void CosOperator::write_nl_string(std::ofstream &f) { f << "o46\n"; } + +void TanOperator::write_nl_string(std::ofstream &f) { f << "o38\n"; } + +void AsinOperator::write_nl_string(std::ofstream &f) { f << "o51\n"; } + +void AcosOperator::write_nl_string(std::ofstream &f) { f << "o53\n"; } + +void AtanOperator::write_nl_string(std::ofstream &f) { f << "o49\n"; } + +bool BinaryOperator::is_binary_operator() { return true; } + +bool UnaryOperator::is_unary_operator() { return true; } + +bool LinearOperator::is_linear_operator() { return true; } + +bool SumOperator::is_sum_operator() { return true; } + +bool MultiplyOperator::is_multiply_operator() { return true; } + +bool DivideOperator::is_divide_operator() { return true; } + +bool PowerOperator::is_power_operator() { return true; } + +bool NegationOperator::is_negation_operator() { return true; } + +bool ExpOperator::is_exp_operator() { return true; } + +bool LogOperator::is_log_operator() { return true; } + +bool AbsOperator::is_abs_operator() { return true; } + +bool SqrtOperator::is_sqrt_operator() { return true; } + +bool ExternalOperator::is_external_operator() { return true; } + +void Leaf::fill_expression(std::shared_ptr *oper_array, + int &oper_ndx) { + ; +} + +void Expression::fill_expression(std::shared_ptr *oper_array, + int &oper_ndx) { + throw std::runtime_error("This should not happen"); +} + +void BinaryOperator::fill_expression(std::shared_ptr *oper_array, + int &oper_ndx) { + oper_ndx -= 1; + oper_array[oper_ndx] = shared_from_this(); + // The order does not actually matter here. It + // will just be easier to debug this way. + operand2->fill_expression(oper_array, oper_ndx); + operand1->fill_expression(oper_array, oper_ndx); +} + +void UnaryOperator::fill_expression(std::shared_ptr *oper_array, + int &oper_ndx) { + oper_ndx -= 1; + oper_array[oper_ndx] = shared_from_this(); + operand->fill_expression(oper_array, oper_ndx); +} + +void LinearOperator::fill_expression(std::shared_ptr *oper_array, + int &oper_ndx) { + oper_ndx -= 1; + oper_array[oper_ndx] = shared_from_this(); +} + +void SumOperator::fill_expression(std::shared_ptr *oper_array, + int &oper_ndx) { + oper_ndx -= 1; + oper_array[oper_ndx] = shared_from_this(); + // The order does not actually matter here. It + // will just be easier to debug this way. + int arg_ndx = nargs - 1; + while (arg_ndx >= 0) { + operands[arg_ndx]->fill_expression(oper_array, oper_ndx); + arg_ndx -= 1; + } +} + +void ExternalOperator::fill_expression(std::shared_ptr *oper_array, + int &oper_ndx) { + oper_ndx -= 1; + oper_array[oper_ndx] = shared_from_this(); + // The order does not actually matter here. It + // will just be easier to debug this way. + int arg_ndx = nargs - 1; + while (arg_ndx >= 0) { + operands[arg_ndx]->fill_expression(oper_array, oper_ndx); + arg_ndx -= 1; + } +} + +double Leaf::get_lb_from_array(double *lbs) { return value; } + +double Leaf::get_ub_from_array(double *ubs) { return value; } + +double Var::get_lb_from_array(double *lbs) { return get_lb(); } + +double Var::get_ub_from_array(double *ubs) { return get_ub(); } + +double Expression::get_lb_from_array(double *lbs) { + return lbs[n_operators - 1]; +} + +double Expression::get_ub_from_array(double *ubs) { + return ubs[n_operators - 1]; +} + +double Operator::get_lb_from_array(double *lbs) { return lbs[index]; } + +double Operator::get_ub_from_array(double *ubs) { return ubs[index]; } + +void Leaf::set_bounds_in_array(double new_lb, double new_ub, double *lbs, + double *ubs, double feasibility_tol, + double integer_tol, double improvement_tol, + std::set> &improved_vars) { + if (new_lb < value - feasibility_tol || new_lb > value + feasibility_tol) { + throw InfeasibleConstraintException( + "Infeasible constraint; bounds computed on parameter or constant " + "disagree with the value of the parameter or constant\n value: " + + std::to_string(value) + "\n computed LB: " + std::to_string(new_lb) + + "\n computed UB: " + std::to_string(new_ub)); + } + + if (new_ub < value - feasibility_tol || new_ub > value + feasibility_tol) { + throw InfeasibleConstraintException( + "Infeasible constraint; bounds computed on parameter or constant " + "disagree with the value of the parameter or constant\n value: " + + std::to_string(value) + "\n computed LB: " + std::to_string(new_lb) + + "\n computed UB: " + std::to_string(new_ub)); + } +} + +void Var::set_bounds_in_array(double new_lb, double new_ub, double *lbs, + double *ubs, double feasibility_tol, + double integer_tol, double improvement_tol, + std::set> &improved_vars) { + if (new_lb > new_ub) { + if (new_lb - feasibility_tol > new_ub) + throw InfeasibleConstraintException( + "Infeasible constraint; The computed lower bound for a variable is " + "larger than the computed upper bound.\n computed LB: " + + std::to_string(new_lb) + + "\n computed UB: " + std::to_string(new_ub)); + else { + new_lb -= feasibility_tol; + new_ub += feasibility_tol; + } + } + if (new_lb >= inf) + throw InfeasibleConstraintException( + "Infeasible constraint; The compute lower bound for " + name + + " is inf"); + if (new_ub <= -inf) + throw InfeasibleConstraintException( + "Infeasible constraint; The computed upper bound for " + name + + " is -inf"); + + if (domain == integers || domain == binary) { + if (new_lb > -inf) { + double lb_floor = floor(new_lb); + double lb_ceil = ceil(new_lb - integer_tol); + if (lb_floor > lb_ceil) + new_lb = lb_floor; + else + new_lb = lb_ceil; + } + if (new_ub < inf) { + double ub_ceil = ceil(new_ub); + double ub_floor = floor(new_ub + integer_tol); + if (ub_ceil < ub_floor) + new_ub = ub_ceil; + else + new_ub = ub_floor; + } + } + + double current_lb = get_lb(); + double current_ub = get_ub(); + + if (new_lb > current_lb + improvement_tol || + new_ub < current_ub - improvement_tol) + improved_vars.insert(shared_from_this()); + + if (new_lb > current_lb) { + if (lb->is_leaf()) + std::dynamic_pointer_cast(lb)->value = new_lb; + else + throw py::value_error( + "variable bounds cannot be expressions when performing FBBT"); + } + + if (new_ub < current_ub) { + if (ub->is_leaf()) + std::dynamic_pointer_cast(ub)->value = new_ub; + else + throw py::value_error( + "variable bounds cannot be expressions when performing FBBT"); + } +} + +void Expression::set_bounds_in_array( + double new_lb, double new_ub, double *lbs, double *ubs, + double feasibility_tol, double integer_tol, double improvement_tol, + std::set> &improved_vars) { + lbs[n_operators - 1] = new_lb; + ubs[n_operators - 1] = new_ub; +} + +void Operator::set_bounds_in_array( + double new_lb, double new_ub, double *lbs, double *ubs, + double feasibility_tol, double integer_tol, double improvement_tol, + std::set> &improved_vars) { + lbs[index] = new_lb; + ubs[index] = new_ub; +} + +void Expression::propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) { + for (unsigned int ndx = 0; ndx < n_operators; ++ndx) { + operators[ndx]->index = ndx; + operators[ndx]->propagate_bounds_forward(lbs, ubs, feasibility_tol, + integer_tol); + } +} + +void Expression::propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, std::set> &improved_vars) { + int ndx = n_operators - 1; + while (ndx >= 0) { + operators[ndx]->propagate_bounds_backward( + lbs, ubs, feasibility_tol, integer_tol, improvement_tol, improved_vars); + ndx -= 1; + } +} + +void Operator::propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) { + lbs[index] = -inf; + ubs[index] = inf; +} + +void Operator::propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, std::set> &improved_vars) { + ; +} + +void MultiplyOperator::propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) { + if (operand1 == operand2) { + interval_power(operand1->get_lb_from_array(lbs), + operand1->get_ub_from_array(ubs), 2, 2, &lbs[index], + &ubs[index], feasibility_tol); + } else { + interval_mul(operand1->get_lb_from_array(lbs), + operand1->get_ub_from_array(ubs), + operand2->get_lb_from_array(lbs), + operand2->get_ub_from_array(ubs), &lbs[index], &ubs[index]); + } +} + +void MultiplyOperator::propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, std::set> &improved_vars) { + double xl = operand1->get_lb_from_array(lbs); + double xu = operand1->get_ub_from_array(ubs); + double yl = operand2->get_lb_from_array(lbs); + double yu = operand2->get_ub_from_array(ubs); + double lb = get_lb_from_array(lbs); + double ub = get_ub_from_array(ubs); + + double new_xl, new_xu, new_yl, new_yu; + + if (operand1 == operand2) { + _inverse_power1(lb, ub, 2, 2, xl, xu, &new_xl, &new_xu, feasibility_tol); + new_yl = new_xl; + new_yu = new_xu; + } else { + interval_div(lb, ub, yl, yu, &new_xl, &new_xu, feasibility_tol); + interval_div(lb, ub, xl, xu, &new_yl, &new_yu, feasibility_tol); + } + + if (new_xl > xl) + xl = new_xl; + if (new_xu < xu) + xu = new_xu; + operand1->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, + improvement_tol, improved_vars); + + if (new_yl > yl) + yl = new_yl; + if (new_yu < yu) + yu = new_yu; + operand2->set_bounds_in_array(yl, yu, lbs, ubs, feasibility_tol, integer_tol, + improvement_tol, improved_vars); +} + +void SumOperator::propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) { + double lb = operands[0]->get_lb_from_array(lbs); + double ub = operands[0]->get_ub_from_array(ubs); + double tmp_lb; + double tmp_ub; + + for (unsigned int ndx = 1; ndx < nargs; ++ndx) { + interval_add(lb, ub, operands[ndx]->get_lb_from_array(lbs), + operands[ndx]->get_ub_from_array(ubs), &tmp_lb, &tmp_ub); + lb = tmp_lb; + ub = tmp_ub; + } + + lbs[index] = lb; + ubs[index] = ub; +} + +void SumOperator::propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, std::set> &improved_vars) { + double *accumulated_lbs = new double[nargs]; + double *accumulated_ubs = new double[nargs]; + + accumulated_lbs[0] = operands[0]->get_lb_from_array(lbs); + accumulated_ubs[0] = operands[0]->get_ub_from_array(ubs); + for (unsigned int ndx = 1; ndx < nargs; ++ndx) { + interval_add(accumulated_lbs[ndx - 1], accumulated_ubs[ndx - 1], + operands[ndx]->get_lb_from_array(lbs), + operands[ndx]->get_ub_from_array(ubs), &accumulated_lbs[ndx], + &accumulated_ubs[ndx]); + } + + double new_sum_lb = get_lb_from_array(lbs); + double new_sum_ub = get_ub_from_array(ubs); + + if (new_sum_lb > accumulated_lbs[nargs - 1]) + accumulated_lbs[nargs - 1] = new_sum_lb; + if (new_sum_ub < accumulated_ubs[nargs - 1]) + accumulated_ubs[nargs - 1] = new_sum_ub; + + double lb0, ub0, lb1, ub1, lb2, ub2, _lb1, _ub1, _lb2, _ub2; + + int ndx = nargs - 1; + while (ndx >= 1) { + lb0 = accumulated_lbs[ndx]; + ub0 = accumulated_ubs[ndx]; + lb1 = accumulated_lbs[ndx - 1]; + ub1 = accumulated_ubs[ndx - 1]; + lb2 = operands[ndx]->get_lb_from_array(lbs); + ub2 = operands[ndx]->get_ub_from_array(ubs); + interval_sub(lb0, ub0, lb2, ub2, &_lb1, &_ub1); + interval_sub(lb0, ub0, lb1, ub1, &_lb2, &_ub2); + if (_lb1 > lb1) + lb1 = _lb1; + if (_ub1 < ub1) + ub1 = _ub1; + if (_lb2 > lb2) + lb2 = _lb2; + if (_ub2 < ub2) + ub2 = _ub2; + accumulated_lbs[ndx - 1] = lb1; + accumulated_ubs[ndx - 1] = ub1; + operands[ndx]->set_bounds_in_array(lb2, ub2, lbs, ubs, feasibility_tol, + integer_tol, improvement_tol, + improved_vars); + ndx -= 1; + } + + // take care of ndx = 0 + lb1 = operands[0]->get_lb_from_array(lbs); + ub1 = operands[0]->get_ub_from_array(ubs); + _lb1 = accumulated_lbs[0]; + _ub1 = accumulated_ubs[0]; + if (_lb1 > lb1) + lb1 = _lb1; + if (_ub1 < ub1) + ub1 = _ub1; + operands[0]->set_bounds_in_array(lb1, ub1, lbs, ubs, feasibility_tol, + integer_tol, improvement_tol, improved_vars); + + delete[] accumulated_lbs; + delete[] accumulated_ubs; +} + +void LinearOperator::propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) { + double lb = constant->evaluate(); + double ub = lb; + double tmp_lb; + double tmp_ub; + double coef; + + for (unsigned int ndx = 0; ndx < nterms; ++ndx) { + coef = coefficients[ndx]->evaluate(); + interval_mul(coef, coef, variables[ndx]->get_lb(), variables[ndx]->get_ub(), + &tmp_lb, &tmp_ub); + interval_add(lb, ub, tmp_lb, tmp_ub, &lb, &ub); + } + + lbs[index] = lb; + ubs[index] = ub; +} + +void LinearOperator::propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, std::set> &improved_vars) { + double *accumulated_lbs = new double[nterms + 1]; + double *accumulated_ubs = new double[nterms + 1]; + + double coef; + + accumulated_lbs[0] = constant->evaluate(); + accumulated_ubs[0] = constant->evaluate(); + for (unsigned int ndx = 0; ndx < nterms; ++ndx) { + coef = coefficients[ndx]->evaluate(); + interval_mul(coef, coef, variables[ndx]->get_lb(), variables[ndx]->get_ub(), + &accumulated_lbs[ndx + 1], &accumulated_ubs[ndx + 1]); + interval_add(accumulated_lbs[ndx], accumulated_ubs[ndx], + accumulated_lbs[ndx + 1], accumulated_ubs[ndx + 1], + &accumulated_lbs[ndx + 1], &accumulated_ubs[ndx + 1]); + } + + double new_sum_lb = get_lb_from_array(lbs); + double new_sum_ub = get_ub_from_array(ubs); + + if (new_sum_lb > accumulated_lbs[nterms]) + accumulated_lbs[nterms] = new_sum_lb; + if (new_sum_ub < accumulated_ubs[nterms]) + accumulated_ubs[nterms] = new_sum_ub; + + double lb0, ub0, lb1, ub1, lb2, ub2, _lb1, _ub1, _lb2, _ub2, new_v_lb, + new_v_ub; + + int ndx = nterms - 1; + while (ndx >= 0) { + lb0 = accumulated_lbs[ndx + 1]; + ub0 = accumulated_ubs[ndx + 1]; + lb1 = accumulated_lbs[ndx]; + ub1 = accumulated_ubs[ndx]; + coef = coefficients[ndx]->evaluate(); + interval_mul(coef, coef, variables[ndx]->get_lb(), variables[ndx]->get_ub(), + &lb2, &ub2); + interval_sub(lb0, ub0, lb2, ub2, &_lb1, &_ub1); + interval_sub(lb0, ub0, lb1, ub1, &_lb2, &_ub2); + if (_lb1 > lb1) + lb1 = _lb1; + if (_ub1 < ub1) + ub1 = _ub1; + if (_lb2 > lb2) + lb2 = _lb2; + if (_ub2 < ub2) + ub2 = _ub2; + accumulated_lbs[ndx] = lb1; + accumulated_ubs[ndx] = ub1; + interval_div(lb2, ub2, coef, coef, &new_v_lb, &new_v_ub, feasibility_tol); + variables[ndx]->set_bounds_in_array(new_v_lb, new_v_ub, lbs, ubs, + feasibility_tol, integer_tol, + improvement_tol, improved_vars); + ndx -= 1; + } + + delete[] accumulated_lbs; + delete[] accumulated_ubs; +} + +void DivideOperator::propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) { + interval_div( + operand1->get_lb_from_array(lbs), operand1->get_ub_from_array(ubs), + operand2->get_lb_from_array(lbs), operand2->get_ub_from_array(ubs), + &lbs[index], &ubs[index], feasibility_tol); +} + +void DivideOperator::propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, std::set> &improved_vars) { + double xl = operand1->get_lb_from_array(lbs); + double xu = operand1->get_ub_from_array(ubs); + double yl = operand2->get_lb_from_array(lbs); + double yu = operand2->get_ub_from_array(ubs); + double lb = get_lb_from_array(lbs); + double ub = get_ub_from_array(ubs); + + double new_xl; + double new_xu; + double new_yl; + double new_yu; + + interval_mul(lb, ub, yl, yu, &new_xl, &new_xu); + interval_div(xl, xu, lb, ub, &new_yl, &new_yu, feasibility_tol); + + if (new_xl > xl) + xl = new_xl; + if (new_xu < xu) + xu = new_xu; + operand1->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, + improvement_tol, improved_vars); + + if (new_yl > yl) + yl = new_yl; + if (new_yu < yu) + yu = new_yu; + operand2->set_bounds_in_array(yl, yu, lbs, ubs, feasibility_tol, integer_tol, + improvement_tol, improved_vars); +} + +void NegationOperator::propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) { + interval_sub(0, 0, operand->get_lb_from_array(lbs), + operand->get_ub_from_array(ubs), &lbs[index], &ubs[index]); +} + +void NegationOperator::propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, std::set> &improved_vars) { + double xl = operand->get_lb_from_array(lbs); + double xu = operand->get_ub_from_array(ubs); + double lb = get_lb_from_array(lbs); + double ub = get_ub_from_array(ubs); + + double new_xl; + double new_xu; + + interval_sub(0, 0, lb, ub, &new_xl, &new_xu); + + if (new_xl > xl) + xl = new_xl; + if (new_xu < xu) + xu = new_xu; + operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, + improvement_tol, improved_vars); +} + +void PowerOperator::propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) { + interval_power( + operand1->get_lb_from_array(lbs), operand1->get_ub_from_array(ubs), + operand2->get_lb_from_array(lbs), operand2->get_ub_from_array(ubs), + &lbs[index], &ubs[index], feasibility_tol); +} + +void PowerOperator::propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, std::set> &improved_vars) { + double xl = operand1->get_lb_from_array(lbs); + double xu = operand1->get_ub_from_array(ubs); + double yl = operand2->get_lb_from_array(lbs); + double yu = operand2->get_ub_from_array(ubs); + double lb = get_lb_from_array(lbs); + double ub = get_ub_from_array(ubs); + + double new_xl, new_xu, new_yl, new_yu; + _inverse_power1(lb, ub, yl, yu, xl, xu, &new_xl, &new_xu, feasibility_tol); + if (yl != yu) + _inverse_power2(lb, ub, xl, xu, &new_yl, &new_yu, feasibility_tol); + else { + new_yl = yl; + new_yu = yu; + } + + if (new_xl > xl) + xl = new_xl; + if (new_xu < xu) + xu = new_xu; + operand1->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, + improvement_tol, improved_vars); + + if (new_yl > yl) + yl = new_yl; + if (new_yu < yu) + yu = new_yu; + operand2->set_bounds_in_array(yl, yu, lbs, ubs, feasibility_tol, integer_tol, + improvement_tol, improved_vars); +} + +void SqrtOperator::propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) { + interval_power(operand->get_lb_from_array(lbs), + operand->get_ub_from_array(ubs), 0.5, 0.5, &lbs[index], + &ubs[index], feasibility_tol); +} + +void SqrtOperator::propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, std::set> &improved_vars) { + double xl = operand->get_lb_from_array(lbs); + double xu = operand->get_ub_from_array(ubs); + double yl = 0.5; + double yu = 0.5; + double lb = get_lb_from_array(lbs); + double ub = get_ub_from_array(ubs); + + double new_xl, new_xu; + _inverse_power1(lb, ub, yl, yu, xl, xu, &new_xl, &new_xu, feasibility_tol); + + if (new_xl > xl) + xl = new_xl; + if (new_xu < xu) + xu = new_xu; + operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, + improvement_tol, improved_vars); +} + +void ExpOperator::propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) { + interval_exp(operand->get_lb_from_array(lbs), operand->get_ub_from_array(ubs), + &lbs[index], &ubs[index]); +} + +void ExpOperator::propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, std::set> &improved_vars) { + double xl = operand->get_lb_from_array(lbs); + double xu = operand->get_ub_from_array(ubs); + double lb = get_lb_from_array(lbs); + double ub = get_ub_from_array(ubs); + + double new_xl, new_xu; + interval_log(lb, ub, &new_xl, &new_xu); + + if (new_xl > xl) + xl = new_xl; + if (new_xu < xu) + xu = new_xu; + operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, + improvement_tol, improved_vars); +} + +void LogOperator::propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) { + interval_log(operand->get_lb_from_array(lbs), operand->get_ub_from_array(ubs), + &lbs[index], &ubs[index]); +} + +void LogOperator::propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, std::set> &improved_vars) { + double xl = operand->get_lb_from_array(lbs); + double xu = operand->get_ub_from_array(ubs); + double lb = get_lb_from_array(lbs); + double ub = get_ub_from_array(ubs); + + double new_xl, new_xu; + interval_exp(lb, ub, &new_xl, &new_xu); + + if (new_xl > xl) + xl = new_xl; + if (new_xu < xu) + xu = new_xu; + operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, + improvement_tol, improved_vars); +} + +void AbsOperator::propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) { + interval_abs(operand->get_lb_from_array(lbs), operand->get_ub_from_array(ubs), + &lbs[index], &ubs[index]); +} + +void AbsOperator::propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, std::set> &improved_vars) { + double xl = operand->get_lb_from_array(lbs); + double xu = operand->get_ub_from_array(ubs); + double lb = get_lb_from_array(lbs); + double ub = get_ub_from_array(ubs); + + double new_xl, new_xu; + _inverse_abs(lb, ub, &new_xl, &new_xu); + + if (new_xl > xl) + xl = new_xl; + if (new_xu < xu) + xu = new_xu; + operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, + improvement_tol, improved_vars); +} + +void Log10Operator::propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) { + interval_log10(operand->get_lb_from_array(lbs), + operand->get_ub_from_array(ubs), &lbs[index], &ubs[index]); +} + +void Log10Operator::propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, std::set> &improved_vars) { + double xl = operand->get_lb_from_array(lbs); + double xu = operand->get_ub_from_array(ubs); + double lb = get_lb_from_array(lbs); + double ub = get_ub_from_array(ubs); + + double new_xl, new_xu; + interval_power(10, 10, lb, ub, &new_xl, &new_xu, feasibility_tol); + + if (new_xl > xl) + xl = new_xl; + if (new_xu < xu) + xu = new_xu; + operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, + improvement_tol, improved_vars); +} + +void SinOperator::propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) { + interval_sin(operand->get_lb_from_array(lbs), operand->get_ub_from_array(ubs), + &lbs[index], &ubs[index]); +} + +void SinOperator::propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, std::set> &improved_vars) { + double xl = operand->get_lb_from_array(lbs); + double xu = operand->get_ub_from_array(ubs); + double lb = get_lb_from_array(lbs); + double ub = get_ub_from_array(ubs); + + double new_xl, new_xu; + interval_asin(lb, ub, xl, xu, &new_xl, &new_xu, feasibility_tol); + + if (new_xl > xl) + xl = new_xl; + if (new_xu < xu) + xu = new_xu; + operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, + improvement_tol, improved_vars); +} + +void CosOperator::propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) { + interval_cos(operand->get_lb_from_array(lbs), operand->get_ub_from_array(ubs), + &lbs[index], &ubs[index]); +} + +void CosOperator::propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, std::set> &improved_vars) { + double xl = operand->get_lb_from_array(lbs); + double xu = operand->get_ub_from_array(ubs); + double lb = get_lb_from_array(lbs); + double ub = get_ub_from_array(ubs); + + double new_xl, new_xu; + interval_acos(lb, ub, xl, xu, &new_xl, &new_xu, feasibility_tol); + + if (new_xl > xl) + xl = new_xl; + if (new_xu < xu) + xu = new_xu; + operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, + improvement_tol, improved_vars); +} + +void TanOperator::propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) { + interval_tan(operand->get_lb_from_array(lbs), operand->get_ub_from_array(ubs), + &lbs[index], &ubs[index]); +} + +void TanOperator::propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, std::set> &improved_vars) { + double xl = operand->get_lb_from_array(lbs); + double xu = operand->get_ub_from_array(ubs); + double lb = get_lb_from_array(lbs); + double ub = get_ub_from_array(ubs); + + double new_xl, new_xu; + interval_atan(lb, ub, xl, xu, &new_xl, &new_xu); + + if (new_xl > xl) + xl = new_xl; + if (new_xu < xu) + xu = new_xu; + operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, + improvement_tol, improved_vars); +} + +void AsinOperator::propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) { + interval_asin(operand->get_lb_from_array(lbs), + operand->get_ub_from_array(ubs), -inf, inf, &lbs[index], + &ubs[index], feasibility_tol); +} + +void AsinOperator::propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, std::set> &improved_vars) { + double xl = operand->get_lb_from_array(lbs); + double xu = operand->get_ub_from_array(ubs); + double lb = get_lb_from_array(lbs); + double ub = get_ub_from_array(ubs); + + double new_xl, new_xu; + interval_sin(lb, ub, &new_xl, &new_xu); + + if (new_xl > xl) + xl = new_xl; + if (new_xu < xu) + xu = new_xu; + operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, + improvement_tol, improved_vars); +} + +void AcosOperator::propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) { + interval_acos(operand->get_lb_from_array(lbs), + operand->get_ub_from_array(ubs), -inf, inf, &lbs[index], + &ubs[index], feasibility_tol); +} + +void AcosOperator::propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, std::set> &improved_vars) { + double xl = operand->get_lb_from_array(lbs); + double xu = operand->get_ub_from_array(ubs); + double lb = get_lb_from_array(lbs); + double ub = get_ub_from_array(ubs); + + double new_xl, new_xu; + interval_cos(lb, ub, &new_xl, &new_xu); + + if (new_xl > xl) + xl = new_xl; + if (new_xu < xu) + xu = new_xu; + operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, + improvement_tol, improved_vars); +} + +void AtanOperator::propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) { + interval_atan(operand->get_lb_from_array(lbs), + operand->get_ub_from_array(ubs), -inf, inf, &lbs[index], + &ubs[index]); +} + +void AtanOperator::propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, std::set> &improved_vars) { + double xl = operand->get_lb_from_array(lbs); + double xu = operand->get_ub_from_array(ubs); + double lb = get_lb_from_array(lbs); + double ub = get_ub_from_array(ubs); + + double new_xl, new_xu; + interval_tan(lb, ub, &new_xl, &new_xu); + + if (new_xl > xl) + xl = new_xl; + if (new_xu < xu) + xu = new_xu; + operand->set_bounds_in_array(xl, xu, lbs, ubs, feasibility_tol, integer_tol, + improvement_tol, improved_vars); +} + +std::vector> create_vars(int n_vars) { + std::vector> res; + for (int i = 0; i < n_vars; ++i) { + res.push_back(std::make_shared()); + } + return res; +} + +std::vector> create_params(int n_params) { + std::vector> res; + for (int i = 0; i < n_params; ++i) { + res.push_back(std::make_shared()); + } + return res; +} + +std::vector> create_constants(int n_constants) { + std::vector> res; + for (int i = 0; i < n_constants; ++i) { + res.push_back(std::make_shared()); + } + return res; +} + +std::shared_ptr +appsi_operator_from_pyomo_expr(py::handle expr, py::handle var_map, + py::handle param_map, + PyomoExprTypes &expr_types) { + std::shared_ptr res; + ExprType tmp_type = + expr_types.expr_type_map[py::type::of(expr)].cast(); + + switch (tmp_type) { + case py_float: { + res = std::make_shared(expr.cast()); + break; + } + case var: { + res = var_map[expr_types.id(expr)].cast>(); + break; + } + case param: { + res = param_map[expr_types.id(expr)].cast>(); + break; + } + case product: { + res = std::make_shared(); + break; + } + case sum: { + res = std::make_shared(expr.attr("nargs")().cast()); + break; + } + case negation: { + res = std::make_shared(); + break; + } + case external_func: { + res = std::make_shared(expr.attr("nargs")().cast()); + std::shared_ptr oper = + std::dynamic_pointer_cast(res); + oper->function_name = + expr.attr("_fcn").attr("_function").cast(); + break; + } + case power: { + res = std::make_shared(); + break; + } + case division: { + res = std::make_shared(); + break; + } + case unary_func: { + std::string function_name = expr.attr("getname")().cast(); + if (function_name == "exp") + res = std::make_shared(); + else if (function_name == "log") + res = std::make_shared(); + else if (function_name == "log10") + res = std::make_shared(); + else if (function_name == "sin") + res = std::make_shared(); + else if (function_name == "cos") + res = std::make_shared(); + else if (function_name == "tan") + res = std::make_shared(); + else if (function_name == "asin") + res = std::make_shared(); + else if (function_name == "acos") + res = std::make_shared(); + else if (function_name == "atan") + res = std::make_shared(); + else if (function_name == "sqrt") + res = std::make_shared(); + else + throw py::value_error("Unrecognized expression type: " + function_name); + break; + } + case linear: { + res = std::make_shared( + expr_types.len(expr.attr("linear_vars")).cast()); + break; + } + case named_expr: { + res = appsi_operator_from_pyomo_expr(expr.attr("expr"), var_map, param_map, + expr_types); + break; + } + case numeric_constant: { + res = std::make_shared(expr.attr("value").cast()); + break; + } + case pyomo_unit: { + res = std::make_shared(1.0); + break; + } + case unary_abs: { + res = std::make_shared(); + break; + } + default: { + throw py::value_error("Unrecognized expression type: " + + expr_types.builtins.attr("str")(py::type::of(expr)) + .cast()); + break; + } + } + return res; +} + +void prep_for_repn_helper(py::handle expr, py::handle named_exprs, + py::handle variables, py::handle fixed_vars, + py::handle external_funcs, + PyomoExprTypes &expr_types) { + ExprType tmp_type = + expr_types.expr_type_map[py::type::of(expr)].cast(); + + switch (tmp_type) { + case py_float: { + break; + } + case var: { + variables[expr_types.id(expr)] = expr; + if (expr.attr("fixed").cast()) { + fixed_vars[expr_types.id(expr)] = expr; + } + break; + } + case param: { + break; + } + case product: { + py::tuple args = expr.attr("_args_"); + for (py::handle arg : args) { + prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, + external_funcs, expr_types); + } + break; + } + case sum: { + py::tuple args = expr.attr("args"); + for (py::handle arg : args) { + prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, + external_funcs, expr_types); + } + break; + } + case negation: { + py::tuple args = expr.attr("_args_"); + for (py::handle arg : args) { + prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, + external_funcs, expr_types); + } + break; + } + case external_func: { + external_funcs[expr_types.id(expr)] = expr; + py::tuple args = expr.attr("args"); + for (py::handle arg : args) { + prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, + external_funcs, expr_types); + } + break; + } + case power: { + py::tuple args = expr.attr("_args_"); + for (py::handle arg : args) { + prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, + external_funcs, expr_types); + } + break; + } + case division: { + py::tuple args = expr.attr("_args_"); + for (py::handle arg : args) { + prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, + external_funcs, expr_types); + } + break; + } + case unary_func: { + py::tuple args = expr.attr("_args_"); + for (py::handle arg : args) { + prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, + external_funcs, expr_types); + } + break; + } + case linear: { + py::list linear_vars = expr.attr("linear_vars"); + py::list linear_coefs = expr.attr("linear_coefs"); + for (py::handle arg : linear_vars) { + prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, + external_funcs, expr_types); + } + for (py::handle arg : linear_coefs) { + prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, + external_funcs, expr_types); + } + prep_for_repn_helper(expr.attr("constant"), named_exprs, variables, + fixed_vars, external_funcs, expr_types); + break; + } + case named_expr: { + named_exprs[expr_types.id(expr)] = expr; + prep_for_repn_helper(expr.attr("expr"), named_exprs, variables, fixed_vars, + external_funcs, expr_types); + break; + } + case numeric_constant: { + break; + } + case pyomo_unit: { + break; + } + case unary_abs: { + py::tuple args = expr.attr("_args_"); + for (py::handle arg : args) { + prep_for_repn_helper(arg, named_exprs, variables, fixed_vars, + external_funcs, expr_types); + } + break; + } + default: { + if (expr_types.builtins.attr("hasattr")(expr, "is_constant").cast()) { + if (expr.attr("is_constant")().cast()) + break; + } + throw py::value_error("Unrecognized expression type: " + + expr_types.builtins.attr("str")(py::type::of(expr)) + .cast()); + break; + } + } +} + +py::tuple prep_for_repn(py::handle expr, PyomoExprTypes &expr_types) { + py::dict named_exprs; + py::dict variables; + py::dict fixed_vars; + py::dict external_funcs; + + prep_for_repn_helper(expr, named_exprs, variables, fixed_vars, external_funcs, + expr_types); + + py::list named_expr_list = named_exprs.attr("values")(); + py::list variable_list = variables.attr("values")(); + py::list fixed_var_list = fixed_vars.attr("values")(); + py::list external_func_list = external_funcs.attr("values")(); + + py::tuple res = py::make_tuple(named_expr_list, variable_list, fixed_var_list, + external_func_list); + return res; +} + +int build_expression_tree(py::handle pyomo_expr, + std::shared_ptr appsi_expr, py::handle var_map, + py::handle param_map, PyomoExprTypes &expr_types) { + int num_nodes = 0; + + if (expr_types.expr_type_map[py::type::of(pyomo_expr)].cast() == + named_expr) + pyomo_expr = pyomo_expr.attr("expr"); + + if (appsi_expr->is_leaf()) { + ; + } else if (appsi_expr->is_binary_operator()) { + num_nodes += 1; + std::shared_ptr oper = + std::dynamic_pointer_cast(appsi_expr); + py::list pyomo_args = pyomo_expr.attr("args"); + oper->operand1 = appsi_operator_from_pyomo_expr(pyomo_args[0], var_map, + param_map, expr_types); + oper->operand2 = appsi_operator_from_pyomo_expr(pyomo_args[1], var_map, + param_map, expr_types); + num_nodes += build_expression_tree(pyomo_args[0], oper->operand1, var_map, + param_map, expr_types); + num_nodes += build_expression_tree(pyomo_args[1], oper->operand2, var_map, + param_map, expr_types); + } else if (appsi_expr->is_unary_operator()) { + num_nodes += 1; + std::shared_ptr oper = + std::dynamic_pointer_cast(appsi_expr); + py::list pyomo_args = pyomo_expr.attr("args"); + oper->operand = appsi_operator_from_pyomo_expr(pyomo_args[0], var_map, + param_map, expr_types); + num_nodes += build_expression_tree(pyomo_args[0], oper->operand, var_map, + param_map, expr_types); + } else if (appsi_expr->is_sum_operator()) { + num_nodes += 1; + std::shared_ptr oper = + std::dynamic_pointer_cast(appsi_expr); + py::list pyomo_args = pyomo_expr.attr("args"); + for (unsigned int arg_ndx = 0; arg_ndx < oper->nargs; ++arg_ndx) { + oper->operands[arg_ndx] = appsi_operator_from_pyomo_expr( + pyomo_args[arg_ndx], var_map, param_map, expr_types); + num_nodes += + build_expression_tree(pyomo_args[arg_ndx], oper->operands[arg_ndx], + var_map, param_map, expr_types); + } + } else if (appsi_expr->is_linear_operator()) { + num_nodes += 1; + std::shared_ptr oper = + std::dynamic_pointer_cast(appsi_expr); + oper->constant = appsi_expr_from_pyomo_expr(pyomo_expr.attr("constant"), + var_map, param_map, expr_types); + py::list pyomo_vars = pyomo_expr.attr("linear_vars"); + py::list pyomo_coefs = pyomo_expr.attr("linear_coefs"); + for (unsigned int arg_ndx = 0; arg_ndx < oper->nterms; ++arg_ndx) { + oper->variables[arg_ndx] = var_map[expr_types.id(pyomo_vars[arg_ndx])] + .cast>(); + oper->coefficients[arg_ndx] = appsi_expr_from_pyomo_expr( + pyomo_coefs[arg_ndx], var_map, param_map, expr_types); + } + } else if (appsi_expr->is_external_operator()) { + num_nodes += 1; + std::shared_ptr oper = + std::dynamic_pointer_cast(appsi_expr); + py::list pyomo_args = pyomo_expr.attr("args"); + for (unsigned int arg_ndx = 0; arg_ndx < oper->nargs; ++arg_ndx) { + oper->operands[arg_ndx] = appsi_operator_from_pyomo_expr( + pyomo_args[arg_ndx], var_map, param_map, expr_types); + num_nodes += + build_expression_tree(pyomo_args[arg_ndx], oper->operands[arg_ndx], + var_map, param_map, expr_types); + } + } else { + throw py::value_error( + "Unrecognized expression type: " + + expr_types.builtins.attr("str")(py::type::of(pyomo_expr)) + .cast()); + } + return num_nodes; +} + +std::shared_ptr +appsi_expr_from_pyomo_expr(py::handle expr, py::handle var_map, + py::handle param_map, PyomoExprTypes &expr_types) { + std::shared_ptr node = + appsi_operator_from_pyomo_expr(expr, var_map, param_map, expr_types); + int num_nodes = + build_expression_tree(expr, node, var_map, param_map, expr_types); + if (num_nodes == 0) { + return std::dynamic_pointer_cast(node); + } else { + std::shared_ptr res = std::make_shared(num_nodes); + node->fill_expression(res->operators, num_nodes); + return res; + } +} + +std::vector> +appsi_exprs_from_pyomo_exprs(py::list expr_list, py::dict var_map, + py::dict param_map) { + PyomoExprTypes expr_types = PyomoExprTypes(); + int num_exprs = expr_types.builtins.attr("len")(expr_list).cast(); + std::vector> res(num_exprs); + + int ndx = 0; + for (py::handle expr : expr_list) { + res[ndx] = appsi_expr_from_pyomo_expr(expr, var_map, param_map, expr_types); + ndx += 1; + } + return res; +} + +void process_pyomo_vars(PyomoExprTypes &expr_types, py::list pyomo_vars, + py::dict var_map, py::dict param_map, + py::dict var_attrs, py::dict rev_var_map, + py::bool_ _set_name, py::handle symbol_map, + py::handle labeler, py::bool_ _update) { + py::tuple v_attrs; + std::shared_ptr cv; + py::handle v_lb; + py::handle v_ub; + py::handle v_val; + py::tuple domain_interval; + py::handle interval_lb; + py::handle interval_ub; + py::handle interval_step; + bool v_fixed; + bool set_name = _set_name.cast(); + bool update = _update.cast(); + double domain_step; + + for (py::handle v : pyomo_vars) { + v_attrs = var_attrs[expr_types.id(v)]; + v_lb = v_attrs[1]; + v_ub = v_attrs[2]; + v_fixed = v_attrs[3].cast(); + domain_interval = v_attrs[4]; + v_val = v_attrs[5]; + + interval_lb = domain_interval[0]; + interval_ub = domain_interval[1]; + interval_step = domain_interval[2]; + domain_step = interval_step.cast(); + + if (update) { + cv = var_map[expr_types.id(v)].cast>(); + } else { + cv = std::make_shared(); + } + + if (!(v_lb.is(py::none()))) { + cv->lb = appsi_expr_from_pyomo_expr(v_lb, var_map, param_map, expr_types); + } else { + cv->lb = std::make_shared(-inf); + } + if (!(v_ub.is(py::none()))) { + cv->ub = appsi_expr_from_pyomo_expr(v_ub, var_map, param_map, expr_types); + } else { + cv->ub = std::make_shared(inf); + } + + if (!(v_val.is(py::none()))) { + cv->value = v_val.cast(); + } + + if (v_fixed) { + cv->fixed = true; + } else { + cv->fixed = false; + } + + if (set_name && !update) { + cv->name = symbol_map.attr("getSymbol")(v, labeler).cast(); + } + + if (interval_lb.is(py::none())) + cv->domain_lb = -inf; + else + cv->domain_lb = interval_lb.cast(); + if (interval_ub.is(py::none())) + cv->domain_ub = inf; + else + cv->domain_ub = interval_ub.cast(); + if (domain_step == 0) + cv->domain = continuous; + else if (domain_step == 1) { + if ((cv->domain_lb == 0) && (cv->domain_ub == 1)) + cv->domain = binary; + else + cv->domain = integers; + } else + throw py::value_error("Unrecognized domain step"); + + if (!update) { + var_map[expr_types.id(v)] = py::cast(cv); + rev_var_map[py::cast(cv)] = v; + } + } +} diff --git a/pyomo/contrib/appsi/cmodel/src/expression.hpp b/pyomo/contrib/appsi/cmodel/src/expression.hpp index 9a991102a90..220f5f22b0d 100644 --- a/pyomo/contrib/appsi/cmodel/src/expression.hpp +++ b/pyomo/contrib/appsi/cmodel/src/expression.hpp @@ -1,788 +1,800 @@ -#ifndef EXPRESSION_HEADER -#define EXPRESSION_HEADER - -#include "interval.hpp" -#include - -class Node; -class ExpressionBase; -class Leaf; -class Var; -class Constant; -class Param; -class Expression; -class Operator; -class BinaryOperator; -class UnaryOperator; -class LinearOperator; -class SumOperator; -class MultiplyOperator; -class DivideOperator; -class PowerOperator; -class NegationOperator; -class ExpOperator; -class LogOperator; -class AbsOperator; -class ExternalOperator; -class PyomoExprTypes; - -extern double inf; - -class Node : public std::enable_shared_from_this { -public: - Node() = default; - virtual ~Node() = default; - virtual bool is_variable_type() { return false; } - virtual bool is_param_type() { return false; } - virtual bool is_expression_type() { return false; } - virtual bool is_operator_type() { return false; } - virtual bool is_constant_type() { return false; } - virtual bool is_leaf() { return false; } - virtual bool is_binary_operator() { return false; } - virtual bool is_unary_operator() { return false; } - virtual bool is_linear_operator() { return false; } - virtual bool is_sum_operator() { return false; } - virtual bool is_multiply_operator() { return false; } - virtual bool is_divide_operator() { return false; } - virtual bool is_power_operator() { return false; } - virtual bool is_negation_operator() { return false; } - virtual bool is_exp_operator() { return false; } - virtual bool is_log_operator() { return false; } - virtual bool is_abs_operator() { return false; } - virtual bool is_sqrt_operator() { return false; } - virtual bool is_external_operator() { return false; } - virtual double get_value_from_array(double *) = 0; - virtual int get_degree_from_array(int *) = 0; - virtual std::string get_string_from_array(std::string *) = 0; - virtual void fill_prefix_notation_stack( - std::shared_ptr>> stack) = 0; - virtual void write_nl_string(std::ofstream &) = 0; - virtual void fill_expression(std::shared_ptr *oper_array, - int &oper_ndx) = 0; - virtual double get_lb_from_array(double *lbs) = 0; - virtual double get_ub_from_array(double *ubs) = 0; - virtual void - set_bounds_in_array(double new_lb, double new_ub, double *lbs, double *ubs, - double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) = 0; -}; - -class ExpressionBase : public Node { -public: - ExpressionBase() = default; - virtual double evaluate() = 0; - virtual std::string __str__() = 0; - virtual std::shared_ptr>> - identify_variables() = 0; - virtual std::shared_ptr>> - identify_external_operators() = 0; - virtual std::shared_ptr>> - get_prefix_notation() = 0; - std::shared_ptr shared_from_this() { - return std::static_pointer_cast(Node::shared_from_this()); - } - void fill_prefix_notation_stack( - std::shared_ptr>> stack) override { - ; - } -}; - -class Leaf : public ExpressionBase { -public: - Leaf() = default; - Leaf(double value) : value(value) {} - virtual ~Leaf() = default; - double value = 0.0; - bool is_leaf() override; - double evaluate() override; - double get_value_from_array(double *) override; - std::string get_string_from_array(std::string *) override; - std::shared_ptr>> - get_prefix_notation() override; - void fill_expression(std::shared_ptr *oper_array, - int &oper_ndx) override; - double get_lb_from_array(double *lbs) override; - double get_ub_from_array(double *ubs) override; - void - set_bounds_in_array(double new_lb, double new_ub, double *lbs, double *ubs, - double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class Constant : public Leaf { -public: - Constant() = default; - Constant(double value) : Leaf(value) {} - bool is_constant_type() override; - std::string __str__() override; - int get_degree_from_array(int *) override; - std::shared_ptr>> - identify_variables() override; - std::shared_ptr>> - identify_external_operators() override; - void write_nl_string(std::ofstream &) override; -}; - -enum Domain { continuous, binary, integers }; - -class Var : public Leaf { -public: - Var() = default; - Var(double val) : Leaf(val) {} - Var(std::string _name) : name(_name) {} - Var(std::string _name, double val) : Leaf(val), name(_name) {} - std::string name = "v"; - std::string __str__() override; - std::shared_ptr lb; - std::shared_ptr ub; - int index = -1; - bool fixed = false; - double domain_lb = -inf; - double domain_ub = inf; - Domain domain = continuous; - bool is_variable_type() override; - int get_degree_from_array(int *) override; - std::shared_ptr>> - identify_variables() override; - std::shared_ptr>> - identify_external_operators() override; - void write_nl_string(std::ofstream &) override; - std::shared_ptr shared_from_this() { - return std::static_pointer_cast(Node::shared_from_this()); - } - double get_lb(); - double get_ub(); - Domain get_domain(); - double get_lb_from_array(double *lbs) override; - double get_ub_from_array(double *ubs) override; - void - set_bounds_in_array(double new_lb, double new_ub, double *lbs, double *ubs, - double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class Param : public Leaf { -public: - Param() = default; - Param(double val) : Leaf(val) {} - Param(std::string _name) : name(_name) {} - Param(std::string _name, double val) : Leaf(val), name(_name) {} - std::string name = "p"; - std::string __str__() override; - bool is_param_type() override; - int get_degree_from_array(int *) override; - std::shared_ptr>> - identify_variables() override; - std::shared_ptr>> - identify_external_operators() override; - void write_nl_string(std::ofstream &) override; -}; - -class Expression : public ExpressionBase { -public: - Expression(int _n_operators) : ExpressionBase() { - operators = new std::shared_ptr[_n_operators]; - n_operators = _n_operators; - } - ~Expression() { delete[] operators; } - std::string __str__() override; - bool is_expression_type() override; - double evaluate() override; - double get_value_from_array(double *) override; - int get_degree_from_array(int *) override; - std::shared_ptr>> - identify_variables() override; - std::shared_ptr>> - identify_external_operators() override; - std::string get_string_from_array(std::string *) override; - std::shared_ptr>> - get_prefix_notation() override; - void write_nl_string(std::ofstream &) override; - std::vector> get_operators(); - std::shared_ptr *operators; - unsigned int n_operators; - void fill_expression(std::shared_ptr *oper_array, - int &oper_ndx) override; - void propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, double integer_tol); - void propagate_bounds_backward(double *lbs, double *ubs, - double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars); - double get_lb_from_array(double *lbs) override; - double get_ub_from_array(double *ubs) override; - void - set_bounds_in_array(double new_lb, double new_ub, double *lbs, double *ubs, - double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class Operator : public Node { -public: - Operator() = default; - int index = 0; - virtual void evaluate(double *values) = 0; - virtual void propagate_degree_forward(int *degrees, double *values) = 0; - virtual void - identify_variables(std::set> &, - std::shared_ptr>>) = 0; - std::shared_ptr shared_from_this() { - return std::static_pointer_cast(Node::shared_from_this()); - } - bool is_operator_type() override; - double get_value_from_array(double *) override; - int get_degree_from_array(int *) override; - std::string get_string_from_array(std::string *) override; - virtual void print(std::string *) = 0; - virtual std::string name() = 0; - virtual void propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol); - virtual void - propagate_bounds_backward(double *lbs, double *ubs, double feasibility_tol, - double integer_tol, double improvement_tol, - std::set> &improved_vars); - double get_lb_from_array(double *lbs) override; - double get_ub_from_array(double *ubs) override; - void - set_bounds_in_array(double new_lb, double new_ub, double *lbs, double *ubs, - double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class BinaryOperator : public Operator { -public: - BinaryOperator() = default; - virtual ~BinaryOperator() = default; - void identify_variables( - std::set> &, - std::shared_ptr>>) override; - std::shared_ptr operand1; - std::shared_ptr operand2; - void fill_prefix_notation_stack( - std::shared_ptr>> stack) override; - bool is_binary_operator() override; - void fill_expression(std::shared_ptr *oper_array, - int &oper_ndx) override; -}; - -class UnaryOperator : public Operator { -public: - UnaryOperator() = default; - virtual ~UnaryOperator() = default; - void identify_variables( - std::set> &, - std::shared_ptr>>) override; - std::shared_ptr operand; - void fill_prefix_notation_stack( - std::shared_ptr>> stack) override; - bool is_unary_operator() override; - void propagate_degree_forward(int *degrees, double *values) override; - void fill_expression(std::shared_ptr *oper_array, - int &oper_ndx) override; -}; - -class LinearOperator : public Operator { -public: - LinearOperator(int _nterms) { - variables = new std::shared_ptr[_nterms]; - coefficients = new std::shared_ptr[_nterms]; - nterms = _nterms; - } - ~LinearOperator() { - delete[] variables; - delete[] coefficients; - } - void identify_variables( - std::set> &, - std::shared_ptr>>) override; - std::shared_ptr *variables; - std::shared_ptr *coefficients; - std::shared_ptr constant = std::make_shared(0); - void evaluate(double *values) override; - void propagate_degree_forward(int *degrees, double *values) override; - void print(std::string *) override; - std::string name() override { return "LinearOperator"; }; - void write_nl_string(std::ofstream &) override; - void fill_prefix_notation_stack( - std::shared_ptr>> stack) override; - bool is_linear_operator() override; - unsigned int nterms; - void fill_expression(std::shared_ptr *oper_array, - int &oper_ndx) override; - void propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) override; - void propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class SumOperator : public Operator { -public: - SumOperator(int _nargs) { - operands = new std::shared_ptr[_nargs]; - nargs = _nargs; - } - ~SumOperator() { delete[] operands; } - void identify_variables( - std::set> &, - std::shared_ptr>>) override; - void evaluate(double *values) override; - void propagate_degree_forward(int *degrees, double *values) override; - void print(std::string *) override; - std::string name() override { return "SumOperator"; }; - void write_nl_string(std::ofstream &) override; - void fill_prefix_notation_stack( - std::shared_ptr>> stack) override; - bool is_sum_operator() override; - std::shared_ptr *operands; - unsigned int nargs; - void fill_expression(std::shared_ptr *oper_array, - int &oper_ndx) override; - void propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) override; - void propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class MultiplyOperator : public BinaryOperator { -public: - MultiplyOperator() = default; - void evaluate(double *values) override; - void propagate_degree_forward(int *degrees, double *values) override; - void print(std::string *) override; - std::string name() override { return "MultiplyOperator"; }; - void write_nl_string(std::ofstream &) override; - bool is_multiply_operator() override; - void propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) override; - void propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class ExternalOperator : public Operator { -public: - ExternalOperator(int _nargs) { - operands = new std::shared_ptr[_nargs]; - nargs = _nargs; - } - ~ExternalOperator() { delete[] operands; } - void evaluate(double *values) override; - void propagate_degree_forward(int *degrees, double *values) override; - void print(std::string *) override; - std::string name() override { return "ExternalOperator"; }; - void write_nl_string(std::ofstream &) override; - void fill_prefix_notation_stack( - std::shared_ptr>> stack) override; - void identify_variables( - std::set> &, - std::shared_ptr>>) override; - bool is_external_operator() override; - std::string function_name; - int external_function_index = -1; - std::shared_ptr *operands; - unsigned int nargs; - void fill_expression(std::shared_ptr *oper_array, - int &oper_ndx) override; -}; - -class DivideOperator : public BinaryOperator { -public: - DivideOperator() = default; - void evaluate(double *values) override; - void propagate_degree_forward(int *degrees, double *values) override; - void print(std::string *) override; - std::string name() override { return "DivideOperator"; }; - void write_nl_string(std::ofstream &) override; - bool is_divide_operator() override; - void propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) override; - void propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class PowerOperator : public BinaryOperator { -public: - PowerOperator() = default; - void evaluate(double *values) override; - void propagate_degree_forward(int *degrees, double *values) override; - void print(std::string *) override; - std::string name() override { return "PowerOperator"; }; - void write_nl_string(std::ofstream &) override; - bool is_power_operator() override; - void propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) override; - void propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class NegationOperator : public UnaryOperator { -public: - NegationOperator() = default; - void evaluate(double *values) override; - void propagate_degree_forward(int *degrees, double *values) override; - void print(std::string *) override; - std::string name() override { return "NegationOperator"; }; - void write_nl_string(std::ofstream &) override; - bool is_negation_operator() override; - void propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) override; - void propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class ExpOperator : public UnaryOperator { -public: - ExpOperator() = default; - void evaluate(double *values) override; - void print(std::string *) override; - std::string name() override { return "ExpOperator"; }; - void write_nl_string(std::ofstream &) override; - bool is_exp_operator() override; - void propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) override; - void propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class LogOperator : public UnaryOperator { -public: - LogOperator() = default; - void evaluate(double *values) override; - void print(std::string *) override; - std::string name() override { return "LogOperator"; }; - void write_nl_string(std::ofstream &) override; - bool is_log_operator() override; - void propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) override; - void propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class AbsOperator : public UnaryOperator { -public: - AbsOperator() = default; - void evaluate(double *values) override; - void print(std::string *) override; - std::string name() override { return "AbsOperator"; }; - void write_nl_string(std::ofstream &) override; - bool is_abs_operator() override; - void propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) override; - void propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class SqrtOperator : public UnaryOperator { -public: - SqrtOperator() = default; - void evaluate(double *values) override; - void print(std::string *) override; - std::string name() override { return "SqrtOperator"; }; - void write_nl_string(std::ofstream &) override; - bool is_sqrt_operator() override; - void propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) override; - void propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class Log10Operator : public UnaryOperator { -public: - Log10Operator() = default; - void evaluate(double *values) override; - void print(std::string *) override; - std::string name() override { return "Log10Operator"; }; - void write_nl_string(std::ofstream &) override; - void propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) override; - void propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class SinOperator : public UnaryOperator { -public: - SinOperator() = default; - void evaluate(double *values) override; - void print(std::string *) override; - std::string name() override { return "SinOperator"; }; - void write_nl_string(std::ofstream &) override; - void propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) override; - void propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class CosOperator : public UnaryOperator { -public: - CosOperator() = default; - void evaluate(double *values) override; - void print(std::string *) override; - std::string name() override { return "CosOperator"; }; - void write_nl_string(std::ofstream &) override; - void propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) override; - void propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class TanOperator : public UnaryOperator { -public: - TanOperator() = default; - void evaluate(double *values) override; - void print(std::string *) override; - std::string name() override { return "TanOperator"; }; - void write_nl_string(std::ofstream &) override; - void propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) override; - void propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class AsinOperator : public UnaryOperator { -public: - AsinOperator() = default; - void evaluate(double *values) override; - void print(std::string *) override; - std::string name() override { return "AsinOperator"; }; - void write_nl_string(std::ofstream &) override; - void propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) override; - void propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class AcosOperator : public UnaryOperator { -public: - AcosOperator() = default; - void evaluate(double *values) override; - void print(std::string *) override; - std::string name() override { return "AcosOperator"; }; - void write_nl_string(std::ofstream &) override; - void propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) override; - void propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -class AtanOperator : public UnaryOperator { -public: - AtanOperator() = default; - void evaluate(double *values) override; - void print(std::string *) override; - std::string name() override { return "AtanOperator"; }; - void write_nl_string(std::ofstream &) override; - void propagate_bounds_forward(double *lbs, double *ubs, - double feasibility_tol, - double integer_tol) override; - void propagate_bounds_backward( - double *lbs, double *ubs, double feasibility_tol, double integer_tol, - double improvement_tol, - std::set> &improved_vars) override; -}; - -enum ExprType { - py_float = 0, - var = 1, - param = 2, - product = 3, - sum = 4, - negation = 5, - external_func = 6, - power = 7, - division = 8, - unary_func = 9, - linear = 10, - named_expr = 11, - numeric_constant = 12, - pyomo_unit = 13, - unary_abs = 14 -}; - -class PyomoExprTypes { -public: - PyomoExprTypes() { - expr_type_map[int_] = py_float; - expr_type_map[float_] = py_float; - expr_type_map[np_int16] = py_float; - expr_type_map[np_int32] = py_float; - expr_type_map[np_int64] = py_float; - expr_type_map[np_longlong] = py_float; - expr_type_map[np_uint16] = py_float; - expr_type_map[np_uint32] = py_float; - expr_type_map[np_uint64] = py_float; - expr_type_map[np_ulonglong] = py_float; - expr_type_map[np_float16] = py_float; - expr_type_map[np_float32] = py_float; - expr_type_map[np_float64] = py_float; - expr_type_map[ScalarVar] = var; - expr_type_map[_GeneralVarData] = var; - expr_type_map[AutoLinkedBinaryVar] = var; - expr_type_map[ScalarParam] = param; - expr_type_map[_ParamData] = param; - expr_type_map[MonomialTermExpression] = product; - expr_type_map[ProductExpression] = product; - expr_type_map[NPV_ProductExpression] = product; - expr_type_map[SumExpression] = sum; - expr_type_map[NPV_SumExpression] = sum; - expr_type_map[NegationExpression] = negation; - expr_type_map[NPV_NegationExpression] = negation; - expr_type_map[ExternalFunctionExpression] = external_func; - expr_type_map[NPV_ExternalFunctionExpression] = external_func; - expr_type_map[PowExpression] = power; - expr_type_map[NPV_PowExpression] = power; - expr_type_map[DivisionExpression] = division; - expr_type_map[NPV_DivisionExpression] = division; - expr_type_map[UnaryFunctionExpression] = unary_func; - expr_type_map[NPV_UnaryFunctionExpression] = unary_func; - expr_type_map[LinearExpression] = linear; - expr_type_map[_GeneralExpressionData] = named_expr; - expr_type_map[ScalarExpression] = named_expr; - expr_type_map[Integral] = named_expr; - expr_type_map[ScalarIntegral] = named_expr; - expr_type_map[NumericConstant] = numeric_constant; - expr_type_map[_PyomoUnit] = pyomo_unit; - expr_type_map[AbsExpression] = unary_abs; - expr_type_map[NPV_AbsExpression] = unary_abs; - } - ~PyomoExprTypes() = default; - py::int_ ione = 1; - py::float_ fone = 1.0; - py::type int_ = py::type::of(ione); - py::type float_ = py::type::of(fone); - py::object np = py::module_::import("numpy"); - py::type np_int16 = np.attr("int16"); - py::type np_int32 = np.attr("int32"); - py::type np_int64 = np.attr("int64"); - py::type np_longlong = np.attr("longlong"); - py::type np_uint16 = np.attr("uint16"); - py::type np_uint32 = np.attr("uint32"); - py::type np_uint64 = np.attr("uint64"); - py::type np_ulonglong = np.attr("ulonglong"); - py::type np_float16 = np.attr("float16"); - py::type np_float32 = np.attr("float32"); - py::type np_float64 = np.attr("float64"); - py::object ScalarParam = - py::module_::import("pyomo.core.base.param").attr("ScalarParam"); - py::object _ParamData = - py::module_::import("pyomo.core.base.param").attr("_ParamData"); - py::object ScalarVar = - py::module_::import("pyomo.core.base.var").attr("ScalarVar"); - py::object _GeneralVarData = - py::module_::import("pyomo.core.base.var").attr("_GeneralVarData"); - py::object AutoLinkedBinaryVar = - py::module_::import("pyomo.gdp.disjunct").attr("AutoLinkedBinaryVar"); - py::object numeric_expr = py::module_::import("pyomo.core.expr.numeric_expr"); - py::object NegationExpression = numeric_expr.attr("NegationExpression"); - py::object NPV_NegationExpression = - numeric_expr.attr("NPV_NegationExpression"); - py::object ExternalFunctionExpression = - numeric_expr.attr("ExternalFunctionExpression"); - py::object NPV_ExternalFunctionExpression = - numeric_expr.attr("NPV_ExternalFunctionExpression"); - py::object PowExpression = numeric_expr.attr("PowExpression"); - py::object NPV_PowExpression = numeric_expr.attr("NPV_PowExpression"); - py::object ProductExpression = numeric_expr.attr("ProductExpression"); - py::object NPV_ProductExpression = numeric_expr.attr("NPV_ProductExpression"); - py::object MonomialTermExpression = - numeric_expr.attr("MonomialTermExpression"); - py::object DivisionExpression = numeric_expr.attr("DivisionExpression"); - py::object NPV_DivisionExpression = - numeric_expr.attr("NPV_DivisionExpression"); - py::object SumExpression = numeric_expr.attr("SumExpression"); - py::object NPV_SumExpression = numeric_expr.attr("NPV_SumExpression"); - py::object UnaryFunctionExpression = - numeric_expr.attr("UnaryFunctionExpression"); - py::object AbsExpression = numeric_expr.attr("AbsExpression"); - py::object NPV_AbsExpression = numeric_expr.attr("NPV_AbsExpression"); - py::object NPV_UnaryFunctionExpression = - numeric_expr.attr("NPV_UnaryFunctionExpression"); - py::object LinearExpression = numeric_expr.attr("LinearExpression"); - py::object NumericConstant = - py::module_::import("pyomo.core.expr.numvalue").attr("NumericConstant"); - py::object expr_module = py::module_::import("pyomo.core.base.expression"); - py::object _GeneralExpressionData = - expr_module.attr("_GeneralExpressionData"); - py::object ScalarExpression = expr_module.attr("ScalarExpression"); - py::object ScalarIntegral = - py::module_::import("pyomo.dae.integral").attr("ScalarIntegral"); - py::object Integral = - py::module_::import("pyomo.dae.integral").attr("Integral"); - py::object _PyomoUnit = - py::module_::import("pyomo.core.base.units_container").attr("_PyomoUnit"); - py::object builtins = py::module_::import("builtins"); - py::object id = builtins.attr("id"); - py::object len = builtins.attr("len"); - py::dict expr_type_map; -}; - -std::vector> create_vars(int n_vars); -std::vector> create_params(int n_params); -std::vector> create_constants(int n_constants); -std::shared_ptr -appsi_expr_from_pyomo_expr(py::handle expr, py::handle var_map, - py::handle param_map, PyomoExprTypes &expr_types); -std::vector> -appsi_exprs_from_pyomo_exprs(py::list expr_list, py::dict var_map, - py::dict param_map); -py::tuple prep_for_repn(py::handle expr, PyomoExprTypes &expr_types); - -void process_pyomo_vars(PyomoExprTypes &expr_types, py::list pyomo_vars, - py::dict var_map, py::dict param_map, - py::dict var_attrs, py::dict rev_var_map, - py::bool_ _set_name, py::handle symbol_map, - py::handle labeler, py::bool_ _update); - -#endif +/**___________________________________________________________________________ + * + * 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. + * ___________________________________________________________________________ +**/ + +#ifndef EXPRESSION_HEADER +#define EXPRESSION_HEADER + +#include "interval.hpp" +#include + +class Node; +class ExpressionBase; +class Leaf; +class Var; +class Constant; +class Param; +class Expression; +class Operator; +class BinaryOperator; +class UnaryOperator; +class LinearOperator; +class SumOperator; +class MultiplyOperator; +class DivideOperator; +class PowerOperator; +class NegationOperator; +class ExpOperator; +class LogOperator; +class AbsOperator; +class ExternalOperator; +class PyomoExprTypes; + +extern double inf; + +class Node : public std::enable_shared_from_this { +public: + Node() = default; + virtual ~Node() = default; + virtual bool is_variable_type() { return false; } + virtual bool is_param_type() { return false; } + virtual bool is_expression_type() { return false; } + virtual bool is_operator_type() { return false; } + virtual bool is_constant_type() { return false; } + virtual bool is_leaf() { return false; } + virtual bool is_binary_operator() { return false; } + virtual bool is_unary_operator() { return false; } + virtual bool is_linear_operator() { return false; } + virtual bool is_sum_operator() { return false; } + virtual bool is_multiply_operator() { return false; } + virtual bool is_divide_operator() { return false; } + virtual bool is_power_operator() { return false; } + virtual bool is_negation_operator() { return false; } + virtual bool is_exp_operator() { return false; } + virtual bool is_log_operator() { return false; } + virtual bool is_abs_operator() { return false; } + virtual bool is_sqrt_operator() { return false; } + virtual bool is_external_operator() { return false; } + virtual double get_value_from_array(double *) = 0; + virtual int get_degree_from_array(int *) = 0; + virtual std::string get_string_from_array(std::string *) = 0; + virtual void fill_prefix_notation_stack( + std::shared_ptr>> stack) = 0; + virtual void write_nl_string(std::ofstream &) = 0; + virtual void fill_expression(std::shared_ptr *oper_array, + int &oper_ndx) = 0; + virtual double get_lb_from_array(double *lbs) = 0; + virtual double get_ub_from_array(double *ubs) = 0; + virtual void + set_bounds_in_array(double new_lb, double new_ub, double *lbs, double *ubs, + double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) = 0; +}; + +class ExpressionBase : public Node { +public: + ExpressionBase() = default; + virtual double evaluate() = 0; + virtual std::string __str__() = 0; + virtual std::shared_ptr>> + identify_variables() = 0; + virtual std::shared_ptr>> + identify_external_operators() = 0; + virtual std::shared_ptr>> + get_prefix_notation() = 0; + std::shared_ptr shared_from_this() { + return std::static_pointer_cast(Node::shared_from_this()); + } + void fill_prefix_notation_stack( + std::shared_ptr>> stack) override { + ; + } +}; + +class Leaf : public ExpressionBase { +public: + Leaf() = default; + Leaf(double value) : value(value) {} + virtual ~Leaf() = default; + double value = 0.0; + bool is_leaf() override; + double evaluate() override; + double get_value_from_array(double *) override; + std::string get_string_from_array(std::string *) override; + std::shared_ptr>> + get_prefix_notation() override; + void fill_expression(std::shared_ptr *oper_array, + int &oper_ndx) override; + double get_lb_from_array(double *lbs) override; + double get_ub_from_array(double *ubs) override; + void + set_bounds_in_array(double new_lb, double new_ub, double *lbs, double *ubs, + double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class Constant : public Leaf { +public: + Constant() = default; + Constant(double value) : Leaf(value) {} + bool is_constant_type() override; + std::string __str__() override; + int get_degree_from_array(int *) override; + std::shared_ptr>> + identify_variables() override; + std::shared_ptr>> + identify_external_operators() override; + void write_nl_string(std::ofstream &) override; +}; + +enum Domain { continuous, binary, integers }; + +class Var : public Leaf { +public: + Var() = default; + Var(double val) : Leaf(val) {} + Var(std::string _name) : name(_name) {} + Var(std::string _name, double val) : Leaf(val), name(_name) {} + std::string name = "v"; + std::string __str__() override; + std::shared_ptr lb; + std::shared_ptr ub; + int index = -1; + bool fixed = false; + double domain_lb = -inf; + double domain_ub = inf; + Domain domain = continuous; + bool is_variable_type() override; + int get_degree_from_array(int *) override; + std::shared_ptr>> + identify_variables() override; + std::shared_ptr>> + identify_external_operators() override; + void write_nl_string(std::ofstream &) override; + std::shared_ptr shared_from_this() { + return std::static_pointer_cast(Node::shared_from_this()); + } + double get_lb(); + double get_ub(); + Domain get_domain(); + double get_lb_from_array(double *lbs) override; + double get_ub_from_array(double *ubs) override; + void + set_bounds_in_array(double new_lb, double new_ub, double *lbs, double *ubs, + double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class Param : public Leaf { +public: + Param() = default; + Param(double val) : Leaf(val) {} + Param(std::string _name) : name(_name) {} + Param(std::string _name, double val) : Leaf(val), name(_name) {} + std::string name = "p"; + std::string __str__() override; + bool is_param_type() override; + int get_degree_from_array(int *) override; + std::shared_ptr>> + identify_variables() override; + std::shared_ptr>> + identify_external_operators() override; + void write_nl_string(std::ofstream &) override; +}; + +class Expression : public ExpressionBase { +public: + Expression(int _n_operators) : ExpressionBase() { + operators = new std::shared_ptr[_n_operators]; + n_operators = _n_operators; + } + ~Expression() { delete[] operators; } + std::string __str__() override; + bool is_expression_type() override; + double evaluate() override; + double get_value_from_array(double *) override; + int get_degree_from_array(int *) override; + std::shared_ptr>> + identify_variables() override; + std::shared_ptr>> + identify_external_operators() override; + std::string get_string_from_array(std::string *) override; + std::shared_ptr>> + get_prefix_notation() override; + void write_nl_string(std::ofstream &) override; + std::vector> get_operators(); + std::shared_ptr *operators; + unsigned int n_operators; + void fill_expression(std::shared_ptr *oper_array, + int &oper_ndx) override; + void propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, double integer_tol); + void propagate_bounds_backward(double *lbs, double *ubs, + double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars); + double get_lb_from_array(double *lbs) override; + double get_ub_from_array(double *ubs) override; + void + set_bounds_in_array(double new_lb, double new_ub, double *lbs, double *ubs, + double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class Operator : public Node { +public: + Operator() = default; + int index = 0; + virtual void evaluate(double *values) = 0; + virtual void propagate_degree_forward(int *degrees, double *values) = 0; + virtual void + identify_variables(std::set> &, + std::shared_ptr>>) = 0; + std::shared_ptr shared_from_this() { + return std::static_pointer_cast(Node::shared_from_this()); + } + bool is_operator_type() override; + double get_value_from_array(double *) override; + int get_degree_from_array(int *) override; + std::string get_string_from_array(std::string *) override; + virtual void print(std::string *) = 0; + virtual std::string name() = 0; + virtual void propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol); + virtual void + propagate_bounds_backward(double *lbs, double *ubs, double feasibility_tol, + double integer_tol, double improvement_tol, + std::set> &improved_vars); + double get_lb_from_array(double *lbs) override; + double get_ub_from_array(double *ubs) override; + void + set_bounds_in_array(double new_lb, double new_ub, double *lbs, double *ubs, + double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class BinaryOperator : public Operator { +public: + BinaryOperator() = default; + virtual ~BinaryOperator() = default; + void identify_variables( + std::set> &, + std::shared_ptr>>) override; + std::shared_ptr operand1; + std::shared_ptr operand2; + void fill_prefix_notation_stack( + std::shared_ptr>> stack) override; + bool is_binary_operator() override; + void fill_expression(std::shared_ptr *oper_array, + int &oper_ndx) override; +}; + +class UnaryOperator : public Operator { +public: + UnaryOperator() = default; + virtual ~UnaryOperator() = default; + void identify_variables( + std::set> &, + std::shared_ptr>>) override; + std::shared_ptr operand; + void fill_prefix_notation_stack( + std::shared_ptr>> stack) override; + bool is_unary_operator() override; + void propagate_degree_forward(int *degrees, double *values) override; + void fill_expression(std::shared_ptr *oper_array, + int &oper_ndx) override; +}; + +class LinearOperator : public Operator { +public: + LinearOperator(int _nterms) { + variables = new std::shared_ptr[_nterms]; + coefficients = new std::shared_ptr[_nterms]; + nterms = _nterms; + } + ~LinearOperator() { + delete[] variables; + delete[] coefficients; + } + void identify_variables( + std::set> &, + std::shared_ptr>>) override; + std::shared_ptr *variables; + std::shared_ptr *coefficients; + std::shared_ptr constant = std::make_shared(0); + void evaluate(double *values) override; + void propagate_degree_forward(int *degrees, double *values) override; + void print(std::string *) override; + std::string name() override { return "LinearOperator"; }; + void write_nl_string(std::ofstream &) override; + void fill_prefix_notation_stack( + std::shared_ptr>> stack) override; + bool is_linear_operator() override; + unsigned int nterms; + void fill_expression(std::shared_ptr *oper_array, + int &oper_ndx) override; + void propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) override; + void propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class SumOperator : public Operator { +public: + SumOperator(int _nargs) { + operands = new std::shared_ptr[_nargs]; + nargs = _nargs; + } + ~SumOperator() { delete[] operands; } + void identify_variables( + std::set> &, + std::shared_ptr>>) override; + void evaluate(double *values) override; + void propagate_degree_forward(int *degrees, double *values) override; + void print(std::string *) override; + std::string name() override { return "SumOperator"; }; + void write_nl_string(std::ofstream &) override; + void fill_prefix_notation_stack( + std::shared_ptr>> stack) override; + bool is_sum_operator() override; + std::shared_ptr *operands; + unsigned int nargs; + void fill_expression(std::shared_ptr *oper_array, + int &oper_ndx) override; + void propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) override; + void propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class MultiplyOperator : public BinaryOperator { +public: + MultiplyOperator() = default; + void evaluate(double *values) override; + void propagate_degree_forward(int *degrees, double *values) override; + void print(std::string *) override; + std::string name() override { return "MultiplyOperator"; }; + void write_nl_string(std::ofstream &) override; + bool is_multiply_operator() override; + void propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) override; + void propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class ExternalOperator : public Operator { +public: + ExternalOperator(int _nargs) { + operands = new std::shared_ptr[_nargs]; + nargs = _nargs; + } + ~ExternalOperator() { delete[] operands; } + void evaluate(double *values) override; + void propagate_degree_forward(int *degrees, double *values) override; + void print(std::string *) override; + std::string name() override { return "ExternalOperator"; }; + void write_nl_string(std::ofstream &) override; + void fill_prefix_notation_stack( + std::shared_ptr>> stack) override; + void identify_variables( + std::set> &, + std::shared_ptr>>) override; + bool is_external_operator() override; + std::string function_name; + int external_function_index = -1; + std::shared_ptr *operands; + unsigned int nargs; + void fill_expression(std::shared_ptr *oper_array, + int &oper_ndx) override; +}; + +class DivideOperator : public BinaryOperator { +public: + DivideOperator() = default; + void evaluate(double *values) override; + void propagate_degree_forward(int *degrees, double *values) override; + void print(std::string *) override; + std::string name() override { return "DivideOperator"; }; + void write_nl_string(std::ofstream &) override; + bool is_divide_operator() override; + void propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) override; + void propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class PowerOperator : public BinaryOperator { +public: + PowerOperator() = default; + void evaluate(double *values) override; + void propagate_degree_forward(int *degrees, double *values) override; + void print(std::string *) override; + std::string name() override { return "PowerOperator"; }; + void write_nl_string(std::ofstream &) override; + bool is_power_operator() override; + void propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) override; + void propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class NegationOperator : public UnaryOperator { +public: + NegationOperator() = default; + void evaluate(double *values) override; + void propagate_degree_forward(int *degrees, double *values) override; + void print(std::string *) override; + std::string name() override { return "NegationOperator"; }; + void write_nl_string(std::ofstream &) override; + bool is_negation_operator() override; + void propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) override; + void propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class ExpOperator : public UnaryOperator { +public: + ExpOperator() = default; + void evaluate(double *values) override; + void print(std::string *) override; + std::string name() override { return "ExpOperator"; }; + void write_nl_string(std::ofstream &) override; + bool is_exp_operator() override; + void propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) override; + void propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class LogOperator : public UnaryOperator { +public: + LogOperator() = default; + void evaluate(double *values) override; + void print(std::string *) override; + std::string name() override { return "LogOperator"; }; + void write_nl_string(std::ofstream &) override; + bool is_log_operator() override; + void propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) override; + void propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class AbsOperator : public UnaryOperator { +public: + AbsOperator() = default; + void evaluate(double *values) override; + void print(std::string *) override; + std::string name() override { return "AbsOperator"; }; + void write_nl_string(std::ofstream &) override; + bool is_abs_operator() override; + void propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) override; + void propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class SqrtOperator : public UnaryOperator { +public: + SqrtOperator() = default; + void evaluate(double *values) override; + void print(std::string *) override; + std::string name() override { return "SqrtOperator"; }; + void write_nl_string(std::ofstream &) override; + bool is_sqrt_operator() override; + void propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) override; + void propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class Log10Operator : public UnaryOperator { +public: + Log10Operator() = default; + void evaluate(double *values) override; + void print(std::string *) override; + std::string name() override { return "Log10Operator"; }; + void write_nl_string(std::ofstream &) override; + void propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) override; + void propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class SinOperator : public UnaryOperator { +public: + SinOperator() = default; + void evaluate(double *values) override; + void print(std::string *) override; + std::string name() override { return "SinOperator"; }; + void write_nl_string(std::ofstream &) override; + void propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) override; + void propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class CosOperator : public UnaryOperator { +public: + CosOperator() = default; + void evaluate(double *values) override; + void print(std::string *) override; + std::string name() override { return "CosOperator"; }; + void write_nl_string(std::ofstream &) override; + void propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) override; + void propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class TanOperator : public UnaryOperator { +public: + TanOperator() = default; + void evaluate(double *values) override; + void print(std::string *) override; + std::string name() override { return "TanOperator"; }; + void write_nl_string(std::ofstream &) override; + void propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) override; + void propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class AsinOperator : public UnaryOperator { +public: + AsinOperator() = default; + void evaluate(double *values) override; + void print(std::string *) override; + std::string name() override { return "AsinOperator"; }; + void write_nl_string(std::ofstream &) override; + void propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) override; + void propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class AcosOperator : public UnaryOperator { +public: + AcosOperator() = default; + void evaluate(double *values) override; + void print(std::string *) override; + std::string name() override { return "AcosOperator"; }; + void write_nl_string(std::ofstream &) override; + void propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) override; + void propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +class AtanOperator : public UnaryOperator { +public: + AtanOperator() = default; + void evaluate(double *values) override; + void print(std::string *) override; + std::string name() override { return "AtanOperator"; }; + void write_nl_string(std::ofstream &) override; + void propagate_bounds_forward(double *lbs, double *ubs, + double feasibility_tol, + double integer_tol) override; + void propagate_bounds_backward( + double *lbs, double *ubs, double feasibility_tol, double integer_tol, + double improvement_tol, + std::set> &improved_vars) override; +}; + +enum ExprType { + py_float = 0, + var = 1, + param = 2, + product = 3, + sum = 4, + negation = 5, + external_func = 6, + power = 7, + division = 8, + unary_func = 9, + linear = 10, + named_expr = 11, + numeric_constant = 12, + pyomo_unit = 13, + unary_abs = 14 +}; + +class PyomoExprTypes { +public: + PyomoExprTypes() { + expr_type_map[int_] = py_float; + expr_type_map[float_] = py_float; + expr_type_map[np_int16] = py_float; + expr_type_map[np_int32] = py_float; + expr_type_map[np_int64] = py_float; + expr_type_map[np_longlong] = py_float; + expr_type_map[np_uint16] = py_float; + expr_type_map[np_uint32] = py_float; + expr_type_map[np_uint64] = py_float; + expr_type_map[np_ulonglong] = py_float; + expr_type_map[np_float16] = py_float; + expr_type_map[np_float32] = py_float; + expr_type_map[np_float64] = py_float; + expr_type_map[ScalarVar] = var; + expr_type_map[_GeneralVarData] = var; + expr_type_map[AutoLinkedBinaryVar] = var; + expr_type_map[ScalarParam] = param; + expr_type_map[_ParamData] = param; + expr_type_map[MonomialTermExpression] = product; + expr_type_map[ProductExpression] = product; + expr_type_map[NPV_ProductExpression] = product; + expr_type_map[SumExpression] = sum; + expr_type_map[NPV_SumExpression] = sum; + expr_type_map[NegationExpression] = negation; + expr_type_map[NPV_NegationExpression] = negation; + expr_type_map[ExternalFunctionExpression] = external_func; + expr_type_map[NPV_ExternalFunctionExpression] = external_func; + expr_type_map[PowExpression] = power; + expr_type_map[NPV_PowExpression] = power; + expr_type_map[DivisionExpression] = division; + expr_type_map[NPV_DivisionExpression] = division; + expr_type_map[UnaryFunctionExpression] = unary_func; + expr_type_map[NPV_UnaryFunctionExpression] = unary_func; + expr_type_map[LinearExpression] = linear; + expr_type_map[_GeneralExpressionData] = named_expr; + expr_type_map[ScalarExpression] = named_expr; + expr_type_map[Integral] = named_expr; + expr_type_map[ScalarIntegral] = named_expr; + expr_type_map[NumericConstant] = numeric_constant; + expr_type_map[_PyomoUnit] = pyomo_unit; + expr_type_map[AbsExpression] = unary_abs; + expr_type_map[NPV_AbsExpression] = unary_abs; + } + ~PyomoExprTypes() = default; + py::int_ ione = 1; + py::float_ fone = 1.0; + py::type int_ = py::type::of(ione); + py::type float_ = py::type::of(fone); + py::object np = py::module_::import("numpy"); + py::type np_int16 = np.attr("int16"); + py::type np_int32 = np.attr("int32"); + py::type np_int64 = np.attr("int64"); + py::type np_longlong = np.attr("longlong"); + py::type np_uint16 = np.attr("uint16"); + py::type np_uint32 = np.attr("uint32"); + py::type np_uint64 = np.attr("uint64"); + py::type np_ulonglong = np.attr("ulonglong"); + py::type np_float16 = np.attr("float16"); + py::type np_float32 = np.attr("float32"); + py::type np_float64 = np.attr("float64"); + py::object ScalarParam = + py::module_::import("pyomo.core.base.param").attr("ScalarParam"); + py::object _ParamData = + py::module_::import("pyomo.core.base.param").attr("_ParamData"); + py::object ScalarVar = + py::module_::import("pyomo.core.base.var").attr("ScalarVar"); + py::object _GeneralVarData = + py::module_::import("pyomo.core.base.var").attr("_GeneralVarData"); + py::object AutoLinkedBinaryVar = + py::module_::import("pyomo.gdp.disjunct").attr("AutoLinkedBinaryVar"); + py::object numeric_expr = py::module_::import("pyomo.core.expr.numeric_expr"); + py::object NegationExpression = numeric_expr.attr("NegationExpression"); + py::object NPV_NegationExpression = + numeric_expr.attr("NPV_NegationExpression"); + py::object ExternalFunctionExpression = + numeric_expr.attr("ExternalFunctionExpression"); + py::object NPV_ExternalFunctionExpression = + numeric_expr.attr("NPV_ExternalFunctionExpression"); + py::object PowExpression = numeric_expr.attr("PowExpression"); + py::object NPV_PowExpression = numeric_expr.attr("NPV_PowExpression"); + py::object ProductExpression = numeric_expr.attr("ProductExpression"); + py::object NPV_ProductExpression = numeric_expr.attr("NPV_ProductExpression"); + py::object MonomialTermExpression = + numeric_expr.attr("MonomialTermExpression"); + py::object DivisionExpression = numeric_expr.attr("DivisionExpression"); + py::object NPV_DivisionExpression = + numeric_expr.attr("NPV_DivisionExpression"); + py::object SumExpression = numeric_expr.attr("SumExpression"); + py::object NPV_SumExpression = numeric_expr.attr("NPV_SumExpression"); + py::object UnaryFunctionExpression = + numeric_expr.attr("UnaryFunctionExpression"); + py::object AbsExpression = numeric_expr.attr("AbsExpression"); + py::object NPV_AbsExpression = numeric_expr.attr("NPV_AbsExpression"); + py::object NPV_UnaryFunctionExpression = + numeric_expr.attr("NPV_UnaryFunctionExpression"); + py::object LinearExpression = numeric_expr.attr("LinearExpression"); + py::object NumericConstant = + py::module_::import("pyomo.core.expr.numvalue").attr("NumericConstant"); + py::object expr_module = py::module_::import("pyomo.core.base.expression"); + py::object _GeneralExpressionData = + expr_module.attr("_GeneralExpressionData"); + py::object ScalarExpression = expr_module.attr("ScalarExpression"); + py::object ScalarIntegral = + py::module_::import("pyomo.dae.integral").attr("ScalarIntegral"); + py::object Integral = + py::module_::import("pyomo.dae.integral").attr("Integral"); + py::object _PyomoUnit = + py::module_::import("pyomo.core.base.units_container").attr("_PyomoUnit"); + py::object builtins = py::module_::import("builtins"); + py::object id = builtins.attr("id"); + py::object len = builtins.attr("len"); + py::dict expr_type_map; +}; + +std::vector> create_vars(int n_vars); +std::vector> create_params(int n_params); +std::vector> create_constants(int n_constants); +std::shared_ptr +appsi_expr_from_pyomo_expr(py::handle expr, py::handle var_map, + py::handle param_map, PyomoExprTypes &expr_types); +std::vector> +appsi_exprs_from_pyomo_exprs(py::list expr_list, py::dict var_map, + py::dict param_map); +py::tuple prep_for_repn(py::handle expr, PyomoExprTypes &expr_types); + +void process_pyomo_vars(PyomoExprTypes &expr_types, py::list pyomo_vars, + py::dict var_map, py::dict param_map, + py::dict var_attrs, py::dict rev_var_map, + py::bool_ _set_name, py::handle symbol_map, + py::handle labeler, py::bool_ _update); + +#endif diff --git a/pyomo/contrib/appsi/cmodel/src/fbbt_model.cpp b/pyomo/contrib/appsi/cmodel/src/fbbt_model.cpp index 2e490659fab..68efa7d9c26 100644 --- a/pyomo/contrib/appsi/cmodel/src/fbbt_model.cpp +++ b/pyomo/contrib/appsi/cmodel/src/fbbt_model.cpp @@ -1,3 +1,15 @@ +/**___________________________________________________________________________ + * + * 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. + * ___________________________________________________________________________ +**/ + #include "fbbt_model.hpp" FBBTObjective::FBBTObjective(std::shared_ptr _expr) diff --git a/pyomo/contrib/appsi/cmodel/src/fbbt_model.hpp b/pyomo/contrib/appsi/cmodel/src/fbbt_model.hpp index 3d1c3a76caa..032ff8c2616 100644 --- a/pyomo/contrib/appsi/cmodel/src/fbbt_model.hpp +++ b/pyomo/contrib/appsi/cmodel/src/fbbt_model.hpp @@ -1,3 +1,15 @@ +/**___________________________________________________________________________ + * + * 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. + * ___________________________________________________________________________ +**/ + #include "model_base.hpp" class FBBTConstraint; diff --git a/pyomo/contrib/appsi/cmodel/src/interval.cpp b/pyomo/contrib/appsi/cmodel/src/interval.cpp index f0a1aa2c2bb..a9f26704825 100644 --- a/pyomo/contrib/appsi/cmodel/src/interval.cpp +++ b/pyomo/contrib/appsi/cmodel/src/interval.cpp @@ -1,3 +1,15 @@ +/**___________________________________________________________________________ + * + * 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. + * ___________________________________________________________________________ +**/ + #include "interval.hpp" bool _is_inf(double x) { diff --git a/pyomo/contrib/appsi/cmodel/src/interval.hpp b/pyomo/contrib/appsi/cmodel/src/interval.hpp index c35438887dd..0f3a2a9a816 100644 --- a/pyomo/contrib/appsi/cmodel/src/interval.hpp +++ b/pyomo/contrib/appsi/cmodel/src/interval.hpp @@ -1,3 +1,15 @@ +/**___________________________________________________________________________ + * + * 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. + * ___________________________________________________________________________ +**/ + #ifndef INTERVAL_HEADER #define INTERVAL_HEADER diff --git a/pyomo/contrib/appsi/cmodel/src/lp_writer.cpp b/pyomo/contrib/appsi/cmodel/src/lp_writer.cpp index 1ce421b7c97..be7ff6d9ac9 100644 --- a/pyomo/contrib/appsi/cmodel/src/lp_writer.cpp +++ b/pyomo/contrib/appsi/cmodel/src/lp_writer.cpp @@ -1,3 +1,15 @@ +/**___________________________________________________________________________ + * + * 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. + * ___________________________________________________________________________ +**/ + #include "lp_writer.hpp" void write_expr(std::ofstream &f, std::shared_ptr obj, diff --git a/pyomo/contrib/appsi/cmodel/src/lp_writer.hpp b/pyomo/contrib/appsi/cmodel/src/lp_writer.hpp index ee4ad77500a..1cb6adb462b 100644 --- a/pyomo/contrib/appsi/cmodel/src/lp_writer.hpp +++ b/pyomo/contrib/appsi/cmodel/src/lp_writer.hpp @@ -1,3 +1,15 @@ +/**___________________________________________________________________________ + * + * 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. + * ___________________________________________________________________________ +**/ + #include "model_base.hpp" class LPBase; diff --git a/pyomo/contrib/appsi/cmodel/src/model_base.cpp b/pyomo/contrib/appsi/cmodel/src/model_base.cpp index ab0b25d8e0d..4503138bf1b 100644 --- a/pyomo/contrib/appsi/cmodel/src/model_base.cpp +++ b/pyomo/contrib/appsi/cmodel/src/model_base.cpp @@ -1,3 +1,15 @@ +/**___________________________________________________________________________ + * + * 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. + * ___________________________________________________________________________ +**/ + #include "model_base.hpp" bool constraint_sorter(std::shared_ptr c1, diff --git a/pyomo/contrib/appsi/cmodel/src/model_base.hpp b/pyomo/contrib/appsi/cmodel/src/model_base.hpp index bc61bc053de..b797976aa2f 100644 --- a/pyomo/contrib/appsi/cmodel/src/model_base.hpp +++ b/pyomo/contrib/appsi/cmodel/src/model_base.hpp @@ -1,3 +1,15 @@ +/**___________________________________________________________________________ + * + * 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. + * ___________________________________________________________________________ +**/ + #ifndef MODEL_HEADER #define MODEL_HEADER diff --git a/pyomo/contrib/appsi/cmodel/src/nl_writer.cpp b/pyomo/contrib/appsi/cmodel/src/nl_writer.cpp index dc7004abc16..a1b699e6355 100644 --- a/pyomo/contrib/appsi/cmodel/src/nl_writer.cpp +++ b/pyomo/contrib/appsi/cmodel/src/nl_writer.cpp @@ -1,3 +1,15 @@ +/**___________________________________________________________________________ + * + * 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. + * ___________________________________________________________________________ +**/ + #include "nl_writer.hpp" NLBase::NLBase( diff --git a/pyomo/contrib/appsi/cmodel/src/nl_writer.hpp b/pyomo/contrib/appsi/cmodel/src/nl_writer.hpp index 40e4c9b1222..557d0645e4a 100644 --- a/pyomo/contrib/appsi/cmodel/src/nl_writer.hpp +++ b/pyomo/contrib/appsi/cmodel/src/nl_writer.hpp @@ -1,3 +1,15 @@ +/**___________________________________________________________________________ + * + * 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. + * ___________________________________________________________________________ +**/ + #include "model_base.hpp" class NLBase; diff --git a/pyomo/contrib/appsi/cmodel/tests/__init__.py b/pyomo/contrib/appsi/cmodel/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/appsi/cmodel/tests/__init__.py +++ b/pyomo/contrib/appsi/cmodel/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/appsi/cmodel/tests/test_import.py b/pyomo/contrib/appsi/cmodel/tests/test_import.py index f4647c216ba..9fce3559aff 100644 --- a/pyomo/contrib/appsi/cmodel/tests/test_import.py +++ b/pyomo/contrib/appsi/cmodel/tests/test_import.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common import unittest from pyomo.common.fileutils import find_library, this_file_dir import os diff --git a/pyomo/contrib/appsi/examples/__init__.py b/pyomo/contrib/appsi/examples/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/appsi/examples/__init__.py +++ b/pyomo/contrib/appsi/examples/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/appsi/examples/getting_started.py b/pyomo/contrib/appsi/examples/getting_started.py index de22d28e0a4..c1500c482d9 100644 --- a/pyomo/contrib/appsi/examples/getting_started.py +++ b/pyomo/contrib/appsi/examples/getting_started.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pe from pyomo.contrib import appsi from pyomo.common.timing import HierarchicalTimer diff --git a/pyomo/contrib/appsi/examples/tests/__init__.py b/pyomo/contrib/appsi/examples/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/appsi/examples/tests/__init__.py +++ b/pyomo/contrib/appsi/examples/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/appsi/examples/tests/test_examples.py b/pyomo/contrib/appsi/examples/tests/test_examples.py index d2c88224a7d..7c04271f6d3 100644 --- a/pyomo/contrib/appsi/examples/tests/test_examples.py +++ b/pyomo/contrib/appsi/examples/tests/test_examples.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.appsi.examples import getting_started import pyomo.common.unittest as unittest import pyomo.environ as pe diff --git a/pyomo/contrib/appsi/fbbt.py b/pyomo/contrib/appsi/fbbt.py index 92a0e0c8cbc..957fdc593d4 100644 --- a/pyomo/contrib/appsi/fbbt.py +++ b/pyomo/contrib/appsi/fbbt.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.appsi.base import PersistentBase from pyomo.common.config import ( ConfigDict, diff --git a/pyomo/contrib/appsi/plugins.py b/pyomo/contrib/appsi/plugins.py index 5333158239e..aea9edb3faf 100644 --- a/pyomo/contrib/appsi/plugins.py +++ b/pyomo/contrib/appsi/plugins.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common.extensions import ExtensionBuilderFactory from .base import SolverFactory from .solvers import Gurobi, Ipopt, Cbc, Cplex, Highs diff --git a/pyomo/contrib/appsi/solvers/__init__.py b/pyomo/contrib/appsi/solvers/__init__.py index 20755d1eb07..359e3f80742 100644 --- a/pyomo/contrib/appsi/solvers/__init__.py +++ b/pyomo/contrib/appsi/solvers/__init__.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 .gurobi import Gurobi, GurobiResults from .ipopt import Ipopt from .cbc import Cbc diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index a3aae2a9213..c0e1f15c01e 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common.tempfiles import TempfileManager from pyomo.common.fileutils import Executable from pyomo.contrib.appsi.base import ( diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index f03bee6ecc5..e8ee204ad63 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common.tempfiles import TempfileManager from pyomo.contrib.appsi.base import ( PersistentSolver, diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index a173c69abc6..842cbbf175d 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 collections.abc import Iterable import logging import math diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 3d498f9388e..2619aa2f0c7 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 logging from typing import List, Dict, Optional from pyomo.common.collections import ComponentMap diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index d38a836a2ac..13cda3e3a19 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common.tempfiles import TempfileManager from pyomo.common.fileutils import Executable from pyomo.contrib.appsi.base import ( diff --git a/pyomo/contrib/appsi/solvers/tests/__init__.py b/pyomo/contrib/appsi/solvers/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/appsi/solvers/tests/__init__.py +++ b/pyomo/contrib/appsi/solvers/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py index b032f5c827e..ed2859fef36 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common.errors import PyomoException import pyomo.common.unittest as unittest import pyomo.environ as pe diff --git a/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py index 6451db18087..02de50542f3 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 subprocess import sys diff --git a/pyomo/contrib/appsi/solvers/tests/test_ipopt_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_ipopt_persistent.py index 6b86deaa535..dc82d04b900 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_ipopt_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_ipopt_persistent.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pe import pyomo.common.unittest as unittest from pyomo.contrib.appsi.cmodel import cmodel_available diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 33f6877aaf8..7b0cbeaf284 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pe from pyomo.common.dependencies import attempt_import import pyomo.common.unittest as unittest diff --git a/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py index d250923f104..df1d36442b9 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pe import pyomo.common.unittest as unittest from pyomo.contrib.appsi.base import TerminationCondition, Results, PersistentSolver diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py index 0a358c6aedf..2937a5f1b7c 100644 --- a/pyomo/contrib/appsi/solvers/wntr.py +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.appsi.base import ( PersistentBase, PersistentSolver, diff --git a/pyomo/contrib/appsi/tests/__init__.py b/pyomo/contrib/appsi/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/appsi/tests/__init__.py +++ b/pyomo/contrib/appsi/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/appsi/tests/test_base.py b/pyomo/contrib/appsi/tests/test_base.py index 0d67ca4d01a..7700d4f5534 100644 --- a/pyomo/contrib/appsi/tests/test_base.py +++ b/pyomo/contrib/appsi/tests/test_base.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common import unittest from pyomo.contrib import appsi import pyomo.environ as pe diff --git a/pyomo/contrib/appsi/tests/test_fbbt.py b/pyomo/contrib/appsi/tests/test_fbbt.py index f92960769cf..b739367b989 100644 --- a/pyomo/contrib/appsi/tests/test_fbbt.py +++ b/pyomo/contrib/appsi/tests/test_fbbt.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common import unittest import pyomo.environ as pyo from pyomo.contrib import appsi diff --git a/pyomo/contrib/appsi/tests/test_interval.py b/pyomo/contrib/appsi/tests/test_interval.py index 7963cc31665..7c66d63a543 100644 --- a/pyomo/contrib/appsi/tests/test_interval.py +++ b/pyomo/contrib/appsi/tests/test_interval.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.appsi.cmodel import cmodel, cmodel_available import pyomo.common.unittest as unittest import math diff --git a/pyomo/contrib/appsi/utils/__init__.py b/pyomo/contrib/appsi/utils/__init__.py index f665736fd4a..147d82a923a 100644 --- a/pyomo/contrib/appsi/utils/__init__.py +++ b/pyomo/contrib/appsi/utils/__init__.py @@ -1,2 +1,13 @@ +# ___________________________________________________________________________ +# +# 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 .get_objective import get_objective from .collect_vars_and_named_exprs import collect_vars_and_named_exprs diff --git a/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py b/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py index 9027080f08c..7bf273dbf87 100644 --- a/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py +++ b/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core.expr.visitor import ExpressionValueVisitor, nonpyomo_leaf_types import pyomo.core.expr as EXPR diff --git a/pyomo/contrib/appsi/utils/get_objective.py b/pyomo/contrib/appsi/utils/get_objective.py index 30dd911f9c8..7b43a981622 100644 --- a/pyomo/contrib/appsi/utils/get_objective.py +++ b/pyomo/contrib/appsi/utils/get_objective.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core.base.objective import Objective diff --git a/pyomo/contrib/appsi/utils/tests/__init__.py b/pyomo/contrib/appsi/utils/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/appsi/utils/tests/__init__.py +++ b/pyomo/contrib/appsi/utils/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/appsi/utils/tests/test_collect_vars_and_named_exprs.py b/pyomo/contrib/appsi/utils/tests/test_collect_vars_and_named_exprs.py index 4c2a167a017..9a5e08385f3 100644 --- a/pyomo/contrib/appsi/utils/tests/test_collect_vars_and_named_exprs.py +++ b/pyomo/contrib/appsi/utils/tests/test_collect_vars_and_named_exprs.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common import unittest import pyomo.environ as pe from pyomo.contrib.appsi.utils import collect_vars_and_named_exprs diff --git a/pyomo/contrib/appsi/writers/__init__.py b/pyomo/contrib/appsi/writers/__init__.py index eeadfa73d03..0d5191e8b97 100644 --- a/pyomo/contrib/appsi/writers/__init__.py +++ b/pyomo/contrib/appsi/writers/__init__.py @@ -1,2 +1,13 @@ +# ___________________________________________________________________________ +# +# 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 .nl_writer import NLWriter from .lp_writer import LPWriter diff --git a/pyomo/contrib/appsi/writers/config.py b/pyomo/contrib/appsi/writers/config.py index 7a7faadaabe..9d66aba2037 100644 --- a/pyomo/contrib/appsi/writers/config.py +++ b/pyomo/contrib/appsi/writers/config.py @@ -1,3 +1,15 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + + class WriterConfig(object): def __init__(self): self.symbolic_solver_labels = False diff --git a/pyomo/contrib/appsi/writers/lp_writer.py b/pyomo/contrib/appsi/writers/lp_writer.py index 8a76fa5f9eb..09470202be3 100644 --- a/pyomo/contrib/appsi/writers/lp_writer.py +++ b/pyomo/contrib/appsi/writers/lp_writer.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 typing import List from pyomo.core.base.param import _ParamData from pyomo.core.base.var import _GeneralVarData diff --git a/pyomo/contrib/appsi/writers/nl_writer.py b/pyomo/contrib/appsi/writers/nl_writer.py index 9c739fd6ebb..c2c93992140 100644 --- a/pyomo/contrib/appsi/writers/nl_writer.py +++ b/pyomo/contrib/appsi/writers/nl_writer.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 typing import List from pyomo.core.base.param import _ParamData from pyomo.core.base.var import _GeneralVarData diff --git a/pyomo/contrib/appsi/writers/tests/__init__.py b/pyomo/contrib/appsi/writers/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/appsi/writers/tests/__init__.py +++ b/pyomo/contrib/appsi/writers/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/appsi/writers/tests/test_nl_writer.py b/pyomo/contrib/appsi/writers/tests/test_nl_writer.py index 3b61a5901c3..d0844263d5a 100644 --- a/pyomo/contrib/appsi/writers/tests/test_nl_writer.py +++ b/pyomo/contrib/appsi/writers/tests/test_nl_writer.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common.unittest as unittest from pyomo.common.tempfiles import TempfileManager import pyomo.environ as pe diff --git a/pyomo/contrib/benders/__init__.py b/pyomo/contrib/benders/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/benders/__init__.py +++ b/pyomo/contrib/benders/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/benders/examples/__init__.py b/pyomo/contrib/benders/examples/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/benders/examples/__init__.py +++ b/pyomo/contrib/benders/examples/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/benders/tests/__init__.py b/pyomo/contrib/benders/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/benders/tests/__init__.py +++ b/pyomo/contrib/benders/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/community_detection/__init__.py b/pyomo/contrib/community_detection/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/community_detection/__init__.py +++ b/pyomo/contrib/community_detection/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/community_detection/community_graph.py b/pyomo/contrib/community_detection/community_graph.py index f0a1f9149bd..d1bd49df20c 100644 --- a/pyomo/contrib/community_detection/community_graph.py +++ b/pyomo/contrib/community_detection/community_graph.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Model Graph Generator Code""" from pyomo.common.dependencies import networkx as nx diff --git a/pyomo/contrib/community_detection/detection.py b/pyomo/contrib/community_detection/detection.py index c5366394530..9fe7005f1f2 100644 --- a/pyomo/contrib/community_detection/detection.py +++ b/pyomo/contrib/community_detection/detection.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """ Main module for community detection integration with Pyomo models. diff --git a/pyomo/contrib/community_detection/event_log.py b/pyomo/contrib/community_detection/event_log.py index 30e28257de8..09b1039a8f7 100644 --- a/pyomo/contrib/community_detection/event_log.py +++ b/pyomo/contrib/community_detection/event_log.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """ Logger function for community_graph.py """ from logging import getLogger diff --git a/pyomo/contrib/community_detection/plugins.py b/pyomo/contrib/community_detection/plugins.py index 0cdc95ad02a..578da835d5e 100644 --- a/pyomo/contrib/community_detection/plugins.py +++ b/pyomo/contrib/community_detection/plugins.py @@ -1,2 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + + def load(): import pyomo.contrib.community_detection.detection diff --git a/pyomo/contrib/community_detection/tests/__init__.py b/pyomo/contrib/community_detection/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/community_detection/tests/__init__.py +++ b/pyomo/contrib/community_detection/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/cp/__init__.py b/pyomo/contrib/cp/__init__.py index c51160bf931..71ba479523f 100644 --- a/pyomo/contrib/cp/__init__.py +++ b/pyomo/contrib/cp/__init__.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.cp.interval_var import ( IntervalVar, IntervalVarStartTime, diff --git a/pyomo/contrib/cp/repn/__init__.py b/pyomo/contrib/cp/repn/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/cp/repn/__init__.py +++ b/pyomo/contrib/cp/repn/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/cp/scheduling_expr/__init__.py b/pyomo/contrib/cp/scheduling_expr/__init__.py index 8b137891791..d93cfd77b3c 100644 --- a/pyomo/contrib/cp/scheduling_expr/__init__.py +++ b/pyomo/contrib/cp/scheduling_expr/__init__.py @@ -1 +1,10 @@ - +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/cp/tests/__init__.py b/pyomo/contrib/cp/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/cp/tests/__init__.py +++ b/pyomo/contrib/cp/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/cp/transform/__init__.py b/pyomo/contrib/cp/transform/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/cp/transform/__init__.py +++ b/pyomo/contrib/cp/transform/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/doe/examples/__init__.py b/pyomo/contrib/doe/examples/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/doe/examples/__init__.py +++ b/pyomo/contrib/doe/examples/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/doe/tests/__init__.py b/pyomo/contrib/doe/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/doe/tests/__init__.py +++ b/pyomo/contrib/doe/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/example/__init__.py b/pyomo/contrib/example/__init__.py index 7f2d08a0292..7a9e6e76de4 100644 --- a/pyomo/contrib/example/__init__.py +++ b/pyomo/contrib/example/__init__.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 symbols and sub-packages # diff --git a/pyomo/contrib/example/bar.py b/pyomo/contrib/example/bar.py index 295540d3318..eb39c5f8748 100644 --- a/pyomo/contrib/example/bar.py +++ b/pyomo/contrib/example/bar.py @@ -1 +1,12 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + b = "1" diff --git a/pyomo/contrib/example/foo.py b/pyomo/contrib/example/foo.py index 1337a530cbc..a1a10b1dd62 100644 --- a/pyomo/contrib/example/foo.py +++ b/pyomo/contrib/example/foo.py @@ -1 +1,12 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + a = 1 diff --git a/pyomo/contrib/example/plugins/__init__.py b/pyomo/contrib/example/plugins/__init__.py index dc71adec9dc..0c6c248c122 100644 --- a/pyomo/contrib/example/plugins/__init__.py +++ b/pyomo/contrib/example/plugins/__init__.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # Define a 'load()' function, which simply imports # sub-packages that define plugin classes. diff --git a/pyomo/contrib/example/plugins/ex_plugin.py b/pyomo/contrib/example/plugins/ex_plugin.py index 504605205f4..0a23afc0158 100644 --- a/pyomo/contrib/example/plugins/ex_plugin.py +++ b/pyomo/contrib/example/plugins/ex_plugin.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core.base import Transformation, TransformationFactory diff --git a/pyomo/contrib/example/tests/__init__.py b/pyomo/contrib/example/tests/__init__.py index 5a1047f74ae..3ecae26215c 100644 --- a/pyomo/contrib/example/tests/__init__.py +++ b/pyomo/contrib/example/tests/__init__.py @@ -1 +1,12 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # Tests for pyomo.contrib.example diff --git a/pyomo/contrib/fbbt/__init__.py b/pyomo/contrib/fbbt/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/fbbt/__init__.py +++ b/pyomo/contrib/fbbt/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/fbbt/tests/__init__.py b/pyomo/contrib/fbbt/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/fbbt/tests/__init__.py +++ b/pyomo/contrib/fbbt/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/fbbt/tests/test_interval.py b/pyomo/contrib/fbbt/tests/test_interval.py index 59c62be4e84..d5dc7b54ff5 100644 --- a/pyomo/contrib/fbbt/tests/test_interval.py +++ b/pyomo/contrib/fbbt/tests/test_interval.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 math import pyomo.common.unittest as unittest from pyomo.common.dependencies import numpy as np, numpy_available diff --git a/pyomo/contrib/fme/__init__.py b/pyomo/contrib/fme/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/fme/__init__.py +++ b/pyomo/contrib/fme/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/fme/tests/__init__.py b/pyomo/contrib/fme/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/fme/tests/__init__.py +++ b/pyomo/contrib/fme/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/gdp_bounds/__init__.py b/pyomo/contrib/gdp_bounds/__init__.py index 3a02f9e5f8e..4918f6dfa0e 100644 --- a/pyomo/contrib/gdp_bounds/__init__.py +++ b/pyomo/contrib/gdp_bounds/__init__.py @@ -1 +1,12 @@ +# ___________________________________________________________________________ +# +# 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.gdp_bounds.plugins diff --git a/pyomo/contrib/gdp_bounds/info.py b/pyomo/contrib/gdp_bounds/info.py index 3ee87041d25..f7e83ee62c9 100644 --- a/pyomo/contrib/gdp_bounds/info.py +++ b/pyomo/contrib/gdp_bounds/info.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Provides functions for retrieving disjunctive variable bound information stored on a model.""" from pyomo.common.collections import ComponentMap diff --git a/pyomo/contrib/gdp_bounds/tests/__init__.py b/pyomo/contrib/gdp_bounds/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/gdp_bounds/tests/__init__.py +++ b/pyomo/contrib/gdp_bounds/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/gdp_bounds/tests/test_gdp_bounds.py b/pyomo/contrib/gdp_bounds/tests/test_gdp_bounds.py index e856ae247f3..551236c7d97 100644 --- a/pyomo/contrib/gdp_bounds/tests/test_gdp_bounds.py +++ b/pyomo/contrib/gdp_bounds/tests/test_gdp_bounds.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Tests explicit bound to variable bound transformation module.""" import pyomo.common.unittest as unittest diff --git a/pyomo/contrib/gdpopt/__init__.py b/pyomo/contrib/gdpopt/__init__.py index 307fbc1594c..f74855f0206 100644 --- a/pyomo/contrib/gdpopt/__init__.py +++ b/pyomo/contrib/gdpopt/__init__.py @@ -1 +1,12 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + __version__ = (22, 5, 13) # Note: date-based version number diff --git a/pyomo/contrib/gdpopt/tests/__init__.py b/pyomo/contrib/gdpopt/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/gdpopt/tests/__init__.py +++ b/pyomo/contrib/gdpopt/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/iis/__init__.py b/pyomo/contrib/iis/__init__.py index eb9f60b8928..29f5d4f3d40 100644 --- a/pyomo/contrib/iis/__init__.py +++ b/pyomo/contrib/iis/__init__.py @@ -1 +1,12 @@ +# ___________________________________________________________________________ +# +# 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.iis.iis import write_iis diff --git a/pyomo/contrib/iis/iis.py b/pyomo/contrib/iis/iis.py index bd192d04eb3..a279ce0aac3 100644 --- a/pyomo/contrib/iis/iis.py +++ b/pyomo/contrib/iis/iis.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """ This module contains functions for computing an irreducible infeasible set for a Pyomo MILP or LP using a specified commercial solver, one of CPLEX, diff --git a/pyomo/contrib/iis/tests/__init__.py b/pyomo/contrib/iis/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/iis/tests/__init__.py +++ b/pyomo/contrib/iis/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/iis/tests/test_iis.py b/pyomo/contrib/iis/tests/test_iis.py index b1b675d5081..8343798741a 100644 --- a/pyomo/contrib/iis/tests/test_iis.py +++ b/pyomo/contrib/iis/tests/test_iis.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common.unittest as unittest import pyomo.environ as pyo from pyomo.contrib.iis import write_iis diff --git a/pyomo/contrib/incidence_analysis/__init__.py b/pyomo/contrib/incidence_analysis/__init__.py index ee078690f2f..612b4fe7d02 100644 --- a/pyomo/contrib/incidence_analysis/__init__.py +++ b/pyomo/contrib/incidence_analysis/__init__.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 .triangularize import block_triangularize from .matching import maximum_matching from .interface import IncidenceGraphInterface, get_bipartite_incidence_graph diff --git a/pyomo/contrib/incidence_analysis/common/__init__.py b/pyomo/contrib/incidence_analysis/common/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/incidence_analysis/common/__init__.py +++ b/pyomo/contrib/incidence_analysis/common/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/incidence_analysis/common/tests/__init__.py b/pyomo/contrib/incidence_analysis/common/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/incidence_analysis/common/tests/__init__.py +++ b/pyomo/contrib/incidence_analysis/common/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/incidence_analysis/tests/__init__.py b/pyomo/contrib/incidence_analysis/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/incidence_analysis/tests/__init__.py +++ b/pyomo/contrib/incidence_analysis/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/interior_point/__init__.py b/pyomo/contrib/interior_point/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/interior_point/__init__.py +++ b/pyomo/contrib/interior_point/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/interior_point/examples/__init__.py b/pyomo/contrib/interior_point/examples/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/interior_point/examples/__init__.py +++ b/pyomo/contrib/interior_point/examples/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/interior_point/linalg/__init__.py b/pyomo/contrib/interior_point/linalg/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/interior_point/linalg/__init__.py +++ b/pyomo/contrib/interior_point/linalg/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/interior_point/linalg/base_linear_solver_interface.py b/pyomo/contrib/interior_point/linalg/base_linear_solver_interface.py index 722a5c55e8d..2bc7fe2eee5 100644 --- a/pyomo/contrib/interior_point/linalg/base_linear_solver_interface.py +++ b/pyomo/contrib/interior_point/linalg/base_linear_solver_interface.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.pynumero.linalg.base import DirectLinearSolverInterface from abc import ABCMeta, abstractmethod import logging diff --git a/pyomo/contrib/interior_point/linalg/ma27_interface.py b/pyomo/contrib/interior_point/linalg/ma27_interface.py index 7bb98b0b6fd..0a28e50578d 100644 --- a/pyomo/contrib/interior_point/linalg/ma27_interface.py +++ b/pyomo/contrib/interior_point/linalg/ma27_interface.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 .base_linear_solver_interface import IPLinearSolverInterface from pyomo.contrib.pynumero.linalg.base import LinearSolverStatus, LinearSolverResults from pyomo.contrib.pynumero.linalg.ma27_interface import MA27 diff --git a/pyomo/contrib/interior_point/linalg/scipy_interface.py b/pyomo/contrib/interior_point/linalg/scipy_interface.py index b7b7923bad4..87b0cad8ea0 100644 --- a/pyomo/contrib/interior_point/linalg/scipy_interface.py +++ b/pyomo/contrib/interior_point/linalg/scipy_interface.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 .base_linear_solver_interface import IPLinearSolverInterface from pyomo.contrib.pynumero.linalg.base import LinearSolverResults from scipy.linalg import eigvals diff --git a/pyomo/contrib/interior_point/linalg/tests/__init__.py b/pyomo/contrib/interior_point/linalg/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/interior_point/linalg/tests/__init__.py +++ b/pyomo/contrib/interior_point/linalg/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/interior_point/linalg/tests/test_linear_solvers.py b/pyomo/contrib/interior_point/linalg/tests/test_linear_solvers.py index 35863aa7cf7..c13aad215cc 100644 --- a/pyomo/contrib/interior_point/linalg/tests/test_linear_solvers.py +++ b/pyomo/contrib/interior_point/linalg/tests/test_linear_solvers.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common.unittest as unittest from pyomo.common.dependencies import attempt_import diff --git a/pyomo/contrib/interior_point/linalg/tests/test_realloc.py b/pyomo/contrib/interior_point/linalg/tests/test_realloc.py index bfe089dc602..7dce4755261 100644 --- a/pyomo/contrib/interior_point/linalg/tests/test_realloc.py +++ b/pyomo/contrib/interior_point/linalg/tests/test_realloc.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common.unittest as unittest from pyomo.common.dependencies import attempt_import diff --git a/pyomo/contrib/interior_point/tests/__init__.py b/pyomo/contrib/interior_point/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/interior_point/tests/__init__.py +++ b/pyomo/contrib/interior_point/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/interior_point/tests/test_realloc.py b/pyomo/contrib/interior_point/tests/test_realloc.py index b3758c946d4..dcf94eb6da7 100644 --- a/pyomo/contrib/interior_point/tests/test_realloc.py +++ b/pyomo/contrib/interior_point/tests/test_realloc.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common.unittest as unittest import pyomo.environ as pe from pyomo.core.base import ConcreteModel, Var, Constraint, Objective diff --git a/pyomo/contrib/latex_printer/__init__.py b/pyomo/contrib/latex_printer/__init__.py index 27c1552017a..7208b1e7d64 100644 --- a/pyomo/contrib/latex_printer/__init__.py +++ b/pyomo/contrib/latex_printer/__init__.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index b84f9a420fc..f9150f700a3 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects diff --git a/pyomo/contrib/latex_printer/tests/__init__.py b/pyomo/contrib/latex_printer/tests/__init__.py index 8b137891791..d93cfd77b3c 100644 --- a/pyomo/contrib/latex_printer/tests/__init__.py +++ b/pyomo/contrib/latex_printer/tests/__init__.py @@ -1 +1,10 @@ - +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/latex_printer/tests/test_latex_printer.py b/pyomo/contrib/latex_printer/tests/test_latex_printer.py index e9de4e4ad05..1797e0a39a0 100644 --- a/pyomo/contrib/latex_printer/tests/test_latex_printer.py +++ b/pyomo/contrib/latex_printer/tests/test_latex_printer.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects diff --git a/pyomo/contrib/latex_printer/tests/test_latex_printer_vartypes.py b/pyomo/contrib/latex_printer/tests/test_latex_printer_vartypes.py index 14e9ebbe0e6..dc571030fde 100644 --- a/pyomo/contrib/latex_printer/tests/test_latex_printer_vartypes.py +++ b/pyomo/contrib/latex_printer/tests/test_latex_printer_vartypes.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects diff --git a/pyomo/contrib/mindtpy/__init__.py b/pyomo/contrib/mindtpy/__init__.py index 8dcd085211f..94a91238819 100644 --- a/pyomo/contrib/mindtpy/__init__.py +++ b/pyomo/contrib/mindtpy/__init__.py @@ -1 +1,12 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + __version__ = (1, 0, 0) diff --git a/pyomo/contrib/mindtpy/config_options.py b/pyomo/contrib/mindtpy/config_options.py index ed0c86baae9..f1c4a23d46e 100644 --- a/pyomo/contrib/mindtpy/config_options.py +++ b/pyomo/contrib/mindtpy/config_options.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: utf-8 -*- import logging from pyomo.common.config import ( diff --git a/pyomo/contrib/mindtpy/tests/MINLP4_simple.py b/pyomo/contrib/mindtpy/tests/MINLP4_simple.py index 7b57c6b8f0d..684fdf4a932 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP4_simple.py +++ b/pyomo/contrib/mindtpy/tests/MINLP4_simple.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: utf-8 -*- """ Example 1 in Paper 'Using regularization and second order information in outer approximation for convex MINLP' diff --git a/pyomo/contrib/mindtpy/tests/MINLP5_simple.py b/pyomo/contrib/mindtpy/tests/MINLP5_simple.py index 5ab5f98b894..cb78f6e0804 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP5_simple.py +++ b/pyomo/contrib/mindtpy/tests/MINLP5_simple.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: utf-8 -*- """Example in paper 'Using regularization and second order information in outer approximation for convex MINLP' diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index 9c1f33e80cc..789dfea6191 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common.dependencies import numpy as np import pyomo.common.dependencies.scipy.sparse as scipy_sparse from pyomo.common.dependencies import attempt_import diff --git a/pyomo/contrib/mindtpy/tests/__init__.py b/pyomo/contrib/mindtpy/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/mindtpy/tests/__init__.py +++ b/pyomo/contrib/mindtpy/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/mindtpy/tests/constraint_qualification_example.py b/pyomo/contrib/mindtpy/tests/constraint_qualification_example.py index 6038f9a74eb..75ec56df738 100644 --- a/pyomo/contrib/mindtpy/tests/constraint_qualification_example.py +++ b/pyomo/contrib/mindtpy/tests/constraint_qualification_example.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: utf-8 -*- """ Example of constraint qualification. diff --git a/pyomo/contrib/mindtpy/tests/eight_process_problem.py b/pyomo/contrib/mindtpy/tests/eight_process_problem.py index d3876a9dc44..8233fc52c53 100644 --- a/pyomo/contrib/mindtpy/tests/eight_process_problem.py +++ b/pyomo/contrib/mindtpy/tests/eight_process_problem.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: utf-8 -*- """Re-implementation of eight-process problem. diff --git a/pyomo/contrib/mindtpy/tests/feasibility_pump1.py b/pyomo/contrib/mindtpy/tests/feasibility_pump1.py index e0a611c1ed2..3149ccef6e4 100644 --- a/pyomo/contrib/mindtpy/tests/feasibility_pump1.py +++ b/pyomo/contrib/mindtpy/tests/feasibility_pump1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: utf-8 -*- """Example 1 in paper 'A Feasibility Pump for mixed integer nonlinear programs' diff --git a/pyomo/contrib/mindtpy/tests/feasibility_pump2.py b/pyomo/contrib/mindtpy/tests/feasibility_pump2.py index 48b98dc5800..9fed8238fe1 100644 --- a/pyomo/contrib/mindtpy/tests/feasibility_pump2.py +++ b/pyomo/contrib/mindtpy/tests/feasibility_pump2.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: utf-8 -*- """Example 2 in paper 'A Feasibility Pump for mixed integer nonlinear programs' diff --git a/pyomo/contrib/mindtpy/tests/from_proposal.py b/pyomo/contrib/mindtpy/tests/from_proposal.py index 6ddab15ee53..e34fddedcd3 100644 --- a/pyomo/contrib/mindtpy/tests/from_proposal.py +++ b/pyomo/contrib/mindtpy/tests/from_proposal.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: utf-8 -*- """ See David Bernal PhD proposal example. diff --git a/pyomo/contrib/mindtpy/tests/nonconvex1.py b/pyomo/contrib/mindtpy/tests/nonconvex1.py index 94a4de29405..60115a52c32 100644 --- a/pyomo/contrib/mindtpy/tests/nonconvex1.py +++ b/pyomo/contrib/mindtpy/tests/nonconvex1.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: utf-8 -*- """Problem A in paper 'Outer approximation algorithms for separable nonconvex mixed-integer nonlinear programs' diff --git a/pyomo/contrib/mindtpy/tests/nonconvex2.py b/pyomo/contrib/mindtpy/tests/nonconvex2.py index 525db1292c1..ac48167b350 100644 --- a/pyomo/contrib/mindtpy/tests/nonconvex2.py +++ b/pyomo/contrib/mindtpy/tests/nonconvex2.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: utf-8 -*- """Problem B in paper 'Outer approximation algorithms for separable nonconvex mixed-integer nonlinear programs' diff --git a/pyomo/contrib/mindtpy/tests/nonconvex3.py b/pyomo/contrib/mindtpy/tests/nonconvex3.py index b08deb67b63..8337beb8d68 100644 --- a/pyomo/contrib/mindtpy/tests/nonconvex3.py +++ b/pyomo/contrib/mindtpy/tests/nonconvex3.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: utf-8 -*- """Problem C in paper 'Outer approximation algorithms for separable nonconvex mixed-integer nonlinear programs'. The problem in the paper has two optimal solution. Variable y4 and y6 are symmetric. Therefore, we remove variable y6 for simplification. diff --git a/pyomo/contrib/mindtpy/tests/nonconvex4.py b/pyomo/contrib/mindtpy/tests/nonconvex4.py index c30fb9922a0..79e6465239f 100644 --- a/pyomo/contrib/mindtpy/tests/nonconvex4.py +++ b/pyomo/contrib/mindtpy/tests/nonconvex4.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: utf-8 -*- """Problem D in paper 'Outer approximation algorithms for separable nonconvex mixed-integer nonlinear programs' diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py index b5bfbe62553..07f2b1aaff5 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: utf-8 -*- """Tests for the MindtPy solver.""" import pyomo.common.unittest as unittest diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py index dcb5c4bce75..c7b47b7fde2 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: utf-8 -*- """Tests for the MindtPy solver.""" import pyomo.common.unittest as unittest diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_global.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_global.py index 0fa19b30d9c..dbe9270c363 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_global.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_global.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: utf-8 -*- """Tests for the MindtPy solver.""" import pyomo.common.unittest as unittest diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_global_lp_nlp.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_global_lp_nlp.py index 259cfe9dd7c..08bfb8df2de 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_global_lp_nlp.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_global_lp_nlp.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: utf-8 -*- """Tests for global LP/NLP in the MindtPy solver.""" import pyomo.common.unittest as unittest diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_regularization.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_regularization.py index 4c2ae4d1220..33f296083ed 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_regularization.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_regularization.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: utf-8 -*- """Tests for the MindtPy solver.""" import pyomo.common.unittest as unittest diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_solution_pool.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_solution_pool.py index 7a9898d3c7b..775d1a4e117 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_solution_pool.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_solution_pool.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Tests for solution pool in the MindtPy solver.""" from pyomo.core.expr.calculus.diff_with_sympy import differentiate_available diff --git a/pyomo/contrib/mpc/data/tests/__init__.py b/pyomo/contrib/mpc/data/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/mpc/data/tests/__init__.py +++ b/pyomo/contrib/mpc/data/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/mpc/examples/__init__.py b/pyomo/contrib/mpc/examples/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/mpc/examples/__init__.py +++ b/pyomo/contrib/mpc/examples/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/mpc/examples/cstr/__init__.py b/pyomo/contrib/mpc/examples/cstr/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/mpc/examples/cstr/__init__.py +++ b/pyomo/contrib/mpc/examples/cstr/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/mpc/examples/cstr/tests/__init__.py b/pyomo/contrib/mpc/examples/cstr/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/mpc/examples/cstr/tests/__init__.py +++ b/pyomo/contrib/mpc/examples/cstr/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/mpc/interfaces/tests/__init__.py b/pyomo/contrib/mpc/interfaces/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/mpc/interfaces/tests/__init__.py +++ b/pyomo/contrib/mpc/interfaces/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/mpc/modeling/tests/__init__.py b/pyomo/contrib/mpc/modeling/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/mpc/modeling/tests/__init__.py +++ b/pyomo/contrib/mpc/modeling/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/multistart/__init__.py b/pyomo/contrib/multistart/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/multistart/__init__.py +++ b/pyomo/contrib/multistart/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/multistart/high_conf_stop.py b/pyomo/contrib/multistart/high_conf_stop.py index 96b350557ae..153d22e9edd 100644 --- a/pyomo/contrib/multistart/high_conf_stop.py +++ b/pyomo/contrib/multistart/high_conf_stop.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Utility functions for the high confidence stopping rule. This stopping criterion operates by estimating the amount of missing optima, diff --git a/pyomo/contrib/multistart/plugins.py b/pyomo/contrib/multistart/plugins.py index 297b2f059cc..acfd2f06274 100644 --- a/pyomo/contrib/multistart/plugins.py +++ b/pyomo/contrib/multistart/plugins.py @@ -1,2 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + + def load(): import pyomo.contrib.multistart.multi diff --git a/pyomo/contrib/multistart/reinit.py b/pyomo/contrib/multistart/reinit.py index de10fe3ba8b..14dce0352cc 100644 --- a/pyomo/contrib/multistart/reinit.py +++ b/pyomo/contrib/multistart/reinit.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Helper functions for variable reinitialization.""" import logging diff --git a/pyomo/contrib/multistart/test_multi.py b/pyomo/contrib/multistart/test_multi.py index 16c8563ae9e..a8e3d420266 100644 --- a/pyomo/contrib/multistart/test_multi.py +++ b/pyomo/contrib/multistart/test_multi.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 logging from itertools import product diff --git a/pyomo/contrib/parmest/utils/create_ef.py b/pyomo/contrib/parmest/utils/create_ef.py index 2e6c8541fa1..7a7dd72f7da 100644 --- a/pyomo/contrib/parmest/utils/create_ef.py +++ b/pyomo/contrib/parmest/utils/create_ef.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # This software is distributed under the 3-clause BSD License. # Copied with minor modifications from create_EF in mpisppy/utils/sputils.py # from the mpi-sppy library (https://github.com/Pyomo/mpi-sppy). diff --git a/pyomo/contrib/parmest/utils/scenario_tree.py b/pyomo/contrib/parmest/utils/scenario_tree.py index d46a8f2c5f0..46b02b8ddc1 100644 --- a/pyomo/contrib/parmest/utils/scenario_tree.py +++ b/pyomo/contrib/parmest/utils/scenario_tree.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # This software is distributed under the 3-clause BSD License. # Copied with minor modifications from mpisppy/scenario_tree.py # from the mpi-sppy library (https://github.com/Pyomo/mpi-sppy). diff --git a/pyomo/contrib/piecewise/__init__.py b/pyomo/contrib/piecewise/__init__.py index 33cfc6f1606..9e15cfd6670 100644 --- a/pyomo/contrib/piecewise/__init__.py +++ b/pyomo/contrib/piecewise/__init__.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.piecewise_linear_expression import ( PiecewiseLinearExpression, ) diff --git a/pyomo/contrib/piecewise/tests/__init__.py b/pyomo/contrib/piecewise/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/piecewise/tests/__init__.py +++ b/pyomo/contrib/piecewise/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/piecewise/transform/__init__.py b/pyomo/contrib/piecewise/transform/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/piecewise/transform/__init__.py +++ b/pyomo/contrib/piecewise/transform/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/preprocessing/__init__.py b/pyomo/contrib/preprocessing/__init__.py index dcd444ad312..40d38e74d23 100644 --- a/pyomo/contrib/preprocessing/__init__.py +++ b/pyomo/contrib/preprocessing/__init__.py @@ -1 +1,12 @@ +# ___________________________________________________________________________ +# +# 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.preprocessing.plugins diff --git a/pyomo/contrib/preprocessing/plugins/__init__.py b/pyomo/contrib/preprocessing/plugins/__init__.py index 12eee351308..ae5dfe31682 100644 --- a/pyomo/contrib/preprocessing/plugins/__init__.py +++ b/pyomo/contrib/preprocessing/plugins/__init__.py @@ -1,3 +1,15 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + + def load(): import pyomo.contrib.preprocessing.plugins.deactivate_trivial_constraints import pyomo.contrib.preprocessing.plugins.detect_fixed_vars diff --git a/pyomo/contrib/preprocessing/plugins/constraint_tightener.py b/pyomo/contrib/preprocessing/plugins/constraint_tightener.py index 4c8b28e0319..7c5495e72b8 100644 --- a/pyomo/contrib/preprocessing/plugins/constraint_tightener.py +++ b/pyomo/contrib/preprocessing/plugins/constraint_tightener.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 logging from pyomo.common import deprecated diff --git a/pyomo/contrib/preprocessing/plugins/int_to_binary.py b/pyomo/contrib/preprocessing/plugins/int_to_binary.py index 6ed6c3a9cfa..6a08dab9645 100644 --- a/pyomo/contrib/preprocessing/plugins/int_to_binary.py +++ b/pyomo/contrib/preprocessing/plugins/int_to_binary.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Transformation to reformulate integer variables into binary.""" from math import floor, log diff --git a/pyomo/contrib/preprocessing/tests/__init__.py b/pyomo/contrib/preprocessing/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/preprocessing/tests/__init__.py +++ b/pyomo/contrib/preprocessing/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/preprocessing/tests/test_bounds_to_vars_xfrm.py b/pyomo/contrib/preprocessing/tests/test_bounds_to_vars_xfrm.py index c2b8acd3e49..534ba11d22f 100644 --- a/pyomo/contrib/preprocessing/tests/test_bounds_to_vars_xfrm.py +++ b/pyomo/contrib/preprocessing/tests/test_bounds_to_vars_xfrm.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Tests explicit bound to variable bound transformation module.""" import pyomo.common.unittest as unittest diff --git a/pyomo/contrib/preprocessing/tests/test_constraint_tightener.py b/pyomo/contrib/preprocessing/tests/test_constraint_tightener.py index 8f36bee15a1..808eb688087 100644 --- a/pyomo/contrib/preprocessing/tests/test_constraint_tightener.py +++ b/pyomo/contrib/preprocessing/tests/test_constraint_tightener.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Tests the Bounds Tightening module.""" import pyomo.common.unittest as unittest diff --git a/pyomo/contrib/preprocessing/tests/test_deactivate_trivial_constraints.py b/pyomo/contrib/preprocessing/tests/test_deactivate_trivial_constraints.py index fa0ca6cfa9a..0c89b0d7d86 100644 --- a/pyomo/contrib/preprocessing/tests/test_deactivate_trivial_constraints.py +++ b/pyomo/contrib/preprocessing/tests/test_deactivate_trivial_constraints.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: utf-8 -*- """Tests deactivation of trivial constraints.""" import pyomo.common.unittest as unittest diff --git a/pyomo/contrib/preprocessing/tests/test_detect_fixed_vars.py b/pyomo/contrib/preprocessing/tests/test_detect_fixed_vars.py index b3c72531f77..f18ac5c3b8a 100644 --- a/pyomo/contrib/preprocessing/tests/test_detect_fixed_vars.py +++ b/pyomo/contrib/preprocessing/tests/test_detect_fixed_vars.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Tests detection of fixed variables.""" import pyomo.common.unittest as unittest diff --git a/pyomo/contrib/preprocessing/tests/test_equality_propagate.py b/pyomo/contrib/preprocessing/tests/test_equality_propagate.py index b77f5c5f3f5..a2c00a15e72 100644 --- a/pyomo/contrib/preprocessing/tests/test_equality_propagate.py +++ b/pyomo/contrib/preprocessing/tests/test_equality_propagate.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Tests the equality set propagation module.""" import pyomo.common.unittest as unittest diff --git a/pyomo/contrib/preprocessing/tests/test_init_vars.py b/pyomo/contrib/preprocessing/tests/test_init_vars.py index e52c9fd5cc8..6ddf859e930 100644 --- a/pyomo/contrib/preprocessing/tests/test_init_vars.py +++ b/pyomo/contrib/preprocessing/tests/test_init_vars.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Tests initialization of uninitialized variables.""" import pyomo.common.unittest as unittest diff --git a/pyomo/contrib/preprocessing/tests/test_strip_bounds.py b/pyomo/contrib/preprocessing/tests/test_strip_bounds.py index a8526c613c4..4eec0cf7434 100644 --- a/pyomo/contrib/preprocessing/tests/test_strip_bounds.py +++ b/pyomo/contrib/preprocessing/tests/test_strip_bounds.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Tests stripping of variable bounds.""" import pyomo.common.unittest as unittest diff --git a/pyomo/contrib/preprocessing/tests/test_var_aggregator.py b/pyomo/contrib/preprocessing/tests/test_var_aggregator.py index 1f2c06dd0d1..b3630225402 100644 --- a/pyomo/contrib/preprocessing/tests/test_var_aggregator.py +++ b/pyomo/contrib/preprocessing/tests/test_var_aggregator.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Tests the variable aggregation module.""" import pyomo.common.unittest as unittest diff --git a/pyomo/contrib/preprocessing/tests/test_zero_sum_propagate.py b/pyomo/contrib/preprocessing/tests/test_zero_sum_propagate.py index bec889c7635..ce88b8ca86e 100644 --- a/pyomo/contrib/preprocessing/tests/test_zero_sum_propagate.py +++ b/pyomo/contrib/preprocessing/tests/test_zero_sum_propagate.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Tests the zero sum propagation module.""" import pyomo.common.unittest as unittest diff --git a/pyomo/contrib/preprocessing/tests/test_zero_term_removal.py b/pyomo/contrib/preprocessing/tests/test_zero_term_removal.py index d1b74822747..abe79034ec2 100644 --- a/pyomo/contrib/preprocessing/tests/test_zero_term_removal.py +++ b/pyomo/contrib/preprocessing/tests/test_zero_term_removal.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Tests detection of zero terms.""" import pyomo.common.unittest as unittest diff --git a/pyomo/contrib/preprocessing/util.py b/pyomo/contrib/preprocessing/util.py index 69182f56656..ffc72f46902 100644 --- a/pyomo/contrib/preprocessing/util.py +++ b/pyomo/contrib/preprocessing/util.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 logging from io import StringIO diff --git a/pyomo/contrib/pynumero/algorithms/solvers/__init__.py b/pyomo/contrib/pynumero/algorithms/solvers/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/__init__.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/pynumero/algorithms/solvers/tests/__init__.py b/pyomo/contrib/pynumero/algorithms/solvers/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/tests/__init__.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/pynumero/examples/callback/__init__.py b/pyomo/contrib/pynumero/examples/callback/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/pynumero/examples/callback/__init__.py +++ b/pyomo/contrib/pynumero/examples/callback/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/pynumero/examples/callback/cyipopt_callback.py b/pyomo/contrib/pynumero/examples/callback/cyipopt_callback.py index 6bd86c006a1..58367e0bc5a 100644 --- a/pyomo/contrib/pynumero/examples/callback/cyipopt_callback.py +++ b/pyomo/contrib/pynumero/examples/callback/cyipopt_callback.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo from pyomo.contrib.pynumero.examples.callback.reactor_design import model as m import logging diff --git a/pyomo/contrib/pynumero/examples/callback/cyipopt_callback_halt.py b/pyomo/contrib/pynumero/examples/callback/cyipopt_callback_halt.py index 18fad2bbcd8..55138c99318 100644 --- a/pyomo/contrib/pynumero/examples/callback/cyipopt_callback_halt.py +++ b/pyomo/contrib/pynumero/examples/callback/cyipopt_callback_halt.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo from pyomo.contrib.pynumero.examples.callback.reactor_design import model as m diff --git a/pyomo/contrib/pynumero/examples/callback/cyipopt_functor_callback.py b/pyomo/contrib/pynumero/examples/callback/cyipopt_functor_callback.py index ca452f33c90..b52897d58b1 100644 --- a/pyomo/contrib/pynumero/examples/callback/cyipopt_functor_callback.py +++ b/pyomo/contrib/pynumero/examples/callback/cyipopt_functor_callback.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo from pyomo.contrib.pynumero.examples.callback.reactor_design import model as m from pyomo.common.dependencies import pandas as pd diff --git a/pyomo/contrib/pynumero/examples/callback/reactor_design.py b/pyomo/contrib/pynumero/examples/callback/reactor_design.py index 927b25f9bc9..98fbc93ee58 100644 --- a/pyomo/contrib/pynumero/examples/callback/reactor_design.py +++ b/pyomo/contrib/pynumero/examples/callback/reactor_design.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ from pyomo.core import * diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/__init__.py b/pyomo/contrib/pynumero/examples/external_grey_box/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/__init__.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/__init__.py b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/__init__.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/generate_data.py b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/generate_data.py index 5bf0defbb8d..3af10a465b7 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/generate_data.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/generate_data.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo import numpy.random as rnd import pyomo.contrib.pynumero.examples.external_grey_box.param_est.models as pm diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/models.py b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/models.py index a8b9befb188..a7962f5634d 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/models.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/models.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo from pyomo.contrib.pynumero.interfaces.external_grey_box import ( ExternalGreyBoxModel, diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/perform_estimation.py b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/perform_estimation.py index f27192f9281..9a18c7fb54b 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/perform_estimation.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/perform_estimation.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 sys import pyomo.environ as pyo import numpy.random as rnd diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/react_example/__init__.py b/pyomo/contrib/pynumero/examples/external_grey_box/react_example/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/react_example/__init__.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/react_example/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/pynumero/examples/mumps_example.py b/pyomo/contrib/pynumero/examples/mumps_example.py index 938fab99279..7f96bfce4ae 100644 --- a/pyomo/contrib/pynumero/examples/mumps_example.py +++ b/pyomo/contrib/pynumero/examples/mumps_example.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 numpy as np import scipy.sparse as sp from scipy.linalg import hilbert diff --git a/pyomo/contrib/pynumero/examples/parallel_matvec.py b/pyomo/contrib/pynumero/examples/parallel_matvec.py index 26a2ec9a632..78095fe1acd 100644 --- a/pyomo/contrib/pynumero/examples/parallel_matvec.py +++ b/pyomo/contrib/pynumero/examples/parallel_matvec.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 numpy as np from pyomo.common.dependencies import mpi4py from pyomo.contrib.pynumero.sparse.mpi_block_vector import MPIBlockVector diff --git a/pyomo/contrib/pynumero/examples/parallel_vector_ops.py b/pyomo/contrib/pynumero/examples/parallel_vector_ops.py index 4b155ce7493..83e40a342db 100644 --- a/pyomo/contrib/pynumero/examples/parallel_vector_ops.py +++ b/pyomo/contrib/pynumero/examples/parallel_vector_ops.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 numpy as np from pyomo.common.dependencies import mpi4py from pyomo.contrib.pynumero.sparse.mpi_block_vector import MPIBlockVector diff --git a/pyomo/contrib/pynumero/examples/sqp.py b/pyomo/contrib/pynumero/examples/sqp.py index 7d321676817..15ad62670f2 100644 --- a/pyomo/contrib/pynumero/examples/sqp.py +++ b/pyomo/contrib/pynumero/examples/sqp.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.pynumero.interfaces.nlp import NLP from pyomo.contrib.pynumero.sparse import BlockVector, BlockMatrix from pyomo.contrib.pynumero.linalg.ma27_interface import MA27 diff --git a/pyomo/contrib/pynumero/examples/tests/__init__.py b/pyomo/contrib/pynumero/examples/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/pynumero/examples/tests/__init__.py +++ b/pyomo/contrib/pynumero/examples/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/pynumero/examples/tests/test_examples.py b/pyomo/contrib/pynumero/examples/tests/test_examples.py index 5c7993ebbb6..d4a5313908c 100644 --- a/pyomo/contrib/pynumero/examples/tests/test_examples.py +++ b/pyomo/contrib/pynumero/examples/tests/test_examples.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.pynumero.dependencies import numpy_available, scipy_available import pyomo.common.unittest as unittest diff --git a/pyomo/contrib/pynumero/examples/tests/test_mpi_examples.py b/pyomo/contrib/pynumero/examples/tests/test_mpi_examples.py index 68fe907a8ef..554305f23c9 100644 --- a/pyomo/contrib/pynumero/examples/tests/test_mpi_examples.py +++ b/pyomo/contrib/pynumero/examples/tests/test_mpi_examples.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common.unittest as unittest from pyomo.contrib.pynumero.dependencies import ( diff --git a/pyomo/contrib/pynumero/interfaces/nlp_projections.py b/pyomo/contrib/pynumero/interfaces/nlp_projections.py index 68cb0eef15f..3f4e8a88c60 100644 --- a/pyomo/contrib/pynumero/interfaces/nlp_projections.py +++ b/pyomo/contrib/pynumero/interfaces/nlp_projections.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.pynumero.interfaces.nlp import NLP, ExtendedNLP import numpy as np import scipy.sparse as sp diff --git a/pyomo/contrib/pynumero/interfaces/tests/external_grey_box_models.py b/pyomo/contrib/pynumero/interfaces/tests/external_grey_box_models.py index e65e9a7eb5c..d7ec499eaf9 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/external_grey_box_models.py +++ b/pyomo/contrib/pynumero/interfaces/tests/external_grey_box_models.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.pynumero.dependencies import ( numpy as np, numpy_available, diff --git a/pyomo/contrib/pynumero/linalg/base.py b/pyomo/contrib/pynumero/linalg/base.py index 2b4eeaef451..7f3d1ffa115 100644 --- a/pyomo/contrib/pynumero/linalg/base.py +++ b/pyomo/contrib/pynumero/linalg/base.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 abc import ABCMeta, abstractmethod import enum from typing import Optional, Union, Tuple diff --git a/pyomo/contrib/pynumero/linalg/ma27_interface.py b/pyomo/contrib/pynumero/linalg/ma27_interface.py index 1ae02fe3290..d974cfc1263 100644 --- a/pyomo/contrib/pynumero/linalg/ma27_interface.py +++ b/pyomo/contrib/pynumero/linalg/ma27_interface.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 .base import DirectLinearSolverInterface, LinearSolverStatus, LinearSolverResults from .ma27 import MA27Interface from scipy.sparse import isspmatrix_coo, tril, spmatrix diff --git a/pyomo/contrib/pynumero/linalg/ma57_interface.py b/pyomo/contrib/pynumero/linalg/ma57_interface.py index ef80ac653cf..dcd47795256 100644 --- a/pyomo/contrib/pynumero/linalg/ma57_interface.py +++ b/pyomo/contrib/pynumero/linalg/ma57_interface.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 .base import DirectLinearSolverInterface, LinearSolverStatus, LinearSolverResults from .ma57 import MA57Interface from scipy.sparse import isspmatrix_coo, tril, spmatrix diff --git a/pyomo/contrib/pynumero/linalg/scipy_interface.py b/pyomo/contrib/pynumero/linalg/scipy_interface.py index 819e22ff1aa..a5a53690eb0 100644 --- a/pyomo/contrib/pynumero/linalg/scipy_interface.py +++ b/pyomo/contrib/pynumero/linalg/scipy_interface.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 .base import ( DirectLinearSolverInterface, LinearSolverStatus, diff --git a/pyomo/contrib/pynumero/linalg/tests/__init__.py b/pyomo/contrib/pynumero/linalg/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/pynumero/linalg/tests/__init__.py +++ b/pyomo/contrib/pynumero/linalg/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/pynumero/linalg/tests/test_linear_solvers.py b/pyomo/contrib/pynumero/linalg/tests/test_linear_solvers.py index 8d19127dde6..d5025042d95 100644 --- a/pyomo/contrib/pynumero/linalg/tests/test_linear_solvers.py +++ b/pyomo/contrib/pynumero/linalg/tests/test_linear_solvers.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common import unittest from pyomo.contrib.pynumero.dependencies import numpy_available, scipy_available diff --git a/pyomo/contrib/pynumero/src/ma27Interface.cpp b/pyomo/contrib/pynumero/src/ma27Interface.cpp index 624c7edd6f3..29ffef73938 100644 --- a/pyomo/contrib/pynumero/src/ma27Interface.cpp +++ b/pyomo/contrib/pynumero/src/ma27Interface.cpp @@ -1,3 +1,15 @@ +/**___________________________________________________________________________ + * + * 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. + * ___________________________________________________________________________ +**/ + #include #include #include diff --git a/pyomo/contrib/pynumero/src/ma57Interface.cpp b/pyomo/contrib/pynumero/src/ma57Interface.cpp index 99b98ef6215..a0fb60edcc8 100644 --- a/pyomo/contrib/pynumero/src/ma57Interface.cpp +++ b/pyomo/contrib/pynumero/src/ma57Interface.cpp @@ -1,3 +1,15 @@ +/**___________________________________________________________________________ + * + * 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. + * ___________________________________________________________________________ +**/ + #include #include #include diff --git a/pyomo/contrib/pynumero/src/tests/simple_test.cpp b/pyomo/contrib/pynumero/src/tests/simple_test.cpp index 4edbbb67a35..30255912c1f 100644 --- a/pyomo/contrib/pynumero/src/tests/simple_test.cpp +++ b/pyomo/contrib/pynumero/src/tests/simple_test.cpp @@ -1,3 +1,15 @@ +/**___________________________________________________________________________ + * + * 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. + * ___________________________________________________________________________ +**/ + #include #include "AmplInterface.hpp" diff --git a/pyomo/contrib/pynumero/tests/__init__.py b/pyomo/contrib/pynumero/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/pynumero/tests/__init__.py +++ b/pyomo/contrib/pynumero/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/pyros/__init__.py b/pyomo/contrib/pyros/__init__.py index aeb92eb13fd..8ecd8ee7478 100644 --- a/pyomo/contrib/pyros/__init__.py +++ b/pyomo/contrib/pyros/__init__.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.pyros.pyros import PyROS from pyomo.contrib.pyros.pyros import ObjectiveType, pyrosTerminationCondition from pyomo.contrib.pyros.uncertainty_sets import ( diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index e2ce74a493e..a4b1785a987 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """ Functions for handling the construction and solving of the GRCS master problem via ROSolver """ diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index 4ae033b9498..61615652a01 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + ''' Methods for the execution of the grcs algorithm ''' diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index b9659f044f4..e37d3325a57 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """ Functions for the construction and solving of the GRCS separation problem via ROsolver """ diff --git a/pyomo/contrib/pyros/solve_data.py b/pyomo/contrib/pyros/solve_data.py index 40a52757bae..3ee22af9749 100644 --- a/pyomo/contrib/pyros/solve_data.py +++ b/pyomo/contrib/pyros/solve_data.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """ Objects to contain all model data and solve results for the ROSolver """ diff --git a/pyomo/contrib/pyros/tests/__init__.py b/pyomo/contrib/pyros/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/pyros/tests/__init__.py +++ b/pyomo/contrib/pyros/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 8de1c2666b9..c49c131fdf8 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + ''' Unit tests for the grcs API One class per function being tested, minimum one test per class diff --git a/pyomo/contrib/pyros/uncertainty_sets.py b/pyomo/contrib/pyros/uncertainty_sets.py index 1b51e41fcaf..7e13026b1e9 100644 --- a/pyomo/contrib/pyros/uncertainty_sets.py +++ b/pyomo/contrib/pyros/uncertainty_sets.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """ Abstract and pre-defined classes for representing uncertainty sets (or uncertain parameter spaces) of two-stage nonlinear robust optimization diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index e2986ae18c7..c5a80d27102 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + ''' Utility functions for the PyROS solver ''' diff --git a/pyomo/contrib/satsolver/__init__.py b/pyomo/contrib/satsolver/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/satsolver/__init__.py +++ b/pyomo/contrib/satsolver/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/satsolver/satsolver.py b/pyomo/contrib/satsolver/satsolver.py index 139b5218169..50e471253e2 100644 --- a/pyomo/contrib/satsolver/satsolver.py +++ b/pyomo/contrib/satsolver/satsolver.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 math from pyomo.common.dependencies import attempt_import diff --git a/pyomo/contrib/sensitivity_toolbox/__init__.py b/pyomo/contrib/sensitivity_toolbox/__init__.py index cac6562157e..feb094b6b76 100644 --- a/pyomo/contrib/sensitivity_toolbox/__init__.py +++ b/pyomo/contrib/sensitivity_toolbox/__init__.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects diff --git a/pyomo/contrib/sensitivity_toolbox/examples/__init__.py b/pyomo/contrib/sensitivity_toolbox/examples/__init__.py index 5223f39bbc1..d67dc03be6c 100644 --- a/pyomo/contrib/sensitivity_toolbox/examples/__init__.py +++ b/pyomo/contrib/sensitivity_toolbox/examples/__init__.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects diff --git a/pyomo/contrib/sensitivity_toolbox/examples/rooney_biegler.py b/pyomo/contrib/sensitivity_toolbox/examples/rooney_biegler.py index f058e8189dc..350860a0b50 100644 --- a/pyomo/contrib/sensitivity_toolbox/examples/rooney_biegler.py +++ b/pyomo/contrib/sensitivity_toolbox/examples/rooney_biegler.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + ############################################################################## # Institute for the Design of Advanced Energy Systems Process Systems # Engineering Framework (IDAES PSE Framework) Copyright (c) 2018-2019, by the diff --git a/pyomo/contrib/sensitivity_toolbox/k_aug.py b/pyomo/contrib/sensitivity_toolbox/k_aug.py index 8d739506492..e7ccb4960a5 100644 --- a/pyomo/contrib/sensitivity_toolbox/k_aug.py +++ b/pyomo/contrib/sensitivity_toolbox/k_aug.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ______________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects diff --git a/pyomo/contrib/sensitivity_toolbox/sens.py b/pyomo/contrib/sensitivity_toolbox/sens.py index e1c69d75974..43279c7bc5e 100644 --- a/pyomo/contrib/sensitivity_toolbox/sens.py +++ b/pyomo/contrib/sensitivity_toolbox/sens.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ______________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects diff --git a/pyomo/contrib/sensitivity_toolbox/tests/__init__.py b/pyomo/contrib/sensitivity_toolbox/tests/__init__.py index 557846ee521..5aecf9868db 100644 --- a/pyomo/contrib/sensitivity_toolbox/tests/__init__.py +++ b/pyomo/contrib/sensitivity_toolbox/tests/__init__.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects diff --git a/pyomo/contrib/sensitivity_toolbox/tests/test_k_aug_interface.py b/pyomo/contrib/sensitivity_toolbox/tests/test_k_aug_interface.py index 8c14cfc91d0..cb219f4f403 100644 --- a/pyomo/contrib/sensitivity_toolbox/tests/test_k_aug_interface.py +++ b/pyomo/contrib/sensitivity_toolbox/tests/test_k_aug_interface.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ____________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects diff --git a/pyomo/contrib/sensitivity_toolbox/tests/test_sens.py b/pyomo/contrib/sensitivity_toolbox/tests/test_sens.py index f4b3fb5548c..76d180ae422 100644 --- a/pyomo/contrib/sensitivity_toolbox/tests/test_sens.py +++ b/pyomo/contrib/sensitivity_toolbox/tests/test_sens.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ____________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects diff --git a/pyomo/contrib/sensitivity_toolbox/tests/test_sens_unit.py b/pyomo/contrib/sensitivity_toolbox/tests/test_sens_unit.py index 05faada3007..d6da0d814e8 100644 --- a/pyomo/contrib/sensitivity_toolbox/tests/test_sens_unit.py +++ b/pyomo/contrib/sensitivity_toolbox/tests/test_sens_unit.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ____________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects diff --git a/pyomo/contrib/viewer/__init__.py b/pyomo/contrib/viewer/__init__.py index 8b137891791..d93cfd77b3c 100644 --- a/pyomo/contrib/viewer/__init__.py +++ b/pyomo/contrib/viewer/__init__.py @@ -1 +1,10 @@ - +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/viewer/tests/__init__.py b/pyomo/contrib/viewer/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/contrib/viewer/tests/__init__.py +++ b/pyomo/contrib/viewer/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/core/expr/calculus/__init__.py b/pyomo/core/expr/calculus/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/core/expr/calculus/__init__.py +++ b/pyomo/core/expr/calculus/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/core/expr/taylor_series.py b/pyomo/core/expr/taylor_series.py index 2c72f8bcfbc..467b1faa679 100644 --- a/pyomo/core/expr/taylor_series.py +++ b/pyomo/core/expr/taylor_series.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core.expr import identify_variables, value, differentiate import logging import math diff --git a/pyomo/core/plugins/transform/logical_to_linear.py b/pyomo/core/plugins/transform/logical_to_linear.py index f4107b8a32c..f2c609348e5 100644 --- a/pyomo/core/plugins/transform/logical_to_linear.py +++ b/pyomo/core/plugins/transform/logical_to_linear.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """Transformation from BooleanVar and LogicalConstraint to Binary and Constraints.""" diff --git a/pyomo/core/tests/unit/test_logical_constraint.py b/pyomo/core/tests/unit/test_logical_constraint.py index ed8120da935..e38a67a39d0 100644 --- a/pyomo/core/tests/unit/test_logical_constraint.py +++ b/pyomo/core/tests/unit/test_logical_constraint.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common.unittest as unittest from pyomo.core.expr.sympy_tools import sympy_available diff --git a/pyomo/core/tests/unit/test_sos_v2.py b/pyomo/core/tests/unit/test_sos_v2.py index 8b6fab549a2..4f4599056b5 100644 --- a/pyomo/core/tests/unit/test_sos_v2.py +++ b/pyomo/core/tests/unit/test_sos_v2.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # ***************************************************************************** # ***************************************************************************** diff --git a/pyomo/dae/simulator.py b/pyomo/dae/simulator.py index b869592553a..149c42ca6b4 100644 --- a/pyomo/dae/simulator.py +++ b/pyomo/dae/simulator.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # _________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects diff --git a/pyomo/gdp/tests/models.py b/pyomo/gdp/tests/models.py index a52f08b790e..f03a847162b 100644 --- a/pyomo/gdp/tests/models.py +++ b/pyomo/gdp/tests/models.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.core import ( Block, ConcreteModel, diff --git a/pyomo/gdp/tests/test_reclassify.py b/pyomo/gdp/tests/test_reclassify.py index fd98f8f0954..dcf3470a211 100644 --- a/pyomo/gdp/tests/test_reclassify.py +++ b/pyomo/gdp/tests/test_reclassify.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: UTF-8 -*- """Tests disjunct reclassifier transformation.""" import pyomo.common.unittest as unittest diff --git a/pyomo/repn/tests/ampl/__init__.py b/pyomo/repn/tests/ampl/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/repn/tests/ampl/__init__.py +++ b/pyomo/repn/tests/ampl/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/solvers/plugins/solvers/XPRESS.py b/pyomo/solvers/plugins/solvers/XPRESS.py index 6ab51cfbbf3..7b85aea1266 100644 --- a/pyomo/solvers/plugins/solvers/XPRESS.py +++ b/pyomo/solvers/plugins/solvers/XPRESS.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.opt.base import OptSolver from pyomo.opt.base.solvers import SolverFactory import logging diff --git a/pyomo/solvers/tests/checks/test_MOSEKPersistent.py b/pyomo/solvers/tests/checks/test_MOSEKPersistent.py index 59ea930c4f0..6db99919177 100644 --- a/pyomo/solvers/tests/checks/test_MOSEKPersistent.py +++ b/pyomo/solvers/tests/checks/test_MOSEKPersistent.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common.unittest as unittest from pyomo.opt import ( diff --git a/pyomo/solvers/tests/checks/test_gurobi.py b/pyomo/solvers/tests/checks/test_gurobi.py index f33a00ce8a2..cfd0f077eab 100644 --- a/pyomo/solvers/tests/checks/test_gurobi.py +++ b/pyomo/solvers/tests/checks/test_gurobi.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common.unittest as unittest from unittest.mock import patch, MagicMock diff --git a/pyomo/solvers/tests/checks/test_gurobi_direct.py b/pyomo/solvers/tests/checks/test_gurobi_direct.py index 7c60b207a9f..d9802894c47 100644 --- a/pyomo/solvers/tests/checks/test_gurobi_direct.py +++ b/pyomo/solvers/tests/checks/test_gurobi_direct.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + """ Tests for working with Gurobi environments. Some require a single-use license and are skipped if this isn't the case. diff --git a/pyomo/solvers/tests/checks/test_xpress_persistent.py b/pyomo/solvers/tests/checks/test_xpress_persistent.py index cd9c30fc73b..abfcf9c0afc 100644 --- a/pyomo/solvers/tests/checks/test_xpress_persistent.py +++ b/pyomo/solvers/tests/checks/test_xpress_persistent.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common.unittest as unittest import pyomo.environ as pe from pyomo.core.expr.taylor_series import taylor_series_expansion diff --git a/pyomo/solvers/tests/mip/test_scip_log_data.py b/pyomo/solvers/tests/mip/test_scip_log_data.py index 8f756de220a..0dc0825afb3 100644 --- a/pyomo/solvers/tests/mip/test_scip_log_data.py +++ b/pyomo/solvers/tests/mip/test_scip_log_data.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ diff --git a/pyomo/util/__init__.py b/pyomo/util/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/util/__init__.py +++ b/pyomo/util/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/pyomo/util/diagnostics.py b/pyomo/util/diagnostics.py index d4b7974b9da..8bad078ad64 100644 --- a/pyomo/util/diagnostics.py +++ b/pyomo/util/diagnostics.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # -*- coding: UTF-8 -*- """Module with miscellaneous diagnostic tools""" from pyomo.core.base.block import TraversalStrategy, Block diff --git a/pyomo/util/tests/__init__.py b/pyomo/util/tests/__init__.py index e69de29bb2d..d93cfd77b3c 100644 --- a/pyomo/util/tests/__init__.py +++ b/pyomo/util/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ diff --git a/scripts/performance/compare_components.py b/scripts/performance/compare_components.py index f390fad8454..1edaa73003b 100644 --- a/scripts/performance/compare_components.py +++ b/scripts/performance/compare_components.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # # This script compares build time and memory usage for # various modeling objects. The output is organized into diff --git a/scripts/performance/expr_perf.py b/scripts/performance/expr_perf.py index 6566431b9f3..6f0d246e1f3 100644 --- a/scripts/performance/expr_perf.py +++ b/scripts/performance/expr_perf.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + # # This script runs performance tests on expressions # diff --git a/scripts/performance/simple.py b/scripts/performance/simple.py index 2990f13f413..bd5ffd99368 100644 --- a/scripts/performance/simple.py +++ b/scripts/performance/simple.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.environ import * import pyomo.core.expr.current as EXPR import timeit From 0b7857475de8741196191e34147760175649957e Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 15 Feb 2024 13:21:48 -0700 Subject: [PATCH 0988/1204] Apply black to doc changes --- pyomo/contrib/solver/base.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 98663d85501..1cd9db2baa9 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -46,11 +46,11 @@ class SolverBase(abc.ABC): - version: The version of the solver - is_persistent: Set to false for all non-persistent solvers. - Additionally, solvers should have a :attr:`config` attribute that - inherits from one of :class:`SolverConfig`, - :class:`BranchAndBoundConfig`, - :class:`PersistentSolverConfig`, or - :class:`PersistentBranchAndBoundConfig`. + Additionally, solvers should have a :attr:`config` attribute that + inherits from one of :class:`SolverConfig`, + :class:`BranchAndBoundConfig`, + :class:`PersistentSolverConfig`, or + :class:`PersistentBranchAndBoundConfig`. """ CONFIG = SolverConfig() @@ -105,9 +105,7 @@ def __str__(self): return self.name @abc.abstractmethod - def solve( - self, model: _BlockData, **kwargs - ) -> Results: + def solve(self, model: _BlockData, **kwargs) -> Results: """ Solve a Pyomo model. From 928018003a010a31a36afe62c822361d27569d32 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 15 Feb 2024 13:44:09 -0700 Subject: [PATCH 0989/1204] Switch APPSI/contrib.solver registrations to use "local" (unqualified) names for solvers --- pyomo/contrib/appsi/base.py | 2 +- pyomo/contrib/appsi/plugins.py | 10 +++++----- pyomo/contrib/solver/factory.py | 7 +++++-- pyomo/contrib/solver/plugins.py | 10 ++++++---- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index e6186eeedd2..941883ab997 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -1685,7 +1685,7 @@ def decorator(cls): class LegacySolver(LegacySolverInterface, cls): pass - LegacySolverFactory.register(name, doc)(LegacySolver) + LegacySolverFactory.register('appsi_' + name, doc)(LegacySolver) return cls diff --git a/pyomo/contrib/appsi/plugins.py b/pyomo/contrib/appsi/plugins.py index 5333158239e..cec95337a9b 100644 --- a/pyomo/contrib/appsi/plugins.py +++ b/pyomo/contrib/appsi/plugins.py @@ -7,17 +7,17 @@ def load(): ExtensionBuilderFactory.register('appsi')(AppsiBuilder) SolverFactory.register( - name='appsi_gurobi', doc='Automated persistent interface to Gurobi' + name='gurobi', doc='Automated persistent interface to Gurobi' )(Gurobi) SolverFactory.register( - name='appsi_cplex', doc='Automated persistent interface to Cplex' + name='cplex', doc='Automated persistent interface to Cplex' )(Cplex) SolverFactory.register( - name='appsi_ipopt', doc='Automated persistent interface to Ipopt' + name='ipopt', doc='Automated persistent interface to Ipopt' )(Ipopt) SolverFactory.register( - name='appsi_cbc', doc='Automated persistent interface to Cbc' + name='cbc', doc='Automated persistent interface to Cbc' )(Cbc) SolverFactory.register( - name='appsi_highs', doc='Automated persistent interface to Highs' + name='highs', doc='Automated persistent interface to Highs' )(Highs) diff --git a/pyomo/contrib/solver/factory.py b/pyomo/contrib/solver/factory.py index e499605afd4..cdd042f9e78 100644 --- a/pyomo/contrib/solver/factory.py +++ b/pyomo/contrib/solver/factory.py @@ -16,7 +16,10 @@ class SolverFactoryClass(Factory): - def register(self, name, doc=None): + def register(self, name, legacy_name=None, doc=None): + if legacy_name is None: + legacy_name = name + def decorator(cls): self._cls[name] = cls self._doc[name] = doc @@ -24,7 +27,7 @@ def decorator(cls): class LegacySolver(LegacySolverWrapper, cls): pass - LegacySolverFactory.register(name, doc)(LegacySolver) + LegacySolverFactory.register(legacy_name, doc)(LegacySolver) return cls diff --git a/pyomo/contrib/solver/plugins.py b/pyomo/contrib/solver/plugins.py index e66818482b4..7d984d10eaa 100644 --- a/pyomo/contrib/solver/plugins.py +++ b/pyomo/contrib/solver/plugins.py @@ -16,7 +16,9 @@ def load(): - SolverFactory.register(name='ipopt_v2', doc='The IPOPT NLP solver (new interface)')( - ipopt - ) - SolverFactory.register(name='gurobi_v2', doc='New interface to Gurobi')(Gurobi) + SolverFactory.register( + name='ipopt', legacy_name='ipopt_v2', doc='The IPOPT NLP solver (new interface)' + )(ipopt) + SolverFactory.register( + name='gurobi', legacy_name='gurobi_v2', doc='New interface to Gurobi' + )(Gurobi) From 316fb3faa4c0faa37a095eceddd60b36957e9ee4 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 15 Feb 2024 13:45:37 -0700 Subject: [PATCH 0990/1204] Add __future__ mechanism for switching solver factories --- pyomo/__future__.py | 69 +++++++++++++++++++++++++++++++++ pyomo/contrib/solver/factory.py | 2 +- pyomo/opt/base/solvers.py | 4 ++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 pyomo/__future__.py diff --git a/pyomo/__future__.py b/pyomo/__future__.py new file mode 100644 index 00000000000..7028265b2ad --- /dev/null +++ b/pyomo/__future__.py @@ -0,0 +1,69 @@ +# ___________________________________________________________________________ +# +# 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.environ as _environ + + +def __getattr__(name): + if name in ('solver_factory_v1', 'solver_factory_v2', 'solver_factory_v3'): + return solver_factory(int(name[-1])) + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") + + +def solver_factory(version=None): + """Get (or set) the active implementation of the SolverFactory + + This allows users to query / set the current implementation of the + SolverFactory that should be used throughout Pyomo. Valid options are: + + 1: the original Pyomo SolverFactor + 2: the SolverFactory from APPSI + 3: the SolverFactory from pyomo.contrib.solver + + """ + import pyomo.opt.base.solvers as _solvers + import pyomo.contrib.solver.factory as _contrib + import pyomo.contrib.appsi.base as _appsi + versions = { + 1: _solvers.LegacySolverFactory, + 2: _appsi.SolverFactory, + 3: _contrib.SolverFactory, + } + + current = getattr(solver_factory, '_active_version', None) + # First time through, _active_version is not defined. Go look and + # see what it was initialized to in pyomo.environ + if current is None: + for ver, cls in versions.items(): + if cls._cls is _environ.SolverFactory._cls: + solver_factory._active_version = ver + break + return solver_factory._active_version + # + # The user is just asking what the current SolverFactory is; tell them. + if version is None: + return solver_factory._active_version + # + # Update the current SolverFactory to be a shim around (shallow copy + # of) the new active factory + src = versions.get(version, None) + if version is not None: + solver_factory._active_version = version + for attr in ('_description', '_cls', '_doc'): + setattr(_environ.SolverFactory, attr, getattr(src, attr)) + else: + raise ValueError( + "Invalid value for target solver factory version; expected {1, 2, 3}, " + f"received {version}" + ) + return src + +solver_factory._active_version = solver_factory() diff --git a/pyomo/contrib/solver/factory.py b/pyomo/contrib/solver/factory.py index cdd042f9e78..73666ff57e4 100644 --- a/pyomo/contrib/solver/factory.py +++ b/pyomo/contrib/solver/factory.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ -from pyomo.opt.base import SolverFactory as LegacySolverFactory +from pyomo.opt.base import LegacySolverFactory from pyomo.common.factory import Factory from pyomo.contrib.solver.base import LegacySolverWrapper diff --git a/pyomo/opt/base/solvers.py b/pyomo/opt/base/solvers.py index b11e6393b02..439dda55b57 100644 --- a/pyomo/opt/base/solvers.py +++ b/pyomo/opt/base/solvers.py @@ -181,7 +181,11 @@ def __call__(self, _name=None, **kwds): return opt +LegacySolverFactory = SolverFactoryClass('solver type') + SolverFactory = SolverFactoryClass('solver type') +SolverFactory._cls = LegacySolverFactory._cls +SolverFactory._doc = LegacySolverFactory._doc # From 0e3e7df49080bc115f34cc6c7682f82b86def763 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 15 Feb 2024 13:55:53 -0700 Subject: [PATCH 0991/1204] Update years on allyear on all copyright statements --- LICENSE.md | 2 +- conftest.py | 2 +- doc/OnlineDocs/conf.py | 2 +- .../library_reference/kernel/examples/aml_example.py | 2 +- doc/OnlineDocs/library_reference/kernel/examples/conic.py | 2 +- .../library_reference/kernel/examples/kernel_containers.py | 2 +- .../library_reference/kernel/examples/kernel_example.py | 2 +- .../library_reference/kernel/examples/kernel_solving.py | 2 +- .../library_reference/kernel/examples/kernel_subclassing.py | 2 +- .../library_reference/kernel/examples/transformer.py | 2 +- doc/OnlineDocs/modeling_extensions/__init__.py | 2 +- doc/OnlineDocs/src/data/ABCD1.py | 2 +- doc/OnlineDocs/src/data/ABCD2.py | 2 +- doc/OnlineDocs/src/data/ABCD3.py | 2 +- doc/OnlineDocs/src/data/ABCD4.py | 2 +- doc/OnlineDocs/src/data/ABCD5.py | 2 +- doc/OnlineDocs/src/data/ABCD6.py | 2 +- doc/OnlineDocs/src/data/ABCD7.py | 2 +- doc/OnlineDocs/src/data/ABCD8.py | 2 +- doc/OnlineDocs/src/data/ABCD9.py | 2 +- doc/OnlineDocs/src/data/diet1.py | 2 +- doc/OnlineDocs/src/data/ex.py | 2 +- doc/OnlineDocs/src/data/import1.tab.py | 2 +- doc/OnlineDocs/src/data/import2.tab.py | 2 +- doc/OnlineDocs/src/data/import3.tab.py | 2 +- doc/OnlineDocs/src/data/import4.tab.py | 2 +- doc/OnlineDocs/src/data/import5.tab.py | 2 +- doc/OnlineDocs/src/data/import6.tab.py | 2 +- doc/OnlineDocs/src/data/import7.tab.py | 2 +- doc/OnlineDocs/src/data/import8.tab.py | 2 +- doc/OnlineDocs/src/data/param1.py | 2 +- doc/OnlineDocs/src/data/param2.py | 2 +- doc/OnlineDocs/src/data/param2a.py | 2 +- doc/OnlineDocs/src/data/param3.py | 2 +- doc/OnlineDocs/src/data/param3a.py | 2 +- doc/OnlineDocs/src/data/param3b.py | 2 +- doc/OnlineDocs/src/data/param3c.py | 2 +- doc/OnlineDocs/src/data/param4.py | 2 +- doc/OnlineDocs/src/data/param5.py | 2 +- doc/OnlineDocs/src/data/param5a.py | 2 +- doc/OnlineDocs/src/data/param6.py | 2 +- doc/OnlineDocs/src/data/param6a.py | 2 +- doc/OnlineDocs/src/data/param7a.py | 2 +- doc/OnlineDocs/src/data/param7b.py | 2 +- doc/OnlineDocs/src/data/param8a.py | 2 +- doc/OnlineDocs/src/data/set1.py | 2 +- doc/OnlineDocs/src/data/set2.py | 2 +- doc/OnlineDocs/src/data/set2a.py | 2 +- doc/OnlineDocs/src/data/set3.py | 2 +- doc/OnlineDocs/src/data/set4.py | 2 +- doc/OnlineDocs/src/data/set5.py | 2 +- doc/OnlineDocs/src/data/table0.py | 2 +- doc/OnlineDocs/src/data/table0.ul.py | 2 +- doc/OnlineDocs/src/data/table1.py | 2 +- doc/OnlineDocs/src/data/table2.py | 2 +- doc/OnlineDocs/src/data/table3.py | 2 +- doc/OnlineDocs/src/data/table3.ul.py | 2 +- doc/OnlineDocs/src/data/table4.py | 2 +- doc/OnlineDocs/src/data/table4.ul.py | 2 +- doc/OnlineDocs/src/data/table5.py | 2 +- doc/OnlineDocs/src/data/table6.py | 2 +- doc/OnlineDocs/src/data/table7.py | 2 +- doc/OnlineDocs/src/dataportal/PP_sqlite.py | 2 +- doc/OnlineDocs/src/dataportal/dataportal_tab.py | 2 +- doc/OnlineDocs/src/dataportal/param_initialization.py | 2 +- doc/OnlineDocs/src/dataportal/set_initialization.py | 2 +- doc/OnlineDocs/src/expr/design.py | 2 +- doc/OnlineDocs/src/expr/index.py | 2 +- doc/OnlineDocs/src/expr/managing.py | 2 +- doc/OnlineDocs/src/expr/overview.py | 2 +- doc/OnlineDocs/src/expr/performance.py | 2 +- doc/OnlineDocs/src/expr/quicksum.py | 2 +- doc/OnlineDocs/src/scripting/AbstractSuffixes.py | 2 +- doc/OnlineDocs/src/scripting/Isinglebuild.py | 2 +- doc/OnlineDocs/src/scripting/NodesIn_init.py | 2 +- doc/OnlineDocs/src/scripting/Z_init.py | 2 +- doc/OnlineDocs/src/scripting/abstract2.py | 2 +- doc/OnlineDocs/src/scripting/abstract2piece.py | 2 +- doc/OnlineDocs/src/scripting/abstract2piecebuild.py | 2 +- doc/OnlineDocs/src/scripting/block_iter_example.py | 2 +- doc/OnlineDocs/src/scripting/concrete1.py | 2 +- doc/OnlineDocs/src/scripting/doubleA.py | 2 +- doc/OnlineDocs/src/scripting/driveabs2.py | 2 +- doc/OnlineDocs/src/scripting/driveconc1.py | 2 +- doc/OnlineDocs/src/scripting/iterative1.py | 2 +- doc/OnlineDocs/src/scripting/iterative2.py | 2 +- doc/OnlineDocs/src/scripting/noiteration1.py | 2 +- doc/OnlineDocs/src/scripting/parallel.py | 2 +- doc/OnlineDocs/src/scripting/spy4Constraints.py | 2 +- doc/OnlineDocs/src/scripting/spy4Expressions.py | 2 +- doc/OnlineDocs/src/scripting/spy4PyomoCommand.py | 2 +- doc/OnlineDocs/src/scripting/spy4Variables.py | 2 +- doc/OnlineDocs/src/scripting/spy4scripts.py | 2 +- doc/OnlineDocs/src/strip_examples.py | 2 +- doc/OnlineDocs/src/test_examples.py | 2 +- examples/dae/Heat_Conduction.py | 2 +- examples/dae/Optimal_Control.py | 2 +- examples/dae/PDE_example.py | 2 +- examples/dae/Parameter_Estimation.py | 2 +- examples/dae/Path_Constraint.py | 2 +- examples/dae/ReactionKinetics.py | 2 +- examples/dae/car_example.py | 2 +- examples/dae/disease_DAE.py | 2 +- examples/dae/distill_DAE.py | 2 +- examples/dae/dynamic_scheduling.py | 2 +- examples/dae/laplace_BVP.py | 2 +- examples/dae/run_Optimal_Control.py | 2 +- examples/dae/run_Parameter_Estimation.py | 2 +- examples/dae/run_Path_Constraint.py | 2 +- examples/dae/run_disease.py | 2 +- examples/dae/run_distill.py | 2 +- examples/dae/run_stochpdegas_automatic.py | 2 +- examples/dae/simulator_dae_example.py | 2 +- examples/dae/simulator_dae_multindex_example.py | 2 +- examples/dae/simulator_ode_example.py | 2 +- examples/dae/simulator_ode_multindex_example.py | 2 +- examples/dae/stochpdegas_automatic.py | 2 +- examples/doc/samples/__init__.py | 2 +- examples/doc/samples/case_studies/deer/DeerProblem.py | 2 +- examples/doc/samples/case_studies/diet/DietProblem.py | 2 +- .../doc/samples/case_studies/disease_est/DiseaseEstimation.py | 2 +- examples/doc/samples/case_studies/max_flow/MaxFlow.py | 2 +- .../doc/samples/case_studies/network_flow/networkFlow1.py | 2 +- examples/doc/samples/case_studies/rosen/Rosenbrock.py | 2 +- .../doc/samples/case_studies/transportation/transportation.py | 2 +- examples/doc/samples/comparisons/cutstock/cutstock_cplex.py | 2 +- examples/doc/samples/comparisons/cutstock/cutstock_grb.py | 2 +- examples/doc/samples/comparisons/cutstock/cutstock_lpsolve.py | 2 +- examples/doc/samples/comparisons/cutstock/cutstock_pulpor.py | 2 +- examples/doc/samples/comparisons/cutstock/cutstock_pyomo.py | 2 +- examples/doc/samples/comparisons/cutstock/cutstock_util.py | 2 +- examples/doc/samples/comparisons/sched/pyomo/sched.py | 2 +- examples/doc/samples/scripts/__init__.py | 2 +- examples/doc/samples/scripts/s1/knapsack.py | 2 +- examples/doc/samples/scripts/s1/script.py | 2 +- examples/doc/samples/scripts/s2/knapsack.py | 2 +- examples/doc/samples/scripts/s2/script.py | 2 +- examples/doc/samples/scripts/test_scripts.py | 2 +- examples/doc/samples/update.py | 2 +- examples/gdp/batchProcessing.py | 2 +- examples/gdp/circles/circles.py | 2 +- examples/gdp/constrained_layout/cons_layout_model.py | 2 +- examples/gdp/disease_model.py | 2 +- examples/gdp/eight_process/eight_proc_logical.py | 2 +- examples/gdp/eight_process/eight_proc_model.py | 2 +- examples/gdp/eight_process/eight_proc_verbose_model.py | 2 +- examples/gdp/farm_layout/farm_layout.py | 2 +- examples/gdp/jobshop-nodisjuncts.py | 2 +- examples/gdp/jobshop.py | 2 +- examples/gdp/medTermPurchasing_Literal.py | 2 +- examples/gdp/nine_process/small_process.py | 2 +- examples/gdp/simple1.py | 2 +- examples/gdp/simple2.py | 2 +- examples/gdp/simple3.py | 2 +- examples/gdp/small_lit/basic_step.py | 2 +- examples/gdp/small_lit/contracts_problem.py | 2 +- examples/gdp/small_lit/ex1_Lee.py | 2 +- examples/gdp/small_lit/ex_633_trespalacios.py | 2 +- examples/gdp/small_lit/nonconvex_HEN.py | 2 +- examples/gdp/stickies.py | 2 +- examples/gdp/strip_packing/stripPacking.py | 2 +- examples/gdp/strip_packing/strip_packing_8rect.py | 2 +- examples/gdp/strip_packing/strip_packing_concrete.py | 2 +- examples/gdp/two_rxn_lee/two_rxn_model.py | 2 +- examples/kernel/blocks.py | 2 +- examples/kernel/conic.py | 2 +- examples/kernel/constraints.py | 2 +- examples/kernel/containers.py | 2 +- examples/kernel/expressions.py | 2 +- examples/kernel/mosek/geometric1.py | 2 +- examples/kernel/mosek/geometric2.py | 2 +- examples/kernel/mosek/maximum_volume_cuboid.py | 2 +- examples/kernel/mosek/power1.py | 2 +- examples/kernel/mosek/semidefinite.py | 2 +- examples/kernel/objectives.py | 2 +- examples/kernel/parameters.py | 2 +- examples/kernel/piecewise_functions.py | 2 +- examples/kernel/piecewise_nd_functions.py | 2 +- examples/kernel/special_ordered_sets.py | 2 +- examples/kernel/suffixes.py | 2 +- examples/kernel/variables.py | 2 +- examples/mpec/bard1.py | 2 +- examples/mpec/df.py | 2 +- examples/mpec/indexed.py | 2 +- examples/mpec/linear1.py | 2 +- examples/mpec/munson1.py | 2 +- examples/mpec/munson1a.py | 2 +- examples/mpec/munson1b.py | 2 +- examples/mpec/munson1c.py | 2 +- examples/mpec/munson1d.py | 2 +- examples/mpec/scholtes4.py | 2 +- examples/performance/dae/run_stochpdegas1_automatic.py | 2 +- examples/performance/dae/stochpdegas1_automatic.py | 2 +- examples/performance/jump/clnlbeam.py | 2 +- examples/performance/jump/facility.py | 2 +- examples/performance/jump/lqcp.py | 2 +- examples/performance/jump/opf_66200bus.py | 2 +- examples/performance/jump/opf_6620bus.py | 2 +- examples/performance/jump/opf_662bus.py | 2 +- examples/performance/misc/bilinear1_100.py | 2 +- examples/performance/misc/bilinear1_100000.py | 2 +- examples/performance/misc/bilinear2_100.py | 2 +- examples/performance/misc/bilinear2_100000.py | 2 +- examples/performance/misc/diag1_100.py | 2 +- examples/performance/misc/diag1_100000.py | 2 +- examples/performance/misc/diag2_100.py | 2 +- examples/performance/misc/diag2_100000.py | 2 +- examples/performance/misc/set1.py | 2 +- examples/performance/misc/sparse1.py | 2 +- examples/performance/pmedian/pmedian1.py | 2 +- examples/performance/pmedian/pmedian2.py | 2 +- examples/pyomo/amplbook2/diet.py | 2 +- examples/pyomo/amplbook2/dieti.py | 2 +- examples/pyomo/amplbook2/econ2min.py | 2 +- examples/pyomo/amplbook2/econmin.py | 2 +- examples/pyomo/amplbook2/prod.py | 2 +- examples/pyomo/amplbook2/steel.py | 2 +- examples/pyomo/amplbook2/steel3.py | 2 +- examples/pyomo/amplbook2/steel4.py | 2 +- examples/pyomo/benders/master.py | 2 +- examples/pyomo/benders/subproblem.py | 2 +- examples/pyomo/callbacks/sc.py | 2 +- examples/pyomo/callbacks/sc_callback.py | 2 +- examples/pyomo/callbacks/sc_script.py | 2 +- examples/pyomo/callbacks/scalability/run.py | 2 +- examples/pyomo/callbacks/tsp.py | 2 +- examples/pyomo/columngeneration/cutting_stock.py | 2 +- examples/pyomo/concrete/Whiskas.py | 2 +- examples/pyomo/concrete/knapsack-abstract.py | 2 +- examples/pyomo/concrete/knapsack-concrete.py | 2 +- examples/pyomo/concrete/rosen.py | 2 +- examples/pyomo/concrete/sodacan.py | 2 +- examples/pyomo/concrete/sodacan_fig.py | 2 +- examples/pyomo/concrete/sp.py | 2 +- examples/pyomo/concrete/sp_data.py | 2 +- examples/pyomo/connectors/network_flow.py | 2 +- examples/pyomo/connectors/network_flow_proposed.py | 2 +- examples/pyomo/core/block1.py | 2 +- examples/pyomo/core/integrality1.py | 2 +- examples/pyomo/core/integrality2.py | 2 +- examples/pyomo/core/simple.py | 2 +- examples/pyomo/core/t1.py | 2 +- examples/pyomo/core/t2.py | 2 +- examples/pyomo/core/t5.py | 2 +- examples/pyomo/diet/diet-sqlite.py | 2 +- examples/pyomo/diet/diet1.py | 2 +- examples/pyomo/diet/diet2.py | 2 +- examples/pyomo/draft/api.py | 2 +- examples/pyomo/draft/bpack.py | 2 +- examples/pyomo/draft/diet2.py | 2 +- examples/pyomo/p-median/decorated_pmedian.py | 2 +- examples/pyomo/p-median/pmedian.py | 2 +- examples/pyomo/p-median/solver1.py | 2 +- examples/pyomo/p-median/solver2.py | 2 +- examples/pyomo/piecewise/convex.py | 2 +- examples/pyomo/piecewise/indexed.py | 2 +- examples/pyomo/piecewise/indexed_nonlinear.py | 2 +- examples/pyomo/piecewise/indexed_points.py | 2 +- examples/pyomo/piecewise/nonconvex.py | 2 +- examples/pyomo/piecewise/points.py | 2 +- examples/pyomo/piecewise/step.py | 2 +- examples/pyomo/quadratic/example1.py | 2 +- examples/pyomo/quadratic/example2.py | 2 +- examples/pyomo/quadratic/example3.py | 2 +- examples/pyomo/quadratic/example4.py | 2 +- examples/pyomo/radertext/Ex2_1.py | 2 +- examples/pyomo/radertext/Ex2_2.py | 2 +- examples/pyomo/radertext/Ex2_3.py | 2 +- examples/pyomo/radertext/Ex2_5.py | 2 +- examples/pyomo/radertext/Ex2_6a.py | 2 +- examples/pyomo/radertext/Ex2_6b.py | 2 +- examples/pyomo/sos/DepotSiting.py | 2 +- examples/pyomo/sos/basic_sos2_example.py | 2 +- examples/pyomo/sos/sos2_piecewise.py | 2 +- examples/pyomo/suffixes/duals_pyomo.py | 2 +- examples/pyomo/suffixes/duals_script.py | 2 +- examples/pyomo/suffixes/gurobi_ampl_basis.py | 2 +- examples/pyomo/suffixes/gurobi_ampl_example.py | 2 +- examples/pyomo/suffixes/gurobi_ampl_iis.py | 2 +- examples/pyomo/suffixes/ipopt_scaling.py | 2 +- examples/pyomo/suffixes/ipopt_warmstart.py | 2 +- examples/pyomo/suffixes/sipopt_hicks.py | 2 +- examples/pyomo/suffixes/sipopt_parametric.py | 2 +- examples/pyomo/transform/scaling_ex.py | 2 +- examples/pyomo/tutorials/data.py | 2 +- examples/pyomo/tutorials/excel.py | 2 +- examples/pyomo/tutorials/param.py | 2 +- examples/pyomo/tutorials/set.py | 2 +- examples/pyomo/tutorials/table.py | 2 +- examples/pyomobook/__init__.py | 2 +- examples/pyomobook/abstract-ch/AbstHLinScript.py | 2 +- examples/pyomobook/abstract-ch/AbstractH.py | 2 +- examples/pyomobook/abstract-ch/AbstractHLinear.py | 2 +- examples/pyomobook/abstract-ch/abstract5.py | 2 +- examples/pyomobook/abstract-ch/abstract6.py | 2 +- examples/pyomobook/abstract-ch/abstract7.py | 2 +- examples/pyomobook/abstract-ch/buildactions.py | 2 +- examples/pyomobook/abstract-ch/concrete1.py | 2 +- examples/pyomobook/abstract-ch/concrete2.py | 2 +- examples/pyomobook/abstract-ch/diet1.py | 2 +- examples/pyomobook/abstract-ch/ex.py | 2 +- examples/pyomobook/abstract-ch/param1.py | 2 +- examples/pyomobook/abstract-ch/param2.py | 2 +- examples/pyomobook/abstract-ch/param2a.py | 2 +- examples/pyomobook/abstract-ch/param3.py | 2 +- examples/pyomobook/abstract-ch/param3a.py | 2 +- examples/pyomobook/abstract-ch/param3b.py | 2 +- examples/pyomobook/abstract-ch/param3c.py | 2 +- examples/pyomobook/abstract-ch/param4.py | 2 +- examples/pyomobook/abstract-ch/param5.py | 2 +- examples/pyomobook/abstract-ch/param5a.py | 2 +- examples/pyomobook/abstract-ch/param6.py | 2 +- examples/pyomobook/abstract-ch/param6a.py | 2 +- examples/pyomobook/abstract-ch/param7a.py | 2 +- examples/pyomobook/abstract-ch/param7b.py | 2 +- examples/pyomobook/abstract-ch/param8a.py | 2 +- examples/pyomobook/abstract-ch/postprocess_fn.py | 2 +- examples/pyomobook/abstract-ch/set1.py | 2 +- examples/pyomobook/abstract-ch/set2.py | 2 +- examples/pyomobook/abstract-ch/set2a.py | 2 +- examples/pyomobook/abstract-ch/set3.py | 2 +- examples/pyomobook/abstract-ch/set4.py | 2 +- examples/pyomobook/abstract-ch/set5.py | 2 +- examples/pyomobook/abstract-ch/wl_abstract.py | 2 +- examples/pyomobook/abstract-ch/wl_abstract_script.py | 2 +- examples/pyomobook/blocks-ch/blocks_gen.py | 2 +- examples/pyomobook/blocks-ch/blocks_intro.py | 2 +- examples/pyomobook/blocks-ch/blocks_lotsizing.py | 2 +- examples/pyomobook/blocks-ch/lotsizing.py | 2 +- examples/pyomobook/blocks-ch/lotsizing_no_time.py | 2 +- examples/pyomobook/blocks-ch/lotsizing_uncertain.py | 2 +- examples/pyomobook/dae-ch/dae_tester_model.py | 2 +- examples/pyomobook/dae-ch/path_constraint.py | 2 +- examples/pyomobook/dae-ch/plot_path_constraint.py | 2 +- examples/pyomobook/dae-ch/run_path_constraint.py | 2 +- examples/pyomobook/dae-ch/run_path_constraint_tester.py | 2 +- examples/pyomobook/gdp-ch/gdp_uc.py | 2 +- examples/pyomobook/gdp-ch/scont.py | 2 +- examples/pyomobook/gdp-ch/scont2.py | 2 +- examples/pyomobook/gdp-ch/scont_script.py | 2 +- examples/pyomobook/gdp-ch/verify_scont.py | 2 +- examples/pyomobook/intro-ch/abstract5.py | 2 +- examples/pyomobook/intro-ch/coloring_concrete.py | 2 +- examples/pyomobook/intro-ch/concrete1.py | 2 +- examples/pyomobook/intro-ch/concrete1_generic.py | 2 +- examples/pyomobook/intro-ch/mydata.py | 2 +- examples/pyomobook/mpec-ch/ex1a.py | 2 +- examples/pyomobook/mpec-ch/ex1b.py | 2 +- examples/pyomobook/mpec-ch/ex1c.py | 2 +- examples/pyomobook/mpec-ch/ex1d.py | 2 +- examples/pyomobook/mpec-ch/ex1e.py | 2 +- examples/pyomobook/mpec-ch/ex2.py | 2 +- examples/pyomobook/mpec-ch/munson1.py | 2 +- examples/pyomobook/mpec-ch/ralph1.py | 2 +- examples/pyomobook/nonlinear-ch/deer/DeerProblem.py | 2 +- .../pyomobook/nonlinear-ch/disease_est/disease_estimation.py | 2 +- .../pyomobook/nonlinear-ch/multimodal/multimodal_init1.py | 2 +- .../pyomobook/nonlinear-ch/multimodal/multimodal_init2.py | 2 +- examples/pyomobook/nonlinear-ch/react_design/ReactorDesign.py | 2 +- .../pyomobook/nonlinear-ch/react_design/ReactorDesignTable.py | 2 +- examples/pyomobook/nonlinear-ch/rosen/rosenbrock.py | 2 +- examples/pyomobook/optimization-ch/ConcHLinScript.py | 2 +- examples/pyomobook/optimization-ch/ConcreteH.py | 2 +- examples/pyomobook/optimization-ch/ConcreteHLinear.py | 2 +- examples/pyomobook/optimization-ch/IC_model_dict.py | 2 +- examples/pyomobook/overview-ch/var_obj_con_snippet.py | 2 +- examples/pyomobook/overview-ch/wl_abstract.py | 2 +- examples/pyomobook/overview-ch/wl_abstract_script.py | 2 +- examples/pyomobook/overview-ch/wl_concrete.py | 2 +- examples/pyomobook/overview-ch/wl_concrete_script.py | 2 +- examples/pyomobook/overview-ch/wl_excel.py | 2 +- examples/pyomobook/overview-ch/wl_list.py | 2 +- examples/pyomobook/overview-ch/wl_mutable.py | 2 +- examples/pyomobook/overview-ch/wl_mutable_excel.py | 2 +- examples/pyomobook/overview-ch/wl_scalar.py | 2 +- examples/pyomobook/performance-ch/SparseSets.py | 2 +- examples/pyomobook/performance-ch/lin_expr.py | 2 +- examples/pyomobook/performance-ch/persistent.py | 2 +- examples/pyomobook/performance-ch/wl.py | 2 +- examples/pyomobook/pyomo-components-ch/con_declaration.py | 2 +- examples/pyomobook/pyomo-components-ch/examples.py | 2 +- examples/pyomobook/pyomo-components-ch/expr_declaration.py | 2 +- examples/pyomobook/pyomo-components-ch/obj_declaration.py | 2 +- examples/pyomobook/pyomo-components-ch/param_declaration.py | 2 +- .../pyomobook/pyomo-components-ch/param_initialization.py | 2 +- examples/pyomobook/pyomo-components-ch/param_misc.py | 2 +- examples/pyomobook/pyomo-components-ch/param_validation.py | 2 +- examples/pyomobook/pyomo-components-ch/rangeset.py | 2 +- examples/pyomobook/pyomo-components-ch/set_declaration.py | 2 +- examples/pyomobook/pyomo-components-ch/set_initialization.py | 2 +- examples/pyomobook/pyomo-components-ch/set_misc.py | 2 +- examples/pyomobook/pyomo-components-ch/set_options.py | 2 +- examples/pyomobook/pyomo-components-ch/set_validation.py | 2 +- examples/pyomobook/pyomo-components-ch/suffix_declaration.py | 2 +- examples/pyomobook/pyomo-components-ch/var_declaration.py | 2 +- examples/pyomobook/python-ch/BadIndent.py | 2 +- examples/pyomobook/python-ch/LineExample.py | 2 +- examples/pyomobook/python-ch/class.py | 2 +- examples/pyomobook/python-ch/ctob.py | 2 +- examples/pyomobook/python-ch/example.py | 2 +- examples/pyomobook/python-ch/example2.py | 2 +- examples/pyomobook/python-ch/functions.py | 2 +- examples/pyomobook/python-ch/iterate.py | 2 +- examples/pyomobook/python-ch/pythonconditional.py | 2 +- examples/pyomobook/scripts-ch/attributes.py | 2 +- examples/pyomobook/scripts-ch/prob_mod_ex.py | 2 +- examples/pyomobook/scripts-ch/sudoku/sudoku.py | 2 +- examples/pyomobook/scripts-ch/sudoku/sudoku_run.py | 2 +- examples/pyomobook/scripts-ch/value_expression.py | 2 +- examples/pyomobook/scripts-ch/warehouse_cuts.py | 2 +- examples/pyomobook/scripts-ch/warehouse_load_solutions.py | 2 +- examples/pyomobook/scripts-ch/warehouse_model.py | 2 +- examples/pyomobook/scripts-ch/warehouse_print.py | 2 +- examples/pyomobook/scripts-ch/warehouse_script.py | 2 +- examples/pyomobook/scripts-ch/warehouse_solver_options.py | 2 +- examples/pyomobook/strip_examples.py | 2 +- examples/pyomobook/test_book_examples.py | 2 +- pyomo/__init__.py | 2 +- pyomo/common/__init__.py | 2 +- pyomo/common/_command.py | 2 +- pyomo/common/_common.py | 2 +- pyomo/common/autoslots.py | 2 +- pyomo/common/backports.py | 2 +- pyomo/common/cmake_builder.py | 2 +- pyomo/common/collections/__init__.py | 2 +- pyomo/common/collections/bunch.py | 2 +- pyomo/common/collections/component_map.py | 2 +- pyomo/common/collections/component_set.py | 2 +- pyomo/common/collections/orderedset.py | 2 +- pyomo/common/config.py | 2 +- pyomo/common/dependencies.py | 2 +- pyomo/common/deprecation.py | 2 +- pyomo/common/download.py | 2 +- pyomo/common/env.py | 2 +- pyomo/common/envvar.py | 2 +- pyomo/common/errors.py | 2 +- pyomo/common/extensions.py | 2 +- pyomo/common/factory.py | 2 +- pyomo/common/fileutils.py | 2 +- pyomo/common/formatting.py | 2 +- pyomo/common/gc_manager.py | 2 +- pyomo/common/getGSL.py | 2 +- pyomo/common/gsl.py | 2 +- pyomo/common/log.py | 2 +- pyomo/common/modeling.py | 2 +- pyomo/common/multithread.py | 2 +- pyomo/common/numeric_types.py | 2 +- pyomo/common/plugin.py | 2 +- pyomo/common/plugin_base.py | 2 +- pyomo/common/plugins.py | 2 +- pyomo/common/pyomo_typing.py | 2 +- pyomo/common/shutdown.py | 2 +- pyomo/common/sorting.py | 2 +- pyomo/common/tee.py | 2 +- pyomo/common/tempfiles.py | 2 +- pyomo/common/tests/__init__.py | 2 +- pyomo/common/tests/config_plugin.py | 2 +- pyomo/common/tests/dep_mod.py | 2 +- pyomo/common/tests/dep_mod_except.py | 2 +- pyomo/common/tests/deps.py | 2 +- pyomo/common/tests/import_ex.py | 2 +- pyomo/common/tests/relo_mod.py | 2 +- pyomo/common/tests/relo_mod_new.py | 2 +- pyomo/common/tests/relocated.py | 2 +- pyomo/common/tests/test_bunch.py | 2 +- pyomo/common/tests/test_config.py | 2 +- pyomo/common/tests/test_dependencies.py | 2 +- pyomo/common/tests/test_deprecated.py | 2 +- pyomo/common/tests/test_download.py | 2 +- pyomo/common/tests/test_env.py | 2 +- pyomo/common/tests/test_errors.py | 2 +- pyomo/common/tests/test_fileutils.py | 2 +- pyomo/common/tests/test_formatting.py | 2 +- pyomo/common/tests/test_gc.py | 2 +- pyomo/common/tests/test_log.py | 2 +- pyomo/common/tests/test_modeling.py | 2 +- pyomo/common/tests/test_multithread.py | 2 +- pyomo/common/tests/test_orderedset.py | 2 +- pyomo/common/tests/test_plugin.py | 2 +- pyomo/common/tests/test_sorting.py | 2 +- pyomo/common/tests/test_tee.py | 2 +- pyomo/common/tests/test_tempfile.py | 2 +- pyomo/common/tests/test_timing.py | 2 +- pyomo/common/tests/test_typing.py | 2 +- pyomo/common/tests/test_unittest.py | 2 +- pyomo/common/timing.py | 2 +- pyomo/common/unittest.py | 2 +- pyomo/contrib/__init__.py | 2 +- pyomo/contrib/ampl_function_demo/__init__.py | 2 +- pyomo/contrib/ampl_function_demo/build.py | 2 +- pyomo/contrib/ampl_function_demo/plugins.py | 2 +- pyomo/contrib/ampl_function_demo/src/CMakeLists.txt | 2 +- pyomo/contrib/ampl_function_demo/src/FindASL.cmake | 2 +- pyomo/contrib/ampl_function_demo/src/functions.c | 2 +- pyomo/contrib/ampl_function_demo/tests/__init__.py | 2 +- .../ampl_function_demo/tests/test_ampl_function_demo.py | 2 +- pyomo/contrib/appsi/__init__.py | 2 +- pyomo/contrib/appsi/base.py | 2 +- pyomo/contrib/appsi/build.py | 2 +- pyomo/contrib/appsi/cmodel/__init__.py | 2 +- pyomo/contrib/appsi/cmodel/src/cmodel_bindings.cpp | 2 +- pyomo/contrib/appsi/cmodel/src/common.cpp | 2 +- pyomo/contrib/appsi/cmodel/src/common.hpp | 2 +- pyomo/contrib/appsi/cmodel/src/expression.cpp | 2 +- pyomo/contrib/appsi/cmodel/src/expression.hpp | 2 +- pyomo/contrib/appsi/cmodel/src/fbbt_model.cpp | 2 +- pyomo/contrib/appsi/cmodel/src/fbbt_model.hpp | 2 +- pyomo/contrib/appsi/cmodel/src/interval.cpp | 2 +- pyomo/contrib/appsi/cmodel/src/interval.hpp | 2 +- pyomo/contrib/appsi/cmodel/src/lp_writer.cpp | 2 +- pyomo/contrib/appsi/cmodel/src/lp_writer.hpp | 2 +- pyomo/contrib/appsi/cmodel/src/model_base.cpp | 2 +- pyomo/contrib/appsi/cmodel/src/model_base.hpp | 2 +- pyomo/contrib/appsi/cmodel/src/nl_writer.cpp | 2 +- pyomo/contrib/appsi/cmodel/src/nl_writer.hpp | 2 +- pyomo/contrib/appsi/cmodel/tests/__init__.py | 2 +- pyomo/contrib/appsi/cmodel/tests/test_import.py | 2 +- pyomo/contrib/appsi/examples/__init__.py | 2 +- pyomo/contrib/appsi/examples/getting_started.py | 2 +- pyomo/contrib/appsi/examples/tests/__init__.py | 2 +- pyomo/contrib/appsi/examples/tests/test_examples.py | 2 +- pyomo/contrib/appsi/fbbt.py | 2 +- pyomo/contrib/appsi/plugins.py | 2 +- pyomo/contrib/appsi/solvers/__init__.py | 2 +- pyomo/contrib/appsi/solvers/cbc.py | 2 +- pyomo/contrib/appsi/solvers/cplex.py | 2 +- pyomo/contrib/appsi/solvers/gurobi.py | 2 +- pyomo/contrib/appsi/solvers/highs.py | 2 +- pyomo/contrib/appsi/solvers/ipopt.py | 2 +- pyomo/contrib/appsi/solvers/tests/__init__.py | 2 +- pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py | 2 +- pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py | 2 +- pyomo/contrib/appsi/solvers/tests/test_ipopt_persistent.py | 2 +- pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py | 2 +- pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py | 2 +- pyomo/contrib/appsi/solvers/wntr.py | 2 +- pyomo/contrib/appsi/tests/__init__.py | 2 +- pyomo/contrib/appsi/tests/test_base.py | 2 +- pyomo/contrib/appsi/tests/test_fbbt.py | 2 +- pyomo/contrib/appsi/tests/test_interval.py | 2 +- pyomo/contrib/appsi/utils/__init__.py | 2 +- pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py | 2 +- pyomo/contrib/appsi/utils/get_objective.py | 2 +- pyomo/contrib/appsi/utils/tests/__init__.py | 2 +- .../appsi/utils/tests/test_collect_vars_and_named_exprs.py | 2 +- pyomo/contrib/appsi/writers/__init__.py | 2 +- pyomo/contrib/appsi/writers/config.py | 2 +- pyomo/contrib/appsi/writers/lp_writer.py | 2 +- pyomo/contrib/appsi/writers/nl_writer.py | 2 +- pyomo/contrib/appsi/writers/tests/__init__.py | 2 +- pyomo/contrib/appsi/writers/tests/test_nl_writer.py | 2 +- pyomo/contrib/benders/__init__.py | 2 +- pyomo/contrib/benders/benders_cuts.py | 2 +- pyomo/contrib/benders/examples/__init__.py | 2 +- pyomo/contrib/benders/examples/farmer.py | 2 +- pyomo/contrib/benders/examples/grothey_ex.py | 2 +- pyomo/contrib/benders/tests/__init__.py | 2 +- pyomo/contrib/benders/tests/test_benders.py | 2 +- pyomo/contrib/community_detection/__init__.py | 2 +- pyomo/contrib/community_detection/community_graph.py | 2 +- pyomo/contrib/community_detection/detection.py | 2 +- pyomo/contrib/community_detection/event_log.py | 2 +- pyomo/contrib/community_detection/plugins.py | 2 +- pyomo/contrib/community_detection/tests/__init__.py | 2 +- pyomo/contrib/community_detection/tests/test_detection.py | 2 +- pyomo/contrib/cp/__init__.py | 2 +- pyomo/contrib/cp/interval_var.py | 2 +- pyomo/contrib/cp/plugins.py | 2 +- pyomo/contrib/cp/repn/__init__.py | 2 +- pyomo/contrib/cp/repn/docplex_writer.py | 2 +- pyomo/contrib/cp/scheduling_expr/__init__.py | 2 +- pyomo/contrib/cp/scheduling_expr/precedence_expressions.py | 2 +- pyomo/contrib/cp/scheduling_expr/step_function_expressions.py | 2 +- pyomo/contrib/cp/tests/__init__.py | 2 +- pyomo/contrib/cp/tests/test_docplex_walker.py | 2 +- pyomo/contrib/cp/tests/test_docplex_writer.py | 2 +- pyomo/contrib/cp/tests/test_interval_var.py | 2 +- pyomo/contrib/cp/tests/test_logical_to_disjunctive.py | 2 +- pyomo/contrib/cp/tests/test_precedence_constraints.py | 2 +- pyomo/contrib/cp/tests/test_step_function_expressions.py | 2 +- pyomo/contrib/cp/transform/__init__.py | 2 +- pyomo/contrib/cp/transform/logical_to_disjunctive_program.py | 2 +- pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py | 2 +- pyomo/contrib/doe/__init__.py | 2 +- pyomo/contrib/doe/doe.py | 2 +- pyomo/contrib/doe/examples/__init__.py | 2 +- pyomo/contrib/doe/examples/reactor_compute_FIM.py | 2 +- pyomo/contrib/doe/examples/reactor_grid_search.py | 2 +- pyomo/contrib/doe/examples/reactor_kinetics.py | 2 +- pyomo/contrib/doe/examples/reactor_optimize_doe.py | 2 +- pyomo/contrib/doe/measurements.py | 2 +- pyomo/contrib/doe/result.py | 2 +- pyomo/contrib/doe/scenario.py | 2 +- pyomo/contrib/doe/tests/__init__.py | 2 +- pyomo/contrib/doe/tests/test_example.py | 2 +- pyomo/contrib/doe/tests/test_fim_doe.py | 2 +- pyomo/contrib/doe/tests/test_reactor_example.py | 2 +- pyomo/contrib/example/__init__.py | 2 +- pyomo/contrib/example/bar.py | 2 +- pyomo/contrib/example/foo.py | 2 +- pyomo/contrib/example/plugins/__init__.py | 2 +- pyomo/contrib/example/plugins/ex_plugin.py | 2 +- pyomo/contrib/example/tests/__init__.py | 2 +- pyomo/contrib/example/tests/test_example.py | 2 +- pyomo/contrib/fbbt/__init__.py | 2 +- pyomo/contrib/fbbt/expression_bounds_walker.py | 2 +- pyomo/contrib/fbbt/fbbt.py | 2 +- pyomo/contrib/fbbt/interval.py | 2 +- pyomo/contrib/fbbt/tests/__init__.py | 2 +- pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py | 2 +- pyomo/contrib/fbbt/tests/test_fbbt.py | 2 +- pyomo/contrib/fbbt/tests/test_interval.py | 2 +- pyomo/contrib/fme/__init__.py | 2 +- pyomo/contrib/fme/fourier_motzkin_elimination.py | 2 +- pyomo/contrib/fme/plugins.py | 2 +- pyomo/contrib/fme/tests/__init__.py | 2 +- pyomo/contrib/fme/tests/test_fourier_motzkin_elimination.py | 2 +- pyomo/contrib/gdp_bounds/__init__.py | 2 +- pyomo/contrib/gdp_bounds/compute_bounds.py | 2 +- pyomo/contrib/gdp_bounds/info.py | 2 +- pyomo/contrib/gdp_bounds/plugins.py | 2 +- pyomo/contrib/gdp_bounds/tests/__init__.py | 2 +- pyomo/contrib/gdp_bounds/tests/test_gdp_bounds.py | 2 +- pyomo/contrib/gdpopt/GDPopt.py | 2 +- pyomo/contrib/gdpopt/__init__.py | 2 +- pyomo/contrib/gdpopt/algorithm_base_class.py | 2 +- pyomo/contrib/gdpopt/branch_and_bound.py | 2 +- pyomo/contrib/gdpopt/config_options.py | 2 +- pyomo/contrib/gdpopt/create_oa_subproblems.py | 2 +- pyomo/contrib/gdpopt/cut_generation.py | 2 +- pyomo/contrib/gdpopt/discrete_problem_initialize.py | 2 +- pyomo/contrib/gdpopt/enumerate.py | 2 +- pyomo/contrib/gdpopt/gloa.py | 2 +- pyomo/contrib/gdpopt/loa.py | 2 +- pyomo/contrib/gdpopt/nlp_initialization.py | 2 +- pyomo/contrib/gdpopt/oa_algorithm_utils.py | 2 +- pyomo/contrib/gdpopt/plugins.py | 2 +- pyomo/contrib/gdpopt/ric.py | 2 +- pyomo/contrib/gdpopt/solve_discrete_problem.py | 2 +- pyomo/contrib/gdpopt/solve_subproblem.py | 2 +- pyomo/contrib/gdpopt/tests/__init__.py | 2 +- pyomo/contrib/gdpopt/tests/common_tests.py | 2 +- pyomo/contrib/gdpopt/tests/test_LBB.py | 2 +- pyomo/contrib/gdpopt/tests/test_enumerate.py | 2 +- pyomo/contrib/gdpopt/tests/test_gdpopt.py | 2 +- pyomo/contrib/gdpopt/util.py | 2 +- pyomo/contrib/gjh/GJH.py | 2 +- pyomo/contrib/gjh/__init__.py | 2 +- pyomo/contrib/gjh/getGJH.py | 2 +- pyomo/contrib/gjh/plugins.py | 2 +- pyomo/contrib/iis/__init__.py | 2 +- pyomo/contrib/iis/iis.py | 2 +- pyomo/contrib/iis/tests/__init__.py | 2 +- pyomo/contrib/iis/tests/test_iis.py | 2 +- pyomo/contrib/incidence_analysis/__init__.py | 2 +- pyomo/contrib/incidence_analysis/common/__init__.py | 2 +- pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.py | 2 +- pyomo/contrib/incidence_analysis/common/tests/__init__.py | 2 +- .../common/tests/test_dulmage_mendelsohn.py | 2 +- pyomo/contrib/incidence_analysis/config.py | 2 +- pyomo/contrib/incidence_analysis/connected.py | 2 +- pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py | 2 +- pyomo/contrib/incidence_analysis/incidence.py | 2 +- pyomo/contrib/incidence_analysis/interface.py | 2 +- pyomo/contrib/incidence_analysis/matching.py | 2 +- pyomo/contrib/incidence_analysis/scc_solver.py | 2 +- pyomo/contrib/incidence_analysis/tests/__init__.py | 2 +- pyomo/contrib/incidence_analysis/tests/models_for_testing.py | 2 +- pyomo/contrib/incidence_analysis/tests/test_connected.py | 2 +- .../incidence_analysis/tests/test_dulmage_mendelsohn.py | 2 +- pyomo/contrib/incidence_analysis/tests/test_incidence.py | 2 +- pyomo/contrib/incidence_analysis/tests/test_interface.py | 2 +- pyomo/contrib/incidence_analysis/tests/test_matching.py | 2 +- pyomo/contrib/incidence_analysis/tests/test_scc_solver.py | 2 +- pyomo/contrib/incidence_analysis/tests/test_triangularize.py | 2 +- pyomo/contrib/incidence_analysis/triangularize.py | 2 +- pyomo/contrib/incidence_analysis/util.py | 2 +- pyomo/contrib/interior_point/__init__.py | 2 +- pyomo/contrib/interior_point/examples/__init__.py | 2 +- pyomo/contrib/interior_point/examples/ex1.py | 2 +- pyomo/contrib/interior_point/interface.py | 2 +- pyomo/contrib/interior_point/interior_point.py | 2 +- pyomo/contrib/interior_point/inverse_reduced_hessian.py | 2 +- pyomo/contrib/interior_point/linalg/__init__.py | 2 +- .../interior_point/linalg/base_linear_solver_interface.py | 2 +- pyomo/contrib/interior_point/linalg/ma27_interface.py | 2 +- pyomo/contrib/interior_point/linalg/mumps_interface.py | 2 +- pyomo/contrib/interior_point/linalg/scipy_interface.py | 2 +- pyomo/contrib/interior_point/linalg/tests/__init__.py | 2 +- .../interior_point/linalg/tests/test_linear_solvers.py | 2 +- pyomo/contrib/interior_point/linalg/tests/test_realloc.py | 2 +- pyomo/contrib/interior_point/tests/__init__.py | 2 +- pyomo/contrib/interior_point/tests/test_interior_point.py | 2 +- .../interior_point/tests/test_inverse_reduced_hessian.py | 2 +- pyomo/contrib/interior_point/tests/test_realloc.py | 2 +- pyomo/contrib/interior_point/tests/test_reg.py | 2 +- pyomo/contrib/latex_printer/__init__.py | 2 +- pyomo/contrib/latex_printer/latex_printer.py | 2 +- pyomo/contrib/latex_printer/tests/__init__.py | 2 +- pyomo/contrib/latex_printer/tests/test_latex_printer.py | 2 +- .../latex_printer/tests/test_latex_printer_vartypes.py | 2 +- pyomo/contrib/mcpp/__init__.py | 2 +- pyomo/contrib/mcpp/build.py | 2 +- pyomo/contrib/mcpp/getMCPP.py | 2 +- pyomo/contrib/mcpp/mcppInterface.cpp | 2 +- pyomo/contrib/mcpp/plugins.py | 2 +- pyomo/contrib/mcpp/pyomo_mcpp.py | 2 +- pyomo/contrib/mcpp/test_mcpp.py | 2 +- pyomo/contrib/mindtpy/MindtPy.py | 2 +- pyomo/contrib/mindtpy/__init__.py | 2 +- pyomo/contrib/mindtpy/algorithm_base_class.py | 2 +- pyomo/contrib/mindtpy/config_options.py | 2 +- pyomo/contrib/mindtpy/cut_generation.py | 2 +- pyomo/contrib/mindtpy/extended_cutting_plane.py | 2 +- pyomo/contrib/mindtpy/feasibility_pump.py | 2 +- pyomo/contrib/mindtpy/global_outer_approximation.py | 2 +- pyomo/contrib/mindtpy/outer_approximation.py | 2 +- pyomo/contrib/mindtpy/plugins.py | 2 +- pyomo/contrib/mindtpy/single_tree.py | 2 +- pyomo/contrib/mindtpy/tabu_list.py | 2 +- pyomo/contrib/mindtpy/tests/MINLP2_simple.py | 2 +- pyomo/contrib/mindtpy/tests/MINLP3_simple.py | 2 +- pyomo/contrib/mindtpy/tests/MINLP4_simple.py | 2 +- pyomo/contrib/mindtpy/tests/MINLP5_simple.py | 2 +- pyomo/contrib/mindtpy/tests/MINLP_simple.py | 2 +- pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py | 2 +- pyomo/contrib/mindtpy/tests/__init__.py | 2 +- .../contrib/mindtpy/tests/constraint_qualification_example.py | 2 +- pyomo/contrib/mindtpy/tests/eight_process_problem.py | 2 +- pyomo/contrib/mindtpy/tests/feasibility_pump1.py | 2 +- pyomo/contrib/mindtpy/tests/feasibility_pump2.py | 2 +- pyomo/contrib/mindtpy/tests/from_proposal.py | 2 +- pyomo/contrib/mindtpy/tests/nonconvex1.py | 2 +- pyomo/contrib/mindtpy/tests/nonconvex2.py | 2 +- pyomo/contrib/mindtpy/tests/nonconvex3.py | 2 +- pyomo/contrib/mindtpy/tests/nonconvex4.py | 2 +- pyomo/contrib/mindtpy/tests/online_doc_example.py | 2 +- pyomo/contrib/mindtpy/tests/test_mindtpy.py | 2 +- pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py | 2 +- pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py | 2 +- pyomo/contrib/mindtpy/tests/test_mindtpy_global.py | 2 +- pyomo/contrib/mindtpy/tests/test_mindtpy_global_lp_nlp.py | 2 +- pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py | 2 +- pyomo/contrib/mindtpy/tests/test_mindtpy_lp_nlp.py | 2 +- pyomo/contrib/mindtpy/tests/test_mindtpy_regularization.py | 2 +- pyomo/contrib/mindtpy/tests/test_mindtpy_solution_pool.py | 2 +- pyomo/contrib/mindtpy/tests/unit_test.py | 2 +- pyomo/contrib/mindtpy/util.py | 2 +- pyomo/contrib/mpc/__init__.py | 2 +- pyomo/contrib/mpc/data/__init__.py | 2 +- pyomo/contrib/mpc/data/convert.py | 2 +- pyomo/contrib/mpc/data/dynamic_data_base.py | 2 +- pyomo/contrib/mpc/data/find_nearest_index.py | 2 +- pyomo/contrib/mpc/data/get_cuid.py | 2 +- pyomo/contrib/mpc/data/interval_data.py | 2 +- pyomo/contrib/mpc/data/scalar_data.py | 2 +- pyomo/contrib/mpc/data/series_data.py | 2 +- pyomo/contrib/mpc/data/tests/__init__.py | 2 +- pyomo/contrib/mpc/data/tests/test_convert.py | 2 +- pyomo/contrib/mpc/data/tests/test_find_nearest_index.py | 2 +- pyomo/contrib/mpc/data/tests/test_get_cuid.py | 2 +- pyomo/contrib/mpc/data/tests/test_interval_data.py | 2 +- pyomo/contrib/mpc/data/tests/test_scalar_data.py | 2 +- pyomo/contrib/mpc/data/tests/test_series_data.py | 2 +- pyomo/contrib/mpc/examples/__init__.py | 2 +- pyomo/contrib/mpc/examples/cstr/__init__.py | 2 +- pyomo/contrib/mpc/examples/cstr/model.py | 2 +- pyomo/contrib/mpc/examples/cstr/run_mpc.py | 2 +- pyomo/contrib/mpc/examples/cstr/run_openloop.py | 2 +- pyomo/contrib/mpc/examples/cstr/tests/__init__.py | 2 +- pyomo/contrib/mpc/examples/cstr/tests/test_mpc.py | 2 +- pyomo/contrib/mpc/examples/cstr/tests/test_openloop.py | 2 +- pyomo/contrib/mpc/interfaces/__init__.py | 2 +- pyomo/contrib/mpc/interfaces/copy_values.py | 2 +- pyomo/contrib/mpc/interfaces/load_data.py | 2 +- pyomo/contrib/mpc/interfaces/model_interface.py | 2 +- pyomo/contrib/mpc/interfaces/tests/__init__.py | 2 +- pyomo/contrib/mpc/interfaces/tests/test_interface.py | 2 +- pyomo/contrib/mpc/interfaces/tests/test_var_linker.py | 2 +- pyomo/contrib/mpc/interfaces/var_linker.py | 2 +- pyomo/contrib/mpc/modeling/__init__.py | 2 +- pyomo/contrib/mpc/modeling/constraints.py | 2 +- pyomo/contrib/mpc/modeling/cost_expressions.py | 2 +- pyomo/contrib/mpc/modeling/terminal.py | 2 +- pyomo/contrib/mpc/modeling/tests/__init__.py | 2 +- pyomo/contrib/mpc/modeling/tests/test_cost_expressions.py | 2 +- pyomo/contrib/mpc/modeling/tests/test_input_constraints.py | 2 +- pyomo/contrib/mpc/modeling/tests/test_terminal.py | 2 +- pyomo/contrib/multistart/__init__.py | 2 +- pyomo/contrib/multistart/high_conf_stop.py | 2 +- pyomo/contrib/multistart/multi.py | 2 +- pyomo/contrib/multistart/plugins.py | 2 +- pyomo/contrib/multistart/reinit.py | 2 +- pyomo/contrib/multistart/test_multi.py | 2 +- pyomo/contrib/parmest/__init__.py | 2 +- pyomo/contrib/parmest/examples/__init__.py | 2 +- pyomo/contrib/parmest/examples/reaction_kinetics/__init__.py | 2 +- .../reaction_kinetics/simple_reaction_parmest_example.py | 2 +- pyomo/contrib/parmest/examples/reactor_design/__init__.py | 2 +- .../parmest/examples/reactor_design/bootstrap_example.py | 2 +- .../parmest/examples/reactor_design/datarec_example.py | 2 +- .../parmest/examples/reactor_design/leaveNout_example.py | 2 +- .../examples/reactor_design/likelihood_ratio_example.py | 2 +- .../examples/reactor_design/multisensor_data_example.py | 2 +- .../examples/reactor_design/parameter_estimation_example.py | 2 +- .../contrib/parmest/examples/reactor_design/reactor_design.py | 2 +- .../examples/reactor_design/timeseries_data_example.py | 2 +- pyomo/contrib/parmest/examples/rooney_biegler/__init__.py | 2 +- .../parmest/examples/rooney_biegler/bootstrap_example.py | 2 +- .../examples/rooney_biegler/likelihood_ratio_example.py | 2 +- .../examples/rooney_biegler/parameter_estimation_example.py | 2 +- .../contrib/parmest/examples/rooney_biegler/rooney_biegler.py | 2 +- .../examples/rooney_biegler/rooney_biegler_with_constraint.py | 2 +- pyomo/contrib/parmest/examples/semibatch/__init__.py | 2 +- pyomo/contrib/parmest/examples/semibatch/parallel_example.py | 2 +- .../examples/semibatch/parameter_estimation_example.py | 2 +- pyomo/contrib/parmest/examples/semibatch/scenario_example.py | 2 +- pyomo/contrib/parmest/examples/semibatch/semibatch.py | 2 +- pyomo/contrib/parmest/graphics.py | 2 +- pyomo/contrib/parmest/ipopt_solver_wrapper.py | 2 +- pyomo/contrib/parmest/parmest.py | 2 +- pyomo/contrib/parmest/scenariocreator.py | 2 +- pyomo/contrib/parmest/tests/__init__.py | 2 +- pyomo/contrib/parmest/tests/test_examples.py | 2 +- pyomo/contrib/parmest/tests/test_graphics.py | 2 +- pyomo/contrib/parmest/tests/test_parmest.py | 2 +- pyomo/contrib/parmest/tests/test_scenariocreator.py | 2 +- pyomo/contrib/parmest/tests/test_solver.py | 2 +- pyomo/contrib/parmest/tests/test_utils.py | 2 +- pyomo/contrib/parmest/utils/__init__.py | 2 +- pyomo/contrib/parmest/utils/create_ef.py | 2 +- pyomo/contrib/parmest/utils/ipopt_solver_wrapper.py | 2 +- pyomo/contrib/parmest/utils/model_utils.py | 2 +- pyomo/contrib/parmest/utils/mpi_utils.py | 2 +- pyomo/contrib/parmest/utils/scenario_tree.py | 2 +- pyomo/contrib/piecewise/__init__.py | 2 +- pyomo/contrib/piecewise/piecewise_linear_expression.py | 2 +- pyomo/contrib/piecewise/piecewise_linear_function.py | 2 +- pyomo/contrib/piecewise/tests/__init__.py | 2 +- pyomo/contrib/piecewise/tests/common_tests.py | 2 +- pyomo/contrib/piecewise/tests/models.py | 2 +- pyomo/contrib/piecewise/tests/test_inner_repn_gdp.py | 2 +- pyomo/contrib/piecewise/tests/test_outer_repn_gdp.py | 2 +- .../contrib/piecewise/tests/test_piecewise_linear_function.py | 2 +- pyomo/contrib/piecewise/tests/test_reduced_inner_repn.py | 2 +- pyomo/contrib/piecewise/transform/__init__.py | 2 +- pyomo/contrib/piecewise/transform/convex_combination.py | 2 +- .../piecewise/transform/disaggregated_convex_combination.py | 2 +- pyomo/contrib/piecewise/transform/inner_representation_gdp.py | 2 +- pyomo/contrib/piecewise/transform/multiple_choice.py | 2 +- pyomo/contrib/piecewise/transform/outer_representation_gdp.py | 2 +- .../piecewise/transform/piecewise_to_gdp_transformation.py | 2 +- pyomo/contrib/piecewise/transform/piecewise_to_mip_visitor.py | 2 +- .../piecewise/transform/reduced_inner_representation_gdp.py | 2 +- pyomo/contrib/preprocessing/__init__.py | 2 +- pyomo/contrib/preprocessing/plugins/__init__.py | 2 +- pyomo/contrib/preprocessing/plugins/bounds_to_vars.py | 2 +- pyomo/contrib/preprocessing/plugins/constraint_tightener.py | 2 +- .../preprocessing/plugins/deactivate_trivial_constraints.py | 2 +- pyomo/contrib/preprocessing/plugins/detect_fixed_vars.py | 2 +- pyomo/contrib/preprocessing/plugins/equality_propagate.py | 2 +- pyomo/contrib/preprocessing/plugins/induced_linearity.py | 2 +- pyomo/contrib/preprocessing/plugins/init_vars.py | 2 +- pyomo/contrib/preprocessing/plugins/int_to_binary.py | 2 +- pyomo/contrib/preprocessing/plugins/remove_zero_terms.py | 2 +- pyomo/contrib/preprocessing/plugins/strip_bounds.py | 2 +- pyomo/contrib/preprocessing/plugins/var_aggregator.py | 2 +- pyomo/contrib/preprocessing/plugins/zero_sum_propagator.py | 2 +- pyomo/contrib/preprocessing/tests/__init__.py | 2 +- pyomo/contrib/preprocessing/tests/test_bounds_to_vars_xfrm.py | 2 +- .../contrib/preprocessing/tests/test_constraint_tightener.py | 2 +- .../tests/test_deactivate_trivial_constraints.py | 2 +- pyomo/contrib/preprocessing/tests/test_detect_fixed_vars.py | 2 +- pyomo/contrib/preprocessing/tests/test_equality_propagate.py | 2 +- pyomo/contrib/preprocessing/tests/test_induced_linearity.py | 2 +- pyomo/contrib/preprocessing/tests/test_init_vars.py | 2 +- pyomo/contrib/preprocessing/tests/test_int_to_binary.py | 2 +- pyomo/contrib/preprocessing/tests/test_strip_bounds.py | 2 +- pyomo/contrib/preprocessing/tests/test_var_aggregator.py | 2 +- pyomo/contrib/preprocessing/tests/test_zero_sum_propagate.py | 2 +- pyomo/contrib/preprocessing/tests/test_zero_term_removal.py | 2 +- pyomo/contrib/preprocessing/util.py | 2 +- pyomo/contrib/pynumero/__init__.py | 2 +- pyomo/contrib/pynumero/algorithms/__init__.py | 2 +- pyomo/contrib/pynumero/algorithms/solvers/__init__.py | 2 +- pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py | 2 +- .../contrib/pynumero/algorithms/solvers/implicit_functions.py | 2 +- .../contrib/pynumero/algorithms/solvers/pyomo_ext_cyipopt.py | 2 +- pyomo/contrib/pynumero/algorithms/solvers/scipy_solvers.py | 2 +- .../contrib/pynumero/algorithms/solvers/square_solver_base.py | 2 +- pyomo/contrib/pynumero/algorithms/solvers/tests/__init__.py | 2 +- .../algorithms/solvers/tests/test_cyipopt_interfaces.py | 2 +- .../pynumero/algorithms/solvers/tests/test_cyipopt_solver.py | 2 +- .../algorithms/solvers/tests/test_implicit_functions.py | 2 +- .../algorithms/solvers/tests/test_pyomo_ext_cyipopt.py | 2 +- .../pynumero/algorithms/solvers/tests/test_scipy_solvers.py | 2 +- pyomo/contrib/pynumero/asl.py | 2 +- pyomo/contrib/pynumero/build.py | 2 +- pyomo/contrib/pynumero/dependencies.py | 2 +- pyomo/contrib/pynumero/examples/__init__.py | 2 +- pyomo/contrib/pynumero/examples/callback/__init__.py | 2 +- pyomo/contrib/pynumero/examples/callback/cyipopt_callback.py | 2 +- .../pynumero/examples/callback/cyipopt_callback_halt.py | 2 +- .../pynumero/examples/callback/cyipopt_functor_callback.py | 2 +- pyomo/contrib/pynumero/examples/callback/reactor_design.py | 2 +- pyomo/contrib/pynumero/examples/external_grey_box/__init__.py | 2 +- .../pynumero/examples/external_grey_box/param_est/__init__.py | 2 +- .../examples/external_grey_box/param_est/generate_data.py | 2 +- .../pynumero/examples/external_grey_box/param_est/models.py | 2 +- .../external_grey_box/param_est/perform_estimation.py | 2 +- .../examples/external_grey_box/react_example/__init__.py | 2 +- .../external_grey_box/react_example/maximize_cb_outputs.py | 2 +- .../react_example/maximize_cb_ratio_residuals.py | 2 +- .../external_grey_box/react_example/reactor_model_outputs.py | 2 +- .../react_example/reactor_model_residuals.py | 2 +- pyomo/contrib/pynumero/examples/feasibility.py | 2 +- pyomo/contrib/pynumero/examples/mumps_example.py | 2 +- pyomo/contrib/pynumero/examples/nlp_interface.py | 2 +- pyomo/contrib/pynumero/examples/nlp_interface_2.py | 2 +- pyomo/contrib/pynumero/examples/parallel_matvec.py | 2 +- pyomo/contrib/pynumero/examples/parallel_vector_ops.py | 2 +- pyomo/contrib/pynumero/examples/sensitivity.py | 2 +- pyomo/contrib/pynumero/examples/sqp.py | 2 +- pyomo/contrib/pynumero/examples/tests/__init__.py | 2 +- .../contrib/pynumero/examples/tests/test_cyipopt_examples.py | 2 +- pyomo/contrib/pynumero/examples/tests/test_examples.py | 2 +- pyomo/contrib/pynumero/examples/tests/test_mpi_examples.py | 2 +- pyomo/contrib/pynumero/exceptions.py | 2 +- pyomo/contrib/pynumero/interfaces/__init__.py | 2 +- pyomo/contrib/pynumero/interfaces/ampl_nlp.py | 2 +- pyomo/contrib/pynumero/interfaces/cyipopt_interface.py | 2 +- pyomo/contrib/pynumero/interfaces/external_grey_box.py | 2 +- pyomo/contrib/pynumero/interfaces/external_pyomo_model.py | 2 +- pyomo/contrib/pynumero/interfaces/nlp.py | 2 +- pyomo/contrib/pynumero/interfaces/nlp_projections.py | 2 +- pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py | 2 +- pyomo/contrib/pynumero/interfaces/pyomo_nlp.py | 2 +- pyomo/contrib/pynumero/interfaces/tests/__init__.py | 2 +- pyomo/contrib/pynumero/interfaces/tests/compare_utils.py | 2 +- .../pynumero/interfaces/tests/external_grey_box_models.py | 2 +- .../pynumero/interfaces/tests/test_cyipopt_interface.py | 2 +- pyomo/contrib/pynumero/interfaces/tests/test_dynamic_model.py | 2 +- .../pynumero/interfaces/tests/test_external_asl_function.py | 2 +- .../pynumero/interfaces/tests/test_external_grey_box_model.py | 2 +- .../pynumero/interfaces/tests/test_external_pyomo_block.py | 2 +- .../pynumero/interfaces/tests/test_external_pyomo_model.py | 2 +- pyomo/contrib/pynumero/interfaces/tests/test_nlp.py | 2 +- .../contrib/pynumero/interfaces/tests/test_nlp_projections.py | 2 +- .../pynumero/interfaces/tests/test_pyomo_grey_box_nlp.py | 2 +- pyomo/contrib/pynumero/interfaces/tests/test_utils.py | 2 +- pyomo/contrib/pynumero/interfaces/utils.py | 2 +- pyomo/contrib/pynumero/intrinsic.py | 2 +- pyomo/contrib/pynumero/linalg/__init__.py | 2 +- pyomo/contrib/pynumero/linalg/base.py | 2 +- pyomo/contrib/pynumero/linalg/ma27.py | 2 +- pyomo/contrib/pynumero/linalg/ma27_interface.py | 2 +- pyomo/contrib/pynumero/linalg/ma57.py | 2 +- pyomo/contrib/pynumero/linalg/ma57_interface.py | 2 +- pyomo/contrib/pynumero/linalg/mumps_interface.py | 2 +- pyomo/contrib/pynumero/linalg/scipy_interface.py | 2 +- pyomo/contrib/pynumero/linalg/tests/__init__.py | 2 +- pyomo/contrib/pynumero/linalg/tests/test_linear_solvers.py | 2 +- pyomo/contrib/pynumero/linalg/tests/test_ma27.py | 2 +- pyomo/contrib/pynumero/linalg/tests/test_ma57.py | 2 +- pyomo/contrib/pynumero/linalg/tests/test_mumps_interface.py | 2 +- pyomo/contrib/pynumero/linalg/utils.py | 2 +- pyomo/contrib/pynumero/plugins.py | 2 +- pyomo/contrib/pynumero/sparse/__init__.py | 2 +- pyomo/contrib/pynumero/sparse/base_block.py | 2 +- pyomo/contrib/pynumero/sparse/block_matrix.py | 2 +- pyomo/contrib/pynumero/sparse/block_vector.py | 2 +- pyomo/contrib/pynumero/sparse/mpi_block_matrix.py | 2 +- pyomo/contrib/pynumero/sparse/mpi_block_vector.py | 2 +- pyomo/contrib/pynumero/sparse/tests/__init__.py | 2 +- pyomo/contrib/pynumero/sparse/tests/test_block_matrix.py | 2 +- pyomo/contrib/pynumero/sparse/tests/test_block_vector.py | 2 +- pyomo/contrib/pynumero/sparse/tests/test_intrinsics.py | 2 +- pyomo/contrib/pynumero/sparse/tests/test_mpi_block_matrix.py | 2 +- pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py | 2 +- pyomo/contrib/pynumero/src/AmplInterface.cpp | 2 +- pyomo/contrib/pynumero/src/AmplInterface.hpp | 2 +- pyomo/contrib/pynumero/src/AssertUtils.hpp | 2 +- pyomo/contrib/pynumero/src/ma27Interface.cpp | 2 +- pyomo/contrib/pynumero/src/ma57Interface.cpp | 2 +- pyomo/contrib/pynumero/src/tests/simple_test.cpp | 2 +- pyomo/contrib/pynumero/tests/__init__.py | 2 +- pyomo/contrib/pyros/__init__.py | 2 +- pyomo/contrib/pyros/master_problem_methods.py | 2 +- pyomo/contrib/pyros/pyros.py | 2 +- pyomo/contrib/pyros/pyros_algorithm_methods.py | 2 +- pyomo/contrib/pyros/separation_problem_methods.py | 2 +- pyomo/contrib/pyros/solve_data.py | 2 +- pyomo/contrib/pyros/tests/__init__.py | 2 +- pyomo/contrib/pyros/tests/test_grcs.py | 2 +- pyomo/contrib/pyros/uncertainty_sets.py | 2 +- pyomo/contrib/pyros/util.py | 2 +- pyomo/contrib/satsolver/__init__.py | 2 +- pyomo/contrib/satsolver/satsolver.py | 2 +- pyomo/contrib/satsolver/test_satsolver.py | 2 +- pyomo/contrib/sensitivity_toolbox/__init__.py | 4 ++-- .../contrib/sensitivity_toolbox/examples/HIV_Transmission.py | 2 +- pyomo/contrib/sensitivity_toolbox/examples/__init__.py | 4 ++-- .../sensitivity_toolbox/examples/feedbackController.py | 2 +- pyomo/contrib/sensitivity_toolbox/examples/parameter.py | 2 +- pyomo/contrib/sensitivity_toolbox/examples/parameter_kaug.py | 2 +- pyomo/contrib/sensitivity_toolbox/examples/rangeInequality.py | 2 +- pyomo/contrib/sensitivity_toolbox/examples/rooney_biegler.py | 2 +- pyomo/contrib/sensitivity_toolbox/k_aug.py | 4 ++-- pyomo/contrib/sensitivity_toolbox/sens.py | 4 ++-- pyomo/contrib/sensitivity_toolbox/tests/__init__.py | 4 ++-- .../contrib/sensitivity_toolbox/tests/test_k_aug_interface.py | 4 ++-- pyomo/contrib/sensitivity_toolbox/tests/test_sens.py | 4 ++-- pyomo/contrib/sensitivity_toolbox/tests/test_sens_unit.py | 4 ++-- pyomo/contrib/simplemodel/__init__.py | 2 +- pyomo/contrib/trustregion/TRF.py | 2 +- pyomo/contrib/trustregion/__init__.py | 2 +- pyomo/contrib/trustregion/examples/__init__.py | 2 +- pyomo/contrib/trustregion/examples/example1.py | 2 +- pyomo/contrib/trustregion/examples/example2.py | 2 +- pyomo/contrib/trustregion/filter.py | 2 +- pyomo/contrib/trustregion/interface.py | 2 +- pyomo/contrib/trustregion/plugins.py | 2 +- pyomo/contrib/trustregion/tests/__init__.py | 2 +- pyomo/contrib/trustregion/tests/test_TRF.py | 2 +- pyomo/contrib/trustregion/tests/test_examples.py | 2 +- pyomo/contrib/trustregion/tests/test_filter.py | 2 +- pyomo/contrib/trustregion/tests/test_interface.py | 2 +- pyomo/contrib/trustregion/tests/test_util.py | 2 +- pyomo/contrib/trustregion/util.py | 2 +- pyomo/contrib/viewer/__init__.py | 2 +- pyomo/contrib/viewer/model_browser.py | 2 +- pyomo/contrib/viewer/model_select.py | 2 +- pyomo/contrib/viewer/pyomo_viewer.py | 2 +- pyomo/contrib/viewer/qt.py | 2 +- pyomo/contrib/viewer/report.py | 2 +- pyomo/contrib/viewer/residual_table.py | 2 +- pyomo/contrib/viewer/tests/__init__.py | 2 +- pyomo/contrib/viewer/tests/test_data_model_item.py | 2 +- pyomo/contrib/viewer/tests/test_data_model_tree.py | 2 +- pyomo/contrib/viewer/tests/test_qt.py | 2 +- pyomo/contrib/viewer/tests/test_report.py | 2 +- pyomo/contrib/viewer/ui.py | 2 +- pyomo/contrib/viewer/ui_data.py | 2 +- pyomo/core/__init__.py | 2 +- pyomo/core/base/PyomoModel.py | 2 +- pyomo/core/base/__init__.py | 2 +- pyomo/core/base/action.py | 2 +- pyomo/core/base/block.py | 2 +- pyomo/core/base/blockutil.py | 2 +- pyomo/core/base/boolean_var.py | 2 +- pyomo/core/base/check.py | 2 +- pyomo/core/base/component.py | 2 +- pyomo/core/base/component_namer.py | 2 +- pyomo/core/base/component_order.py | 2 +- pyomo/core/base/componentuid.py | 2 +- pyomo/core/base/config.py | 2 +- pyomo/core/base/connector.py | 2 +- pyomo/core/base/constraint.py | 2 +- pyomo/core/base/disable_methods.py | 2 +- pyomo/core/base/enums.py | 2 +- pyomo/core/base/expression.py | 2 +- pyomo/core/base/external.py | 2 +- pyomo/core/base/global_set.py | 2 +- pyomo/core/base/indexed_component.py | 2 +- pyomo/core/base/indexed_component_slice.py | 2 +- pyomo/core/base/initializer.py | 2 +- pyomo/core/base/instance2dat.py | 2 +- pyomo/core/base/label.py | 2 +- pyomo/core/base/logical_constraint.py | 2 +- pyomo/core/base/matrix_constraint.py | 2 +- pyomo/core/base/misc.py | 2 +- pyomo/core/base/numvalue.py | 2 +- pyomo/core/base/objective.py | 2 +- pyomo/core/base/param.py | 2 +- pyomo/core/base/piecewise.py | 2 +- pyomo/core/base/plugin.py | 2 +- pyomo/core/base/range.py | 2 +- pyomo/core/base/rangeset.py | 2 +- pyomo/core/base/reference.py | 2 +- pyomo/core/base/set.py | 2 +- pyomo/core/base/set_types.py | 2 +- pyomo/core/base/sets.py | 2 +- pyomo/core/base/sos.py | 2 +- pyomo/core/base/suffix.py | 2 +- pyomo/core/base/symbol_map.py | 2 +- pyomo/core/base/symbolic.py | 2 +- pyomo/core/base/template_expr.py | 2 +- pyomo/core/base/transformation.py | 2 +- pyomo/core/base/units_container.py | 2 +- pyomo/core/base/util.py | 2 +- pyomo/core/base/var.py | 2 +- pyomo/core/beta/__init__.py | 2 +- pyomo/core/beta/dict_objects.py | 2 +- pyomo/core/beta/list_objects.py | 2 +- pyomo/core/expr/__init__.py | 2 +- pyomo/core/expr/base.py | 2 +- pyomo/core/expr/boolean_value.py | 2 +- pyomo/core/expr/calculus/__init__.py | 2 +- pyomo/core/expr/calculus/derivatives.py | 2 +- pyomo/core/expr/calculus/diff_with_pyomo.py | 2 +- pyomo/core/expr/calculus/diff_with_sympy.py | 2 +- pyomo/core/expr/cnf_walker.py | 2 +- pyomo/core/expr/compare.py | 2 +- pyomo/core/expr/current.py | 2 +- pyomo/core/expr/expr_common.py | 2 +- pyomo/core/expr/expr_errors.py | 2 +- pyomo/core/expr/logical_expr.py | 2 +- pyomo/core/expr/ndarray.py | 2 +- pyomo/core/expr/numeric_expr.py | 2 +- pyomo/core/expr/numvalue.py | 2 +- pyomo/core/expr/relational_expr.py | 2 +- pyomo/core/expr/symbol_map.py | 2 +- pyomo/core/expr/sympy_tools.py | 2 +- pyomo/core/expr/taylor_series.py | 2 +- pyomo/core/expr/template_expr.py | 2 +- pyomo/core/expr/visitor.py | 2 +- pyomo/core/kernel/__init__.py | 2 +- pyomo/core/kernel/base.py | 2 +- pyomo/core/kernel/block.py | 2 +- pyomo/core/kernel/component_map.py | 2 +- pyomo/core/kernel/component_set.py | 2 +- pyomo/core/kernel/conic.py | 2 +- pyomo/core/kernel/constraint.py | 2 +- pyomo/core/kernel/container_utils.py | 2 +- pyomo/core/kernel/dict_container.py | 2 +- pyomo/core/kernel/expression.py | 2 +- pyomo/core/kernel/heterogeneous_container.py | 2 +- pyomo/core/kernel/homogeneous_container.py | 2 +- pyomo/core/kernel/list_container.py | 2 +- pyomo/core/kernel/matrix_constraint.py | 2 +- pyomo/core/kernel/objective.py | 2 +- pyomo/core/kernel/parameter.py | 2 +- pyomo/core/kernel/piecewise_library/__init__.py | 2 +- pyomo/core/kernel/piecewise_library/transforms.py | 2 +- pyomo/core/kernel/piecewise_library/transforms_nd.py | 2 +- pyomo/core/kernel/piecewise_library/util.py | 2 +- pyomo/core/kernel/register_numpy_types.py | 2 +- pyomo/core/kernel/set_types.py | 2 +- pyomo/core/kernel/sos.py | 2 +- pyomo/core/kernel/suffix.py | 2 +- pyomo/core/kernel/tuple_container.py | 2 +- pyomo/core/kernel/variable.py | 2 +- pyomo/core/plugins/__init__.py | 2 +- pyomo/core/plugins/transform/__init__.py | 2 +- pyomo/core/plugins/transform/add_slack_vars.py | 2 +- pyomo/core/plugins/transform/discrete_vars.py | 2 +- pyomo/core/plugins/transform/eliminate_fixed_vars.py | 2 +- pyomo/core/plugins/transform/equality_transform.py | 2 +- pyomo/core/plugins/transform/expand_connectors.py | 2 +- pyomo/core/plugins/transform/hierarchy.py | 2 +- pyomo/core/plugins/transform/logical_to_linear.py | 2 +- pyomo/core/plugins/transform/model.py | 2 +- pyomo/core/plugins/transform/nonnegative_transform.py | 2 +- pyomo/core/plugins/transform/radix_linearization.py | 2 +- pyomo/core/plugins/transform/relax_integrality.py | 2 +- pyomo/core/plugins/transform/scaling.py | 2 +- pyomo/core/plugins/transform/standard_form.py | 2 +- pyomo/core/plugins/transform/util.py | 2 +- pyomo/core/pyomoobject.py | 2 +- pyomo/core/staleflag.py | 2 +- pyomo/core/tests/__init__.py | 2 +- pyomo/core/tests/data/__init__.py | 2 +- pyomo/core/tests/data/test_odbc_ini.py | 2 +- pyomo/core/tests/diet/__init__.py | 2 +- pyomo/core/tests/diet/test_diet.py | 2 +- pyomo/core/tests/examples/__init__.py | 2 +- pyomo/core/tests/examples/pmedian.py | 2 +- pyomo/core/tests/examples/pmedian1.py | 2 +- pyomo/core/tests/examples/pmedian2.py | 2 +- pyomo/core/tests/examples/pmedian4.py | 2 +- pyomo/core/tests/examples/test_amplbook2.py | 2 +- pyomo/core/tests/examples/test_kernel_examples.py | 2 +- pyomo/core/tests/examples/test_pyomo.py | 2 +- pyomo/core/tests/examples/test_tutorials.py | 2 +- pyomo/core/tests/transform/__init__.py | 2 +- pyomo/core/tests/transform/test_add_slacks.py | 2 +- pyomo/core/tests/transform/test_scaling.py | 2 +- pyomo/core/tests/transform/test_transform.py | 2 +- pyomo/core/tests/unit/__init__.py | 2 +- pyomo/core/tests/unit/kernel/__init__.py | 2 +- pyomo/core/tests/unit/kernel/test_block.py | 2 +- pyomo/core/tests/unit/kernel/test_component_map.py | 2 +- pyomo/core/tests/unit/kernel/test_component_set.py | 2 +- pyomo/core/tests/unit/kernel/test_conic.py | 2 +- pyomo/core/tests/unit/kernel/test_constraint.py | 2 +- pyomo/core/tests/unit/kernel/test_dict_container.py | 2 +- pyomo/core/tests/unit/kernel/test_expression.py | 2 +- pyomo/core/tests/unit/kernel/test_kernel.py | 2 +- pyomo/core/tests/unit/kernel/test_list_container.py | 2 +- pyomo/core/tests/unit/kernel/test_matrix_constraint.py | 2 +- pyomo/core/tests/unit/kernel/test_objective.py | 2 +- pyomo/core/tests/unit/kernel/test_parameter.py | 2 +- pyomo/core/tests/unit/kernel/test_piecewise.py | 2 +- pyomo/core/tests/unit/kernel/test_sos.py | 2 +- pyomo/core/tests/unit/kernel/test_suffix.py | 2 +- pyomo/core/tests/unit/kernel/test_tuple_container.py | 2 +- pyomo/core/tests/unit/kernel/test_variable.py | 2 +- pyomo/core/tests/unit/test_action.py | 2 +- pyomo/core/tests/unit/test_block.py | 2 +- pyomo/core/tests/unit/test_block_model.py | 2 +- pyomo/core/tests/unit/test_bounds.py | 2 +- pyomo/core/tests/unit/test_check.py | 2 +- pyomo/core/tests/unit/test_compare.py | 2 +- pyomo/core/tests/unit/test_component.py | 2 +- pyomo/core/tests/unit/test_componentuid.py | 2 +- pyomo/core/tests/unit/test_con.py | 2 +- pyomo/core/tests/unit/test_concrete.py | 2 +- pyomo/core/tests/unit/test_connector.py | 2 +- pyomo/core/tests/unit/test_deprecation.py | 2 +- pyomo/core/tests/unit/test_derivs.py | 2 +- pyomo/core/tests/unit/test_dict_objects.py | 2 +- pyomo/core/tests/unit/test_disable_methods.py | 2 +- pyomo/core/tests/unit/test_enums.py | 2 +- pyomo/core/tests/unit/test_expr_misc.py | 2 +- pyomo/core/tests/unit/test_expression.py | 2 +- pyomo/core/tests/unit/test_external.py | 2 +- pyomo/core/tests/unit/test_indexed.py | 2 +- pyomo/core/tests/unit/test_indexed_slice.py | 2 +- pyomo/core/tests/unit/test_initializer.py | 2 +- pyomo/core/tests/unit/test_kernel_register_numpy_types.py | 2 +- pyomo/core/tests/unit/test_labelers.py | 2 +- pyomo/core/tests/unit/test_list_objects.py | 2 +- pyomo/core/tests/unit/test_logical_constraint.py | 2 +- pyomo/core/tests/unit/test_logical_expr_expanded.py | 2 +- pyomo/core/tests/unit/test_logical_to_linear.py | 2 +- pyomo/core/tests/unit/test_matrix_constraint.py | 2 +- pyomo/core/tests/unit/test_misc.py | 2 +- pyomo/core/tests/unit/test_model.py | 2 +- pyomo/core/tests/unit/test_mutable.py | 2 +- pyomo/core/tests/unit/test_numeric_expr.py | 2 +- pyomo/core/tests/unit/test_numeric_expr_api.py | 2 +- pyomo/core/tests/unit/test_numeric_expr_dispatcher.py | 2 +- pyomo/core/tests/unit/test_numeric_expr_zerofilter.py | 2 +- pyomo/core/tests/unit/test_numpy_expr.py | 2 +- pyomo/core/tests/unit/test_numvalue.py | 2 +- pyomo/core/tests/unit/test_obj.py | 2 +- pyomo/core/tests/unit/test_param.py | 2 +- pyomo/core/tests/unit/test_pickle.py | 2 +- pyomo/core/tests/unit/test_piecewise.py | 2 +- pyomo/core/tests/unit/test_preprocess.py | 2 +- pyomo/core/tests/unit/test_range.py | 2 +- pyomo/core/tests/unit/test_reference.py | 2 +- pyomo/core/tests/unit/test_relational_expr.py | 2 +- pyomo/core/tests/unit/test_set.py | 2 +- pyomo/core/tests/unit/test_sets.py | 2 +- pyomo/core/tests/unit/test_smap.py | 2 +- pyomo/core/tests/unit/test_sos.py | 2 +- pyomo/core/tests/unit/test_sos_v2.py | 2 +- pyomo/core/tests/unit/test_suffix.py | 2 +- pyomo/core/tests/unit/test_symbol_map.py | 2 +- pyomo/core/tests/unit/test_symbolic.py | 2 +- pyomo/core/tests/unit/test_taylor_series.py | 2 +- pyomo/core/tests/unit/test_template_expr.py | 2 +- pyomo/core/tests/unit/test_units.py | 2 +- pyomo/core/tests/unit/test_var.py | 2 +- pyomo/core/tests/unit/test_var_set_bounds.py | 2 +- pyomo/core/tests/unit/test_visitor.py | 2 +- pyomo/core/tests/unit/test_xfrm_discrete_vars.py | 2 +- pyomo/core/tests/unit/uninstantiated_model_linear.py | 2 +- pyomo/core/tests/unit/uninstantiated_model_quadratic.py | 2 +- pyomo/core/util.py | 2 +- pyomo/dae/__init__.py | 2 +- pyomo/dae/contset.py | 2 +- pyomo/dae/diffvar.py | 2 +- pyomo/dae/flatten.py | 2 +- pyomo/dae/initialization.py | 2 +- pyomo/dae/integral.py | 2 +- pyomo/dae/misc.py | 2 +- pyomo/dae/plugins/__init__.py | 2 +- pyomo/dae/plugins/colloc.py | 2 +- pyomo/dae/plugins/finitedifference.py | 2 +- pyomo/dae/set_utils.py | 2 +- pyomo/dae/simulator.py | 2 +- pyomo/dae/tests/__init__.py | 2 +- pyomo/dae/tests/test_colloc.py | 2 +- pyomo/dae/tests/test_contset.py | 2 +- pyomo/dae/tests/test_diffvar.py | 2 +- pyomo/dae/tests/test_finite_diff.py | 2 +- pyomo/dae/tests/test_flatten.py | 2 +- pyomo/dae/tests/test_initialization.py | 2 +- pyomo/dae/tests/test_integral.py | 2 +- pyomo/dae/tests/test_misc.py | 2 +- pyomo/dae/tests/test_set_utils.py | 2 +- pyomo/dae/tests/test_simulator.py | 2 +- pyomo/dae/utilities.py | 2 +- pyomo/dataportal/DataPortal.py | 2 +- pyomo/dataportal/TableData.py | 2 +- pyomo/dataportal/__init__.py | 2 +- pyomo/dataportal/factory.py | 2 +- pyomo/dataportal/parse_datacmds.py | 2 +- pyomo/dataportal/plugins/__init__.py | 2 +- pyomo/dataportal/plugins/csv_table.py | 2 +- pyomo/dataportal/plugins/datacommands.py | 2 +- pyomo/dataportal/plugins/db_table.py | 2 +- pyomo/dataportal/plugins/json_dict.py | 2 +- pyomo/dataportal/plugins/sheet.py | 2 +- pyomo/dataportal/plugins/text.py | 2 +- pyomo/dataportal/plugins/xml_table.py | 2 +- pyomo/dataportal/process_data.py | 2 +- pyomo/dataportal/tests/__init__.py | 2 +- pyomo/dataportal/tests/test_dat_parser.py | 2 +- pyomo/dataportal/tests/test_dataportal.py | 2 +- pyomo/duality/__init__.py | 2 +- pyomo/duality/collect.py | 2 +- pyomo/duality/lagrangian_dual.py | 2 +- pyomo/duality/plugins.py | 2 +- pyomo/duality/tests/__init__.py | 2 +- pyomo/duality/tests/test_linear_dual.py | 2 +- pyomo/environ/__init__.py | 2 +- pyomo/environ/tests/__init__.py | 2 +- pyomo/environ/tests/standalone_minimal_pyomo_driver.py | 2 +- pyomo/environ/tests/test_environ.py | 2 +- pyomo/environ/tests/test_package_layout.py | 2 +- pyomo/gdp/__init__.py | 2 +- pyomo/gdp/basic_step.py | 2 +- pyomo/gdp/disjunct.py | 2 +- pyomo/gdp/plugins/__init__.py | 2 +- pyomo/gdp/plugins/between_steps.py | 2 +- pyomo/gdp/plugins/bigm.py | 2 +- pyomo/gdp/plugins/bigm_mixin.py | 2 +- pyomo/gdp/plugins/bilinear.py | 2 +- pyomo/gdp/plugins/binary_multiplication.py | 2 +- pyomo/gdp/plugins/bound_pretransformation.py | 2 +- pyomo/gdp/plugins/chull.py | 2 +- pyomo/gdp/plugins/cuttingplane.py | 2 +- pyomo/gdp/plugins/fix_disjuncts.py | 2 +- pyomo/gdp/plugins/gdp_to_mip_transformation.py | 2 +- pyomo/gdp/plugins/gdp_var_mover.py | 2 +- pyomo/gdp/plugins/hull.py | 2 +- pyomo/gdp/plugins/multiple_bigm.py | 2 +- pyomo/gdp/plugins/partition_disjuncts.py | 2 +- pyomo/gdp/plugins/transform_current_disjunctive_state.py | 2 +- pyomo/gdp/tests/__init__.py | 2 +- pyomo/gdp/tests/common_tests.py | 2 +- pyomo/gdp/tests/models.py | 2 +- pyomo/gdp/tests/test_basic_step.py | 2 +- pyomo/gdp/tests/test_bigm.py | 2 +- pyomo/gdp/tests/test_binary_multiplication.py | 2 +- pyomo/gdp/tests/test_bound_pretransformation.py | 2 +- pyomo/gdp/tests/test_cuttingplane.py | 2 +- pyomo/gdp/tests/test_disjunct.py | 2 +- pyomo/gdp/tests/test_fix_disjuncts.py | 2 +- pyomo/gdp/tests/test_gdp.py | 2 +- pyomo/gdp/tests/test_gdp_reclassification_error.py | 2 +- pyomo/gdp/tests/test_hull.py | 2 +- pyomo/gdp/tests/test_mbigm.py | 2 +- pyomo/gdp/tests/test_partition_disjuncts.py | 2 +- pyomo/gdp/tests/test_reclassify.py | 2 +- pyomo/gdp/tests/test_transform_current_disjunctive_state.py | 2 +- pyomo/gdp/tests/test_util.py | 2 +- pyomo/gdp/transformed_disjunct.py | 2 +- pyomo/gdp/util.py | 2 +- pyomo/kernel/__init__.py | 2 +- pyomo/kernel/util.py | 2 +- pyomo/mpec/__init__.py | 2 +- pyomo/mpec/complementarity.py | 2 +- pyomo/mpec/plugins/__init__.py | 2 +- pyomo/mpec/plugins/mpec1.py | 2 +- pyomo/mpec/plugins/mpec2.py | 2 +- pyomo/mpec/plugins/mpec3.py | 2 +- pyomo/mpec/plugins/mpec4.py | 2 +- pyomo/mpec/plugins/pathampl.py | 2 +- pyomo/mpec/plugins/solver1.py | 2 +- pyomo/mpec/plugins/solver2.py | 2 +- pyomo/mpec/tests/__init__.py | 2 +- pyomo/mpec/tests/test_complementarity.py | 2 +- pyomo/mpec/tests/test_minlp.py | 2 +- pyomo/mpec/tests/test_nlp.py | 2 +- pyomo/mpec/tests/test_path.py | 2 +- pyomo/neos/__init__.py | 2 +- pyomo/neos/kestrel.py | 2 +- pyomo/neos/plugins/NEOS.py | 2 +- pyomo/neos/plugins/__init__.py | 2 +- pyomo/neos/plugins/kestrel_plugin.py | 2 +- pyomo/neos/tests/__init__.py | 2 +- pyomo/neos/tests/model_min_lp.py | 2 +- pyomo/neos/tests/test_neos.py | 2 +- pyomo/network/__init__.py | 2 +- pyomo/network/arc.py | 2 +- pyomo/network/decomposition.py | 2 +- pyomo/network/foqus_graph.py | 2 +- pyomo/network/plugins/__init__.py | 2 +- pyomo/network/plugins/expand_arcs.py | 2 +- pyomo/network/port.py | 2 +- pyomo/network/tests/__init__.py | 2 +- pyomo/network/tests/test_arc.py | 2 +- pyomo/network/tests/test_decomposition.py | 2 +- pyomo/network/tests/test_port.py | 2 +- pyomo/network/util.py | 2 +- pyomo/opt/__init__.py | 2 +- pyomo/opt/base/__init__.py | 2 +- pyomo/opt/base/convert.py | 2 +- pyomo/opt/base/error.py | 2 +- pyomo/opt/base/formats.py | 2 +- pyomo/opt/base/opt_config.py | 2 +- pyomo/opt/base/problem.py | 2 +- pyomo/opt/base/results.py | 2 +- pyomo/opt/base/solvers.py | 2 +- pyomo/opt/parallel/__init__.py | 2 +- pyomo/opt/parallel/async_solver.py | 2 +- pyomo/opt/parallel/local.py | 2 +- pyomo/opt/parallel/manager.py | 2 +- pyomo/opt/plugins/__init__.py | 2 +- pyomo/opt/plugins/driver.py | 2 +- pyomo/opt/plugins/res.py | 2 +- pyomo/opt/plugins/sol.py | 2 +- pyomo/opt/problem/__init__.py | 2 +- pyomo/opt/problem/ampl.py | 2 +- pyomo/opt/results/__init__.py | 2 +- pyomo/opt/results/container.py | 2 +- pyomo/opt/results/problem.py | 2 +- pyomo/opt/results/results_.py | 2 +- pyomo/opt/results/solution.py | 2 +- pyomo/opt/results/solver.py | 2 +- pyomo/opt/solver/__init__.py | 2 +- pyomo/opt/solver/ilmcmd.py | 2 +- pyomo/opt/solver/shellcmd.py | 2 +- pyomo/opt/testing/__init__.py | 2 +- pyomo/opt/testing/pyunit.py | 2 +- pyomo/opt/tests/__init__.py | 2 +- pyomo/opt/tests/base/__init__.py | 2 +- pyomo/opt/tests/base/test_ampl.py | 2 +- pyomo/opt/tests/base/test_convert.py | 2 +- pyomo/opt/tests/base/test_factory.py | 2 +- pyomo/opt/tests/base/test_sol.py | 2 +- pyomo/opt/tests/base/test_soln.py | 2 +- pyomo/opt/tests/base/test_solver.py | 2 +- pyomo/opt/tests/solver/__init__.py | 2 +- pyomo/opt/tests/solver/test_shellcmd.py | 2 +- pyomo/pysp/__init__.py | 2 +- pyomo/repn/__init__.py | 2 +- pyomo/repn/beta/__init__.py | 2 +- pyomo/repn/beta/matrix.py | 2 +- pyomo/repn/linear.py | 2 +- pyomo/repn/plugins/__init__.py | 2 +- pyomo/repn/plugins/ampl/__init__.py | 2 +- pyomo/repn/plugins/ampl/ampl_.py | 2 +- pyomo/repn/plugins/baron_writer.py | 2 +- pyomo/repn/plugins/cpxlp.py | 2 +- pyomo/repn/plugins/gams_writer.py | 2 +- pyomo/repn/plugins/lp_writer.py | 2 +- pyomo/repn/plugins/mps.py | 2 +- pyomo/repn/plugins/nl_writer.py | 2 +- pyomo/repn/plugins/standard_form.py | 2 +- pyomo/repn/quadratic.py | 2 +- pyomo/repn/standard_aux.py | 2 +- pyomo/repn/standard_repn.py | 2 +- pyomo/repn/tests/__init__.py | 2 +- pyomo/repn/tests/ampl/__init__.py | 2 +- pyomo/repn/tests/ampl/helper.py | 2 +- pyomo/repn/tests/ampl/nl_diff.py | 2 +- pyomo/repn/tests/ampl/small10_testCase.py | 2 +- pyomo/repn/tests/ampl/small11_testCase.py | 2 +- pyomo/repn/tests/ampl/small12_testCase.py | 2 +- pyomo/repn/tests/ampl/small13_testCase.py | 2 +- pyomo/repn/tests/ampl/small14_testCase.py | 2 +- pyomo/repn/tests/ampl/small15_testCase.py | 2 +- pyomo/repn/tests/ampl/small1_testCase.py | 2 +- pyomo/repn/tests/ampl/small2_testCase.py | 2 +- pyomo/repn/tests/ampl/small3_testCase.py | 2 +- pyomo/repn/tests/ampl/small4_testCase.py | 2 +- pyomo/repn/tests/ampl/small5_testCase.py | 2 +- pyomo/repn/tests/ampl/small6_testCase.py | 2 +- pyomo/repn/tests/ampl/small7_testCase.py | 2 +- pyomo/repn/tests/ampl/small8_testCase.py | 2 +- pyomo/repn/tests/ampl/small9_testCase.py | 2 +- pyomo/repn/tests/ampl/test_ampl_comparison.py | 2 +- pyomo/repn/tests/ampl/test_ampl_nl.py | 2 +- pyomo/repn/tests/ampl/test_ampl_repn.py | 2 +- pyomo/repn/tests/ampl/test_nlv2.py | 2 +- pyomo/repn/tests/ampl/test_suffixes.py | 2 +- pyomo/repn/tests/baron/__init__.py | 2 +- pyomo/repn/tests/baron/small14a_testCase.py | 2 +- pyomo/repn/tests/baron/test_baron.py | 2 +- pyomo/repn/tests/baron/test_baron_comparison.py | 2 +- pyomo/repn/tests/cpxlp/__init__.py | 2 +- pyomo/repn/tests/cpxlp/test_cpxlp.py | 2 +- pyomo/repn/tests/cpxlp/test_lpv2.py | 2 +- pyomo/repn/tests/diffutils.py | 2 +- pyomo/repn/tests/gams/__init__.py | 2 +- pyomo/repn/tests/gams/small14a_testCase.py | 2 +- pyomo/repn/tests/gams/test_gams.py | 2 +- pyomo/repn/tests/gams/test_gams_comparison.py | 2 +- pyomo/repn/tests/lp_diff.py | 2 +- pyomo/repn/tests/mps/__init__.py | 2 +- pyomo/repn/tests/mps/test_mps.py | 2 +- pyomo/repn/tests/nl_diff.py | 2 +- pyomo/repn/tests/test_linear.py | 2 +- pyomo/repn/tests/test_quadratic.py | 2 +- pyomo/repn/tests/test_standard.py | 2 +- pyomo/repn/tests/test_standard_form.py | 2 +- pyomo/repn/tests/test_util.py | 2 +- pyomo/repn/util.py | 2 +- pyomo/scripting/__init__.py | 2 +- pyomo/scripting/commands.py | 2 +- pyomo/scripting/convert.py | 2 +- pyomo/scripting/driver_help.py | 2 +- pyomo/scripting/interface.py | 2 +- pyomo/scripting/plugins/__init__.py | 2 +- pyomo/scripting/plugins/build_ext.py | 2 +- pyomo/scripting/plugins/convert.py | 2 +- pyomo/scripting/plugins/download.py | 2 +- pyomo/scripting/plugins/extras.py | 2 +- pyomo/scripting/plugins/solve.py | 2 +- pyomo/scripting/pyomo_command.py | 2 +- pyomo/scripting/pyomo_main.py | 2 +- pyomo/scripting/pyomo_parser.py | 2 +- pyomo/scripting/solve_config.py | 2 +- pyomo/scripting/tests/__init__.py | 2 +- pyomo/scripting/tests/test_cmds.py | 2 +- pyomo/scripting/util.py | 2 +- pyomo/solvers/__init__.py | 2 +- pyomo/solvers/mockmip.py | 2 +- pyomo/solvers/plugins/__init__.py | 2 +- pyomo/solvers/plugins/converter/__init__.py | 2 +- pyomo/solvers/plugins/converter/ampl.py | 2 +- pyomo/solvers/plugins/converter/glpsol.py | 2 +- pyomo/solvers/plugins/converter/model.py | 2 +- pyomo/solvers/plugins/converter/pico.py | 2 +- pyomo/solvers/plugins/solvers/ASL.py | 2 +- pyomo/solvers/plugins/solvers/BARON.py | 2 +- pyomo/solvers/plugins/solvers/CBCplugin.py | 2 +- pyomo/solvers/plugins/solvers/CONOPT.py | 2 +- pyomo/solvers/plugins/solvers/CPLEX.py | 2 +- pyomo/solvers/plugins/solvers/GAMS.py | 2 +- pyomo/solvers/plugins/solvers/GLPK.py | 2 +- pyomo/solvers/plugins/solvers/GUROBI.py | 2 +- pyomo/solvers/plugins/solvers/GUROBI_RUN.py | 2 +- pyomo/solvers/plugins/solvers/IPOPT.py | 2 +- pyomo/solvers/plugins/solvers/SCIPAMPL.py | 2 +- pyomo/solvers/plugins/solvers/XPRESS.py | 2 +- pyomo/solvers/plugins/solvers/__init__.py | 2 +- pyomo/solvers/plugins/solvers/cplex_direct.py | 2 +- pyomo/solvers/plugins/solvers/cplex_persistent.py | 2 +- pyomo/solvers/plugins/solvers/direct_or_persistent_solver.py | 2 +- pyomo/solvers/plugins/solvers/direct_solver.py | 2 +- pyomo/solvers/plugins/solvers/gurobi_direct.py | 2 +- pyomo/solvers/plugins/solvers/gurobi_persistent.py | 2 +- pyomo/solvers/plugins/solvers/mosek_direct.py | 2 +- pyomo/solvers/plugins/solvers/mosek_persistent.py | 2 +- pyomo/solvers/plugins/solvers/persistent_solver.py | 2 +- pyomo/solvers/plugins/solvers/pywrapper.py | 2 +- pyomo/solvers/plugins/solvers/xpress_direct.py | 2 +- pyomo/solvers/plugins/solvers/xpress_persistent.py | 2 +- pyomo/solvers/tests/__init__.py | 2 +- pyomo/solvers/tests/checks/__init__.py | 2 +- pyomo/solvers/tests/checks/test_BARON.py | 2 +- pyomo/solvers/tests/checks/test_CBCplugin.py | 2 +- pyomo/solvers/tests/checks/test_CPLEXDirect.py | 2 +- pyomo/solvers/tests/checks/test_CPLEXPersistent.py | 2 +- pyomo/solvers/tests/checks/test_GAMS.py | 2 +- pyomo/solvers/tests/checks/test_MOSEKDirect.py | 2 +- pyomo/solvers/tests/checks/test_MOSEKPersistent.py | 2 +- pyomo/solvers/tests/checks/test_cbc.py | 2 +- pyomo/solvers/tests/checks/test_cplex.py | 2 +- pyomo/solvers/tests/checks/test_gurobi.py | 2 +- pyomo/solvers/tests/checks/test_gurobi_direct.py | 2 +- pyomo/solvers/tests/checks/test_gurobi_persistent.py | 2 +- pyomo/solvers/tests/checks/test_no_solution_behavior.py | 2 +- pyomo/solvers/tests/checks/test_pickle.py | 2 +- pyomo/solvers/tests/checks/test_writers.py | 2 +- pyomo/solvers/tests/checks/test_xpress_persistent.py | 2 +- pyomo/solvers/tests/mip/__init__.py | 2 +- pyomo/solvers/tests/mip/model.py | 2 +- pyomo/solvers/tests/mip/test_asl.py | 2 +- pyomo/solvers/tests/mip/test_convert.py | 2 +- pyomo/solvers/tests/mip/test_factory.py | 2 +- pyomo/solvers/tests/mip/test_ipopt.py | 2 +- pyomo/solvers/tests/mip/test_mip.py | 2 +- pyomo/solvers/tests/mip/test_qp.py | 2 +- pyomo/solvers/tests/mip/test_scip.py | 2 +- pyomo/solvers/tests/mip/test_scip_log_data.py | 2 +- pyomo/solvers/tests/mip/test_scip_version.py | 2 +- pyomo/solvers/tests/mip/test_solver.py | 2 +- pyomo/solvers/tests/models/LP_block.py | 2 +- pyomo/solvers/tests/models/LP_compiled.py | 2 +- pyomo/solvers/tests/models/LP_constant_objective1.py | 2 +- pyomo/solvers/tests/models/LP_constant_objective2.py | 2 +- pyomo/solvers/tests/models/LP_duals_maximize.py | 2 +- pyomo/solvers/tests/models/LP_duals_minimize.py | 2 +- pyomo/solvers/tests/models/LP_inactive_index.py | 2 +- pyomo/solvers/tests/models/LP_infeasible1.py | 2 +- pyomo/solvers/tests/models/LP_infeasible2.py | 2 +- pyomo/solvers/tests/models/LP_piecewise.py | 2 +- pyomo/solvers/tests/models/LP_simple.py | 2 +- pyomo/solvers/tests/models/LP_trivial_constraints.py | 2 +- pyomo/solvers/tests/models/LP_unbounded.py | 2 +- pyomo/solvers/tests/models/LP_unique_duals.py | 2 +- pyomo/solvers/tests/models/LP_unused_vars.py | 2 +- pyomo/solvers/tests/models/MILP_discrete_var_bounds.py | 2 +- pyomo/solvers/tests/models/MILP_infeasible1.py | 2 +- pyomo/solvers/tests/models/MILP_simple.py | 2 +- pyomo/solvers/tests/models/MILP_unbounded.py | 2 +- pyomo/solvers/tests/models/MILP_unused_vars.py | 2 +- pyomo/solvers/tests/models/MIQCP_simple.py | 2 +- pyomo/solvers/tests/models/MIQP_simple.py | 2 +- pyomo/solvers/tests/models/QCP_simple.py | 2 +- pyomo/solvers/tests/models/QP_constant_objective.py | 2 +- pyomo/solvers/tests/models/QP_simple.py | 2 +- pyomo/solvers/tests/models/SOS1_simple.py | 2 +- pyomo/solvers/tests/models/SOS2_simple.py | 2 +- pyomo/solvers/tests/models/__init__.py | 2 +- pyomo/solvers/tests/models/base.py | 2 +- pyomo/solvers/tests/piecewise_linear/__init__.py | 2 +- .../tests/piecewise_linear/kernel_problems/concave_var.py | 2 +- .../tests/piecewise_linear/kernel_problems/convex_var.py | 2 +- .../tests/piecewise_linear/kernel_problems/piecewise_var.py | 2 +- .../tests/piecewise_linear/kernel_problems/step_var.py | 2 +- .../piecewise_linear/problems/concave_multi_vararray1.py | 2 +- .../piecewise_linear/problems/concave_multi_vararray2.py | 2 +- pyomo/solvers/tests/piecewise_linear/problems/concave_var.py | 2 +- .../tests/piecewise_linear/problems/concave_vararray.py | 2 +- .../tests/piecewise_linear/problems/convex_multi_vararray1.py | 2 +- .../tests/piecewise_linear/problems/convex_multi_vararray2.py | 2 +- pyomo/solvers/tests/piecewise_linear/problems/convex_var.py | 2 +- .../tests/piecewise_linear/problems/convex_vararray.py | 2 +- .../piecewise_linear/problems/piecewise_multi_vararray.py | 2 +- .../solvers/tests/piecewise_linear/problems/piecewise_var.py | 2 +- .../tests/piecewise_linear/problems/piecewise_vararray.py | 2 +- pyomo/solvers/tests/piecewise_linear/problems/step_var.py | 2 +- .../solvers/tests/piecewise_linear/problems/step_vararray.py | 2 +- pyomo/solvers/tests/piecewise_linear/problems/tester.py | 2 +- pyomo/solvers/tests/piecewise_linear/test_examples.py | 2 +- pyomo/solvers/tests/piecewise_linear/test_piecewise_linear.py | 2 +- .../tests/piecewise_linear/test_piecewise_linear_kernel.py | 2 +- pyomo/solvers/tests/solvers.py | 2 +- pyomo/solvers/tests/testcases.py | 2 +- pyomo/solvers/wrappers.py | 2 +- pyomo/util/__init__.py | 2 +- pyomo/util/blockutil.py | 2 +- pyomo/util/calc_var_value.py | 2 +- pyomo/util/check_units.py | 2 +- pyomo/util/components.py | 2 +- pyomo/util/diagnostics.py | 2 +- pyomo/util/infeasible.py | 2 +- pyomo/util/model_size.py | 2 +- pyomo/util/report_scaling.py | 2 +- pyomo/util/slices.py | 2 +- pyomo/util/subsystems.py | 2 +- pyomo/util/tests/__init__.py | 2 +- pyomo/util/tests/test_blockutil.py | 2 +- pyomo/util/tests/test_calc_var_value.py | 2 +- pyomo/util/tests/test_check_units.py | 2 +- pyomo/util/tests/test_components.py | 2 +- pyomo/util/tests/test_infeasible.py | 2 +- pyomo/util/tests/test_model_size.py | 2 +- pyomo/util/tests/test_report_scaling.py | 2 +- pyomo/util/tests/test_slices.py | 2 +- pyomo/util/tests/test_subsystems.py | 2 +- pyomo/util/vars_from_expressions.py | 2 +- pyomo/version/__init__.py | 2 +- pyomo/version/info.py | 2 +- pyomo/version/tests/__init__.py | 2 +- pyomo/version/tests/check.py | 2 +- pyomo/version/tests/test_version.py | 2 +- scripts/admin/contributors.py | 2 +- scripts/get_pyomo.py | 2 +- scripts/get_pyomo_extras.py | 2 +- scripts/performance/compare.py | 2 +- scripts/performance/compare_components.py | 2 +- scripts/performance/expr_perf.py | 2 +- scripts/performance/main.py | 2 +- scripts/performance/simple.py | 2 +- setup.py | 2 +- 1664 files changed, 1672 insertions(+), 1672 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 192d315e4b5..9fd5d9b810c 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,7 +1,7 @@ LICENSE ======= -Copyright (c) 2008-2022 National Technology and Engineering Solutions of +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. diff --git a/conftest.py b/conftest.py index df5b0f31e59..7faad6fc89b 100644 --- a/conftest.py +++ b/conftest.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/conf.py b/doc/OnlineDocs/conf.py index 24f8d26c9e8..89c346f5abc 100644 --- a/doc/OnlineDocs/conf.py +++ b/doc/OnlineDocs/conf.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/library_reference/kernel/examples/aml_example.py b/doc/OnlineDocs/library_reference/kernel/examples/aml_example.py index 564764071b7..a640b94cc76 100644 --- a/doc/OnlineDocs/library_reference/kernel/examples/aml_example.py +++ b/doc/OnlineDocs/library_reference/kernel/examples/aml_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/library_reference/kernel/examples/conic.py b/doc/OnlineDocs/library_reference/kernel/examples/conic.py index 866377ed641..0418d188722 100644 --- a/doc/OnlineDocs/library_reference/kernel/examples/conic.py +++ b/doc/OnlineDocs/library_reference/kernel/examples/conic.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/library_reference/kernel/examples/kernel_containers.py b/doc/OnlineDocs/library_reference/kernel/examples/kernel_containers.py index 9b33ed71e1d..1931c6d9b56 100644 --- a/doc/OnlineDocs/library_reference/kernel/examples/kernel_containers.py +++ b/doc/OnlineDocs/library_reference/kernel/examples/kernel_containers.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/library_reference/kernel/examples/kernel_example.py b/doc/OnlineDocs/library_reference/kernel/examples/kernel_example.py index 6ee766e3055..1f80bce9788 100644 --- a/doc/OnlineDocs/library_reference/kernel/examples/kernel_example.py +++ b/doc/OnlineDocs/library_reference/kernel/examples/kernel_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/library_reference/kernel/examples/kernel_solving.py b/doc/OnlineDocs/library_reference/kernel/examples/kernel_solving.py index 30b588f89b8..13d7efc052a 100644 --- a/doc/OnlineDocs/library_reference/kernel/examples/kernel_solving.py +++ b/doc/OnlineDocs/library_reference/kernel/examples/kernel_solving.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/library_reference/kernel/examples/kernel_subclassing.py b/doc/OnlineDocs/library_reference/kernel/examples/kernel_subclassing.py index a603050e828..d6e38f6b0e0 100644 --- a/doc/OnlineDocs/library_reference/kernel/examples/kernel_subclassing.py +++ b/doc/OnlineDocs/library_reference/kernel/examples/kernel_subclassing.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/library_reference/kernel/examples/transformer.py b/doc/OnlineDocs/library_reference/kernel/examples/transformer.py index 0df239c61ad..43a1d0675bf 100644 --- a/doc/OnlineDocs/library_reference/kernel/examples/transformer.py +++ b/doc/OnlineDocs/library_reference/kernel/examples/transformer.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/modeling_extensions/__init__.py b/doc/OnlineDocs/modeling_extensions/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/doc/OnlineDocs/modeling_extensions/__init__.py +++ b/doc/OnlineDocs/modeling_extensions/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/ABCD1.py b/doc/OnlineDocs/src/data/ABCD1.py index 6d34bec756e..aa2f46e71fa 100644 --- a/doc/OnlineDocs/src/data/ABCD1.py +++ b/doc/OnlineDocs/src/data/ABCD1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/ABCD2.py b/doc/OnlineDocs/src/data/ABCD2.py index beadd71916d..ec0e7ccb15c 100644 --- a/doc/OnlineDocs/src/data/ABCD2.py +++ b/doc/OnlineDocs/src/data/ABCD2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/ABCD3.py b/doc/OnlineDocs/src/data/ABCD3.py index 1a3e826c6a9..ba55fd970cc 100644 --- a/doc/OnlineDocs/src/data/ABCD3.py +++ b/doc/OnlineDocs/src/data/ABCD3.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/ABCD4.py b/doc/OnlineDocs/src/data/ABCD4.py index 59055cadf71..2fb397aa3b0 100644 --- a/doc/OnlineDocs/src/data/ABCD4.py +++ b/doc/OnlineDocs/src/data/ABCD4.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/ABCD5.py b/doc/OnlineDocs/src/data/ABCD5.py index 051e2c9ba9e..abc03505e96 100644 --- a/doc/OnlineDocs/src/data/ABCD5.py +++ b/doc/OnlineDocs/src/data/ABCD5.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/ABCD6.py b/doc/OnlineDocs/src/data/ABCD6.py index 8c239eb5332..59e0e8e98ae 100644 --- a/doc/OnlineDocs/src/data/ABCD6.py +++ b/doc/OnlineDocs/src/data/ABCD6.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/ABCD7.py b/doc/OnlineDocs/src/data/ABCD7.py index a52c27af234..1bfb4d1e3fb 100644 --- a/doc/OnlineDocs/src/data/ABCD7.py +++ b/doc/OnlineDocs/src/data/ABCD7.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/ABCD8.py b/doc/OnlineDocs/src/data/ABCD8.py index f979f373a18..aa1ba0b4cf5 100644 --- a/doc/OnlineDocs/src/data/ABCD8.py +++ b/doc/OnlineDocs/src/data/ABCD8.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/ABCD9.py b/doc/OnlineDocs/src/data/ABCD9.py index aaa1e7c908d..194c71486d9 100644 --- a/doc/OnlineDocs/src/data/ABCD9.py +++ b/doc/OnlineDocs/src/data/ABCD9.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/diet1.py b/doc/OnlineDocs/src/data/diet1.py index 9201edb8c4c..40582e16ba0 100644 --- a/doc/OnlineDocs/src/data/diet1.py +++ b/doc/OnlineDocs/src/data/diet1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/ex.py b/doc/OnlineDocs/src/data/ex.py index 3fd91b623b2..a66ee30b494 100644 --- a/doc/OnlineDocs/src/data/ex.py +++ b/doc/OnlineDocs/src/data/ex.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/import1.tab.py b/doc/OnlineDocs/src/data/import1.tab.py index ade8edcc2a3..e160e4fdcde 100644 --- a/doc/OnlineDocs/src/data/import1.tab.py +++ b/doc/OnlineDocs/src/data/import1.tab.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/import2.tab.py b/doc/OnlineDocs/src/data/import2.tab.py index 6491c1ec30e..54339551279 100644 --- a/doc/OnlineDocs/src/data/import2.tab.py +++ b/doc/OnlineDocs/src/data/import2.tab.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/import3.tab.py b/doc/OnlineDocs/src/data/import3.tab.py index ec57c018b00..664151d1438 100644 --- a/doc/OnlineDocs/src/data/import3.tab.py +++ b/doc/OnlineDocs/src/data/import3.tab.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/import4.tab.py b/doc/OnlineDocs/src/data/import4.tab.py index b48278bd28d..91dd3f26a42 100644 --- a/doc/OnlineDocs/src/data/import4.tab.py +++ b/doc/OnlineDocs/src/data/import4.tab.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/import5.tab.py b/doc/OnlineDocs/src/data/import5.tab.py index 9604c328c64..263677c308c 100644 --- a/doc/OnlineDocs/src/data/import5.tab.py +++ b/doc/OnlineDocs/src/data/import5.tab.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/import6.tab.py b/doc/OnlineDocs/src/data/import6.tab.py index a1c269a0abf..8f4824ad3fe 100644 --- a/doc/OnlineDocs/src/data/import6.tab.py +++ b/doc/OnlineDocs/src/data/import6.tab.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/import7.tab.py b/doc/OnlineDocs/src/data/import7.tab.py index f4b60cb42d9..503f9224323 100644 --- a/doc/OnlineDocs/src/data/import7.tab.py +++ b/doc/OnlineDocs/src/data/import7.tab.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/import8.tab.py b/doc/OnlineDocs/src/data/import8.tab.py index d1d2e6f8160..02b8724fe45 100644 --- a/doc/OnlineDocs/src/data/import8.tab.py +++ b/doc/OnlineDocs/src/data/import8.tab.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/param1.py b/doc/OnlineDocs/src/data/param1.py index e606b7f6b4f..336a04287b9 100644 --- a/doc/OnlineDocs/src/data/param1.py +++ b/doc/OnlineDocs/src/data/param1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/param2.py b/doc/OnlineDocs/src/data/param2.py index 725a6002ede..a7d0feafff9 100644 --- a/doc/OnlineDocs/src/data/param2.py +++ b/doc/OnlineDocs/src/data/param2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/param2a.py b/doc/OnlineDocs/src/data/param2a.py index 29416e2dcbc..42056793ffd 100644 --- a/doc/OnlineDocs/src/data/param2a.py +++ b/doc/OnlineDocs/src/data/param2a.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/param3.py b/doc/OnlineDocs/src/data/param3.py index 0cc4df57511..952f9a9b707 100644 --- a/doc/OnlineDocs/src/data/param3.py +++ b/doc/OnlineDocs/src/data/param3.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/param3a.py b/doc/OnlineDocs/src/data/param3a.py index 42204de468f..028e1d07296 100644 --- a/doc/OnlineDocs/src/data/param3a.py +++ b/doc/OnlineDocs/src/data/param3a.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/param3b.py b/doc/OnlineDocs/src/data/param3b.py index 9f0375d7b87..97f8598610a 100644 --- a/doc/OnlineDocs/src/data/param3b.py +++ b/doc/OnlineDocs/src/data/param3b.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/param3c.py b/doc/OnlineDocs/src/data/param3c.py index 9efac11553e..582b0f7db75 100644 --- a/doc/OnlineDocs/src/data/param3c.py +++ b/doc/OnlineDocs/src/data/param3c.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/param4.py b/doc/OnlineDocs/src/data/param4.py index ab184e65ed3..010c46fc9c5 100644 --- a/doc/OnlineDocs/src/data/param4.py +++ b/doc/OnlineDocs/src/data/param4.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/param5.py b/doc/OnlineDocs/src/data/param5.py index f842e48995a..2db07f3f990 100644 --- a/doc/OnlineDocs/src/data/param5.py +++ b/doc/OnlineDocs/src/data/param5.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/param5a.py b/doc/OnlineDocs/src/data/param5a.py index f65de59ca78..32a53d24e9b 100644 --- a/doc/OnlineDocs/src/data/param5a.py +++ b/doc/OnlineDocs/src/data/param5a.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/param6.py b/doc/OnlineDocs/src/data/param6.py index 54cb350298b..e3364a933cf 100644 --- a/doc/OnlineDocs/src/data/param6.py +++ b/doc/OnlineDocs/src/data/param6.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/param6a.py b/doc/OnlineDocs/src/data/param6a.py index 7aabe7ec929..3d2fa645411 100644 --- a/doc/OnlineDocs/src/data/param6a.py +++ b/doc/OnlineDocs/src/data/param6a.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/param7a.py b/doc/OnlineDocs/src/data/param7a.py index 8d0c49210b5..b3aba9ec23d 100644 --- a/doc/OnlineDocs/src/data/param7a.py +++ b/doc/OnlineDocs/src/data/param7a.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/param7b.py b/doc/OnlineDocs/src/data/param7b.py index 8481083c31c..8b022f399a8 100644 --- a/doc/OnlineDocs/src/data/param7b.py +++ b/doc/OnlineDocs/src/data/param7b.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/param8a.py b/doc/OnlineDocs/src/data/param8a.py index 59ddba34091..abfa885ded4 100644 --- a/doc/OnlineDocs/src/data/param8a.py +++ b/doc/OnlineDocs/src/data/param8a.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/set1.py b/doc/OnlineDocs/src/data/set1.py index e1d8a09c394..c84c1ef0819 100644 --- a/doc/OnlineDocs/src/data/set1.py +++ b/doc/OnlineDocs/src/data/set1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/set2.py b/doc/OnlineDocs/src/data/set2.py index 8e2f4b756d9..9048a49fecb 100644 --- a/doc/OnlineDocs/src/data/set2.py +++ b/doc/OnlineDocs/src/data/set2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/set2a.py b/doc/OnlineDocs/src/data/set2a.py index 6358178d109..f2fa4d71916 100644 --- a/doc/OnlineDocs/src/data/set2a.py +++ b/doc/OnlineDocs/src/data/set2a.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/set3.py b/doc/OnlineDocs/src/data/set3.py index dced69c2375..9cdacbe39e0 100644 --- a/doc/OnlineDocs/src/data/set3.py +++ b/doc/OnlineDocs/src/data/set3.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/set4.py b/doc/OnlineDocs/src/data/set4.py index 887d12ebf05..b3485638c6f 100644 --- a/doc/OnlineDocs/src/data/set4.py +++ b/doc/OnlineDocs/src/data/set4.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/set5.py b/doc/OnlineDocs/src/data/set5.py index 8fb5ced0a3f..d745d8408d0 100644 --- a/doc/OnlineDocs/src/data/set5.py +++ b/doc/OnlineDocs/src/data/set5.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/table0.py b/doc/OnlineDocs/src/data/table0.py index 10ace6ea37b..de0fae0c861 100644 --- a/doc/OnlineDocs/src/data/table0.py +++ b/doc/OnlineDocs/src/data/table0.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/table0.ul.py b/doc/OnlineDocs/src/data/table0.ul.py index 79dc2933667..524c3756782 100644 --- a/doc/OnlineDocs/src/data/table0.ul.py +++ b/doc/OnlineDocs/src/data/table0.ul.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/table1.py b/doc/OnlineDocs/src/data/table1.py index d69c35b4860..f36714b8f1f 100644 --- a/doc/OnlineDocs/src/data/table1.py +++ b/doc/OnlineDocs/src/data/table1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/table2.py b/doc/OnlineDocs/src/data/table2.py index 5b4718f60ad..03648a00f8c 100644 --- a/doc/OnlineDocs/src/data/table2.py +++ b/doc/OnlineDocs/src/data/table2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/table3.py b/doc/OnlineDocs/src/data/table3.py index efa438eddd0..2c598f112df 100644 --- a/doc/OnlineDocs/src/data/table3.py +++ b/doc/OnlineDocs/src/data/table3.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/table3.ul.py b/doc/OnlineDocs/src/data/table3.ul.py index ace661ac91c..18ced12b388 100644 --- a/doc/OnlineDocs/src/data/table3.ul.py +++ b/doc/OnlineDocs/src/data/table3.ul.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/table4.py b/doc/OnlineDocs/src/data/table4.py index b571d8ec1e4..bd20682b5a9 100644 --- a/doc/OnlineDocs/src/data/table4.py +++ b/doc/OnlineDocs/src/data/table4.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/table4.ul.py b/doc/OnlineDocs/src/data/table4.ul.py index 1b4f192130b..9f16f21fe19 100644 --- a/doc/OnlineDocs/src/data/table4.ul.py +++ b/doc/OnlineDocs/src/data/table4.ul.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/table5.py b/doc/OnlineDocs/src/data/table5.py index 25269bb0bde..a3cb01209a2 100644 --- a/doc/OnlineDocs/src/data/table5.py +++ b/doc/OnlineDocs/src/data/table5.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/table6.py b/doc/OnlineDocs/src/data/table6.py index 1e2201cb6d6..1db0a764a23 100644 --- a/doc/OnlineDocs/src/data/table6.py +++ b/doc/OnlineDocs/src/data/table6.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/data/table7.py b/doc/OnlineDocs/src/data/table7.py index e5507c546ea..84a841aca86 100644 --- a/doc/OnlineDocs/src/data/table7.py +++ b/doc/OnlineDocs/src/data/table7.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/dataportal/PP_sqlite.py b/doc/OnlineDocs/src/dataportal/PP_sqlite.py index 9c6fc5ddc0b..1592e820900 100644 --- a/doc/OnlineDocs/src/dataportal/PP_sqlite.py +++ b/doc/OnlineDocs/src/dataportal/PP_sqlite.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/dataportal/dataportal_tab.py b/doc/OnlineDocs/src/dataportal/dataportal_tab.py index d6b679078e6..655329d31de 100644 --- a/doc/OnlineDocs/src/dataportal/dataportal_tab.py +++ b/doc/OnlineDocs/src/dataportal/dataportal_tab.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/dataportal/param_initialization.py b/doc/OnlineDocs/src/dataportal/param_initialization.py index 71c54b4a9d9..7f9270b5fda 100644 --- a/doc/OnlineDocs/src/dataportal/param_initialization.py +++ b/doc/OnlineDocs/src/dataportal/param_initialization.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/dataportal/set_initialization.py b/doc/OnlineDocs/src/dataportal/set_initialization.py index a086473fb1c..a5ab03894e3 100644 --- a/doc/OnlineDocs/src/dataportal/set_initialization.py +++ b/doc/OnlineDocs/src/dataportal/set_initialization.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/expr/design.py b/doc/OnlineDocs/src/expr/design.py index a5401a3c554..647a4537ca4 100644 --- a/doc/OnlineDocs/src/expr/design.py +++ b/doc/OnlineDocs/src/expr/design.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/expr/index.py b/doc/OnlineDocs/src/expr/index.py index 65291c0ff6f..fe5b03461c0 100644 --- a/doc/OnlineDocs/src/expr/index.py +++ b/doc/OnlineDocs/src/expr/index.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/expr/managing.py b/doc/OnlineDocs/src/expr/managing.py index 48bb005943e..00d521d16ab 100644 --- a/doc/OnlineDocs/src/expr/managing.py +++ b/doc/OnlineDocs/src/expr/managing.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/expr/overview.py b/doc/OnlineDocs/src/expr/overview.py index 32a5f569115..d33725edb88 100644 --- a/doc/OnlineDocs/src/expr/overview.py +++ b/doc/OnlineDocs/src/expr/overview.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/expr/performance.py b/doc/OnlineDocs/src/expr/performance.py index 59514718cb4..8936bd2ed8c 100644 --- a/doc/OnlineDocs/src/expr/performance.py +++ b/doc/OnlineDocs/src/expr/performance.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/expr/quicksum.py b/doc/OnlineDocs/src/expr/quicksum.py index 6b4d70bd961..1b6cd3f9909 100644 --- a/doc/OnlineDocs/src/expr/quicksum.py +++ b/doc/OnlineDocs/src/expr/quicksum.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/AbstractSuffixes.py b/doc/OnlineDocs/src/scripting/AbstractSuffixes.py index 24162d7bc8f..1c064042c6b 100644 --- a/doc/OnlineDocs/src/scripting/AbstractSuffixes.py +++ b/doc/OnlineDocs/src/scripting/AbstractSuffixes.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/Isinglebuild.py b/doc/OnlineDocs/src/scripting/Isinglebuild.py index b31e692d198..344f8905a4a 100644 --- a/doc/OnlineDocs/src/scripting/Isinglebuild.py +++ b/doc/OnlineDocs/src/scripting/Isinglebuild.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/NodesIn_init.py b/doc/OnlineDocs/src/scripting/NodesIn_init.py index 60268ef3183..c17b70150bc 100644 --- a/doc/OnlineDocs/src/scripting/NodesIn_init.py +++ b/doc/OnlineDocs/src/scripting/NodesIn_init.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/Z_init.py b/doc/OnlineDocs/src/scripting/Z_init.py index ab441eef101..1dd2843f4f0 100644 --- a/doc/OnlineDocs/src/scripting/Z_init.py +++ b/doc/OnlineDocs/src/scripting/Z_init.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/abstract2.py b/doc/OnlineDocs/src/scripting/abstract2.py index 1f7c35508db..544399a8a42 100644 --- a/doc/OnlineDocs/src/scripting/abstract2.py +++ b/doc/OnlineDocs/src/scripting/abstract2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/abstract2piece.py b/doc/OnlineDocs/src/scripting/abstract2piece.py index 8b58184e5e4..03c5139004e 100644 --- a/doc/OnlineDocs/src/scripting/abstract2piece.py +++ b/doc/OnlineDocs/src/scripting/abstract2piece.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/abstract2piecebuild.py b/doc/OnlineDocs/src/scripting/abstract2piecebuild.py index 694ee2b0336..d454d7fbc79 100644 --- a/doc/OnlineDocs/src/scripting/abstract2piecebuild.py +++ b/doc/OnlineDocs/src/scripting/abstract2piecebuild.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/block_iter_example.py b/doc/OnlineDocs/src/scripting/block_iter_example.py index 27d5a0e1819..10c8a4ea43d 100644 --- a/doc/OnlineDocs/src/scripting/block_iter_example.py +++ b/doc/OnlineDocs/src/scripting/block_iter_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/concrete1.py b/doc/OnlineDocs/src/scripting/concrete1.py index 31986c21ded..399715efde6 100644 --- a/doc/OnlineDocs/src/scripting/concrete1.py +++ b/doc/OnlineDocs/src/scripting/concrete1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/doubleA.py b/doc/OnlineDocs/src/scripting/doubleA.py index 2f65266d685..abf35979a05 100644 --- a/doc/OnlineDocs/src/scripting/doubleA.py +++ b/doc/OnlineDocs/src/scripting/doubleA.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/driveabs2.py b/doc/OnlineDocs/src/scripting/driveabs2.py index 87056f0fcb6..f8f972460b1 100644 --- a/doc/OnlineDocs/src/scripting/driveabs2.py +++ b/doc/OnlineDocs/src/scripting/driveabs2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/driveconc1.py b/doc/OnlineDocs/src/scripting/driveconc1.py index 2f7ece65a30..49b92f32d09 100644 --- a/doc/OnlineDocs/src/scripting/driveconc1.py +++ b/doc/OnlineDocs/src/scripting/driveconc1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/iterative1.py b/doc/OnlineDocs/src/scripting/iterative1.py index 8e91ea0d516..939120e834f 100644 --- a/doc/OnlineDocs/src/scripting/iterative1.py +++ b/doc/OnlineDocs/src/scripting/iterative1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/iterative2.py b/doc/OnlineDocs/src/scripting/iterative2.py index 558e7427441..7506337a491 100644 --- a/doc/OnlineDocs/src/scripting/iterative2.py +++ b/doc/OnlineDocs/src/scripting/iterative2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/noiteration1.py b/doc/OnlineDocs/src/scripting/noiteration1.py index 079b99365da..c7a86e9d1e9 100644 --- a/doc/OnlineDocs/src/scripting/noiteration1.py +++ b/doc/OnlineDocs/src/scripting/noiteration1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/parallel.py b/doc/OnlineDocs/src/scripting/parallel.py index ead3e1d674b..e6cfa002780 100644 --- a/doc/OnlineDocs/src/scripting/parallel.py +++ b/doc/OnlineDocs/src/scripting/parallel.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/spy4Constraints.py b/doc/OnlineDocs/src/scripting/spy4Constraints.py index f0f43672602..66f82802402 100644 --- a/doc/OnlineDocs/src/scripting/spy4Constraints.py +++ b/doc/OnlineDocs/src/scripting/spy4Constraints.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/spy4Expressions.py b/doc/OnlineDocs/src/scripting/spy4Expressions.py index 415481203a5..cf7ed1f112f 100644 --- a/doc/OnlineDocs/src/scripting/spy4Expressions.py +++ b/doc/OnlineDocs/src/scripting/spy4Expressions.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/spy4PyomoCommand.py b/doc/OnlineDocs/src/scripting/spy4PyomoCommand.py index 66dcb5e36b4..9f6698d63c9 100644 --- a/doc/OnlineDocs/src/scripting/spy4PyomoCommand.py +++ b/doc/OnlineDocs/src/scripting/spy4PyomoCommand.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/spy4Variables.py b/doc/OnlineDocs/src/scripting/spy4Variables.py index 1dcdfc58a10..1bc2dc9f1ef 100644 --- a/doc/OnlineDocs/src/scripting/spy4Variables.py +++ b/doc/OnlineDocs/src/scripting/spy4Variables.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/scripting/spy4scripts.py b/doc/OnlineDocs/src/scripting/spy4scripts.py index d35030241fc..f71a1b67b11 100644 --- a/doc/OnlineDocs/src/scripting/spy4scripts.py +++ b/doc/OnlineDocs/src/scripting/spy4scripts.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/strip_examples.py b/doc/OnlineDocs/src/strip_examples.py index dc742c1f53b..2fd03256499 100644 --- a/doc/OnlineDocs/src/strip_examples.py +++ b/doc/OnlineDocs/src/strip_examples.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/doc/OnlineDocs/src/test_examples.py b/doc/OnlineDocs/src/test_examples.py index a7991eadf19..c5c9a135ee9 100644 --- a/doc/OnlineDocs/src/test_examples.py +++ b/doc/OnlineDocs/src/test_examples.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/Heat_Conduction.py b/examples/dae/Heat_Conduction.py index 11f35fddd13..7e11ec59263 100644 --- a/examples/dae/Heat_Conduction.py +++ b/examples/dae/Heat_Conduction.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/Optimal_Control.py b/examples/dae/Optimal_Control.py index ed44d5eeb59..676c95271f2 100644 --- a/examples/dae/Optimal_Control.py +++ b/examples/dae/Optimal_Control.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/PDE_example.py b/examples/dae/PDE_example.py index 6cb7eb4a7fe..0aea173415b 100644 --- a/examples/dae/PDE_example.py +++ b/examples/dae/PDE_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/Parameter_Estimation.py b/examples/dae/Parameter_Estimation.py index 7ee2f112b94..332a21d93dc 100644 --- a/examples/dae/Parameter_Estimation.py +++ b/examples/dae/Parameter_Estimation.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/Path_Constraint.py b/examples/dae/Path_Constraint.py index 866b4b3b90a..69f31980c63 100644 --- a/examples/dae/Path_Constraint.py +++ b/examples/dae/Path_Constraint.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/ReactionKinetics.py b/examples/dae/ReactionKinetics.py index ef760820c4b..fa747cf8b21 100644 --- a/examples/dae/ReactionKinetics.py +++ b/examples/dae/ReactionKinetics.py @@ -2,7 +2,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/car_example.py b/examples/dae/car_example.py index f632e83f62a..b6ca2203860 100644 --- a/examples/dae/car_example.py +++ b/examples/dae/car_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/disease_DAE.py b/examples/dae/disease_DAE.py index 319e7276d83..bfeb2530fc9 100644 --- a/examples/dae/disease_DAE.py +++ b/examples/dae/disease_DAE.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/distill_DAE.py b/examples/dae/distill_DAE.py index cdfd543f9a8..e822cfb1752 100644 --- a/examples/dae/distill_DAE.py +++ b/examples/dae/distill_DAE.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/dynamic_scheduling.py b/examples/dae/dynamic_scheduling.py index 13cabeb5bcf..137307e31a9 100644 --- a/examples/dae/dynamic_scheduling.py +++ b/examples/dae/dynamic_scheduling.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/laplace_BVP.py b/examples/dae/laplace_BVP.py index 6b2e2841575..61f911b3826 100644 --- a/examples/dae/laplace_BVP.py +++ b/examples/dae/laplace_BVP.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/run_Optimal_Control.py b/examples/dae/run_Optimal_Control.py index 2523bd8c607..2e7bc79dff4 100644 --- a/examples/dae/run_Optimal_Control.py +++ b/examples/dae/run_Optimal_Control.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/run_Parameter_Estimation.py b/examples/dae/run_Parameter_Estimation.py index a319000cb59..c9b649df8dd 100644 --- a/examples/dae/run_Parameter_Estimation.py +++ b/examples/dae/run_Parameter_Estimation.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/run_Path_Constraint.py b/examples/dae/run_Path_Constraint.py index 17a576a57d8..996b432a555 100644 --- a/examples/dae/run_Path_Constraint.py +++ b/examples/dae/run_Path_Constraint.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/run_disease.py b/examples/dae/run_disease.py index 04457dfc890..5d9595a89d5 100644 --- a/examples/dae/run_disease.py +++ b/examples/dae/run_disease.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/run_distill.py b/examples/dae/run_distill.py index d9ececf34fc..9b09850f90a 100644 --- a/examples/dae/run_distill.py +++ b/examples/dae/run_distill.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/run_stochpdegas_automatic.py b/examples/dae/run_stochpdegas_automatic.py index 92f95b9d828..6fc9f6d594c 100644 --- a/examples/dae/run_stochpdegas_automatic.py +++ b/examples/dae/run_stochpdegas_automatic.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/simulator_dae_example.py b/examples/dae/simulator_dae_example.py index 81fd3af816d..4ea1f9fd5f0 100644 --- a/examples/dae/simulator_dae_example.py +++ b/examples/dae/simulator_dae_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/simulator_dae_multindex_example.py b/examples/dae/simulator_dae_multindex_example.py index bc17fd41643..775eb4f8c79 100644 --- a/examples/dae/simulator_dae_multindex_example.py +++ b/examples/dae/simulator_dae_multindex_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/simulator_ode_example.py b/examples/dae/simulator_ode_example.py index ae30071f4ef..f6f28b87d07 100644 --- a/examples/dae/simulator_ode_example.py +++ b/examples/dae/simulator_ode_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/simulator_ode_multindex_example.py b/examples/dae/simulator_ode_multindex_example.py index e02eabef076..b1b9111084b 100644 --- a/examples/dae/simulator_ode_multindex_example.py +++ b/examples/dae/simulator_ode_multindex_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/dae/stochpdegas_automatic.py b/examples/dae/stochpdegas_automatic.py index e846d045ddc..397b4a18100 100644 --- a/examples/dae/stochpdegas_automatic.py +++ b/examples/dae/stochpdegas_automatic.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/__init__.py b/examples/doc/samples/__init__.py index c967348cb68..0110902b288 100644 --- a/examples/doc/samples/__init__.py +++ b/examples/doc/samples/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/case_studies/deer/DeerProblem.py b/examples/doc/samples/case_studies/deer/DeerProblem.py index dfdc987ade4..d09c9b53887 100644 --- a/examples/doc/samples/case_studies/deer/DeerProblem.py +++ b/examples/doc/samples/case_studies/deer/DeerProblem.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/case_studies/diet/DietProblem.py b/examples/doc/samples/case_studies/diet/DietProblem.py index 462deee03a5..64624310943 100644 --- a/examples/doc/samples/case_studies/diet/DietProblem.py +++ b/examples/doc/samples/case_studies/diet/DietProblem.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/case_studies/disease_est/DiseaseEstimation.py b/examples/doc/samples/case_studies/disease_est/DiseaseEstimation.py index d8f3f94bcc5..6a0edb38350 100644 --- a/examples/doc/samples/case_studies/disease_est/DiseaseEstimation.py +++ b/examples/doc/samples/case_studies/disease_est/DiseaseEstimation.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/case_studies/max_flow/MaxFlow.py b/examples/doc/samples/case_studies/max_flow/MaxFlow.py index 36d42dfd3e3..1e75fa4e79d 100644 --- a/examples/doc/samples/case_studies/max_flow/MaxFlow.py +++ b/examples/doc/samples/case_studies/max_flow/MaxFlow.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/case_studies/network_flow/networkFlow1.py b/examples/doc/samples/case_studies/network_flow/networkFlow1.py index a1d05ccbd20..eb8c8e48a1a 100644 --- a/examples/doc/samples/case_studies/network_flow/networkFlow1.py +++ b/examples/doc/samples/case_studies/network_flow/networkFlow1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/case_studies/rosen/Rosenbrock.py b/examples/doc/samples/case_studies/rosen/Rosenbrock.py index f70fa30199c..51e7d51b57d 100644 --- a/examples/doc/samples/case_studies/rosen/Rosenbrock.py +++ b/examples/doc/samples/case_studies/rosen/Rosenbrock.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/case_studies/transportation/transportation.py b/examples/doc/samples/case_studies/transportation/transportation.py index 620e6c3fa1d..588ae764953 100644 --- a/examples/doc/samples/case_studies/transportation/transportation.py +++ b/examples/doc/samples/case_studies/transportation/transportation.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/comparisons/cutstock/cutstock_cplex.py b/examples/doc/samples/comparisons/cutstock/cutstock_cplex.py index e61f82b388d..f49c5b591ae 100644 --- a/examples/doc/samples/comparisons/cutstock/cutstock_cplex.py +++ b/examples/doc/samples/comparisons/cutstock/cutstock_cplex.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/comparisons/cutstock/cutstock_grb.py b/examples/doc/samples/comparisons/cutstock/cutstock_grb.py index 9940c729b6e..483d84b02e6 100644 --- a/examples/doc/samples/comparisons/cutstock/cutstock_grb.py +++ b/examples/doc/samples/comparisons/cutstock/cutstock_grb.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/comparisons/cutstock/cutstock_lpsolve.py b/examples/doc/samples/comparisons/cutstock/cutstock_lpsolve.py index 5bc7aea2116..9a6c8301e8f 100644 --- a/examples/doc/samples/comparisons/cutstock/cutstock_lpsolve.py +++ b/examples/doc/samples/comparisons/cutstock/cutstock_lpsolve.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/comparisons/cutstock/cutstock_pulpor.py b/examples/doc/samples/comparisons/cutstock/cutstock_pulpor.py index 3f366e9b3e2..d14b0fe46c1 100644 --- a/examples/doc/samples/comparisons/cutstock/cutstock_pulpor.py +++ b/examples/doc/samples/comparisons/cutstock/cutstock_pulpor.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/comparisons/cutstock/cutstock_pyomo.py b/examples/doc/samples/comparisons/cutstock/cutstock_pyomo.py index c87b93c37a5..48d7e6b26fd 100644 --- a/examples/doc/samples/comparisons/cutstock/cutstock_pyomo.py +++ b/examples/doc/samples/comparisons/cutstock/cutstock_pyomo.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/comparisons/cutstock/cutstock_util.py b/examples/doc/samples/comparisons/cutstock/cutstock_util.py index 3fde234a0f9..da5349ec06c 100644 --- a/examples/doc/samples/comparisons/cutstock/cutstock_util.py +++ b/examples/doc/samples/comparisons/cutstock/cutstock_util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/comparisons/sched/pyomo/sched.py b/examples/doc/samples/comparisons/sched/pyomo/sched.py index 2c03bebb421..cf781713641 100644 --- a/examples/doc/samples/comparisons/sched/pyomo/sched.py +++ b/examples/doc/samples/comparisons/sched/pyomo/sched.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/scripts/__init__.py b/examples/doc/samples/scripts/__init__.py index c967348cb68..0110902b288 100644 --- a/examples/doc/samples/scripts/__init__.py +++ b/examples/doc/samples/scripts/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/scripts/s1/knapsack.py b/examples/doc/samples/scripts/s1/knapsack.py index 2965d76650c..cee3937b668 100644 --- a/examples/doc/samples/scripts/s1/knapsack.py +++ b/examples/doc/samples/scripts/s1/knapsack.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/scripts/s1/script.py b/examples/doc/samples/scripts/s1/script.py index b5d60af6182..4ddaea45e19 100644 --- a/examples/doc/samples/scripts/s1/script.py +++ b/examples/doc/samples/scripts/s1/script.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/scripts/s2/knapsack.py b/examples/doc/samples/scripts/s2/knapsack.py index 66e55188871..3131cee7bc5 100644 --- a/examples/doc/samples/scripts/s2/knapsack.py +++ b/examples/doc/samples/scripts/s2/knapsack.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/scripts/s2/script.py b/examples/doc/samples/scripts/s2/script.py index 481ae7b26bb..fe97d6ab8fd 100644 --- a/examples/doc/samples/scripts/s2/script.py +++ b/examples/doc/samples/scripts/s2/script.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/scripts/test_scripts.py b/examples/doc/samples/scripts/test_scripts.py index ca0c8a7cc4e..691a44aea2d 100644 --- a/examples/doc/samples/scripts/test_scripts.py +++ b/examples/doc/samples/scripts/test_scripts.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/doc/samples/update.py b/examples/doc/samples/update.py index ab2195d1f32..8789413303c 100644 --- a/examples/doc/samples/update.py +++ b/examples/doc/samples/update.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/batchProcessing.py b/examples/gdp/batchProcessing.py index 4f3fb02df25..9810f5d63f1 100644 --- a/examples/gdp/batchProcessing.py +++ b/examples/gdp/batchProcessing.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/circles/circles.py b/examples/gdp/circles/circles.py index 3a7846f1441..a8b7a156fad 100644 --- a/examples/gdp/circles/circles.py +++ b/examples/gdp/circles/circles.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/constrained_layout/cons_layout_model.py b/examples/gdp/constrained_layout/cons_layout_model.py index 9f9169ede22..d38fd0cc66b 100644 --- a/examples/gdp/constrained_layout/cons_layout_model.py +++ b/examples/gdp/constrained_layout/cons_layout_model.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/disease_model.py b/examples/gdp/disease_model.py index bc3e69600ec..498337e35e6 100644 --- a/examples/gdp/disease_model.py +++ b/examples/gdp/disease_model.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/eight_process/eight_proc_logical.py b/examples/gdp/eight_process/eight_proc_logical.py index 23827a52d71..4496427d421 100644 --- a/examples/gdp/eight_process/eight_proc_logical.py +++ b/examples/gdp/eight_process/eight_proc_logical.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/eight_process/eight_proc_model.py b/examples/gdp/eight_process/eight_proc_model.py index d333405e469..41bb6d462f1 100644 --- a/examples/gdp/eight_process/eight_proc_model.py +++ b/examples/gdp/eight_process/eight_proc_model.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/eight_process/eight_proc_verbose_model.py b/examples/gdp/eight_process/eight_proc_verbose_model.py index fc748cce20f..1fd68909146 100644 --- a/examples/gdp/eight_process/eight_proc_verbose_model.py +++ b/examples/gdp/eight_process/eight_proc_verbose_model.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/farm_layout/farm_layout.py b/examples/gdp/farm_layout/farm_layout.py index 87043bc4ff5..1b232b9cfa6 100644 --- a/examples/gdp/farm_layout/farm_layout.py +++ b/examples/gdp/farm_layout/farm_layout.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/jobshop-nodisjuncts.py b/examples/gdp/jobshop-nodisjuncts.py index bc656dc4717..0cd5b5ab274 100644 --- a/examples/gdp/jobshop-nodisjuncts.py +++ b/examples/gdp/jobshop-nodisjuncts.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/jobshop.py b/examples/gdp/jobshop.py index 619ece47e72..7119ee7655c 100644 --- a/examples/gdp/jobshop.py +++ b/examples/gdp/jobshop.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/medTermPurchasing_Literal.py b/examples/gdp/medTermPurchasing_Literal.py index 14ec25d750c..b6d16c216fe 100755 --- a/examples/gdp/medTermPurchasing_Literal.py +++ b/examples/gdp/medTermPurchasing_Literal.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/nine_process/small_process.py b/examples/gdp/nine_process/small_process.py index adc7098d991..2abffef1af6 100644 --- a/examples/gdp/nine_process/small_process.py +++ b/examples/gdp/nine_process/small_process.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/simple1.py b/examples/gdp/simple1.py index 323943cd552..de41c0bfd00 100644 --- a/examples/gdp/simple1.py +++ b/examples/gdp/simple1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/simple2.py b/examples/gdp/simple2.py index 9c13872100c..b066d705036 100644 --- a/examples/gdp/simple2.py +++ b/examples/gdp/simple2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/simple3.py b/examples/gdp/simple3.py index bbe9745d193..890daf8882b 100644 --- a/examples/gdp/simple3.py +++ b/examples/gdp/simple3.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/small_lit/basic_step.py b/examples/gdp/small_lit/basic_step.py index fd466dfc1f4..2d9da97167c 100644 --- a/examples/gdp/small_lit/basic_step.py +++ b/examples/gdp/small_lit/basic_step.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/small_lit/contracts_problem.py b/examples/gdp/small_lit/contracts_problem.py index 9d1254688b2..0c59d2264ee 100644 --- a/examples/gdp/small_lit/contracts_problem.py +++ b/examples/gdp/small_lit/contracts_problem.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/small_lit/ex1_Lee.py b/examples/gdp/small_lit/ex1_Lee.py index 05bd1bd1bc0..abbf470a1c3 100644 --- a/examples/gdp/small_lit/ex1_Lee.py +++ b/examples/gdp/small_lit/ex1_Lee.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/small_lit/ex_633_trespalacios.py b/examples/gdp/small_lit/ex_633_trespalacios.py index 499294be2ae..b0c5fbd85ac 100644 --- a/examples/gdp/small_lit/ex_633_trespalacios.py +++ b/examples/gdp/small_lit/ex_633_trespalacios.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/small_lit/nonconvex_HEN.py b/examples/gdp/small_lit/nonconvex_HEN.py index bdec0e2823a..05fad970b84 100644 --- a/examples/gdp/small_lit/nonconvex_HEN.py +++ b/examples/gdp/small_lit/nonconvex_HEN.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/stickies.py b/examples/gdp/stickies.py index 154a9cbc0cd..73b537ff13d 100644 --- a/examples/gdp/stickies.py +++ b/examples/gdp/stickies.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/strip_packing/stripPacking.py b/examples/gdp/strip_packing/stripPacking.py index fb2ed3f91fd..39f7208b838 100644 --- a/examples/gdp/strip_packing/stripPacking.py +++ b/examples/gdp/strip_packing/stripPacking.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/strip_packing/strip_packing_8rect.py b/examples/gdp/strip_packing/strip_packing_8rect.py index f03b3f798f9..2bd7c4840ca 100644 --- a/examples/gdp/strip_packing/strip_packing_8rect.py +++ b/examples/gdp/strip_packing/strip_packing_8rect.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/strip_packing/strip_packing_concrete.py b/examples/gdp/strip_packing/strip_packing_concrete.py index d5ace9632fd..b0907cdea61 100644 --- a/examples/gdp/strip_packing/strip_packing_concrete.py +++ b/examples/gdp/strip_packing/strip_packing_concrete.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/gdp/two_rxn_lee/two_rxn_model.py b/examples/gdp/two_rxn_lee/two_rxn_model.py index 4f9471b583a..98e4cc2e878 100644 --- a/examples/gdp/two_rxn_lee/two_rxn_model.py +++ b/examples/gdp/two_rxn_lee/two_rxn_model.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/kernel/blocks.py b/examples/kernel/blocks.py index b19108ffb44..db1cb6655c2 100644 --- a/examples/kernel/blocks.py +++ b/examples/kernel/blocks.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/kernel/conic.py b/examples/kernel/conic.py index 86e5a95580c..5ee66a00ee9 100644 --- a/examples/kernel/conic.py +++ b/examples/kernel/conic.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/kernel/constraints.py b/examples/kernel/constraints.py index e5bf9797987..69823a6ebbe 100644 --- a/examples/kernel/constraints.py +++ b/examples/kernel/constraints.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/kernel/containers.py b/examples/kernel/containers.py index 44c65bfbda8..9ec749b8c3e 100644 --- a/examples/kernel/containers.py +++ b/examples/kernel/containers.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/kernel/expressions.py b/examples/kernel/expressions.py index 2f27239f26e..faef8d1d4ad 100644 --- a/examples/kernel/expressions.py +++ b/examples/kernel/expressions.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/kernel/mosek/geometric1.py b/examples/kernel/mosek/geometric1.py index 9cd492f36cf..8148e707819 100644 --- a/examples/kernel/mosek/geometric1.py +++ b/examples/kernel/mosek/geometric1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/kernel/mosek/geometric2.py b/examples/kernel/mosek/geometric2.py index 2f75f721dc4..3fb62c86312 100644 --- a/examples/kernel/mosek/geometric2.py +++ b/examples/kernel/mosek/geometric2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/kernel/mosek/maximum_volume_cuboid.py b/examples/kernel/mosek/maximum_volume_cuboid.py index 661adc3bf5a..df200cc801c 100644 --- a/examples/kernel/mosek/maximum_volume_cuboid.py +++ b/examples/kernel/mosek/maximum_volume_cuboid.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/kernel/mosek/power1.py b/examples/kernel/mosek/power1.py index b8a306e7cdf..a6d6ebbe47d 100644 --- a/examples/kernel/mosek/power1.py +++ b/examples/kernel/mosek/power1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/kernel/mosek/semidefinite.py b/examples/kernel/mosek/semidefinite.py index 177662205f8..6be47d85451 100644 --- a/examples/kernel/mosek/semidefinite.py +++ b/examples/kernel/mosek/semidefinite.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/kernel/objectives.py b/examples/kernel/objectives.py index bbb0c704211..27a41f4edb5 100644 --- a/examples/kernel/objectives.py +++ b/examples/kernel/objectives.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/kernel/parameters.py b/examples/kernel/parameters.py index a8bce6ca6af..e9e412525bb 100644 --- a/examples/kernel/parameters.py +++ b/examples/kernel/parameters.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/kernel/piecewise_functions.py b/examples/kernel/piecewise_functions.py index f372227fcb4..73a7f680725 100644 --- a/examples/kernel/piecewise_functions.py +++ b/examples/kernel/piecewise_functions.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/kernel/piecewise_nd_functions.py b/examples/kernel/piecewise_nd_functions.py index 78739e3825c..7de37fcbfc6 100644 --- a/examples/kernel/piecewise_nd_functions.py +++ b/examples/kernel/piecewise_nd_functions.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/kernel/special_ordered_sets.py b/examples/kernel/special_ordered_sets.py index 53328923a60..abacc3d4205 100644 --- a/examples/kernel/special_ordered_sets.py +++ b/examples/kernel/special_ordered_sets.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/kernel/suffixes.py b/examples/kernel/suffixes.py index 029dd046f26..ae95fbbdd09 100644 --- a/examples/kernel/suffixes.py +++ b/examples/kernel/suffixes.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/kernel/variables.py b/examples/kernel/variables.py index b2dd0ae8dff..36865b58183 100644 --- a/examples/kernel/variables.py +++ b/examples/kernel/variables.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/mpec/bard1.py b/examples/mpec/bard1.py index 4a6f7ab6642..59955eefb8e 100644 --- a/examples/mpec/bard1.py +++ b/examples/mpec/bard1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/mpec/df.py b/examples/mpec/df.py index 41984992bdd..7bb25b11e07 100644 --- a/examples/mpec/df.py +++ b/examples/mpec/df.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/mpec/indexed.py b/examples/mpec/indexed.py index b69d5093477..0aff5de5b20 100644 --- a/examples/mpec/indexed.py +++ b/examples/mpec/indexed.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/mpec/linear1.py b/examples/mpec/linear1.py index eba04759ae3..f24fd357e62 100644 --- a/examples/mpec/linear1.py +++ b/examples/mpec/linear1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/mpec/munson1.py b/examples/mpec/munson1.py index debdf709db9..99c240b5c06 100644 --- a/examples/mpec/munson1.py +++ b/examples/mpec/munson1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/mpec/munson1a.py b/examples/mpec/munson1a.py index 519db4e6ec2..67f8f318531 100644 --- a/examples/mpec/munson1a.py +++ b/examples/mpec/munson1a.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/mpec/munson1b.py b/examples/mpec/munson1b.py index ff2b7b51294..46fff90a785 100644 --- a/examples/mpec/munson1b.py +++ b/examples/mpec/munson1b.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/mpec/munson1c.py b/examples/mpec/munson1c.py index 2592b25c515..dee5b224e75 100644 --- a/examples/mpec/munson1c.py +++ b/examples/mpec/munson1c.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/mpec/munson1d.py b/examples/mpec/munson1d.py index 0fb08ce73fb..157177f2eb0 100644 --- a/examples/mpec/munson1d.py +++ b/examples/mpec/munson1d.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/mpec/scholtes4.py b/examples/mpec/scholtes4.py index 93cdb8fa6fe..8d574dd1916 100644 --- a/examples/mpec/scholtes4.py +++ b/examples/mpec/scholtes4.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/dae/run_stochpdegas1_automatic.py b/examples/performance/dae/run_stochpdegas1_automatic.py index 5eacc8992d1..fffa1a71ae1 100644 --- a/examples/performance/dae/run_stochpdegas1_automatic.py +++ b/examples/performance/dae/run_stochpdegas1_automatic.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/dae/stochpdegas1_automatic.py b/examples/performance/dae/stochpdegas1_automatic.py index 962ed266148..ce6132e6cf5 100644 --- a/examples/performance/dae/stochpdegas1_automatic.py +++ b/examples/performance/dae/stochpdegas1_automatic.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/jump/clnlbeam.py b/examples/performance/jump/clnlbeam.py index 18948c1b549..410068a6753 100644 --- a/examples/performance/jump/clnlbeam.py +++ b/examples/performance/jump/clnlbeam.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/jump/facility.py b/examples/performance/jump/facility.py index b67f21bd048..fa0c306d6e5 100644 --- a/examples/performance/jump/facility.py +++ b/examples/performance/jump/facility.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/jump/lqcp.py b/examples/performance/jump/lqcp.py index b4da6b62a5e..b8ef096d7be 100644 --- a/examples/performance/jump/lqcp.py +++ b/examples/performance/jump/lqcp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/jump/opf_66200bus.py b/examples/performance/jump/opf_66200bus.py index 022eb938fb0..702ff59a61c 100644 --- a/examples/performance/jump/opf_66200bus.py +++ b/examples/performance/jump/opf_66200bus.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/jump/opf_6620bus.py b/examples/performance/jump/opf_6620bus.py index 0e139cd8c5f..34b910f43c0 100644 --- a/examples/performance/jump/opf_6620bus.py +++ b/examples/performance/jump/opf_6620bus.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/jump/opf_662bus.py b/examples/performance/jump/opf_662bus.py index 5270c573236..8a768ca16e0 100644 --- a/examples/performance/jump/opf_662bus.py +++ b/examples/performance/jump/opf_662bus.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/misc/bilinear1_100.py b/examples/performance/misc/bilinear1_100.py index 527cf5d7ccc..d86091c4c76 100644 --- a/examples/performance/misc/bilinear1_100.py +++ b/examples/performance/misc/bilinear1_100.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/misc/bilinear1_100000.py b/examples/performance/misc/bilinear1_100000.py index 9fdef98c059..0fa2eafedc6 100644 --- a/examples/performance/misc/bilinear1_100000.py +++ b/examples/performance/misc/bilinear1_100000.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/misc/bilinear2_100.py b/examples/performance/misc/bilinear2_100.py index 77b8737339d..227bfe000e0 100644 --- a/examples/performance/misc/bilinear2_100.py +++ b/examples/performance/misc/bilinear2_100.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/misc/bilinear2_100000.py b/examples/performance/misc/bilinear2_100000.py index 7bf224f8b47..9d2a4d6fb7c 100644 --- a/examples/performance/misc/bilinear2_100000.py +++ b/examples/performance/misc/bilinear2_100000.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/misc/diag1_100.py b/examples/performance/misc/diag1_100.py index e92fc50201f..369d81982f0 100644 --- a/examples/performance/misc/diag1_100.py +++ b/examples/performance/misc/diag1_100.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/misc/diag1_100000.py b/examples/performance/misc/diag1_100000.py index 2bdfe99e749..536758fda5d 100644 --- a/examples/performance/misc/diag1_100000.py +++ b/examples/performance/misc/diag1_100000.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/misc/diag2_100.py b/examples/performance/misc/diag2_100.py index fe005eb74f1..6ad47528ff2 100644 --- a/examples/performance/misc/diag2_100.py +++ b/examples/performance/misc/diag2_100.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/misc/diag2_100000.py b/examples/performance/misc/diag2_100000.py index eca192b9679..b95e2dd1d6f 100644 --- a/examples/performance/misc/diag2_100000.py +++ b/examples/performance/misc/diag2_100000.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/misc/set1.py b/examples/performance/misc/set1.py index abf656ee350..8a8b84fdcc3 100644 --- a/examples/performance/misc/set1.py +++ b/examples/performance/misc/set1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/misc/sparse1.py b/examples/performance/misc/sparse1.py index 0858f374248..b4883d379bc 100644 --- a/examples/performance/misc/sparse1.py +++ b/examples/performance/misc/sparse1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/pmedian/pmedian1.py b/examples/performance/pmedian/pmedian1.py index 3d3f6c5407f..a22540efdd5 100644 --- a/examples/performance/pmedian/pmedian1.py +++ b/examples/performance/pmedian/pmedian1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/performance/pmedian/pmedian2.py b/examples/performance/pmedian/pmedian2.py index 434ded6dcbc..ff25a6c15eb 100644 --- a/examples/performance/pmedian/pmedian2.py +++ b/examples/performance/pmedian/pmedian2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/amplbook2/diet.py b/examples/pyomo/amplbook2/diet.py index 8cdffefa20f..cc52eacae20 100644 --- a/examples/pyomo/amplbook2/diet.py +++ b/examples/pyomo/amplbook2/diet.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/amplbook2/dieti.py b/examples/pyomo/amplbook2/dieti.py index 0934dcf83c6..45d403dd810 100644 --- a/examples/pyomo/amplbook2/dieti.py +++ b/examples/pyomo/amplbook2/dieti.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/amplbook2/econ2min.py b/examples/pyomo/amplbook2/econ2min.py index 0d27df780bb..fb870e02364 100644 --- a/examples/pyomo/amplbook2/econ2min.py +++ b/examples/pyomo/amplbook2/econ2min.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/amplbook2/econmin.py b/examples/pyomo/amplbook2/econmin.py index 84e41107ff2..d9c95758d4d 100644 --- a/examples/pyomo/amplbook2/econmin.py +++ b/examples/pyomo/amplbook2/econmin.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/amplbook2/prod.py b/examples/pyomo/amplbook2/prod.py index 74e456e013f..236f7254b29 100644 --- a/examples/pyomo/amplbook2/prod.py +++ b/examples/pyomo/amplbook2/prod.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/amplbook2/steel.py b/examples/pyomo/amplbook2/steel.py index 43bea775526..8c5c9b2a1d3 100644 --- a/examples/pyomo/amplbook2/steel.py +++ b/examples/pyomo/amplbook2/steel.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/amplbook2/steel3.py b/examples/pyomo/amplbook2/steel3.py index e9e494b6a1a..dd3b3ac202f 100644 --- a/examples/pyomo/amplbook2/steel3.py +++ b/examples/pyomo/amplbook2/steel3.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/amplbook2/steel4.py b/examples/pyomo/amplbook2/steel4.py index b6709e478e9..10cb0979d24 100644 --- a/examples/pyomo/amplbook2/steel4.py +++ b/examples/pyomo/amplbook2/steel4.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/benders/master.py b/examples/pyomo/benders/master.py index a457bf28b06..372810dc024 100644 --- a/examples/pyomo/benders/master.py +++ b/examples/pyomo/benders/master.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/benders/subproblem.py b/examples/pyomo/benders/subproblem.py index 886f71ff321..ae46dad2d41 100644 --- a/examples/pyomo/benders/subproblem.py +++ b/examples/pyomo/benders/subproblem.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/callbacks/sc.py b/examples/pyomo/callbacks/sc.py index ce32b0a1074..0882815c6b7 100644 --- a/examples/pyomo/callbacks/sc.py +++ b/examples/pyomo/callbacks/sc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/callbacks/sc_callback.py b/examples/pyomo/callbacks/sc_callback.py index 0dae9e1befc..cacc438b380 100644 --- a/examples/pyomo/callbacks/sc_callback.py +++ b/examples/pyomo/callbacks/sc_callback.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/callbacks/sc_script.py b/examples/pyomo/callbacks/sc_script.py index 8e4ade21b51..d3044e4d667 100644 --- a/examples/pyomo/callbacks/sc_script.py +++ b/examples/pyomo/callbacks/sc_script.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/callbacks/scalability/run.py b/examples/pyomo/callbacks/scalability/run.py index 8465e3f5019..cf95076fcc3 100644 --- a/examples/pyomo/callbacks/scalability/run.py +++ b/examples/pyomo/callbacks/scalability/run.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/callbacks/tsp.py b/examples/pyomo/callbacks/tsp.py index d3e28a98d3f..8526a540b66 100644 --- a/examples/pyomo/callbacks/tsp.py +++ b/examples/pyomo/callbacks/tsp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/columngeneration/cutting_stock.py b/examples/pyomo/columngeneration/cutting_stock.py index 58df6a5ad16..2d9399c7db4 100644 --- a/examples/pyomo/columngeneration/cutting_stock.py +++ b/examples/pyomo/columngeneration/cutting_stock.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/concrete/Whiskas.py b/examples/pyomo/concrete/Whiskas.py index 9bc8dd87e9d..3d3c19e94ac 100644 --- a/examples/pyomo/concrete/Whiskas.py +++ b/examples/pyomo/concrete/Whiskas.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/concrete/knapsack-abstract.py b/examples/pyomo/concrete/knapsack-abstract.py index bbef95f7810..9766d902722 100644 --- a/examples/pyomo/concrete/knapsack-abstract.py +++ b/examples/pyomo/concrete/knapsack-abstract.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/concrete/knapsack-concrete.py b/examples/pyomo/concrete/knapsack-concrete.py index cd115ab40a3..8966d0b8498 100644 --- a/examples/pyomo/concrete/knapsack-concrete.py +++ b/examples/pyomo/concrete/knapsack-concrete.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/concrete/rosen.py b/examples/pyomo/concrete/rosen.py index a8eb89081e8..ae51ae50ac0 100644 --- a/examples/pyomo/concrete/rosen.py +++ b/examples/pyomo/concrete/rosen.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/concrete/sodacan.py b/examples/pyomo/concrete/sodacan.py index fddc8d5aa95..5429b27a9d5 100644 --- a/examples/pyomo/concrete/sodacan.py +++ b/examples/pyomo/concrete/sodacan.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/concrete/sodacan_fig.py b/examples/pyomo/concrete/sodacan_fig.py index ab33f522dfe..b263eaf558d 100644 --- a/examples/pyomo/concrete/sodacan_fig.py +++ b/examples/pyomo/concrete/sodacan_fig.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/concrete/sp.py b/examples/pyomo/concrete/sp.py index 3a1b8aeef5a..e82a4bca0a9 100644 --- a/examples/pyomo/concrete/sp.py +++ b/examples/pyomo/concrete/sp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/concrete/sp_data.py b/examples/pyomo/concrete/sp_data.py index d65ae5a1d83..4453a10cead 100644 --- a/examples/pyomo/concrete/sp_data.py +++ b/examples/pyomo/concrete/sp_data.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/connectors/network_flow.py b/examples/pyomo/connectors/network_flow.py index cb75ca7ecf2..d5587fdf4c8 100644 --- a/examples/pyomo/connectors/network_flow.py +++ b/examples/pyomo/connectors/network_flow.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/connectors/network_flow_proposed.py b/examples/pyomo/connectors/network_flow_proposed.py index ed603ff6626..f234f2decf4 100644 --- a/examples/pyomo/connectors/network_flow_proposed.py +++ b/examples/pyomo/connectors/network_flow_proposed.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/core/block1.py b/examples/pyomo/core/block1.py index 96f8114f19c..161fc2ca2f7 100644 --- a/examples/pyomo/core/block1.py +++ b/examples/pyomo/core/block1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/core/integrality1.py b/examples/pyomo/core/integrality1.py index db81805555f..0ab3a433dac 100644 --- a/examples/pyomo/core/integrality1.py +++ b/examples/pyomo/core/integrality1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/core/integrality2.py b/examples/pyomo/core/integrality2.py index 2d85c9f2455..6461d36f923 100644 --- a/examples/pyomo/core/integrality2.py +++ b/examples/pyomo/core/integrality2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/core/simple.py b/examples/pyomo/core/simple.py index d0359c143bf..6976f3d25ad 100644 --- a/examples/pyomo/core/simple.py +++ b/examples/pyomo/core/simple.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/core/t1.py b/examples/pyomo/core/t1.py index 4135049d4be..5d5416985a9 100644 --- a/examples/pyomo/core/t1.py +++ b/examples/pyomo/core/t1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/core/t2.py b/examples/pyomo/core/t2.py index 5d687917fba..4d3f1934cbe 100644 --- a/examples/pyomo/core/t2.py +++ b/examples/pyomo/core/t2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/core/t5.py b/examples/pyomo/core/t5.py index 38605751015..6b9d94e0ff1 100644 --- a/examples/pyomo/core/t5.py +++ b/examples/pyomo/core/t5.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/diet/diet-sqlite.py b/examples/pyomo/diet/diet-sqlite.py index e8963485294..dccd3c338d0 100644 --- a/examples/pyomo/diet/diet-sqlite.py +++ b/examples/pyomo/diet/diet-sqlite.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/diet/diet1.py b/examples/pyomo/diet/diet1.py index 1fd61ca268c..217f80b9c25 100644 --- a/examples/pyomo/diet/diet1.py +++ b/examples/pyomo/diet/diet1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/diet/diet2.py b/examples/pyomo/diet/diet2.py index 526dbcef484..291261b0901 100644 --- a/examples/pyomo/diet/diet2.py +++ b/examples/pyomo/diet/diet2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/draft/api.py b/examples/pyomo/draft/api.py index 5b506882d9b..d785f41935e 100644 --- a/examples/pyomo/draft/api.py +++ b/examples/pyomo/draft/api.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/draft/bpack.py b/examples/pyomo/draft/bpack.py index 697ce531013..7b076f7737b 100644 --- a/examples/pyomo/draft/bpack.py +++ b/examples/pyomo/draft/bpack.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/draft/diet2.py b/examples/pyomo/draft/diet2.py index 9e4d2c5d9c4..d23fa3cf5db 100644 --- a/examples/pyomo/draft/diet2.py +++ b/examples/pyomo/draft/diet2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/p-median/decorated_pmedian.py b/examples/pyomo/p-median/decorated_pmedian.py index be4cc5994be..c66971945f3 100644 --- a/examples/pyomo/p-median/decorated_pmedian.py +++ b/examples/pyomo/p-median/decorated_pmedian.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/p-median/pmedian.py b/examples/pyomo/p-median/pmedian.py index 88731f287d8..865aa7cb61f 100644 --- a/examples/pyomo/p-median/pmedian.py +++ b/examples/pyomo/p-median/pmedian.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/p-median/solver1.py b/examples/pyomo/p-median/solver1.py index 113bf9fdd29..2652ab13943 100644 --- a/examples/pyomo/p-median/solver1.py +++ b/examples/pyomo/p-median/solver1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/p-median/solver2.py b/examples/pyomo/p-median/solver2.py index c62f161fd24..50ec5388811 100644 --- a/examples/pyomo/p-median/solver2.py +++ b/examples/pyomo/p-median/solver2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/piecewise/convex.py b/examples/pyomo/piecewise/convex.py index a3233ae5c3e..fb8095f80e3 100644 --- a/examples/pyomo/piecewise/convex.py +++ b/examples/pyomo/piecewise/convex.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/piecewise/indexed.py b/examples/pyomo/piecewise/indexed.py index dea56df3911..cde21ec847e 100644 --- a/examples/pyomo/piecewise/indexed.py +++ b/examples/pyomo/piecewise/indexed.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/piecewise/indexed_nonlinear.py b/examples/pyomo/piecewise/indexed_nonlinear.py index e871508d1be..d72fbc8a899 100644 --- a/examples/pyomo/piecewise/indexed_nonlinear.py +++ b/examples/pyomo/piecewise/indexed_nonlinear.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/piecewise/indexed_points.py b/examples/pyomo/piecewise/indexed_points.py index 15b1c33a7ec..66110bea342 100644 --- a/examples/pyomo/piecewise/indexed_points.py +++ b/examples/pyomo/piecewise/indexed_points.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/piecewise/nonconvex.py b/examples/pyomo/piecewise/nonconvex.py index 004748ab2eb..5300278d5b9 100644 --- a/examples/pyomo/piecewise/nonconvex.py +++ b/examples/pyomo/piecewise/nonconvex.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/piecewise/points.py b/examples/pyomo/piecewise/points.py index c822ceb5860..91d45684c4f 100644 --- a/examples/pyomo/piecewise/points.py +++ b/examples/pyomo/piecewise/points.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/piecewise/step.py b/examples/pyomo/piecewise/step.py index c3fbb4762ab..95aac74d7f7 100644 --- a/examples/pyomo/piecewise/step.py +++ b/examples/pyomo/piecewise/step.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/quadratic/example1.py b/examples/pyomo/quadratic/example1.py index dff911a0f0c..ab77c5a1733 100644 --- a/examples/pyomo/quadratic/example1.py +++ b/examples/pyomo/quadratic/example1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/quadratic/example2.py b/examples/pyomo/quadratic/example2.py index 981f2ef0bfb..ce02c6f70c8 100644 --- a/examples/pyomo/quadratic/example2.py +++ b/examples/pyomo/quadratic/example2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/quadratic/example3.py b/examples/pyomo/quadratic/example3.py index 4d96afe3328..bdba936f694 100644 --- a/examples/pyomo/quadratic/example3.py +++ b/examples/pyomo/quadratic/example3.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/quadratic/example4.py b/examples/pyomo/quadratic/example4.py index 256fc862a16..ecfc9981162 100644 --- a/examples/pyomo/quadratic/example4.py +++ b/examples/pyomo/quadratic/example4.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/radertext/Ex2_1.py b/examples/pyomo/radertext/Ex2_1.py index d352325798a..981388d4c72 100644 --- a/examples/pyomo/radertext/Ex2_1.py +++ b/examples/pyomo/radertext/Ex2_1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/radertext/Ex2_2.py b/examples/pyomo/radertext/Ex2_2.py index 13c23dd1816..41b56e52669 100644 --- a/examples/pyomo/radertext/Ex2_2.py +++ b/examples/pyomo/radertext/Ex2_2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/radertext/Ex2_3.py b/examples/pyomo/radertext/Ex2_3.py index d4dc3109ea1..7dc39afa773 100644 --- a/examples/pyomo/radertext/Ex2_3.py +++ b/examples/pyomo/radertext/Ex2_3.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/radertext/Ex2_5.py b/examples/pyomo/radertext/Ex2_5.py index da90b473b1f..fee49b46cb0 100644 --- a/examples/pyomo/radertext/Ex2_5.py +++ b/examples/pyomo/radertext/Ex2_5.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/radertext/Ex2_6a.py b/examples/pyomo/radertext/Ex2_6a.py index dc33a9b64e2..24bb866ec51 100644 --- a/examples/pyomo/radertext/Ex2_6a.py +++ b/examples/pyomo/radertext/Ex2_6a.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/radertext/Ex2_6b.py b/examples/pyomo/radertext/Ex2_6b.py index 8049d4ebb05..1be55461b9e 100644 --- a/examples/pyomo/radertext/Ex2_6b.py +++ b/examples/pyomo/radertext/Ex2_6b.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/sos/DepotSiting.py b/examples/pyomo/sos/DepotSiting.py index 98697681f44..40826e989b7 100644 --- a/examples/pyomo/sos/DepotSiting.py +++ b/examples/pyomo/sos/DepotSiting.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/sos/basic_sos2_example.py b/examples/pyomo/sos/basic_sos2_example.py index 655169ffe54..3aa0887356c 100644 --- a/examples/pyomo/sos/basic_sos2_example.py +++ b/examples/pyomo/sos/basic_sos2_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/sos/sos2_piecewise.py b/examples/pyomo/sos/sos2_piecewise.py index 4e79ce2ee62..79195761f3d 100644 --- a/examples/pyomo/sos/sos2_piecewise.py +++ b/examples/pyomo/sos/sos2_piecewise.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/suffixes/duals_pyomo.py b/examples/pyomo/suffixes/duals_pyomo.py index 9743add3ddd..6ce88fde429 100644 --- a/examples/pyomo/suffixes/duals_pyomo.py +++ b/examples/pyomo/suffixes/duals_pyomo.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/suffixes/duals_script.py b/examples/pyomo/suffixes/duals_script.py index a9db615cad3..e8ef9aef1bc 100644 --- a/examples/pyomo/suffixes/duals_script.py +++ b/examples/pyomo/suffixes/duals_script.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/suffixes/gurobi_ampl_basis.py b/examples/pyomo/suffixes/gurobi_ampl_basis.py index cd8e4e8f129..eab86f8aa47 100644 --- a/examples/pyomo/suffixes/gurobi_ampl_basis.py +++ b/examples/pyomo/suffixes/gurobi_ampl_basis.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/suffixes/gurobi_ampl_example.py b/examples/pyomo/suffixes/gurobi_ampl_example.py index d133fa422dc..4f3364c09dc 100644 --- a/examples/pyomo/suffixes/gurobi_ampl_example.py +++ b/examples/pyomo/suffixes/gurobi_ampl_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/suffixes/gurobi_ampl_iis.py b/examples/pyomo/suffixes/gurobi_ampl_iis.py index ccba226db78..da5bad073e7 100644 --- a/examples/pyomo/suffixes/gurobi_ampl_iis.py +++ b/examples/pyomo/suffixes/gurobi_ampl_iis.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/suffixes/ipopt_scaling.py b/examples/pyomo/suffixes/ipopt_scaling.py index c192a98dd98..7113128c21d 100644 --- a/examples/pyomo/suffixes/ipopt_scaling.py +++ b/examples/pyomo/suffixes/ipopt_scaling.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/suffixes/ipopt_warmstart.py b/examples/pyomo/suffixes/ipopt_warmstart.py index 6975bbaaa62..4882c48c8c8 100644 --- a/examples/pyomo/suffixes/ipopt_warmstart.py +++ b/examples/pyomo/suffixes/ipopt_warmstart.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/suffixes/sipopt_hicks.py b/examples/pyomo/suffixes/sipopt_hicks.py index dbf4e07b8f7..c7e058d5907 100644 --- a/examples/pyomo/suffixes/sipopt_hicks.py +++ b/examples/pyomo/suffixes/sipopt_hicks.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/suffixes/sipopt_parametric.py b/examples/pyomo/suffixes/sipopt_parametric.py index 29bba934bd8..0cb1c35f441 100644 --- a/examples/pyomo/suffixes/sipopt_parametric.py +++ b/examples/pyomo/suffixes/sipopt_parametric.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/transform/scaling_ex.py b/examples/pyomo/transform/scaling_ex.py index a5960393e75..34f937cbb45 100644 --- a/examples/pyomo/transform/scaling_ex.py +++ b/examples/pyomo/transform/scaling_ex.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/tutorials/data.py b/examples/pyomo/tutorials/data.py index d065c9ff9bc..ea2569af934 100644 --- a/examples/pyomo/tutorials/data.py +++ b/examples/pyomo/tutorials/data.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/tutorials/excel.py b/examples/pyomo/tutorials/excel.py index 127db722c07..f9a5f66826b 100644 --- a/examples/pyomo/tutorials/excel.py +++ b/examples/pyomo/tutorials/excel.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/tutorials/param.py b/examples/pyomo/tutorials/param.py index ba31975ab4b..5a94bafaa5e 100644 --- a/examples/pyomo/tutorials/param.py +++ b/examples/pyomo/tutorials/param.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/tutorials/set.py b/examples/pyomo/tutorials/set.py index 78f2656d739..a14301484c9 100644 --- a/examples/pyomo/tutorials/set.py +++ b/examples/pyomo/tutorials/set.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomo/tutorials/table.py b/examples/pyomo/tutorials/table.py index 16951352ee1..7d9fceda14a 100644 --- a/examples/pyomo/tutorials/table.py +++ b/examples/pyomo/tutorials/table.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/__init__.py b/examples/pyomobook/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/examples/pyomobook/__init__.py +++ b/examples/pyomobook/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/AbstHLinScript.py b/examples/pyomobook/abstract-ch/AbstHLinScript.py index 48946e0fb3d..687d3fc4e6b 100644 --- a/examples/pyomobook/abstract-ch/AbstHLinScript.py +++ b/examples/pyomobook/abstract-ch/AbstHLinScript.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/AbstractH.py b/examples/pyomobook/abstract-ch/AbstractH.py index cda8b489f28..7595cbc4933 100644 --- a/examples/pyomobook/abstract-ch/AbstractH.py +++ b/examples/pyomobook/abstract-ch/AbstractH.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/AbstractHLinear.py b/examples/pyomobook/abstract-ch/AbstractHLinear.py index 78ac4813709..f312020a9d5 100644 --- a/examples/pyomobook/abstract-ch/AbstractHLinear.py +++ b/examples/pyomobook/abstract-ch/AbstractHLinear.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/abstract5.py b/examples/pyomobook/abstract-ch/abstract5.py index 20abd31dbd6..8849d2dfe7f 100644 --- a/examples/pyomobook/abstract-ch/abstract5.py +++ b/examples/pyomobook/abstract-ch/abstract5.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/abstract6.py b/examples/pyomobook/abstract-ch/abstract6.py index fdbbed88d25..121b12a51fa 100644 --- a/examples/pyomobook/abstract-ch/abstract6.py +++ b/examples/pyomobook/abstract-ch/abstract6.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/abstract7.py b/examples/pyomobook/abstract-ch/abstract7.py index 21d264d53e1..3e8131bf42b 100644 --- a/examples/pyomobook/abstract-ch/abstract7.py +++ b/examples/pyomobook/abstract-ch/abstract7.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/buildactions.py b/examples/pyomobook/abstract-ch/buildactions.py index a64de052176..6963f285c4c 100644 --- a/examples/pyomobook/abstract-ch/buildactions.py +++ b/examples/pyomobook/abstract-ch/buildactions.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/concrete1.py b/examples/pyomobook/abstract-ch/concrete1.py index d2d6d09ac4f..2c89fbafaad 100644 --- a/examples/pyomobook/abstract-ch/concrete1.py +++ b/examples/pyomobook/abstract-ch/concrete1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/concrete2.py b/examples/pyomobook/abstract-ch/concrete2.py index d0500df53fa..f68c4d6e242 100644 --- a/examples/pyomobook/abstract-ch/concrete2.py +++ b/examples/pyomobook/abstract-ch/concrete2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/diet1.py b/examples/pyomobook/abstract-ch/diet1.py index 319bdec5144..fa8bf5f549f 100644 --- a/examples/pyomobook/abstract-ch/diet1.py +++ b/examples/pyomobook/abstract-ch/diet1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/ex.py b/examples/pyomobook/abstract-ch/ex.py index 2309f3330a0..83cfd445e01 100644 --- a/examples/pyomobook/abstract-ch/ex.py +++ b/examples/pyomobook/abstract-ch/ex.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/param1.py b/examples/pyomobook/abstract-ch/param1.py index f5f34838215..3ff8b648661 100644 --- a/examples/pyomobook/abstract-ch/param1.py +++ b/examples/pyomobook/abstract-ch/param1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/param2.py b/examples/pyomobook/abstract-ch/param2.py index ac3b5b8bd27..aca8fac0baf 100644 --- a/examples/pyomobook/abstract-ch/param2.py +++ b/examples/pyomobook/abstract-ch/param2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/param2a.py b/examples/pyomobook/abstract-ch/param2a.py index 59f455bc290..6b6f77f2a8f 100644 --- a/examples/pyomobook/abstract-ch/param2a.py +++ b/examples/pyomobook/abstract-ch/param2a.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/param3.py b/examples/pyomobook/abstract-ch/param3.py index 5c3462f2e64..7545b47dadc 100644 --- a/examples/pyomobook/abstract-ch/param3.py +++ b/examples/pyomobook/abstract-ch/param3.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/param3a.py b/examples/pyomobook/abstract-ch/param3a.py index 25b575e3266..4c52b6432fb 100644 --- a/examples/pyomobook/abstract-ch/param3a.py +++ b/examples/pyomobook/abstract-ch/param3a.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/param3b.py b/examples/pyomobook/abstract-ch/param3b.py index a4ad2d4ffc5..786d6b58a16 100644 --- a/examples/pyomobook/abstract-ch/param3b.py +++ b/examples/pyomobook/abstract-ch/param3b.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/param3c.py b/examples/pyomobook/abstract-ch/param3c.py index 96e2f4e88a4..3f5da5f837e 100644 --- a/examples/pyomobook/abstract-ch/param3c.py +++ b/examples/pyomobook/abstract-ch/param3c.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/param4.py b/examples/pyomobook/abstract-ch/param4.py index 4f427f44ee6..c1926ddea74 100644 --- a/examples/pyomobook/abstract-ch/param4.py +++ b/examples/pyomobook/abstract-ch/param4.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/param5.py b/examples/pyomobook/abstract-ch/param5.py index 6cdb46db30d..7e0020f70b7 100644 --- a/examples/pyomobook/abstract-ch/param5.py +++ b/examples/pyomobook/abstract-ch/param5.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/param5a.py b/examples/pyomobook/abstract-ch/param5a.py index cd0187dabb1..efdd1855f3f 100644 --- a/examples/pyomobook/abstract-ch/param5a.py +++ b/examples/pyomobook/abstract-ch/param5a.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/param6.py b/examples/pyomobook/abstract-ch/param6.py index e4cbb40f984..f6d60f11e4b 100644 --- a/examples/pyomobook/abstract-ch/param6.py +++ b/examples/pyomobook/abstract-ch/param6.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/param6a.py b/examples/pyomobook/abstract-ch/param6a.py index c2995ee864c..280e942d01d 100644 --- a/examples/pyomobook/abstract-ch/param6a.py +++ b/examples/pyomobook/abstract-ch/param6a.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/param7a.py b/examples/pyomobook/abstract-ch/param7a.py index 3ed9163daec..21839bf3b64 100644 --- a/examples/pyomobook/abstract-ch/param7a.py +++ b/examples/pyomobook/abstract-ch/param7a.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/param7b.py b/examples/pyomobook/abstract-ch/param7b.py index 59f5e28f979..a4d79b6dee9 100644 --- a/examples/pyomobook/abstract-ch/param7b.py +++ b/examples/pyomobook/abstract-ch/param7b.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/param8a.py b/examples/pyomobook/abstract-ch/param8a.py index 2e57f9c3bb7..f00ed649c30 100644 --- a/examples/pyomobook/abstract-ch/param8a.py +++ b/examples/pyomobook/abstract-ch/param8a.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/postprocess_fn.py b/examples/pyomobook/abstract-ch/postprocess_fn.py index b54c11b5a0e..2f2d114c216 100644 --- a/examples/pyomobook/abstract-ch/postprocess_fn.py +++ b/examples/pyomobook/abstract-ch/postprocess_fn.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/set1.py b/examples/pyomobook/abstract-ch/set1.py index 6c549f61c49..5a23fe683e0 100644 --- a/examples/pyomobook/abstract-ch/set1.py +++ b/examples/pyomobook/abstract-ch/set1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/set2.py b/examples/pyomobook/abstract-ch/set2.py index bd7f98d5174..5ecc0914bee 100644 --- a/examples/pyomobook/abstract-ch/set2.py +++ b/examples/pyomobook/abstract-ch/set2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/set2a.py b/examples/pyomobook/abstract-ch/set2a.py index e6960396dd7..7252ec0ad69 100644 --- a/examples/pyomobook/abstract-ch/set2a.py +++ b/examples/pyomobook/abstract-ch/set2a.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/set3.py b/examples/pyomobook/abstract-ch/set3.py index 4a3a27aa342..f3e3efc33c7 100644 --- a/examples/pyomobook/abstract-ch/set3.py +++ b/examples/pyomobook/abstract-ch/set3.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/set4.py b/examples/pyomobook/abstract-ch/set4.py index 7d782cb268e..0c29798b816 100644 --- a/examples/pyomobook/abstract-ch/set4.py +++ b/examples/pyomobook/abstract-ch/set4.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/set5.py b/examples/pyomobook/abstract-ch/set5.py index 7478316897a..781b956404e 100644 --- a/examples/pyomobook/abstract-ch/set5.py +++ b/examples/pyomobook/abstract-ch/set5.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/wl_abstract.py b/examples/pyomobook/abstract-ch/wl_abstract.py index 361729a1eff..61eeed6b506 100644 --- a/examples/pyomobook/abstract-ch/wl_abstract.py +++ b/examples/pyomobook/abstract-ch/wl_abstract.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/abstract-ch/wl_abstract_script.py b/examples/pyomobook/abstract-ch/wl_abstract_script.py index b70c6dbb8d2..7f0871350fc 100644 --- a/examples/pyomobook/abstract-ch/wl_abstract_script.py +++ b/examples/pyomobook/abstract-ch/wl_abstract_script.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/blocks-ch/blocks_gen.py b/examples/pyomobook/blocks-ch/blocks_gen.py index 7a74986ed81..31a4462f7d6 100644 --- a/examples/pyomobook/blocks-ch/blocks_gen.py +++ b/examples/pyomobook/blocks-ch/blocks_gen.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/blocks-ch/blocks_intro.py b/examples/pyomobook/blocks-ch/blocks_intro.py index 3160c29b385..ba2bd9d3a97 100644 --- a/examples/pyomobook/blocks-ch/blocks_intro.py +++ b/examples/pyomobook/blocks-ch/blocks_intro.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/blocks-ch/blocks_lotsizing.py b/examples/pyomobook/blocks-ch/blocks_lotsizing.py index 897ba9a4e5c..758ad964dc5 100644 --- a/examples/pyomobook/blocks-ch/blocks_lotsizing.py +++ b/examples/pyomobook/blocks-ch/blocks_lotsizing.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/blocks-ch/lotsizing.py b/examples/pyomobook/blocks-ch/lotsizing.py index 766c1892111..ece4d6b541c 100644 --- a/examples/pyomobook/blocks-ch/lotsizing.py +++ b/examples/pyomobook/blocks-ch/lotsizing.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/blocks-ch/lotsizing_no_time.py b/examples/pyomobook/blocks-ch/lotsizing_no_time.py index e0fa69922c1..60e8ba44424 100644 --- a/examples/pyomobook/blocks-ch/lotsizing_no_time.py +++ b/examples/pyomobook/blocks-ch/lotsizing_no_time.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/blocks-ch/lotsizing_uncertain.py b/examples/pyomobook/blocks-ch/lotsizing_uncertain.py index 9870d195841..f72161db5c6 100644 --- a/examples/pyomobook/blocks-ch/lotsizing_uncertain.py +++ b/examples/pyomobook/blocks-ch/lotsizing_uncertain.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/dae-ch/dae_tester_model.py b/examples/pyomobook/dae-ch/dae_tester_model.py index 00d51e8e05d..396b8a53db1 100644 --- a/examples/pyomobook/dae-ch/dae_tester_model.py +++ b/examples/pyomobook/dae-ch/dae_tester_model.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/dae-ch/path_constraint.py b/examples/pyomobook/dae-ch/path_constraint.py index 5fe41dd132d..5e252d1b99f 100644 --- a/examples/pyomobook/dae-ch/path_constraint.py +++ b/examples/pyomobook/dae-ch/path_constraint.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/dae-ch/plot_path_constraint.py b/examples/pyomobook/dae-ch/plot_path_constraint.py index d1af5c617ff..be86f13cbc0 100644 --- a/examples/pyomobook/dae-ch/plot_path_constraint.py +++ b/examples/pyomobook/dae-ch/plot_path_constraint.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/dae-ch/run_path_constraint.py b/examples/pyomobook/dae-ch/run_path_constraint.py index d4345e9e424..fc115f5649c 100644 --- a/examples/pyomobook/dae-ch/run_path_constraint.py +++ b/examples/pyomobook/dae-ch/run_path_constraint.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/dae-ch/run_path_constraint_tester.py b/examples/pyomobook/dae-ch/run_path_constraint_tester.py index d71c5126609..22d887e9b11 100644 --- a/examples/pyomobook/dae-ch/run_path_constraint_tester.py +++ b/examples/pyomobook/dae-ch/run_path_constraint_tester.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/gdp-ch/gdp_uc.py b/examples/pyomobook/gdp-ch/gdp_uc.py index 9f2562efad0..6268bcce068 100644 --- a/examples/pyomobook/gdp-ch/gdp_uc.py +++ b/examples/pyomobook/gdp-ch/gdp_uc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/gdp-ch/scont.py b/examples/pyomobook/gdp-ch/scont.py index 99beb042728..d1cf4b172bd 100644 --- a/examples/pyomobook/gdp-ch/scont.py +++ b/examples/pyomobook/gdp-ch/scont.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/gdp-ch/scont2.py b/examples/pyomobook/gdp-ch/scont2.py index cf392441487..2c77fe670d5 100644 --- a/examples/pyomobook/gdp-ch/scont2.py +++ b/examples/pyomobook/gdp-ch/scont2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/gdp-ch/scont_script.py b/examples/pyomobook/gdp-ch/scont_script.py index fee14bedaac..fe0702dc262 100644 --- a/examples/pyomobook/gdp-ch/scont_script.py +++ b/examples/pyomobook/gdp-ch/scont_script.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/gdp-ch/verify_scont.py b/examples/pyomobook/gdp-ch/verify_scont.py index a0acd3cf376..222453560b6 100644 --- a/examples/pyomobook/gdp-ch/verify_scont.py +++ b/examples/pyomobook/gdp-ch/verify_scont.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/intro-ch/abstract5.py b/examples/pyomobook/intro-ch/abstract5.py index b273d49b2ea..2caad5f9351 100644 --- a/examples/pyomobook/intro-ch/abstract5.py +++ b/examples/pyomobook/intro-ch/abstract5.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/intro-ch/coloring_concrete.py b/examples/pyomobook/intro-ch/coloring_concrete.py index 5b4baca99af..9931b5d80de 100644 --- a/examples/pyomobook/intro-ch/coloring_concrete.py +++ b/examples/pyomobook/intro-ch/coloring_concrete.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/intro-ch/concrete1.py b/examples/pyomobook/intro-ch/concrete1.py index c7aea6ff0b6..169fbeb281c 100644 --- a/examples/pyomobook/intro-ch/concrete1.py +++ b/examples/pyomobook/intro-ch/concrete1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/intro-ch/concrete1_generic.py b/examples/pyomobook/intro-ch/concrete1_generic.py index 183eb480fa1..9a2d26bded8 100644 --- a/examples/pyomobook/intro-ch/concrete1_generic.py +++ b/examples/pyomobook/intro-ch/concrete1_generic.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/intro-ch/mydata.py b/examples/pyomobook/intro-ch/mydata.py index aaf8ec3d8be..209546ebeaf 100644 --- a/examples/pyomobook/intro-ch/mydata.py +++ b/examples/pyomobook/intro-ch/mydata.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/mpec-ch/ex1a.py b/examples/pyomobook/mpec-ch/ex1a.py index a57e714cd1c..e6f1c33fbbc 100644 --- a/examples/pyomobook/mpec-ch/ex1a.py +++ b/examples/pyomobook/mpec-ch/ex1a.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/mpec-ch/ex1b.py b/examples/pyomobook/mpec-ch/ex1b.py index 37a658f5294..2b0ac2ce1b7 100644 --- a/examples/pyomobook/mpec-ch/ex1b.py +++ b/examples/pyomobook/mpec-ch/ex1b.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/mpec-ch/ex1c.py b/examples/pyomobook/mpec-ch/ex1c.py index 35c0be9345d..eaf0292b50d 100644 --- a/examples/pyomobook/mpec-ch/ex1c.py +++ b/examples/pyomobook/mpec-ch/ex1c.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/mpec-ch/ex1d.py b/examples/pyomobook/mpec-ch/ex1d.py index 05105df265c..4c0e0d9fd0f 100644 --- a/examples/pyomobook/mpec-ch/ex1d.py +++ b/examples/pyomobook/mpec-ch/ex1d.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/mpec-ch/ex1e.py b/examples/pyomobook/mpec-ch/ex1e.py index 66831a58255..c552847fcfb 100644 --- a/examples/pyomobook/mpec-ch/ex1e.py +++ b/examples/pyomobook/mpec-ch/ex1e.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/mpec-ch/ex2.py b/examples/pyomobook/mpec-ch/ex2.py index 69d3813432d..6981af33376 100644 --- a/examples/pyomobook/mpec-ch/ex2.py +++ b/examples/pyomobook/mpec-ch/ex2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/mpec-ch/munson1.py b/examples/pyomobook/mpec-ch/munson1.py index 1c73c6279af..e85d9359768 100644 --- a/examples/pyomobook/mpec-ch/munson1.py +++ b/examples/pyomobook/mpec-ch/munson1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/mpec-ch/ralph1.py b/examples/pyomobook/mpec-ch/ralph1.py index 38ee803b1f1..b6a8b45e8df 100644 --- a/examples/pyomobook/mpec-ch/ralph1.py +++ b/examples/pyomobook/mpec-ch/ralph1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/nonlinear-ch/deer/DeerProblem.py b/examples/pyomobook/nonlinear-ch/deer/DeerProblem.py index 574a92ed0a2..dc3ca179a58 100644 --- a/examples/pyomobook/nonlinear-ch/deer/DeerProblem.py +++ b/examples/pyomobook/nonlinear-ch/deer/DeerProblem.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/nonlinear-ch/disease_est/disease_estimation.py b/examples/pyomobook/nonlinear-ch/disease_est/disease_estimation.py index 4b805b9cf7f..5675d7a715b 100644 --- a/examples/pyomobook/nonlinear-ch/disease_est/disease_estimation.py +++ b/examples/pyomobook/nonlinear-ch/disease_est/disease_estimation.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/nonlinear-ch/multimodal/multimodal_init1.py b/examples/pyomobook/nonlinear-ch/multimodal/multimodal_init1.py index 6cebe59a612..a50bf3321d6 100644 --- a/examples/pyomobook/nonlinear-ch/multimodal/multimodal_init1.py +++ b/examples/pyomobook/nonlinear-ch/multimodal/multimodal_init1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/nonlinear-ch/multimodal/multimodal_init2.py b/examples/pyomobook/nonlinear-ch/multimodal/multimodal_init2.py index a2c9d9c5a60..6a209334521 100644 --- a/examples/pyomobook/nonlinear-ch/multimodal/multimodal_init2.py +++ b/examples/pyomobook/nonlinear-ch/multimodal/multimodal_init2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/nonlinear-ch/react_design/ReactorDesign.py b/examples/pyomobook/nonlinear-ch/react_design/ReactorDesign.py index c3115f396ce..1cfe3b7193f 100644 --- a/examples/pyomobook/nonlinear-ch/react_design/ReactorDesign.py +++ b/examples/pyomobook/nonlinear-ch/react_design/ReactorDesign.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/nonlinear-ch/react_design/ReactorDesignTable.py b/examples/pyomobook/nonlinear-ch/react_design/ReactorDesignTable.py index c748cd7d41e..2bd9574b427 100644 --- a/examples/pyomobook/nonlinear-ch/react_design/ReactorDesignTable.py +++ b/examples/pyomobook/nonlinear-ch/react_design/ReactorDesignTable.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/nonlinear-ch/rosen/rosenbrock.py b/examples/pyomobook/nonlinear-ch/rosen/rosenbrock.py index 3d14d15aa93..bec1d04c12c 100644 --- a/examples/pyomobook/nonlinear-ch/rosen/rosenbrock.py +++ b/examples/pyomobook/nonlinear-ch/rosen/rosenbrock.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/optimization-ch/ConcHLinScript.py b/examples/pyomobook/optimization-ch/ConcHLinScript.py index f4f5fac6b6c..b94903585dc 100644 --- a/examples/pyomobook/optimization-ch/ConcHLinScript.py +++ b/examples/pyomobook/optimization-ch/ConcHLinScript.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/optimization-ch/ConcreteH.py b/examples/pyomobook/optimization-ch/ConcreteH.py index 6cb3f7c5052..d7474291d0d 100644 --- a/examples/pyomobook/optimization-ch/ConcreteH.py +++ b/examples/pyomobook/optimization-ch/ConcreteH.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/optimization-ch/ConcreteHLinear.py b/examples/pyomobook/optimization-ch/ConcreteHLinear.py index 3cc7478f1c9..772c18cb6d5 100644 --- a/examples/pyomobook/optimization-ch/ConcreteHLinear.py +++ b/examples/pyomobook/optimization-ch/ConcreteHLinear.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/optimization-ch/IC_model_dict.py b/examples/pyomobook/optimization-ch/IC_model_dict.py index a76f19797af..b7e359777c7 100644 --- a/examples/pyomobook/optimization-ch/IC_model_dict.py +++ b/examples/pyomobook/optimization-ch/IC_model_dict.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/overview-ch/var_obj_con_snippet.py b/examples/pyomobook/overview-ch/var_obj_con_snippet.py index e979e4b18de..22524b5815a 100644 --- a/examples/pyomobook/overview-ch/var_obj_con_snippet.py +++ b/examples/pyomobook/overview-ch/var_obj_con_snippet.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/overview-ch/wl_abstract.py b/examples/pyomobook/overview-ch/wl_abstract.py index 361729a1eff..61eeed6b506 100644 --- a/examples/pyomobook/overview-ch/wl_abstract.py +++ b/examples/pyomobook/overview-ch/wl_abstract.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/overview-ch/wl_abstract_script.py b/examples/pyomobook/overview-ch/wl_abstract_script.py index b70c6dbb8d2..7f0871350fc 100644 --- a/examples/pyomobook/overview-ch/wl_abstract_script.py +++ b/examples/pyomobook/overview-ch/wl_abstract_script.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/overview-ch/wl_concrete.py b/examples/pyomobook/overview-ch/wl_concrete.py index da32c7ba5bf..c1bf70b07f1 100644 --- a/examples/pyomobook/overview-ch/wl_concrete.py +++ b/examples/pyomobook/overview-ch/wl_concrete.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/overview-ch/wl_concrete_script.py b/examples/pyomobook/overview-ch/wl_concrete_script.py index 59baa241718..b369521994c 100644 --- a/examples/pyomobook/overview-ch/wl_concrete_script.py +++ b/examples/pyomobook/overview-ch/wl_concrete_script.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/overview-ch/wl_excel.py b/examples/pyomobook/overview-ch/wl_excel.py index 777412abb23..180e36422fe 100644 --- a/examples/pyomobook/overview-ch/wl_excel.py +++ b/examples/pyomobook/overview-ch/wl_excel.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/overview-ch/wl_list.py b/examples/pyomobook/overview-ch/wl_list.py index 375a1c7400e..37cba5a9595 100644 --- a/examples/pyomobook/overview-ch/wl_list.py +++ b/examples/pyomobook/overview-ch/wl_list.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/overview-ch/wl_mutable.py b/examples/pyomobook/overview-ch/wl_mutable.py index 1b65dcc84a1..8e129dd3c49 100644 --- a/examples/pyomobook/overview-ch/wl_mutable.py +++ b/examples/pyomobook/overview-ch/wl_mutable.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/overview-ch/wl_mutable_excel.py b/examples/pyomobook/overview-ch/wl_mutable_excel.py index 52cac31f5f6..935fa4963e5 100644 --- a/examples/pyomobook/overview-ch/wl_mutable_excel.py +++ b/examples/pyomobook/overview-ch/wl_mutable_excel.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/overview-ch/wl_scalar.py b/examples/pyomobook/overview-ch/wl_scalar.py index b524f22c82d..6f538baedb8 100644 --- a/examples/pyomobook/overview-ch/wl_scalar.py +++ b/examples/pyomobook/overview-ch/wl_scalar.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/performance-ch/SparseSets.py b/examples/pyomobook/performance-ch/SparseSets.py index 913b7587368..519808306de 100644 --- a/examples/pyomobook/performance-ch/SparseSets.py +++ b/examples/pyomobook/performance-ch/SparseSets.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/performance-ch/lin_expr.py b/examples/pyomobook/performance-ch/lin_expr.py index 20585d4719b..af50ddd6228 100644 --- a/examples/pyomobook/performance-ch/lin_expr.py +++ b/examples/pyomobook/performance-ch/lin_expr.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/performance-ch/persistent.py b/examples/pyomobook/performance-ch/persistent.py index e468b281579..67f8c656cfe 100644 --- a/examples/pyomobook/performance-ch/persistent.py +++ b/examples/pyomobook/performance-ch/persistent.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/performance-ch/wl.py b/examples/pyomobook/performance-ch/wl.py index 614ffc0fd66..000f81272a1 100644 --- a/examples/pyomobook/performance-ch/wl.py +++ b/examples/pyomobook/performance-ch/wl.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/pyomo-components-ch/con_declaration.py b/examples/pyomobook/pyomo-components-ch/con_declaration.py index b014697fd62..0890ba4771b 100644 --- a/examples/pyomobook/pyomo-components-ch/con_declaration.py +++ b/examples/pyomobook/pyomo-components-ch/con_declaration.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/pyomo-components-ch/examples.py b/examples/pyomobook/pyomo-components-ch/examples.py index 5f154c0ecc9..1a59e9e308e 100644 --- a/examples/pyomobook/pyomo-components-ch/examples.py +++ b/examples/pyomobook/pyomo-components-ch/examples.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/pyomo-components-ch/expr_declaration.py b/examples/pyomobook/pyomo-components-ch/expr_declaration.py index 9baff1e4dba..da0d854e513 100644 --- a/examples/pyomobook/pyomo-components-ch/expr_declaration.py +++ b/examples/pyomobook/pyomo-components-ch/expr_declaration.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/pyomo-components-ch/obj_declaration.py b/examples/pyomobook/pyomo-components-ch/obj_declaration.py index ac8b56a3a03..a63fc441206 100644 --- a/examples/pyomobook/pyomo-components-ch/obj_declaration.py +++ b/examples/pyomobook/pyomo-components-ch/obj_declaration.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/pyomo-components-ch/param_declaration.py b/examples/pyomobook/pyomo-components-ch/param_declaration.py index ded0adfcb22..98b16548c28 100644 --- a/examples/pyomobook/pyomo-components-ch/param_declaration.py +++ b/examples/pyomobook/pyomo-components-ch/param_declaration.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/pyomo-components-ch/param_initialization.py b/examples/pyomobook/pyomo-components-ch/param_initialization.py index e9a90210df5..88da8a68354 100644 --- a/examples/pyomobook/pyomo-components-ch/param_initialization.py +++ b/examples/pyomobook/pyomo-components-ch/param_initialization.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/pyomo-components-ch/param_misc.py b/examples/pyomobook/pyomo-components-ch/param_misc.py index cc3be7a6ac5..72fca60f787 100644 --- a/examples/pyomobook/pyomo-components-ch/param_misc.py +++ b/examples/pyomobook/pyomo-components-ch/param_misc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/pyomo-components-ch/param_validation.py b/examples/pyomobook/pyomo-components-ch/param_validation.py index cf540ac8a70..baf5f0ac1e2 100644 --- a/examples/pyomobook/pyomo-components-ch/param_validation.py +++ b/examples/pyomobook/pyomo-components-ch/param_validation.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/pyomo-components-ch/rangeset.py b/examples/pyomobook/pyomo-components-ch/rangeset.py index a5ef4a85017..169060e9ab2 100644 --- a/examples/pyomobook/pyomo-components-ch/rangeset.py +++ b/examples/pyomobook/pyomo-components-ch/rangeset.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/pyomo-components-ch/set_declaration.py b/examples/pyomobook/pyomo-components-ch/set_declaration.py index a60904ff510..bf3cfa1be15 100644 --- a/examples/pyomobook/pyomo-components-ch/set_declaration.py +++ b/examples/pyomobook/pyomo-components-ch/set_declaration.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/pyomo-components-ch/set_initialization.py b/examples/pyomobook/pyomo-components-ch/set_initialization.py index 972d65e0499..bdfd662c985 100644 --- a/examples/pyomobook/pyomo-components-ch/set_initialization.py +++ b/examples/pyomobook/pyomo-components-ch/set_initialization.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/pyomo-components-ch/set_misc.py b/examples/pyomobook/pyomo-components-ch/set_misc.py index 2bd8297cc80..20ed9518f52 100644 --- a/examples/pyomobook/pyomo-components-ch/set_misc.py +++ b/examples/pyomobook/pyomo-components-ch/set_misc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/pyomo-components-ch/set_options.py b/examples/pyomobook/pyomo-components-ch/set_options.py index 27c47ee95c7..30c0b49706d 100644 --- a/examples/pyomobook/pyomo-components-ch/set_options.py +++ b/examples/pyomobook/pyomo-components-ch/set_options.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/pyomo-components-ch/set_validation.py b/examples/pyomobook/pyomo-components-ch/set_validation.py index 3b6b8bee25b..2300c0be693 100644 --- a/examples/pyomobook/pyomo-components-ch/set_validation.py +++ b/examples/pyomobook/pyomo-components-ch/set_validation.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/pyomo-components-ch/suffix_declaration.py b/examples/pyomobook/pyomo-components-ch/suffix_declaration.py index a5c0bc988bb..619093712f1 100644 --- a/examples/pyomobook/pyomo-components-ch/suffix_declaration.py +++ b/examples/pyomobook/pyomo-components-ch/suffix_declaration.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/pyomo-components-ch/var_declaration.py b/examples/pyomobook/pyomo-components-ch/var_declaration.py index b3180f25381..2ee5d7fb749 100644 --- a/examples/pyomobook/pyomo-components-ch/var_declaration.py +++ b/examples/pyomobook/pyomo-components-ch/var_declaration.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/python-ch/BadIndent.py b/examples/pyomobook/python-ch/BadIndent.py index 63013067468..4a00cae12ef 100644 --- a/examples/pyomobook/python-ch/BadIndent.py +++ b/examples/pyomobook/python-ch/BadIndent.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/python-ch/LineExample.py b/examples/pyomobook/python-ch/LineExample.py index 320289a2a79..31cface5760 100644 --- a/examples/pyomobook/python-ch/LineExample.py +++ b/examples/pyomobook/python-ch/LineExample.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/python-ch/class.py b/examples/pyomobook/python-ch/class.py index 12eafe23a44..a09f991d37b 100644 --- a/examples/pyomobook/python-ch/class.py +++ b/examples/pyomobook/python-ch/class.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/python-ch/ctob.py b/examples/pyomobook/python-ch/ctob.py index fe2c474de4d..8945e4863de 100644 --- a/examples/pyomobook/python-ch/ctob.py +++ b/examples/pyomobook/python-ch/ctob.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/python-ch/example.py b/examples/pyomobook/python-ch/example.py index 2bab6d4b9fe..184153545a3 100644 --- a/examples/pyomobook/python-ch/example.py +++ b/examples/pyomobook/python-ch/example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/python-ch/example2.py b/examples/pyomobook/python-ch/example2.py index 0c282eccacd..9a6a28bedbd 100644 --- a/examples/pyomobook/python-ch/example2.py +++ b/examples/pyomobook/python-ch/example2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/python-ch/functions.py b/examples/pyomobook/python-ch/functions.py index b23b6dc6bee..97fb77edbe4 100644 --- a/examples/pyomobook/python-ch/functions.py +++ b/examples/pyomobook/python-ch/functions.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/python-ch/iterate.py b/examples/pyomobook/python-ch/iterate.py index cd8fe697afb..50d74f93da7 100644 --- a/examples/pyomobook/python-ch/iterate.py +++ b/examples/pyomobook/python-ch/iterate.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/python-ch/pythonconditional.py b/examples/pyomobook/python-ch/pythonconditional.py index 2c48a2db6f4..a39e148622b 100644 --- a/examples/pyomobook/python-ch/pythonconditional.py +++ b/examples/pyomobook/python-ch/pythonconditional.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/scripts-ch/attributes.py b/examples/pyomobook/scripts-ch/attributes.py index fccdb6932da..c406bbf3e1c 100644 --- a/examples/pyomobook/scripts-ch/attributes.py +++ b/examples/pyomobook/scripts-ch/attributes.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/scripts-ch/prob_mod_ex.py b/examples/pyomobook/scripts-ch/prob_mod_ex.py index f94fec5eb8a..dceafe9d4f0 100644 --- a/examples/pyomobook/scripts-ch/prob_mod_ex.py +++ b/examples/pyomobook/scripts-ch/prob_mod_ex.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/scripts-ch/sudoku/sudoku.py b/examples/pyomobook/scripts-ch/sudoku/sudoku.py index ac6d1eabf14..8aa39f91203 100644 --- a/examples/pyomobook/scripts-ch/sudoku/sudoku.py +++ b/examples/pyomobook/scripts-ch/sudoku/sudoku.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/scripts-ch/sudoku/sudoku_run.py b/examples/pyomobook/scripts-ch/sudoku/sudoku_run.py index 948c5a59ee8..b3f861f86b5 100644 --- a/examples/pyomobook/scripts-ch/sudoku/sudoku_run.py +++ b/examples/pyomobook/scripts-ch/sudoku/sudoku_run.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/scripts-ch/value_expression.py b/examples/pyomobook/scripts-ch/value_expression.py index ca154341b43..00c79fec501 100644 --- a/examples/pyomobook/scripts-ch/value_expression.py +++ b/examples/pyomobook/scripts-ch/value_expression.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/scripts-ch/warehouse_cuts.py b/examples/pyomobook/scripts-ch/warehouse_cuts.py index 82dabfcb6f8..345dc5540cb 100644 --- a/examples/pyomobook/scripts-ch/warehouse_cuts.py +++ b/examples/pyomobook/scripts-ch/warehouse_cuts.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/scripts-ch/warehouse_load_solutions.py b/examples/pyomobook/scripts-ch/warehouse_load_solutions.py index 4d47a8ab916..d38412f84df 100644 --- a/examples/pyomobook/scripts-ch/warehouse_load_solutions.py +++ b/examples/pyomobook/scripts-ch/warehouse_load_solutions.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/scripts-ch/warehouse_model.py b/examples/pyomobook/scripts-ch/warehouse_model.py index cb9a43563fb..149eb212759 100644 --- a/examples/pyomobook/scripts-ch/warehouse_model.py +++ b/examples/pyomobook/scripts-ch/warehouse_model.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/scripts-ch/warehouse_print.py b/examples/pyomobook/scripts-ch/warehouse_print.py index 2353a8d6b44..8c862506bf0 100644 --- a/examples/pyomobook/scripts-ch/warehouse_print.py +++ b/examples/pyomobook/scripts-ch/warehouse_print.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/scripts-ch/warehouse_script.py b/examples/pyomobook/scripts-ch/warehouse_script.py index 37d71b466d2..617b8036abf 100644 --- a/examples/pyomobook/scripts-ch/warehouse_script.py +++ b/examples/pyomobook/scripts-ch/warehouse_script.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/scripts-ch/warehouse_solver_options.py b/examples/pyomobook/scripts-ch/warehouse_solver_options.py index 5a482bf3216..4e79e158d50 100644 --- a/examples/pyomobook/scripts-ch/warehouse_solver_options.py +++ b/examples/pyomobook/scripts-ch/warehouse_solver_options.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/strip_examples.py b/examples/pyomobook/strip_examples.py index 68d9e0d99a5..84017299fb6 100644 --- a/examples/pyomobook/strip_examples.py +++ b/examples/pyomobook/strip_examples.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/examples/pyomobook/test_book_examples.py b/examples/pyomobook/test_book_examples.py index e946864c1aa..192330dc1bf 100644 --- a/examples/pyomobook/test_book_examples.py +++ b/examples/pyomobook/test_book_examples.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/__init__.py b/pyomo/__init__.py index 20ee59d48b2..14cc42b626e 100644 --- a/pyomo/__init__.py +++ b/pyomo/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/__init__.py b/pyomo/common/__init__.py index 563974b5617..d7297c067c9 100644 --- a/pyomo/common/__init__.py +++ b/pyomo/common/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/_command.py b/pyomo/common/_command.py index ae633648ace..0777155a557 100644 --- a/pyomo/common/_command.py +++ b/pyomo/common/_command.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/_common.py b/pyomo/common/_common.py index 21a5ddcc7bc..0d50f74537a 100644 --- a/pyomo/common/_common.py +++ b/pyomo/common/_common.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/autoslots.py b/pyomo/common/autoslots.py index 1b55a818b83..cb79d4a0338 100644 --- a/pyomo/common/autoslots.py +++ b/pyomo/common/autoslots.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/backports.py b/pyomo/common/backports.py index 36f2dac87ab..e70b0f6d267 100644 --- a/pyomo/common/backports.py +++ b/pyomo/common/backports.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/cmake_builder.py b/pyomo/common/cmake_builder.py index bb612b43b72..523dbf64c91 100644 --- a/pyomo/common/cmake_builder.py +++ b/pyomo/common/cmake_builder.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/collections/__init__.py b/pyomo/common/collections/__init__.py index 9ffd1e931f6..93785124e3c 100644 --- a/pyomo/common/collections/__init__.py +++ b/pyomo/common/collections/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/collections/bunch.py b/pyomo/common/collections/bunch.py index f19e4ad64e3..2ae9cf8c517 100644 --- a/pyomo/common/collections/bunch.py +++ b/pyomo/common/collections/bunch.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/collections/component_map.py b/pyomo/common/collections/component_map.py index 41796876d7c..80ba5fe0d1c 100644 --- a/pyomo/common/collections/component_map.py +++ b/pyomo/common/collections/component_map.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/collections/component_set.py b/pyomo/common/collections/component_set.py index e205773220f..dfeac5cbfa5 100644 --- a/pyomo/common/collections/component_set.py +++ b/pyomo/common/collections/component_set.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/collections/orderedset.py b/pyomo/common/collections/orderedset.py index 448939c8822..f29245b75fe 100644 --- a/pyomo/common/collections/orderedset.py +++ b/pyomo/common/collections/orderedset.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/config.py b/pyomo/common/config.py index 15f15872fc6..2e14359d1af 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 0a179b5c2de..9e96fdd5860 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/deprecation.py b/pyomo/common/deprecation.py index 2e39083770d..5a6ca456079 100644 --- a/pyomo/common/deprecation.py +++ b/pyomo/common/deprecation.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/download.py b/pyomo/common/download.py index 79d5302a58e..5332287cfc7 100644 --- a/pyomo/common/download.py +++ b/pyomo/common/download.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/env.py b/pyomo/common/env.py index 2ce0f368b9e..ee07cdc1e6a 100644 --- a/pyomo/common/env.py +++ b/pyomo/common/env.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/envvar.py b/pyomo/common/envvar.py index d74cb764641..1f933d4b08c 100644 --- a/pyomo/common/envvar.py +++ b/pyomo/common/envvar.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/errors.py b/pyomo/common/errors.py index 17013ce4dca..3c82f2b07c1 100644 --- a/pyomo/common/errors.py +++ b/pyomo/common/errors.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/extensions.py b/pyomo/common/extensions.py index e4f7b047bb3..0ac27f125a7 100644 --- a/pyomo/common/extensions.py +++ b/pyomo/common/extensions.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/factory.py b/pyomo/common/factory.py index 6a97759c714..c449cf826b4 100644 --- a/pyomo/common/factory.py +++ b/pyomo/common/factory.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/fileutils.py b/pyomo/common/fileutils.py index 557901c401e..2cade36154d 100644 --- a/pyomo/common/fileutils.py +++ b/pyomo/common/fileutils.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/formatting.py b/pyomo/common/formatting.py index 5c2b329ce21..430ec96ca09 100644 --- a/pyomo/common/formatting.py +++ b/pyomo/common/formatting.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/gc_manager.py b/pyomo/common/gc_manager.py index 54fbca32736..751eb95cf18 100644 --- a/pyomo/common/gc_manager.py +++ b/pyomo/common/gc_manager.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/getGSL.py b/pyomo/common/getGSL.py index e8b2507ab81..66b75b45665 100644 --- a/pyomo/common/getGSL.py +++ b/pyomo/common/getGSL.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/gsl.py b/pyomo/common/gsl.py index 5243758a0de..1c14b64bd70 100644 --- a/pyomo/common/gsl.py +++ b/pyomo/common/gsl.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/log.py b/pyomo/common/log.py index 3097fe1c6de..d61ed62f373 100644 --- a/pyomo/common/log.py +++ b/pyomo/common/log.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/modeling.py b/pyomo/common/modeling.py index 5ecc56cce9b..4c07048d77a 100644 --- a/pyomo/common/modeling.py +++ b/pyomo/common/modeling.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/multithread.py b/pyomo/common/multithread.py index f90e7f7c89e..a2dace2be0f 100644 --- a/pyomo/common/multithread.py +++ b/pyomo/common/multithread.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/numeric_types.py b/pyomo/common/numeric_types.py index 19718b308b6..ba104203667 100644 --- a/pyomo/common/numeric_types.py +++ b/pyomo/common/numeric_types.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/plugin.py b/pyomo/common/plugin.py index b48fa96a483..ac88388ebc0 100644 --- a/pyomo/common/plugin.py +++ b/pyomo/common/plugin.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/plugin_base.py b/pyomo/common/plugin_base.py index 67960ebbb12..75b8657d1a9 100644 --- a/pyomo/common/plugin_base.py +++ b/pyomo/common/plugin_base.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/plugins.py b/pyomo/common/plugins.py index 7db8077855a..ed44f8bf776 100644 --- a/pyomo/common/plugins.py +++ b/pyomo/common/plugins.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/pyomo_typing.py b/pyomo/common/pyomo_typing.py index 64ab2ddafc9..22ec3480842 100644 --- a/pyomo/common/pyomo_typing.py +++ b/pyomo/common/pyomo_typing.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/shutdown.py b/pyomo/common/shutdown.py index 984fa8e8a52..a96a6bc04fc 100644 --- a/pyomo/common/shutdown.py +++ b/pyomo/common/shutdown.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/sorting.py b/pyomo/common/sorting.py index 31e796c6a9e..4f78a7892b8 100644 --- a/pyomo/common/sorting.py +++ b/pyomo/common/sorting.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tee.py b/pyomo/common/tee.py index 029d66f5767..500f7b6f58d 100644 --- a/pyomo/common/tee.py +++ b/pyomo/common/tee.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tempfiles.py b/pyomo/common/tempfiles.py index f51fad3f3ac..b9dface71b2 100644 --- a/pyomo/common/tempfiles.py +++ b/pyomo/common/tempfiles.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/__init__.py b/pyomo/common/tests/__init__.py index bc8dfa27c9c..d8d8856e52f 100644 --- a/pyomo/common/tests/__init__.py +++ b/pyomo/common/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/config_plugin.py b/pyomo/common/tests/config_plugin.py index ada788fd7d4..6aebc40806a 100644 --- a/pyomo/common/tests/config_plugin.py +++ b/pyomo/common/tests/config_plugin.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/dep_mod.py b/pyomo/common/tests/dep_mod.py index 54530393783..f6add596ed4 100644 --- a/pyomo/common/tests/dep_mod.py +++ b/pyomo/common/tests/dep_mod.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/dep_mod_except.py b/pyomo/common/tests/dep_mod_except.py index 8132e8a08ac..16936996eeb 100644 --- a/pyomo/common/tests/dep_mod_except.py +++ b/pyomo/common/tests/dep_mod_except.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/deps.py b/pyomo/common/tests/deps.py index e5236d0f7ec..d00281553f4 100644 --- a/pyomo/common/tests/deps.py +++ b/pyomo/common/tests/deps.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/import_ex.py b/pyomo/common/tests/import_ex.py index d1bf02752eb..73375bdc819 100644 --- a/pyomo/common/tests/import_ex.py +++ b/pyomo/common/tests/import_ex.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/relo_mod.py b/pyomo/common/tests/relo_mod.py index 20b0712e09b..4881caba671 100644 --- a/pyomo/common/tests/relo_mod.py +++ b/pyomo/common/tests/relo_mod.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/relo_mod_new.py b/pyomo/common/tests/relo_mod_new.py index 1ef27681b66..0f59f3beebc 100644 --- a/pyomo/common/tests/relo_mod_new.py +++ b/pyomo/common/tests/relo_mod_new.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/relocated.py b/pyomo/common/tests/relocated.py index 9de63e0cec9..90cb28c23ba 100644 --- a/pyomo/common/tests/relocated.py +++ b/pyomo/common/tests/relocated.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_bunch.py b/pyomo/common/tests/test_bunch.py index a8daf5a0071..8c10df83005 100644 --- a/pyomo/common/tests/test_bunch.py +++ b/pyomo/common/tests/test_bunch.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_config.py b/pyomo/common/tests/test_config.py index 1b732d86c0a..0cc71169a34 100644 --- a/pyomo/common/tests/test_config.py +++ b/pyomo/common/tests/test_config.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_dependencies.py b/pyomo/common/tests/test_dependencies.py index 65058e01812..30822a4f81f 100644 --- a/pyomo/common/tests/test_dependencies.py +++ b/pyomo/common/tests/test_dependencies.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_deprecated.py b/pyomo/common/tests/test_deprecated.py index 1fb4a471740..377e229c775 100644 --- a/pyomo/common/tests/test_deprecated.py +++ b/pyomo/common/tests/test_deprecated.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_download.py b/pyomo/common/tests/test_download.py index 8c41edc1512..87108be1c59 100644 --- a/pyomo/common/tests/test_download.py +++ b/pyomo/common/tests/test_download.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_env.py b/pyomo/common/tests/test_env.py index d14326ddc19..93802fc40bb 100644 --- a/pyomo/common/tests/test_env.py +++ b/pyomo/common/tests/test_env.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_errors.py b/pyomo/common/tests/test_errors.py index ec77643f722..67a200e84e3 100644 --- a/pyomo/common/tests/test_errors.py +++ b/pyomo/common/tests/test_errors.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_fileutils.py b/pyomo/common/tests/test_fileutils.py index 63570774e5b..068360b55cb 100644 --- a/pyomo/common/tests/test_fileutils.py +++ b/pyomo/common/tests/test_fileutils.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_formatting.py b/pyomo/common/tests/test_formatting.py index d502c81da5a..29db26676ab 100644 --- a/pyomo/common/tests/test_formatting.py +++ b/pyomo/common/tests/test_formatting.py @@ -2,7 +2,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_gc.py b/pyomo/common/tests/test_gc.py index b2f23102a0e..176010b8d0d 100644 --- a/pyomo/common/tests/test_gc.py +++ b/pyomo/common/tests/test_gc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_log.py b/pyomo/common/tests/test_log.py index 39fab153e98..64691c0015a 100644 --- a/pyomo/common/tests/test_log.py +++ b/pyomo/common/tests/test_log.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_modeling.py b/pyomo/common/tests/test_modeling.py index 0684d77b2e9..97bef76c2c0 100644 --- a/pyomo/common/tests/test_modeling.py +++ b/pyomo/common/tests/test_modeling.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_multithread.py b/pyomo/common/tests/test_multithread.py index a6c0cac32c7..fa1a46fa25f 100644 --- a/pyomo/common/tests/test_multithread.py +++ b/pyomo/common/tests/test_multithread.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_orderedset.py b/pyomo/common/tests/test_orderedset.py index d87bebc1e4a..8f944e66bd7 100644 --- a/pyomo/common/tests/test_orderedset.py +++ b/pyomo/common/tests/test_orderedset.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_plugin.py b/pyomo/common/tests/test_plugin.py index 86d136dd9d1..54431334d5b 100644 --- a/pyomo/common/tests/test_plugin.py +++ b/pyomo/common/tests/test_plugin.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_sorting.py b/pyomo/common/tests/test_sorting.py index 7a9fe5ac923..7fbefda6a19 100644 --- a/pyomo/common/tests/test_sorting.py +++ b/pyomo/common/tests/test_sorting.py @@ -2,7 +2,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_tee.py b/pyomo/common/tests/test_tee.py index 666a431631f..a5c6ee894b2 100644 --- a/pyomo/common/tests/test_tee.py +++ b/pyomo/common/tests/test_tee.py @@ -2,7 +2,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_tempfile.py b/pyomo/common/tests/test_tempfile.py index 5e75c55305a..c49aa8c6771 100644 --- a/pyomo/common/tests/test_tempfile.py +++ b/pyomo/common/tests/test_tempfile.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_timing.py b/pyomo/common/tests/test_timing.py index d885359e6c6..0a4224c5476 100644 --- a/pyomo/common/tests/test_timing.py +++ b/pyomo/common/tests/test_timing.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_typing.py b/pyomo/common/tests/test_typing.py index 982462f8a8d..e65effe7f29 100644 --- a/pyomo/common/tests/test_typing.py +++ b/pyomo/common/tests/test_typing.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/tests/test_unittest.py b/pyomo/common/tests/test_unittest.py index e3779e6f86e..9344853b737 100644 --- a/pyomo/common/tests/test_unittest.py +++ b/pyomo/common/tests/test_unittest.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/timing.py b/pyomo/common/timing.py index b37570fa666..d502b38d12d 100644 --- a/pyomo/common/timing.py +++ b/pyomo/common/timing.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/common/unittest.py b/pyomo/common/unittest.py index 1ed26f72320..9a21b35faa8 100644 --- a/pyomo/common/unittest.py +++ b/pyomo/common/unittest.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/__init__.py b/pyomo/contrib/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/__init__.py +++ b/pyomo/contrib/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/ampl_function_demo/__init__.py b/pyomo/contrib/ampl_function_demo/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/ampl_function_demo/__init__.py +++ b/pyomo/contrib/ampl_function_demo/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/ampl_function_demo/build.py b/pyomo/contrib/ampl_function_demo/build.py index cd35064ea4e..764a613b3d7 100644 --- a/pyomo/contrib/ampl_function_demo/build.py +++ b/pyomo/contrib/ampl_function_demo/build.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/ampl_function_demo/plugins.py b/pyomo/contrib/ampl_function_demo/plugins.py index 230d9c4b667..5a200174c43 100644 --- a/pyomo/contrib/ampl_function_demo/plugins.py +++ b/pyomo/contrib/ampl_function_demo/plugins.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/ampl_function_demo/src/CMakeLists.txt b/pyomo/contrib/ampl_function_demo/src/CMakeLists.txt index ce2c1a60f82..67efc13d3c8 100644 --- a/pyomo/contrib/ampl_function_demo/src/CMakeLists.txt +++ b/pyomo/contrib/ampl_function_demo/src/CMakeLists.txt @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/ampl_function_demo/src/FindASL.cmake b/pyomo/contrib/ampl_function_demo/src/FindASL.cmake index f413176f1cc..8bbc048fa6e 100644 --- a/pyomo/contrib/ampl_function_demo/src/FindASL.cmake +++ b/pyomo/contrib/ampl_function_demo/src/FindASL.cmake @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/ampl_function_demo/src/functions.c b/pyomo/contrib/ampl_function_demo/src/functions.c index f62148c995a..e87af745aea 100644 --- a/pyomo/contrib/ampl_function_demo/src/functions.c +++ b/pyomo/contrib/ampl_function_demo/src/functions.c @@ -1,6 +1,6 @@ /* ___________________________________________________________________________ * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/ampl_function_demo/tests/__init__.py b/pyomo/contrib/ampl_function_demo/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/ampl_function_demo/tests/__init__.py +++ b/pyomo/contrib/ampl_function_demo/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/ampl_function_demo/tests/test_ampl_function_demo.py b/pyomo/contrib/ampl_function_demo/tests/test_ampl_function_demo.py index af52c2def9f..39890494d55 100644 --- a/pyomo/contrib/ampl_function_demo/tests/test_ampl_function_demo.py +++ b/pyomo/contrib/ampl_function_demo/tests/test_ampl_function_demo.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/__init__.py b/pyomo/contrib/appsi/__init__.py index 305231001c4..2f06fc89e70 100644 --- a/pyomo/contrib/appsi/__init__.py +++ b/pyomo/contrib/appsi/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index a34bbdb5e1f..80e3cecec6d 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/build.py b/pyomo/contrib/appsi/build.py index 2c8d02dd3ac..b3d78467f01 100644 --- a/pyomo/contrib/appsi/build.py +++ b/pyomo/contrib/appsi/build.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/cmodel/__init__.py b/pyomo/contrib/appsi/cmodel/__init__.py index 9c276b518de..cc2aec28241 100644 --- a/pyomo/contrib/appsi/cmodel/__init__.py +++ b/pyomo/contrib/appsi/cmodel/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/cmodel/src/cmodel_bindings.cpp b/pyomo/contrib/appsi/cmodel/src/cmodel_bindings.cpp index db9d3112069..6acc1d79845 100644 --- a/pyomo/contrib/appsi/cmodel/src/cmodel_bindings.cpp +++ b/pyomo/contrib/appsi/cmodel/src/cmodel_bindings.cpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/appsi/cmodel/src/common.cpp b/pyomo/contrib/appsi/cmodel/src/common.cpp index e9f1398fa2f..6f8002cb50e 100644 --- a/pyomo/contrib/appsi/cmodel/src/common.cpp +++ b/pyomo/contrib/appsi/cmodel/src/common.cpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/appsi/cmodel/src/common.hpp b/pyomo/contrib/appsi/cmodel/src/common.hpp index 9a025e031ae..9edc9571a4d 100644 --- a/pyomo/contrib/appsi/cmodel/src/common.hpp +++ b/pyomo/contrib/appsi/cmodel/src/common.hpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/appsi/cmodel/src/expression.cpp b/pyomo/contrib/appsi/cmodel/src/expression.cpp index f9e6b5c326a..234ef47e86f 100644 --- a/pyomo/contrib/appsi/cmodel/src/expression.cpp +++ b/pyomo/contrib/appsi/cmodel/src/expression.cpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/appsi/cmodel/src/expression.hpp b/pyomo/contrib/appsi/cmodel/src/expression.hpp index 220f5f22b0d..0c0777ef468 100644 --- a/pyomo/contrib/appsi/cmodel/src/expression.hpp +++ b/pyomo/contrib/appsi/cmodel/src/expression.hpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/appsi/cmodel/src/fbbt_model.cpp b/pyomo/contrib/appsi/cmodel/src/fbbt_model.cpp index 68efa7d9c26..bd8d7dbf854 100644 --- a/pyomo/contrib/appsi/cmodel/src/fbbt_model.cpp +++ b/pyomo/contrib/appsi/cmodel/src/fbbt_model.cpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/appsi/cmodel/src/fbbt_model.hpp b/pyomo/contrib/appsi/cmodel/src/fbbt_model.hpp index 032ff8c2616..ca1980a797b 100644 --- a/pyomo/contrib/appsi/cmodel/src/fbbt_model.hpp +++ b/pyomo/contrib/appsi/cmodel/src/fbbt_model.hpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/appsi/cmodel/src/interval.cpp b/pyomo/contrib/appsi/cmodel/src/interval.cpp index a9f26704825..1d9b3a6f82e 100644 --- a/pyomo/contrib/appsi/cmodel/src/interval.cpp +++ b/pyomo/contrib/appsi/cmodel/src/interval.cpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/appsi/cmodel/src/interval.hpp b/pyomo/contrib/appsi/cmodel/src/interval.hpp index 0f3a2a9a816..a57f107f8db 100644 --- a/pyomo/contrib/appsi/cmodel/src/interval.hpp +++ b/pyomo/contrib/appsi/cmodel/src/interval.hpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/appsi/cmodel/src/lp_writer.cpp b/pyomo/contrib/appsi/cmodel/src/lp_writer.cpp index be7ff6d9ac9..68baf2b8ae8 100644 --- a/pyomo/contrib/appsi/cmodel/src/lp_writer.cpp +++ b/pyomo/contrib/appsi/cmodel/src/lp_writer.cpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/appsi/cmodel/src/lp_writer.hpp b/pyomo/contrib/appsi/cmodel/src/lp_writer.hpp index 1cb6adb462b..0b2e2882510 100644 --- a/pyomo/contrib/appsi/cmodel/src/lp_writer.hpp +++ b/pyomo/contrib/appsi/cmodel/src/lp_writer.hpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/appsi/cmodel/src/model_base.cpp b/pyomo/contrib/appsi/cmodel/src/model_base.cpp index 4503138bf1b..b0ae4013b32 100644 --- a/pyomo/contrib/appsi/cmodel/src/model_base.cpp +++ b/pyomo/contrib/appsi/cmodel/src/model_base.cpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/appsi/cmodel/src/model_base.hpp b/pyomo/contrib/appsi/cmodel/src/model_base.hpp index b797976aa2f..a47f1d14a0b 100644 --- a/pyomo/contrib/appsi/cmodel/src/model_base.hpp +++ b/pyomo/contrib/appsi/cmodel/src/model_base.hpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/appsi/cmodel/src/nl_writer.cpp b/pyomo/contrib/appsi/cmodel/src/nl_writer.cpp index a1b699e6355..8de6cc74ab4 100644 --- a/pyomo/contrib/appsi/cmodel/src/nl_writer.cpp +++ b/pyomo/contrib/appsi/cmodel/src/nl_writer.cpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/appsi/cmodel/src/nl_writer.hpp b/pyomo/contrib/appsi/cmodel/src/nl_writer.hpp index 557d0645e4a..b7439875301 100644 --- a/pyomo/contrib/appsi/cmodel/src/nl_writer.hpp +++ b/pyomo/contrib/appsi/cmodel/src/nl_writer.hpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/appsi/cmodel/tests/__init__.py b/pyomo/contrib/appsi/cmodel/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/appsi/cmodel/tests/__init__.py +++ b/pyomo/contrib/appsi/cmodel/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/cmodel/tests/test_import.py b/pyomo/contrib/appsi/cmodel/tests/test_import.py index 9fce3559aff..76eda902ac0 100644 --- a/pyomo/contrib/appsi/cmodel/tests/test_import.py +++ b/pyomo/contrib/appsi/cmodel/tests/test_import.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/examples/__init__.py b/pyomo/contrib/appsi/examples/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/appsi/examples/__init__.py +++ b/pyomo/contrib/appsi/examples/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/examples/getting_started.py b/pyomo/contrib/appsi/examples/getting_started.py index c1500c482d9..6bc42d1d377 100644 --- a/pyomo/contrib/appsi/examples/getting_started.py +++ b/pyomo/contrib/appsi/examples/getting_started.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/examples/tests/__init__.py b/pyomo/contrib/appsi/examples/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/appsi/examples/tests/__init__.py +++ b/pyomo/contrib/appsi/examples/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/examples/tests/test_examples.py b/pyomo/contrib/appsi/examples/tests/test_examples.py index 7c04271f6d3..a7608d36b98 100644 --- a/pyomo/contrib/appsi/examples/tests/test_examples.py +++ b/pyomo/contrib/appsi/examples/tests/test_examples.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/fbbt.py b/pyomo/contrib/appsi/fbbt.py index 957fdc593d4..8b6cc52d2aa 100644 --- a/pyomo/contrib/appsi/fbbt.py +++ b/pyomo/contrib/appsi/fbbt.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/plugins.py b/pyomo/contrib/appsi/plugins.py index aea9edb3faf..b5cfd080b32 100644 --- a/pyomo/contrib/appsi/plugins.py +++ b/pyomo/contrib/appsi/plugins.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/solvers/__init__.py b/pyomo/contrib/appsi/solvers/__init__.py index 359e3f80742..c03523a69d4 100644 --- a/pyomo/contrib/appsi/solvers/__init__.py +++ b/pyomo/contrib/appsi/solvers/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index c0e1f15c01e..2c522af864d 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index e8ee204ad63..1d7147f16e8 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index 842cbbf175d..aa233ef77d6 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 2619aa2f0c7..a9a23682355 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index 13cda3e3a19..d7a786e6c2c 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/solvers/tests/__init__.py b/pyomo/contrib/appsi/solvers/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/appsi/solvers/tests/__init__.py +++ b/pyomo/contrib/appsi/solvers/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py index ed2859fef36..2f674a2eb6a 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_gurobi_persistent.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py index 02de50542f3..b26f45ff2cc 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/solvers/tests/test_ipopt_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_ipopt_persistent.py index dc82d04b900..8e6473a6b01 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_ipopt_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_ipopt_persistent.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 7b0cbeaf284..af615d1ed8b 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py b/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py index df1d36442b9..6fb25bfb529 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py +++ b/pyomo/contrib/appsi/solvers/tests/test_wntr_persistent.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py index 2937a5f1b7c..e1835b810b0 100644 --- a/pyomo/contrib/appsi/solvers/wntr.py +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/tests/__init__.py b/pyomo/contrib/appsi/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/appsi/tests/__init__.py +++ b/pyomo/contrib/appsi/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/tests/test_base.py b/pyomo/contrib/appsi/tests/test_base.py index 7700d4f5534..e537cc0f219 100644 --- a/pyomo/contrib/appsi/tests/test_base.py +++ b/pyomo/contrib/appsi/tests/test_base.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/tests/test_fbbt.py b/pyomo/contrib/appsi/tests/test_fbbt.py index b739367b989..a3f520e7bd6 100644 --- a/pyomo/contrib/appsi/tests/test_fbbt.py +++ b/pyomo/contrib/appsi/tests/test_fbbt.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/tests/test_interval.py b/pyomo/contrib/appsi/tests/test_interval.py index 7c66d63a543..2184f69621a 100644 --- a/pyomo/contrib/appsi/tests/test_interval.py +++ b/pyomo/contrib/appsi/tests/test_interval.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/utils/__init__.py b/pyomo/contrib/appsi/utils/__init__.py index 147d82a923a..e1278431835 100644 --- a/pyomo/contrib/appsi/utils/__init__.py +++ b/pyomo/contrib/appsi/utils/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py b/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py index 7bf273dbf87..4e117b04094 100644 --- a/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py +++ b/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/utils/get_objective.py b/pyomo/contrib/appsi/utils/get_objective.py index 7b43a981622..110c0188d16 100644 --- a/pyomo/contrib/appsi/utils/get_objective.py +++ b/pyomo/contrib/appsi/utils/get_objective.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/utils/tests/__init__.py b/pyomo/contrib/appsi/utils/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/appsi/utils/tests/__init__.py +++ b/pyomo/contrib/appsi/utils/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/utils/tests/test_collect_vars_and_named_exprs.py b/pyomo/contrib/appsi/utils/tests/test_collect_vars_and_named_exprs.py index 9a5e08385f3..62f98728850 100644 --- a/pyomo/contrib/appsi/utils/tests/test_collect_vars_and_named_exprs.py +++ b/pyomo/contrib/appsi/utils/tests/test_collect_vars_and_named_exprs.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/writers/__init__.py b/pyomo/contrib/appsi/writers/__init__.py index 0d5191e8b97..18f90e8aa96 100644 --- a/pyomo/contrib/appsi/writers/__init__.py +++ b/pyomo/contrib/appsi/writers/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/writers/config.py b/pyomo/contrib/appsi/writers/config.py index 9d66aba2037..32d45325e96 100644 --- a/pyomo/contrib/appsi/writers/config.py +++ b/pyomo/contrib/appsi/writers/config.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/writers/lp_writer.py b/pyomo/contrib/appsi/writers/lp_writer.py index 09470202be3..9984cb7465d 100644 --- a/pyomo/contrib/appsi/writers/lp_writer.py +++ b/pyomo/contrib/appsi/writers/lp_writer.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/writers/nl_writer.py b/pyomo/contrib/appsi/writers/nl_writer.py index c2c93992140..bd24a86216a 100644 --- a/pyomo/contrib/appsi/writers/nl_writer.py +++ b/pyomo/contrib/appsi/writers/nl_writer.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/writers/tests/__init__.py b/pyomo/contrib/appsi/writers/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/appsi/writers/tests/__init__.py +++ b/pyomo/contrib/appsi/writers/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/appsi/writers/tests/test_nl_writer.py b/pyomo/contrib/appsi/writers/tests/test_nl_writer.py index d0844263d5a..c6005afceb2 100644 --- a/pyomo/contrib/appsi/writers/tests/test_nl_writer.py +++ b/pyomo/contrib/appsi/writers/tests/test_nl_writer.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/benders/__init__.py b/pyomo/contrib/benders/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/benders/__init__.py +++ b/pyomo/contrib/benders/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/benders/benders_cuts.py b/pyomo/contrib/benders/benders_cuts.py index 5eb2e91cc82..01734993552 100644 --- a/pyomo/contrib/benders/benders_cuts.py +++ b/pyomo/contrib/benders/benders_cuts.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/benders/examples/__init__.py b/pyomo/contrib/benders/examples/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/benders/examples/__init__.py +++ b/pyomo/contrib/benders/examples/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/benders/examples/farmer.py b/pyomo/contrib/benders/examples/farmer.py index bf5d40e112c..47cdb3511a3 100644 --- a/pyomo/contrib/benders/examples/farmer.py +++ b/pyomo/contrib/benders/examples/farmer.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/benders/examples/grothey_ex.py b/pyomo/contrib/benders/examples/grothey_ex.py index 66457fa7293..27d37cac124 100644 --- a/pyomo/contrib/benders/examples/grothey_ex.py +++ b/pyomo/contrib/benders/examples/grothey_ex.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/benders/tests/__init__.py b/pyomo/contrib/benders/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/benders/tests/__init__.py +++ b/pyomo/contrib/benders/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/benders/tests/test_benders.py b/pyomo/contrib/benders/tests/test_benders.py index 26a2a0b7910..52ae9e56db8 100644 --- a/pyomo/contrib/benders/tests/test_benders.py +++ b/pyomo/contrib/benders/tests/test_benders.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/community_detection/__init__.py b/pyomo/contrib/community_detection/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/community_detection/__init__.py +++ b/pyomo/contrib/community_detection/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/community_detection/community_graph.py b/pyomo/contrib/community_detection/community_graph.py index d1bd49df20c..889940b5996 100644 --- a/pyomo/contrib/community_detection/community_graph.py +++ b/pyomo/contrib/community_detection/community_graph.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/community_detection/detection.py b/pyomo/contrib/community_detection/detection.py index 9fe7005f1f2..5bf8187a243 100644 --- a/pyomo/contrib/community_detection/detection.py +++ b/pyomo/contrib/community_detection/detection.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/community_detection/event_log.py b/pyomo/contrib/community_detection/event_log.py index 09b1039a8f7..767ff0f50f5 100644 --- a/pyomo/contrib/community_detection/event_log.py +++ b/pyomo/contrib/community_detection/event_log.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/community_detection/plugins.py b/pyomo/contrib/community_detection/plugins.py index 578da835d5e..229b7255a27 100644 --- a/pyomo/contrib/community_detection/plugins.py +++ b/pyomo/contrib/community_detection/plugins.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/community_detection/tests/__init__.py b/pyomo/contrib/community_detection/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/community_detection/tests/__init__.py +++ b/pyomo/contrib/community_detection/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/community_detection/tests/test_detection.py b/pyomo/contrib/community_detection/tests/test_detection.py index acfd441005f..6a43ea1b61a 100644 --- a/pyomo/contrib/community_detection/tests/test_detection.py +++ b/pyomo/contrib/community_detection/tests/test_detection.py @@ -4,7 +4,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/cp/__init__.py b/pyomo/contrib/cp/__init__.py index 71ba479523f..ed45344fb95 100644 --- a/pyomo/contrib/cp/__init__.py +++ b/pyomo/contrib/cp/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/cp/interval_var.py b/pyomo/contrib/cp/interval_var.py index 4e22c2b2d3d..953b859ea20 100644 --- a/pyomo/contrib/cp/interval_var.py +++ b/pyomo/contrib/cp/interval_var.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/cp/plugins.py b/pyomo/contrib/cp/plugins.py index 445599daab0..b0f7c84eb65 100644 --- a/pyomo/contrib/cp/plugins.py +++ b/pyomo/contrib/cp/plugins.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/cp/repn/__init__.py b/pyomo/contrib/cp/repn/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/cp/repn/__init__.py +++ b/pyomo/contrib/cp/repn/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index c2687662fe8..8356a1e752f 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/cp/scheduling_expr/__init__.py b/pyomo/contrib/cp/scheduling_expr/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/cp/scheduling_expr/__init__.py +++ b/pyomo/contrib/cp/scheduling_expr/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/cp/scheduling_expr/precedence_expressions.py b/pyomo/contrib/cp/scheduling_expr/precedence_expressions.py index 5340583a216..1dec02bba23 100644 --- a/pyomo/contrib/cp/scheduling_expr/precedence_expressions.py +++ b/pyomo/contrib/cp/scheduling_expr/precedence_expressions.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/cp/scheduling_expr/step_function_expressions.py b/pyomo/contrib/cp/scheduling_expr/step_function_expressions.py index b4f8fbb4977..b75306f72c9 100644 --- a/pyomo/contrib/cp/scheduling_expr/step_function_expressions.py +++ b/pyomo/contrib/cp/scheduling_expr/step_function_expressions.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/cp/tests/__init__.py b/pyomo/contrib/cp/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/cp/tests/__init__.py +++ b/pyomo/contrib/cp/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/cp/tests/test_docplex_walker.py b/pyomo/contrib/cp/tests/test_docplex_walker.py index b897053c93a..8e0e8c6955e 100644 --- a/pyomo/contrib/cp/tests/test_docplex_walker.py +++ b/pyomo/contrib/cp/tests/test_docplex_walker.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/cp/tests/test_docplex_writer.py b/pyomo/contrib/cp/tests/test_docplex_writer.py index b563052ef3a..b5f30f24440 100644 --- a/pyomo/contrib/cp/tests/test_docplex_writer.py +++ b/pyomo/contrib/cp/tests/test_docplex_writer.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/cp/tests/test_interval_var.py b/pyomo/contrib/cp/tests/test_interval_var.py index edbf889fcda..1645258d98a 100644 --- a/pyomo/contrib/cp/tests/test_interval_var.py +++ b/pyomo/contrib/cp/tests/test_interval_var.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/cp/tests/test_logical_to_disjunctive.py b/pyomo/contrib/cp/tests/test_logical_to_disjunctive.py index c6733f34f83..3f66aa57726 100755 --- a/pyomo/contrib/cp/tests/test_logical_to_disjunctive.py +++ b/pyomo/contrib/cp/tests/test_logical_to_disjunctive.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/cp/tests/test_precedence_constraints.py b/pyomo/contrib/cp/tests/test_precedence_constraints.py index 461dabf564c..0a84a4d1960 100644 --- a/pyomo/contrib/cp/tests/test_precedence_constraints.py +++ b/pyomo/contrib/cp/tests/test_precedence_constraints.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/cp/tests/test_step_function_expressions.py b/pyomo/contrib/cp/tests/test_step_function_expressions.py index 7212cc870d5..a7b30c1d4e6 100644 --- a/pyomo/contrib/cp/tests/test_step_function_expressions.py +++ b/pyomo/contrib/cp/tests/test_step_function_expressions.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/cp/transform/__init__.py b/pyomo/contrib/cp/transform/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/cp/transform/__init__.py +++ b/pyomo/contrib/cp/transform/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/cp/transform/logical_to_disjunctive_program.py b/pyomo/contrib/cp/transform/logical_to_disjunctive_program.py index cd7681d4d87..e318e621e88 100644 --- a/pyomo/contrib/cp/transform/logical_to_disjunctive_program.py +++ b/pyomo/contrib/cp/transform/logical_to_disjunctive_program.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py b/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py index 624629d326d..d5f13e91535 100644 --- a/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py +++ b/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/doe/__init__.py b/pyomo/contrib/doe/__init__.py index e38b5dce1d9..e45aa3b44a3 100644 --- a/pyomo/contrib/doe/__init__.py +++ b/pyomo/contrib/doe/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/doe/doe.py b/pyomo/contrib/doe/doe.py index b451c431f21..d2ba2f277d6 100644 --- a/pyomo/contrib/doe/doe.py +++ b/pyomo/contrib/doe/doe.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/doe/examples/__init__.py b/pyomo/contrib/doe/examples/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/doe/examples/__init__.py +++ b/pyomo/contrib/doe/examples/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/doe/examples/reactor_compute_FIM.py b/pyomo/contrib/doe/examples/reactor_compute_FIM.py index c004ad36f00..108f5bd16a0 100644 --- a/pyomo/contrib/doe/examples/reactor_compute_FIM.py +++ b/pyomo/contrib/doe/examples/reactor_compute_FIM.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/doe/examples/reactor_grid_search.py b/pyomo/contrib/doe/examples/reactor_grid_search.py index a4516c36451..1f5aae77f85 100644 --- a/pyomo/contrib/doe/examples/reactor_grid_search.py +++ b/pyomo/contrib/doe/examples/reactor_grid_search.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/doe/examples/reactor_kinetics.py b/pyomo/contrib/doe/examples/reactor_kinetics.py index 57d06e146c5..ed2175085f2 100644 --- a/pyomo/contrib/doe/examples/reactor_kinetics.py +++ b/pyomo/contrib/doe/examples/reactor_kinetics.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/doe/examples/reactor_optimize_doe.py b/pyomo/contrib/doe/examples/reactor_optimize_doe.py index 56ea1ffeac3..f7b4a74c891 100644 --- a/pyomo/contrib/doe/examples/reactor_optimize_doe.py +++ b/pyomo/contrib/doe/examples/reactor_optimize_doe.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/doe/measurements.py b/pyomo/contrib/doe/measurements.py index 75fd4f7c485..5a3c44a76e4 100644 --- a/pyomo/contrib/doe/measurements.py +++ b/pyomo/contrib/doe/measurements.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/doe/result.py b/pyomo/contrib/doe/result.py index 65ded38a63b..1593214c30a 100644 --- a/pyomo/contrib/doe/result.py +++ b/pyomo/contrib/doe/result.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/doe/scenario.py b/pyomo/contrib/doe/scenario.py index eff9c883e0b..6c6f5ef7d1b 100644 --- a/pyomo/contrib/doe/scenario.py +++ b/pyomo/contrib/doe/scenario.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/doe/tests/__init__.py b/pyomo/contrib/doe/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/doe/tests/__init__.py +++ b/pyomo/contrib/doe/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/doe/tests/test_example.py b/pyomo/contrib/doe/tests/test_example.py index 0f143e03677..b59014a8110 100644 --- a/pyomo/contrib/doe/tests/test_example.py +++ b/pyomo/contrib/doe/tests/test_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/doe/tests/test_fim_doe.py b/pyomo/contrib/doe/tests/test_fim_doe.py index 42b463162b2..31d250f0d10 100644 --- a/pyomo/contrib/doe/tests/test_fim_doe.py +++ b/pyomo/contrib/doe/tests/test_fim_doe.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/doe/tests/test_reactor_example.py b/pyomo/contrib/doe/tests/test_reactor_example.py index 86c914ec4e0..daf2ee89194 100644 --- a/pyomo/contrib/doe/tests/test_reactor_example.py +++ b/pyomo/contrib/doe/tests/test_reactor_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/example/__init__.py b/pyomo/contrib/example/__init__.py index 7a9e6e76de4..c70b50e84de 100644 --- a/pyomo/contrib/example/__init__.py +++ b/pyomo/contrib/example/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/example/bar.py b/pyomo/contrib/example/bar.py index eb39c5f8748..22e5c3997e9 100644 --- a/pyomo/contrib/example/bar.py +++ b/pyomo/contrib/example/bar.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/example/foo.py b/pyomo/contrib/example/foo.py index a1a10b1dd62..f879bc70722 100644 --- a/pyomo/contrib/example/foo.py +++ b/pyomo/contrib/example/foo.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/example/plugins/__init__.py b/pyomo/contrib/example/plugins/__init__.py index 0c6c248c122..179098bc18e 100644 --- a/pyomo/contrib/example/plugins/__init__.py +++ b/pyomo/contrib/example/plugins/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/example/plugins/ex_plugin.py b/pyomo/contrib/example/plugins/ex_plugin.py index 0a23afc0158..7ee4c414ccf 100644 --- a/pyomo/contrib/example/plugins/ex_plugin.py +++ b/pyomo/contrib/example/plugins/ex_plugin.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/example/tests/__init__.py b/pyomo/contrib/example/tests/__init__.py index 3ecae26215c..9c45a6ef8b6 100644 --- a/pyomo/contrib/example/tests/__init__.py +++ b/pyomo/contrib/example/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/example/tests/test_example.py b/pyomo/contrib/example/tests/test_example.py index c38de1b914f..55394f5d0c1 100644 --- a/pyomo/contrib/example/tests/test_example.py +++ b/pyomo/contrib/example/tests/test_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/fbbt/__init__.py b/pyomo/contrib/fbbt/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/fbbt/__init__.py +++ b/pyomo/contrib/fbbt/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/fbbt/expression_bounds_walker.py b/pyomo/contrib/fbbt/expression_bounds_walker.py index 340af94c83e..cb287d54df5 100644 --- a/pyomo/contrib/fbbt/expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/expression_bounds_walker.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index bf42cbe7f33..bde33b3caa0 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/fbbt/interval.py b/pyomo/contrib/fbbt/interval.py index 8bebe128988..a12d1a4529f 100644 --- a/pyomo/contrib/fbbt/interval.py +++ b/pyomo/contrib/fbbt/interval.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/fbbt/tests/__init__.py b/pyomo/contrib/fbbt/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/fbbt/tests/__init__.py +++ b/pyomo/contrib/fbbt/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py b/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py index 75d273422d1..5d27a2e4087 100644 --- a/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py +++ b/pyomo/contrib/fbbt/tests/test_expression_bounds_walker.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/fbbt/tests/test_fbbt.py b/pyomo/contrib/fbbt/tests/test_fbbt.py index 5e8d656eeab..f7d08d11215 100644 --- a/pyomo/contrib/fbbt/tests/test_fbbt.py +++ b/pyomo/contrib/fbbt/tests/test_fbbt.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/fbbt/tests/test_interval.py b/pyomo/contrib/fbbt/tests/test_interval.py index d5dc7b54ff5..1e42162a35e 100644 --- a/pyomo/contrib/fbbt/tests/test_interval.py +++ b/pyomo/contrib/fbbt/tests/test_interval.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/fme/__init__.py b/pyomo/contrib/fme/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/fme/__init__.py +++ b/pyomo/contrib/fme/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/fme/fourier_motzkin_elimination.py b/pyomo/contrib/fme/fourier_motzkin_elimination.py index 18aa157545e..a1b5d744cf4 100644 --- a/pyomo/contrib/fme/fourier_motzkin_elimination.py +++ b/pyomo/contrib/fme/fourier_motzkin_elimination.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/fme/plugins.py b/pyomo/contrib/fme/plugins.py index 324dd583d0f..b8278ccbb27 100644 --- a/pyomo/contrib/fme/plugins.py +++ b/pyomo/contrib/fme/plugins.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/fme/tests/__init__.py b/pyomo/contrib/fme/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/fme/tests/__init__.py +++ b/pyomo/contrib/fme/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/fme/tests/test_fourier_motzkin_elimination.py b/pyomo/contrib/fme/tests/test_fourier_motzkin_elimination.py index 11c008acf82..3c01acab531 100644 --- a/pyomo/contrib/fme/tests/test_fourier_motzkin_elimination.py +++ b/pyomo/contrib/fme/tests/test_fourier_motzkin_elimination.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdp_bounds/__init__.py b/pyomo/contrib/gdp_bounds/__init__.py index 4918f6dfa0e..ac71890cf7c 100644 --- a/pyomo/contrib/gdp_bounds/__init__.py +++ b/pyomo/contrib/gdp_bounds/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdp_bounds/compute_bounds.py b/pyomo/contrib/gdp_bounds/compute_bounds.py index f4f046e79df..3c04e4e1af7 100644 --- a/pyomo/contrib/gdp_bounds/compute_bounds.py +++ b/pyomo/contrib/gdp_bounds/compute_bounds.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdp_bounds/info.py b/pyomo/contrib/gdp_bounds/info.py index f7e83ee62c9..6f39af5908d 100644 --- a/pyomo/contrib/gdp_bounds/info.py +++ b/pyomo/contrib/gdp_bounds/info.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdp_bounds/plugins.py b/pyomo/contrib/gdp_bounds/plugins.py index 1ebe44378f0..016a1fc7b13 100644 --- a/pyomo/contrib/gdp_bounds/plugins.py +++ b/pyomo/contrib/gdp_bounds/plugins.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdp_bounds/tests/__init__.py b/pyomo/contrib/gdp_bounds/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/gdp_bounds/tests/__init__.py +++ b/pyomo/contrib/gdp_bounds/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdp_bounds/tests/test_gdp_bounds.py b/pyomo/contrib/gdp_bounds/tests/test_gdp_bounds.py index 551236c7d97..0c8eae2c43b 100644 --- a/pyomo/contrib/gdp_bounds/tests/test_gdp_bounds.py +++ b/pyomo/contrib/gdp_bounds/tests/test_gdp_bounds.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/GDPopt.py b/pyomo/contrib/gdpopt/GDPopt.py index 3d45fa504cb..f0ff6d690d6 100644 --- a/pyomo/contrib/gdpopt/GDPopt.py +++ b/pyomo/contrib/gdpopt/GDPopt.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/__init__.py b/pyomo/contrib/gdpopt/__init__.py index f74855f0206..a84b8385ad3 100644 --- a/pyomo/contrib/gdpopt/__init__.py +++ b/pyomo/contrib/gdpopt/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/algorithm_base_class.py b/pyomo/contrib/gdpopt/algorithm_base_class.py index 5bf41148700..c5929ad4a88 100644 --- a/pyomo/contrib/gdpopt/algorithm_base_class.py +++ b/pyomo/contrib/gdpopt/algorithm_base_class.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/branch_and_bound.py b/pyomo/contrib/gdpopt/branch_and_bound.py index 26dc2b5f2eb..918f3d459a0 100644 --- a/pyomo/contrib/gdpopt/branch_and_bound.py +++ b/pyomo/contrib/gdpopt/branch_and_bound.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/config_options.py b/pyomo/contrib/gdpopt/config_options.py index 386826b844c..467c4a6ec32 100644 --- a/pyomo/contrib/gdpopt/config_options.py +++ b/pyomo/contrib/gdpopt/config_options.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/create_oa_subproblems.py b/pyomo/contrib/gdpopt/create_oa_subproblems.py index 12266866dbc..690fe1f15f1 100644 --- a/pyomo/contrib/gdpopt/create_oa_subproblems.py +++ b/pyomo/contrib/gdpopt/create_oa_subproblems.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/cut_generation.py b/pyomo/contrib/gdpopt/cut_generation.py index 36a826a4f83..742a2cde395 100644 --- a/pyomo/contrib/gdpopt/cut_generation.py +++ b/pyomo/contrib/gdpopt/cut_generation.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/discrete_problem_initialize.py b/pyomo/contrib/gdpopt/discrete_problem_initialize.py index 3dc18132c5b..81c339b94a2 100644 --- a/pyomo/contrib/gdpopt/discrete_problem_initialize.py +++ b/pyomo/contrib/gdpopt/discrete_problem_initialize.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/enumerate.py b/pyomo/contrib/gdpopt/enumerate.py index 45ecc8864f9..6c25d0088f4 100644 --- a/pyomo/contrib/gdpopt/enumerate.py +++ b/pyomo/contrib/gdpopt/enumerate.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/gloa.py b/pyomo/contrib/gdpopt/gloa.py index 68bd692f967..212da057e05 100644 --- a/pyomo/contrib/gdpopt/gloa.py +++ b/pyomo/contrib/gdpopt/gloa.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/loa.py b/pyomo/contrib/gdpopt/loa.py index 44c1f8609e8..354b61ae940 100644 --- a/pyomo/contrib/gdpopt/loa.py +++ b/pyomo/contrib/gdpopt/loa.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/nlp_initialization.py b/pyomo/contrib/gdpopt/nlp_initialization.py index fc083c095da..dbc33eb20be 100644 --- a/pyomo/contrib/gdpopt/nlp_initialization.py +++ b/pyomo/contrib/gdpopt/nlp_initialization.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/oa_algorithm_utils.py b/pyomo/contrib/gdpopt/oa_algorithm_utils.py index 9aba59e4527..ce4012d8800 100644 --- a/pyomo/contrib/gdpopt/oa_algorithm_utils.py +++ b/pyomo/contrib/gdpopt/oa_algorithm_utils.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/plugins.py b/pyomo/contrib/gdpopt/plugins.py index 9d729c63d9c..d0068d25993 100644 --- a/pyomo/contrib/gdpopt/plugins.py +++ b/pyomo/contrib/gdpopt/plugins.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/ric.py b/pyomo/contrib/gdpopt/ric.py index 586a27362a1..2aa1aaf8c67 100644 --- a/pyomo/contrib/gdpopt/ric.py +++ b/pyomo/contrib/gdpopt/ric.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/solve_discrete_problem.py b/pyomo/contrib/gdpopt/solve_discrete_problem.py index 3de66fbaca0..54218edc50a 100644 --- a/pyomo/contrib/gdpopt/solve_discrete_problem.py +++ b/pyomo/contrib/gdpopt/solve_discrete_problem.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/solve_subproblem.py b/pyomo/contrib/gdpopt/solve_subproblem.py index bd9b85c0cef..e3980c3c784 100644 --- a/pyomo/contrib/gdpopt/solve_subproblem.py +++ b/pyomo/contrib/gdpopt/solve_subproblem.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/tests/__init__.py b/pyomo/contrib/gdpopt/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/gdpopt/tests/__init__.py +++ b/pyomo/contrib/gdpopt/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/tests/common_tests.py b/pyomo/contrib/gdpopt/tests/common_tests.py index 5a363430381..88a2642704a 100644 --- a/pyomo/contrib/gdpopt/tests/common_tests.py +++ b/pyomo/contrib/gdpopt/tests/common_tests.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/tests/test_LBB.py b/pyomo/contrib/gdpopt/tests/test_LBB.py index 7d25767020e..273327b02a4 100644 --- a/pyomo/contrib/gdpopt/tests/test_LBB.py +++ b/pyomo/contrib/gdpopt/tests/test_LBB.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/tests/test_enumerate.py b/pyomo/contrib/gdpopt/tests/test_enumerate.py index 606dd172064..8798557ddc9 100644 --- a/pyomo/contrib/gdpopt/tests/test_enumerate.py +++ b/pyomo/contrib/gdpopt/tests/test_enumerate.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/tests/test_gdpopt.py b/pyomo/contrib/gdpopt/tests/test_gdpopt.py index 1d5559a9b33..005df56ced5 100644 --- a/pyomo/contrib/gdpopt/tests/test_gdpopt.py +++ b/pyomo/contrib/gdpopt/tests/test_gdpopt.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gdpopt/util.py b/pyomo/contrib/gdpopt/util.py index f288f9e2647..2cb70f0ea60 100644 --- a/pyomo/contrib/gdpopt/util.py +++ b/pyomo/contrib/gdpopt/util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gjh/GJH.py b/pyomo/contrib/gjh/GJH.py index df9dfebf477..dc7c8de89c1 100644 --- a/pyomo/contrib/gjh/GJH.py +++ b/pyomo/contrib/gjh/GJH.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gjh/__init__.py b/pyomo/contrib/gjh/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/gjh/__init__.py +++ b/pyomo/contrib/gjh/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gjh/getGJH.py b/pyomo/contrib/gjh/getGJH.py index 112de054745..2d503c71438 100644 --- a/pyomo/contrib/gjh/getGJH.py +++ b/pyomo/contrib/gjh/getGJH.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/gjh/plugins.py b/pyomo/contrib/gjh/plugins.py index 4af2f38becd..f072f7b2c38 100644 --- a/pyomo/contrib/gjh/plugins.py +++ b/pyomo/contrib/gjh/plugins.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/iis/__init__.py b/pyomo/contrib/iis/__init__.py index 29f5d4f3d40..e8d6a7ac2c3 100644 --- a/pyomo/contrib/iis/__init__.py +++ b/pyomo/contrib/iis/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/iis/iis.py b/pyomo/contrib/iis/iis.py index a279ce0aac3..1ffd6cb0bd3 100644 --- a/pyomo/contrib/iis/iis.py +++ b/pyomo/contrib/iis/iis.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/iis/tests/__init__.py b/pyomo/contrib/iis/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/iis/tests/__init__.py +++ b/pyomo/contrib/iis/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/iis/tests/test_iis.py b/pyomo/contrib/iis/tests/test_iis.py index 8343798741a..cf7b5613a3a 100644 --- a/pyomo/contrib/iis/tests/test_iis.py +++ b/pyomo/contrib/iis/tests/test_iis.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/__init__.py b/pyomo/contrib/incidence_analysis/__init__.py index 612b4fe7d02..8942d09b6b9 100644 --- a/pyomo/contrib/incidence_analysis/__init__.py +++ b/pyomo/contrib/incidence_analysis/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/common/__init__.py b/pyomo/contrib/incidence_analysis/common/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/incidence_analysis/common/__init__.py +++ b/pyomo/contrib/incidence_analysis/common/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.py b/pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.py index 09a926cdec2..5bc724fafc1 100644 --- a/pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.py +++ b/pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/common/tests/__init__.py b/pyomo/contrib/incidence_analysis/common/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/incidence_analysis/common/tests/__init__.py +++ b/pyomo/contrib/incidence_analysis/common/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/common/tests/test_dulmage_mendelsohn.py b/pyomo/contrib/incidence_analysis/common/tests/test_dulmage_mendelsohn.py index 1675fc7420a..b17ae9b1dfc 100644 --- a/pyomo/contrib/incidence_analysis/common/tests/test_dulmage_mendelsohn.py +++ b/pyomo/contrib/incidence_analysis/common/tests/test_dulmage_mendelsohn.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/config.py b/pyomo/contrib/incidence_analysis/config.py index 72d1a41ac74..128273b4dec 100644 --- a/pyomo/contrib/incidence_analysis/config.py +++ b/pyomo/contrib/incidence_analysis/config.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/connected.py b/pyomo/contrib/incidence_analysis/connected.py index 2dcf31c0fe0..28d4bdee73f 100644 --- a/pyomo/contrib/incidence_analysis/connected.py +++ b/pyomo/contrib/incidence_analysis/connected.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py b/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py index eb24b0559fc..3a6d06a809c 100644 --- a/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py +++ b/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/incidence.py b/pyomo/contrib/incidence_analysis/incidence.py index 13e9997d6c3..96cbf77c47d 100644 --- a/pyomo/contrib/incidence_analysis/incidence.py +++ b/pyomo/contrib/incidence_analysis/incidence.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index acf2d318578..8361c32a43c 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/matching.py b/pyomo/contrib/incidence_analysis/matching.py index 14b3cd5b18d..e37b35cd973 100644 --- a/pyomo/contrib/incidence_analysis/matching.py +++ b/pyomo/contrib/incidence_analysis/matching.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/scc_solver.py b/pyomo/contrib/incidence_analysis/scc_solver.py index d7620278fd3..835e07c7c02 100644 --- a/pyomo/contrib/incidence_analysis/scc_solver.py +++ b/pyomo/contrib/incidence_analysis/scc_solver.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/tests/__init__.py b/pyomo/contrib/incidence_analysis/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/incidence_analysis/tests/__init__.py +++ b/pyomo/contrib/incidence_analysis/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/tests/models_for_testing.py b/pyomo/contrib/incidence_analysis/tests/models_for_testing.py index 98d61201619..6040e80e068 100644 --- a/pyomo/contrib/incidence_analysis/tests/models_for_testing.py +++ b/pyomo/contrib/incidence_analysis/tests/models_for_testing.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/tests/test_connected.py b/pyomo/contrib/incidence_analysis/tests/test_connected.py index a937a5029a1..421231d3dd0 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_connected.py +++ b/pyomo/contrib/incidence_analysis/tests/test_connected.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/tests/test_dulmage_mendelsohn.py b/pyomo/contrib/incidence_analysis/tests/test_dulmage_mendelsohn.py index 98fefea2d80..6195d6afca7 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_dulmage_mendelsohn.py +++ b/pyomo/contrib/incidence_analysis/tests/test_dulmage_mendelsohn.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/tests/test_incidence.py b/pyomo/contrib/incidence_analysis/tests/test_incidence.py index 2d178c62119..832fbbfb10c 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_incidence.py +++ b/pyomo/contrib/incidence_analysis/tests/test_incidence.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/tests/test_interface.py b/pyomo/contrib/incidence_analysis/tests/test_interface.py index 10777a35f78..e6c3f341e81 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_interface.py +++ b/pyomo/contrib/incidence_analysis/tests/test_interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/tests/test_matching.py b/pyomo/contrib/incidence_analysis/tests/test_matching.py index b5550b3b84c..2327439f0a2 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_matching.py +++ b/pyomo/contrib/incidence_analysis/tests/test_matching.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/tests/test_scc_solver.py b/pyomo/contrib/incidence_analysis/tests/test_scc_solver.py index 6efe52a7d80..b75f93e4a12 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_scc_solver.py +++ b/pyomo/contrib/incidence_analysis/tests/test_scc_solver.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/tests/test_triangularize.py b/pyomo/contrib/incidence_analysis/tests/test_triangularize.py index 76ba4403310..22548a15998 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_triangularize.py +++ b/pyomo/contrib/incidence_analysis/tests/test_triangularize.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/triangularize.py b/pyomo/contrib/incidence_analysis/triangularize.py index ac6680a367e..6af251b1ec6 100644 --- a/pyomo/contrib/incidence_analysis/triangularize.py +++ b/pyomo/contrib/incidence_analysis/triangularize.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/incidence_analysis/util.py b/pyomo/contrib/incidence_analysis/util.py index a127161d33d..8b6572eb900 100644 --- a/pyomo/contrib/incidence_analysis/util.py +++ b/pyomo/contrib/incidence_analysis/util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/interior_point/__init__.py b/pyomo/contrib/interior_point/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/interior_point/__init__.py +++ b/pyomo/contrib/interior_point/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/interior_point/examples/__init__.py b/pyomo/contrib/interior_point/examples/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/interior_point/examples/__init__.py +++ b/pyomo/contrib/interior_point/examples/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/interior_point/examples/ex1.py b/pyomo/contrib/interior_point/examples/ex1.py index d9931e1daa8..f6d8f14ac0a 100644 --- a/pyomo/contrib/interior_point/examples/ex1.py +++ b/pyomo/contrib/interior_point/examples/ex1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/interior_point/interface.py b/pyomo/contrib/interior_point/interface.py index 7d04f578238..93b83f385ba 100644 --- a/pyomo/contrib/interior_point/interface.py +++ b/pyomo/contrib/interior_point/interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/interior_point/interior_point.py b/pyomo/contrib/interior_point/interior_point.py index 00d26ddef03..502de338fdc 100644 --- a/pyomo/contrib/interior_point/interior_point.py +++ b/pyomo/contrib/interior_point/interior_point.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/interior_point/inverse_reduced_hessian.py b/pyomo/contrib/interior_point/inverse_reduced_hessian.py index 6144a4afeb8..ac3c6a98463 100644 --- a/pyomo/contrib/interior_point/inverse_reduced_hessian.py +++ b/pyomo/contrib/interior_point/inverse_reduced_hessian.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/interior_point/linalg/__init__.py b/pyomo/contrib/interior_point/linalg/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/interior_point/linalg/__init__.py +++ b/pyomo/contrib/interior_point/linalg/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/interior_point/linalg/base_linear_solver_interface.py b/pyomo/contrib/interior_point/linalg/base_linear_solver_interface.py index 2bc7fe2eee5..c3304fd1395 100644 --- a/pyomo/contrib/interior_point/linalg/base_linear_solver_interface.py +++ b/pyomo/contrib/interior_point/linalg/base_linear_solver_interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/interior_point/linalg/ma27_interface.py b/pyomo/contrib/interior_point/linalg/ma27_interface.py index 0a28e50578d..7604bd432bb 100644 --- a/pyomo/contrib/interior_point/linalg/ma27_interface.py +++ b/pyomo/contrib/interior_point/linalg/ma27_interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/interior_point/linalg/mumps_interface.py b/pyomo/contrib/interior_point/linalg/mumps_interface.py index 98f0ef03210..c7480e2b6d0 100644 --- a/pyomo/contrib/interior_point/linalg/mumps_interface.py +++ b/pyomo/contrib/interior_point/linalg/mumps_interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/interior_point/linalg/scipy_interface.py b/pyomo/contrib/interior_point/linalg/scipy_interface.py index 87b0cad8ea0..d0f773fcb81 100644 --- a/pyomo/contrib/interior_point/linalg/scipy_interface.py +++ b/pyomo/contrib/interior_point/linalg/scipy_interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/interior_point/linalg/tests/__init__.py b/pyomo/contrib/interior_point/linalg/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/interior_point/linalg/tests/__init__.py +++ b/pyomo/contrib/interior_point/linalg/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/interior_point/linalg/tests/test_linear_solvers.py b/pyomo/contrib/interior_point/linalg/tests/test_linear_solvers.py index c13aad215cc..93071a5f215 100644 --- a/pyomo/contrib/interior_point/linalg/tests/test_linear_solvers.py +++ b/pyomo/contrib/interior_point/linalg/tests/test_linear_solvers.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/interior_point/linalg/tests/test_realloc.py b/pyomo/contrib/interior_point/linalg/tests/test_realloc.py index 7dce4755261..3a53d0e7db9 100644 --- a/pyomo/contrib/interior_point/linalg/tests/test_realloc.py +++ b/pyomo/contrib/interior_point/linalg/tests/test_realloc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/interior_point/tests/__init__.py b/pyomo/contrib/interior_point/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/interior_point/tests/__init__.py +++ b/pyomo/contrib/interior_point/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/interior_point/tests/test_interior_point.py b/pyomo/contrib/interior_point/tests/test_interior_point.py index bff80934d20..a05408abe1e 100644 --- a/pyomo/contrib/interior_point/tests/test_interior_point.py +++ b/pyomo/contrib/interior_point/tests/test_interior_point.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/interior_point/tests/test_inverse_reduced_hessian.py b/pyomo/contrib/interior_point/tests/test_inverse_reduced_hessian.py index 67657dfce47..61f5e90e3cf 100644 --- a/pyomo/contrib/interior_point/tests/test_inverse_reduced_hessian.py +++ b/pyomo/contrib/interior_point/tests/test_inverse_reduced_hessian.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/interior_point/tests/test_realloc.py b/pyomo/contrib/interior_point/tests/test_realloc.py index dcf94eb6da7..b7a5d00e488 100644 --- a/pyomo/contrib/interior_point/tests/test_realloc.py +++ b/pyomo/contrib/interior_point/tests/test_realloc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/interior_point/tests/test_reg.py b/pyomo/contrib/interior_point/tests/test_reg.py index b37d9532428..a7fc686545b 100644 --- a/pyomo/contrib/interior_point/tests/test_reg.py +++ b/pyomo/contrib/interior_point/tests/test_reg.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/latex_printer/__init__.py b/pyomo/contrib/latex_printer/__init__.py index 7208b1e7d64..c434b53dfe1 100644 --- a/pyomo/contrib/latex_printer/__init__.py +++ b/pyomo/contrib/latex_printer/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index f9150f700a3..110df7cd5ca 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/latex_printer/tests/__init__.py b/pyomo/contrib/latex_printer/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/latex_printer/tests/__init__.py +++ b/pyomo/contrib/latex_printer/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/latex_printer/tests/test_latex_printer.py b/pyomo/contrib/latex_printer/tests/test_latex_printer.py index 1797e0a39a0..2d7dd69dba8 100644 --- a/pyomo/contrib/latex_printer/tests/test_latex_printer.py +++ b/pyomo/contrib/latex_printer/tests/test_latex_printer.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/latex_printer/tests/test_latex_printer_vartypes.py b/pyomo/contrib/latex_printer/tests/test_latex_printer_vartypes.py index dc571030fde..dc3a415618b 100644 --- a/pyomo/contrib/latex_printer/tests/test_latex_printer_vartypes.py +++ b/pyomo/contrib/latex_printer/tests/test_latex_printer_vartypes.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mcpp/__init__.py b/pyomo/contrib/mcpp/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/mcpp/__init__.py +++ b/pyomo/contrib/mcpp/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mcpp/build.py b/pyomo/contrib/mcpp/build.py index 55c893335d2..7e119caec9f 100644 --- a/pyomo/contrib/mcpp/build.py +++ b/pyomo/contrib/mcpp/build.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mcpp/getMCPP.py b/pyomo/contrib/mcpp/getMCPP.py index caf9566df64..dbce611d1a0 100644 --- a/pyomo/contrib/mcpp/getMCPP.py +++ b/pyomo/contrib/mcpp/getMCPP.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mcpp/mcppInterface.cpp b/pyomo/contrib/mcpp/mcppInterface.cpp index 30491fde1b1..a1e74567896 100644 --- a/pyomo/contrib/mcpp/mcppInterface.cpp +++ b/pyomo/contrib/mcpp/mcppInterface.cpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/mcpp/plugins.py b/pyomo/contrib/mcpp/plugins.py index eed8874b1e7..577feec7fe3 100644 --- a/pyomo/contrib/mcpp/plugins.py +++ b/pyomo/contrib/mcpp/plugins.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mcpp/pyomo_mcpp.py b/pyomo/contrib/mcpp/pyomo_mcpp.py index 25a4237ff16..35e883f98da 100644 --- a/pyomo/contrib/mcpp/pyomo_mcpp.py +++ b/pyomo/contrib/mcpp/pyomo_mcpp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mcpp/test_mcpp.py b/pyomo/contrib/mcpp/test_mcpp.py index 23b963e11bf..1cfb46ce328 100644 --- a/pyomo/contrib/mcpp/test_mcpp.py +++ b/pyomo/contrib/mcpp/test_mcpp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/MindtPy.py b/pyomo/contrib/mindtpy/MindtPy.py index bd873d950fd..7b41e0078a3 100644 --- a/pyomo/contrib/mindtpy/MindtPy.py +++ b/pyomo/contrib/mindtpy/MindtPy.py @@ -3,7 +3,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/__init__.py b/pyomo/contrib/mindtpy/__init__.py index 94a91238819..652493b03a6 100644 --- a/pyomo/contrib/mindtpy/__init__.py +++ b/pyomo/contrib/mindtpy/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 3d5a7ebad03..785a89d8982 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/config_options.py b/pyomo/contrib/mindtpy/config_options.py index f1c4a23d46e..ba2b74cdfe0 100644 --- a/pyomo/contrib/mindtpy/config_options.py +++ b/pyomo/contrib/mindtpy/config_options.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/cut_generation.py b/pyomo/contrib/mindtpy/cut_generation.py index 4ee7a6ff07b..e932755e9fd 100644 --- a/pyomo/contrib/mindtpy/cut_generation.py +++ b/pyomo/contrib/mindtpy/cut_generation.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/extended_cutting_plane.py b/pyomo/contrib/mindtpy/extended_cutting_plane.py index 0a98f88ed3f..7bb3ff783c9 100644 --- a/pyomo/contrib/mindtpy/extended_cutting_plane.py +++ b/pyomo/contrib/mindtpy/extended_cutting_plane.py @@ -3,7 +3,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/feasibility_pump.py b/pyomo/contrib/mindtpy/feasibility_pump.py index a34cceb014c..5ee1260dd42 100644 --- a/pyomo/contrib/mindtpy/feasibility_pump.py +++ b/pyomo/contrib/mindtpy/feasibility_pump.py @@ -3,7 +3,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/global_outer_approximation.py b/pyomo/contrib/mindtpy/global_outer_approximation.py index 70fc4cffb90..c43409a8493 100644 --- a/pyomo/contrib/mindtpy/global_outer_approximation.py +++ b/pyomo/contrib/mindtpy/global_outer_approximation.py @@ -3,7 +3,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/outer_approximation.py b/pyomo/contrib/mindtpy/outer_approximation.py index f6e6147724e..ead5cadfeac 100644 --- a/pyomo/contrib/mindtpy/outer_approximation.py +++ b/pyomo/contrib/mindtpy/outer_approximation.py @@ -3,7 +3,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/plugins.py b/pyomo/contrib/mindtpy/plugins.py index f25706d086a..bf0ab0d1581 100644 --- a/pyomo/contrib/mindtpy/plugins.py +++ b/pyomo/contrib/mindtpy/plugins.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index c1e52ed72d3..05ba6bbee86 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tabu_list.py b/pyomo/contrib/mindtpy/tabu_list.py index 313bd6f6271..15c1d3b3a2b 100644 --- a/pyomo/contrib/mindtpy/tabu_list.py +++ b/pyomo/contrib/mindtpy/tabu_list.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/MINLP2_simple.py b/pyomo/contrib/mindtpy/tests/MINLP2_simple.py index 10da243d332..f3fd51af79a 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP2_simple.py +++ b/pyomo/contrib/mindtpy/tests/MINLP2_simple.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/MINLP3_simple.py b/pyomo/contrib/mindtpy/tests/MINLP3_simple.py index f387b0e26a1..a17659e0c51 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP3_simple.py +++ b/pyomo/contrib/mindtpy/tests/MINLP3_simple.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/MINLP4_simple.py b/pyomo/contrib/mindtpy/tests/MINLP4_simple.py index 684fdf4a932..44b6c7df543 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP4_simple.py +++ b/pyomo/contrib/mindtpy/tests/MINLP4_simple.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/MINLP5_simple.py b/pyomo/contrib/mindtpy/tests/MINLP5_simple.py index cb78f6e0804..d5b04d0915c 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP5_simple.py +++ b/pyomo/contrib/mindtpy/tests/MINLP5_simple.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple.py b/pyomo/contrib/mindtpy/tests/MINLP_simple.py index 7454b595986..cde65536f43 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index 789dfea6191..412067de0b5 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/__init__.py b/pyomo/contrib/mindtpy/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/mindtpy/tests/__init__.py +++ b/pyomo/contrib/mindtpy/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/constraint_qualification_example.py b/pyomo/contrib/mindtpy/tests/constraint_qualification_example.py index 75ec56df738..c0849094300 100644 --- a/pyomo/contrib/mindtpy/tests/constraint_qualification_example.py +++ b/pyomo/contrib/mindtpy/tests/constraint_qualification_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/eight_process_problem.py b/pyomo/contrib/mindtpy/tests/eight_process_problem.py index 8233fc52c53..ed9059ae4ae 100644 --- a/pyomo/contrib/mindtpy/tests/eight_process_problem.py +++ b/pyomo/contrib/mindtpy/tests/eight_process_problem.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/feasibility_pump1.py b/pyomo/contrib/mindtpy/tests/feasibility_pump1.py index 3149ccef6e4..fec750f9f12 100644 --- a/pyomo/contrib/mindtpy/tests/feasibility_pump1.py +++ b/pyomo/contrib/mindtpy/tests/feasibility_pump1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/feasibility_pump2.py b/pyomo/contrib/mindtpy/tests/feasibility_pump2.py index 9fed8238fe1..d739e4efbbe 100644 --- a/pyomo/contrib/mindtpy/tests/feasibility_pump2.py +++ b/pyomo/contrib/mindtpy/tests/feasibility_pump2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/from_proposal.py b/pyomo/contrib/mindtpy/tests/from_proposal.py index e34fddedcd3..f29fbcd2cf7 100644 --- a/pyomo/contrib/mindtpy/tests/from_proposal.py +++ b/pyomo/contrib/mindtpy/tests/from_proposal.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/nonconvex1.py b/pyomo/contrib/mindtpy/tests/nonconvex1.py index 60115a52c32..71b7e22af96 100644 --- a/pyomo/contrib/mindtpy/tests/nonconvex1.py +++ b/pyomo/contrib/mindtpy/tests/nonconvex1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/nonconvex2.py b/pyomo/contrib/mindtpy/tests/nonconvex2.py index ac48167b350..94c519ab0e1 100644 --- a/pyomo/contrib/mindtpy/tests/nonconvex2.py +++ b/pyomo/contrib/mindtpy/tests/nonconvex2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/nonconvex3.py b/pyomo/contrib/mindtpy/tests/nonconvex3.py index 8337beb8d68..5b6a1de8d7d 100644 --- a/pyomo/contrib/mindtpy/tests/nonconvex3.py +++ b/pyomo/contrib/mindtpy/tests/nonconvex3.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/nonconvex4.py b/pyomo/contrib/mindtpy/tests/nonconvex4.py index 79e6465239f..3b7f6660ddf 100644 --- a/pyomo/contrib/mindtpy/tests/nonconvex4.py +++ b/pyomo/contrib/mindtpy/tests/nonconvex4.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/online_doc_example.py b/pyomo/contrib/mindtpy/tests/online_doc_example.py index d741455e7f7..17a758552c0 100644 --- a/pyomo/contrib/mindtpy/tests/online_doc_example.py +++ b/pyomo/contrib/mindtpy/tests/online_doc_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy.py b/pyomo/contrib/mindtpy/tests/test_mindtpy.py index ae531f9bd84..37969276d55 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py index 07f2b1aaff5..24679047793 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py index c7b47b7fde2..cbc906851bf 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_global.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_global.py index dbe9270c363..07774805364 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_global.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_global.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_global_lp_nlp.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_global_lp_nlp.py index 08bfb8df2de..792bdb8d993 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_global_lp_nlp.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_global_lp_nlp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py index f84136ca6bf..d50a41ad000 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_lp_nlp.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_lp_nlp.py index 2662a0e6f56..97f73ece525 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_lp_nlp.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_lp_nlp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_regularization.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_regularization.py index 33f296083ed..2e864a49578 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_regularization.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_regularization.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_solution_pool.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_solution_pool.py index 775d1a4e117..a41f41d4d65 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_solution_pool.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_solution_pool.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/tests/unit_test.py b/pyomo/contrib/mindtpy/tests/unit_test.py index a1ceadda41e..af6ffad282d 100644 --- a/pyomo/contrib/mindtpy/tests/unit_test.py +++ b/pyomo/contrib/mindtpy/tests/unit_test.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index 69c7ca5030a..1543497838f 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/__init__.py b/pyomo/contrib/mpc/__init__.py index da977f365d2..2e1c51e154f 100644 --- a/pyomo/contrib/mpc/__init__.py +++ b/pyomo/contrib/mpc/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/data/__init__.py b/pyomo/contrib/mpc/data/__init__.py index 9061fda4bfd..6051f4ba3a2 100644 --- a/pyomo/contrib/mpc/data/__init__.py +++ b/pyomo/contrib/mpc/data/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/data/convert.py b/pyomo/contrib/mpc/data/convert.py index f1d35592a9f..10885370032 100644 --- a/pyomo/contrib/mpc/data/convert.py +++ b/pyomo/contrib/mpc/data/convert.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/data/dynamic_data_base.py b/pyomo/contrib/mpc/data/dynamic_data_base.py index c0223d2dcbe..5e567f060cf 100644 --- a/pyomo/contrib/mpc/data/dynamic_data_base.py +++ b/pyomo/contrib/mpc/data/dynamic_data_base.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/data/find_nearest_index.py b/pyomo/contrib/mpc/data/find_nearest_index.py index 0875bde63e9..c53a7a79841 100644 --- a/pyomo/contrib/mpc/data/find_nearest_index.py +++ b/pyomo/contrib/mpc/data/find_nearest_index.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/data/get_cuid.py b/pyomo/contrib/mpc/data/get_cuid.py index 1f229b35645..03659d6153f 100644 --- a/pyomo/contrib/mpc/data/get_cuid.py +++ b/pyomo/contrib/mpc/data/get_cuid.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/data/interval_data.py b/pyomo/contrib/mpc/data/interval_data.py index cdd3b0e37dc..54b7ca7e906 100644 --- a/pyomo/contrib/mpc/data/interval_data.py +++ b/pyomo/contrib/mpc/data/interval_data.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/data/scalar_data.py b/pyomo/contrib/mpc/data/scalar_data.py index 5426921ef06..b67384c8159 100644 --- a/pyomo/contrib/mpc/data/scalar_data.py +++ b/pyomo/contrib/mpc/data/scalar_data.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/data/series_data.py b/pyomo/contrib/mpc/data/series_data.py index d09ab8cae24..c812e76c9fc 100644 --- a/pyomo/contrib/mpc/data/series_data.py +++ b/pyomo/contrib/mpc/data/series_data.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/data/tests/__init__.py b/pyomo/contrib/mpc/data/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/mpc/data/tests/__init__.py +++ b/pyomo/contrib/mpc/data/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/data/tests/test_convert.py b/pyomo/contrib/mpc/data/tests/test_convert.py index 0f8a4623e20..dda3583cb00 100644 --- a/pyomo/contrib/mpc/data/tests/test_convert.py +++ b/pyomo/contrib/mpc/data/tests/test_convert.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/data/tests/test_find_nearest_index.py b/pyomo/contrib/mpc/data/tests/test_find_nearest_index.py index e90024ef108..8fb92e17534 100644 --- a/pyomo/contrib/mpc/data/tests/test_find_nearest_index.py +++ b/pyomo/contrib/mpc/data/tests/test_find_nearest_index.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/data/tests/test_get_cuid.py b/pyomo/contrib/mpc/data/tests/test_get_cuid.py index 30ba2b58b1b..66bfb613bcb 100644 --- a/pyomo/contrib/mpc/data/tests/test_get_cuid.py +++ b/pyomo/contrib/mpc/data/tests/test_get_cuid.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/data/tests/test_interval_data.py b/pyomo/contrib/mpc/data/tests/test_interval_data.py index 8afe3eb3021..b208c9066f9 100644 --- a/pyomo/contrib/mpc/data/tests/test_interval_data.py +++ b/pyomo/contrib/mpc/data/tests/test_interval_data.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/data/tests/test_scalar_data.py b/pyomo/contrib/mpc/data/tests/test_scalar_data.py index 110ed749bda..6522242e267 100644 --- a/pyomo/contrib/mpc/data/tests/test_scalar_data.py +++ b/pyomo/contrib/mpc/data/tests/test_scalar_data.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/data/tests/test_series_data.py b/pyomo/contrib/mpc/data/tests/test_series_data.py index e32559ac074..88b672279f2 100644 --- a/pyomo/contrib/mpc/data/tests/test_series_data.py +++ b/pyomo/contrib/mpc/data/tests/test_series_data.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/examples/__init__.py b/pyomo/contrib/mpc/examples/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/mpc/examples/__init__.py +++ b/pyomo/contrib/mpc/examples/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/examples/cstr/__init__.py b/pyomo/contrib/mpc/examples/cstr/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/mpc/examples/cstr/__init__.py +++ b/pyomo/contrib/mpc/examples/cstr/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/examples/cstr/model.py b/pyomo/contrib/mpc/examples/cstr/model.py index d794084f122..376e77186dd 100644 --- a/pyomo/contrib/mpc/examples/cstr/model.py +++ b/pyomo/contrib/mpc/examples/cstr/model.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/examples/cstr/run_mpc.py b/pyomo/contrib/mpc/examples/cstr/run_mpc.py index 86ae7e4e47b..588ed7d49fe 100644 --- a/pyomo/contrib/mpc/examples/cstr/run_mpc.py +++ b/pyomo/contrib/mpc/examples/cstr/run_mpc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/examples/cstr/run_openloop.py b/pyomo/contrib/mpc/examples/cstr/run_openloop.py index 36ddb990545..66fd0680a01 100644 --- a/pyomo/contrib/mpc/examples/cstr/run_openloop.py +++ b/pyomo/contrib/mpc/examples/cstr/run_openloop.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/examples/cstr/tests/__init__.py b/pyomo/contrib/mpc/examples/cstr/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/mpc/examples/cstr/tests/__init__.py +++ b/pyomo/contrib/mpc/examples/cstr/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/examples/cstr/tests/test_mpc.py b/pyomo/contrib/mpc/examples/cstr/tests/test_mpc.py index 741a1533da3..e808b8fc414 100644 --- a/pyomo/contrib/mpc/examples/cstr/tests/test_mpc.py +++ b/pyomo/contrib/mpc/examples/cstr/tests/test_mpc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/examples/cstr/tests/test_openloop.py b/pyomo/contrib/mpc/examples/cstr/tests/test_openloop.py index 218865ceabb..c21cb55233e 100644 --- a/pyomo/contrib/mpc/examples/cstr/tests/test_openloop.py +++ b/pyomo/contrib/mpc/examples/cstr/tests/test_openloop.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/interfaces/__init__.py b/pyomo/contrib/mpc/interfaces/__init__.py index 8e02003f99e..9b70a983e24 100644 --- a/pyomo/contrib/mpc/interfaces/__init__.py +++ b/pyomo/contrib/mpc/interfaces/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/interfaces/copy_values.py b/pyomo/contrib/mpc/interfaces/copy_values.py index 896656b230d..faf1594f114 100644 --- a/pyomo/contrib/mpc/interfaces/copy_values.py +++ b/pyomo/contrib/mpc/interfaces/copy_values.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/interfaces/load_data.py b/pyomo/contrib/mpc/interfaces/load_data.py index efa9515901e..b1851c3aa51 100644 --- a/pyomo/contrib/mpc/interfaces/load_data.py +++ b/pyomo/contrib/mpc/interfaces/load_data.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/interfaces/model_interface.py b/pyomo/contrib/mpc/interfaces/model_interface.py index 35f81af4a7a..9a30878c921 100644 --- a/pyomo/contrib/mpc/interfaces/model_interface.py +++ b/pyomo/contrib/mpc/interfaces/model_interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/interfaces/tests/__init__.py b/pyomo/contrib/mpc/interfaces/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/mpc/interfaces/tests/__init__.py +++ b/pyomo/contrib/mpc/interfaces/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/interfaces/tests/test_interface.py b/pyomo/contrib/mpc/interfaces/tests/test_interface.py index 65ffc7bb40a..e67e58bf900 100644 --- a/pyomo/contrib/mpc/interfaces/tests/test_interface.py +++ b/pyomo/contrib/mpc/interfaces/tests/test_interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/interfaces/tests/test_var_linker.py b/pyomo/contrib/mpc/interfaces/tests/test_var_linker.py index ceec9fada36..e169af686f3 100644 --- a/pyomo/contrib/mpc/interfaces/tests/test_var_linker.py +++ b/pyomo/contrib/mpc/interfaces/tests/test_var_linker.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/interfaces/var_linker.py b/pyomo/contrib/mpc/interfaces/var_linker.py index fd831c9a2c1..87831379204 100644 --- a/pyomo/contrib/mpc/interfaces/var_linker.py +++ b/pyomo/contrib/mpc/interfaces/var_linker.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/modeling/__init__.py b/pyomo/contrib/mpc/modeling/__init__.py index 0eb255a9f56..a174bafc944 100644 --- a/pyomo/contrib/mpc/modeling/__init__.py +++ b/pyomo/contrib/mpc/modeling/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/modeling/constraints.py b/pyomo/contrib/mpc/modeling/constraints.py index 6fb6a311afb..e6a1edf648b 100644 --- a/pyomo/contrib/mpc/modeling/constraints.py +++ b/pyomo/contrib/mpc/modeling/constraints.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/modeling/cost_expressions.py b/pyomo/contrib/mpc/modeling/cost_expressions.py index 65a376e42d2..aeb26705a38 100644 --- a/pyomo/contrib/mpc/modeling/cost_expressions.py +++ b/pyomo/contrib/mpc/modeling/cost_expressions.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/modeling/terminal.py b/pyomo/contrib/mpc/modeling/terminal.py index c25efca280a..d2118c7d92e 100644 --- a/pyomo/contrib/mpc/modeling/terminal.py +++ b/pyomo/contrib/mpc/modeling/terminal.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/modeling/tests/__init__.py b/pyomo/contrib/mpc/modeling/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/mpc/modeling/tests/__init__.py +++ b/pyomo/contrib/mpc/modeling/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/modeling/tests/test_cost_expressions.py b/pyomo/contrib/mpc/modeling/tests/test_cost_expressions.py index 5db390ffa47..67c474f7722 100644 --- a/pyomo/contrib/mpc/modeling/tests/test_cost_expressions.py +++ b/pyomo/contrib/mpc/modeling/tests/test_cost_expressions.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/modeling/tests/test_input_constraints.py b/pyomo/contrib/mpc/modeling/tests/test_input_constraints.py index e3ba3bf3760..be9edad37b9 100644 --- a/pyomo/contrib/mpc/modeling/tests/test_input_constraints.py +++ b/pyomo/contrib/mpc/modeling/tests/test_input_constraints.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/mpc/modeling/tests/test_terminal.py b/pyomo/contrib/mpc/modeling/tests/test_terminal.py index b835f0b1087..ef89fe24b57 100644 --- a/pyomo/contrib/mpc/modeling/tests/test_terminal.py +++ b/pyomo/contrib/mpc/modeling/tests/test_terminal.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/multistart/__init__.py b/pyomo/contrib/multistart/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/multistart/__init__.py +++ b/pyomo/contrib/multistart/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/multistart/high_conf_stop.py b/pyomo/contrib/multistart/high_conf_stop.py index 153d22e9edd..ce24d2dc1fc 100644 --- a/pyomo/contrib/multistart/high_conf_stop.py +++ b/pyomo/contrib/multistart/high_conf_stop.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/multistart/multi.py b/pyomo/contrib/multistart/multi.py index 867d47d4951..377ac8182e2 100644 --- a/pyomo/contrib/multistart/multi.py +++ b/pyomo/contrib/multistart/multi.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/multistart/plugins.py b/pyomo/contrib/multistart/plugins.py index acfd2f06274..f094e2f58cc 100644 --- a/pyomo/contrib/multistart/plugins.py +++ b/pyomo/contrib/multistart/plugins.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/multistart/reinit.py b/pyomo/contrib/multistart/reinit.py index 14dce0352cc..2b097bbc898 100644 --- a/pyomo/contrib/multistart/reinit.py +++ b/pyomo/contrib/multistart/reinit.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/multistart/test_multi.py b/pyomo/contrib/multistart/test_multi.py index a8e3d420266..f8103eed3b8 100644 --- a/pyomo/contrib/multistart/test_multi.py +++ b/pyomo/contrib/multistart/test_multi.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/__init__.py b/pyomo/contrib/parmest/__init__.py index d340885b3fd..e7d513dd95c 100644 --- a/pyomo/contrib/parmest/__init__.py +++ b/pyomo/contrib/parmest/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/__init__.py b/pyomo/contrib/parmest/examples/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/parmest/examples/__init__.py +++ b/pyomo/contrib/parmest/examples/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/reaction_kinetics/__init__.py b/pyomo/contrib/parmest/examples/reaction_kinetics/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/parmest/examples/reaction_kinetics/__init__.py +++ b/pyomo/contrib/parmest/examples/reaction_kinetics/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/reaction_kinetics/simple_reaction_parmest_example.py b/pyomo/contrib/parmest/examples/reaction_kinetics/simple_reaction_parmest_example.py index 719a930251c..7dfe0829262 100644 --- a/pyomo/contrib/parmest/examples/reaction_kinetics/simple_reaction_parmest_example.py +++ b/pyomo/contrib/parmest/examples/reaction_kinetics/simple_reaction_parmest_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/reactor_design/__init__.py b/pyomo/contrib/parmest/examples/reactor_design/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/__init__.py +++ b/pyomo/contrib/parmest/examples/reactor_design/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py b/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py index 16ae9343dfd..1a4dc75e083 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py index 507a3ee7582..e995502d4ae 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py b/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py index cda50ef3efd..8cac82e3879 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py b/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py index 448354f600a..0d5665123b4 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py b/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py index 10d56c8e457..cf77f46f08a 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py b/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py index 43af4fbcb94..c53d9ef36dc 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py index e86446febd7..65046d76a05 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py +++ b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py b/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py index ff6c167f68d..b0b213752cb 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/__init__.py b/pyomo/contrib/parmest/examples/rooney_biegler/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/__init__.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py b/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py index 1c82adb909a..49fed17c5b2 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.py b/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.py index 7cd77166a4b..a87beeb4d39 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.py b/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.py index 9aa59be6a17..d11f0738ab4 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py index 7a48dcf190d..724578b419f 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler_with_constraint.py b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler_with_constraint.py index 0ad65b1eb7a..0c0de4dc6d8 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler_with_constraint.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler_with_constraint.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/semibatch/__init__.py b/pyomo/contrib/parmest/examples/semibatch/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/parmest/examples/semibatch/__init__.py +++ b/pyomo/contrib/parmest/examples/semibatch/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/semibatch/parallel_example.py b/pyomo/contrib/parmest/examples/semibatch/parallel_example.py index ba69b9f2d06..d7cc497803e 100644 --- a/pyomo/contrib/parmest/examples/semibatch/parallel_example.py +++ b/pyomo/contrib/parmest/examples/semibatch/parallel_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.py b/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.py index fc4c9f5c675..c95d9084dc5 100644 --- a/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.py +++ b/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/semibatch/scenario_example.py b/pyomo/contrib/parmest/examples/semibatch/scenario_example.py index 071e53236c4..853a3770bb7 100644 --- a/pyomo/contrib/parmest/examples/semibatch/scenario_example.py +++ b/pyomo/contrib/parmest/examples/semibatch/scenario_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/examples/semibatch/semibatch.py b/pyomo/contrib/parmest/examples/semibatch/semibatch.py index 6762531a338..462e5554142 100644 --- a/pyomo/contrib/parmest/examples/semibatch/semibatch.py +++ b/pyomo/contrib/parmest/examples/semibatch/semibatch.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/graphics.py b/pyomo/contrib/parmest/graphics.py index 65efb5cfd64..c57bfb19696 100644 --- a/pyomo/contrib/parmest/graphics.py +++ b/pyomo/contrib/parmest/graphics.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/ipopt_solver_wrapper.py b/pyomo/contrib/parmest/ipopt_solver_wrapper.py index a6d5e0506fb..75c470a4b81 100644 --- a/pyomo/contrib/parmest/ipopt_solver_wrapper.py +++ b/pyomo/contrib/parmest/ipopt_solver_wrapper.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index 82bf893dd06..44c256f2019 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/scenariocreator.py b/pyomo/contrib/parmest/scenariocreator.py index 58d2d4da722..b599e5952d2 100644 --- a/pyomo/contrib/parmest/scenariocreator.py +++ b/pyomo/contrib/parmest/scenariocreator.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/tests/__init__.py b/pyomo/contrib/parmest/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/parmest/tests/__init__.py +++ b/pyomo/contrib/parmest/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/tests/test_examples.py b/pyomo/contrib/parmest/tests/test_examples.py index 67e06130384..59a3e0adde2 100644 --- a/pyomo/contrib/parmest/tests/test_examples.py +++ b/pyomo/contrib/parmest/tests/test_examples.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/tests/test_graphics.py b/pyomo/contrib/parmest/tests/test_graphics.py index c18659e9948..3b4d0224ebe 100644 --- a/pyomo/contrib/parmest/tests/test_graphics.py +++ b/pyomo/contrib/parmest/tests/test_graphics.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/tests/test_parmest.py b/pyomo/contrib/parmest/tests/test_parmest.py index b5c1fe1bfac..31e083a5f33 100644 --- a/pyomo/contrib/parmest/tests/test_parmest.py +++ b/pyomo/contrib/parmest/tests/test_parmest.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/tests/test_scenariocreator.py b/pyomo/contrib/parmest/tests/test_scenariocreator.py index 22a851ae32e..7db7d0ed5db 100644 --- a/pyomo/contrib/parmest/tests/test_scenariocreator.py +++ b/pyomo/contrib/parmest/tests/test_scenariocreator.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/tests/test_solver.py b/pyomo/contrib/parmest/tests/test_solver.py index eb655023b9b..77eca3a13b6 100644 --- a/pyomo/contrib/parmest/tests/test_solver.py +++ b/pyomo/contrib/parmest/tests/test_solver.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/tests/test_utils.py b/pyomo/contrib/parmest/tests/test_utils.py index 514c14b1e82..e75cc9d3bcd 100644 --- a/pyomo/contrib/parmest/tests/test_utils.py +++ b/pyomo/contrib/parmest/tests/test_utils.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/utils/__init__.py b/pyomo/contrib/parmest/utils/__init__.py index 1615ab206f7..3c6900aa5d9 100644 --- a/pyomo/contrib/parmest/utils/__init__.py +++ b/pyomo/contrib/parmest/utils/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/utils/create_ef.py b/pyomo/contrib/parmest/utils/create_ef.py index 7a7dd72f7da..aaadc7f98b9 100644 --- a/pyomo/contrib/parmest/utils/create_ef.py +++ b/pyomo/contrib/parmest/utils/create_ef.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/utils/ipopt_solver_wrapper.py b/pyomo/contrib/parmest/utils/ipopt_solver_wrapper.py index 7d8289cd181..08388dc5ec1 100644 --- a/pyomo/contrib/parmest/utils/ipopt_solver_wrapper.py +++ b/pyomo/contrib/parmest/utils/ipopt_solver_wrapper.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/utils/model_utils.py b/pyomo/contrib/parmest/utils/model_utils.py index c3c71dc2d6c..77491f74b02 100644 --- a/pyomo/contrib/parmest/utils/model_utils.py +++ b/pyomo/contrib/parmest/utils/model_utils.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/utils/mpi_utils.py b/pyomo/contrib/parmest/utils/mpi_utils.py index 35c4bf137bc..45e3260117d 100644 --- a/pyomo/contrib/parmest/utils/mpi_utils.py +++ b/pyomo/contrib/parmest/utils/mpi_utils.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/parmest/utils/scenario_tree.py b/pyomo/contrib/parmest/utils/scenario_tree.py index 46b02b8ddc1..e71f51877b5 100644 --- a/pyomo/contrib/parmest/utils/scenario_tree.py +++ b/pyomo/contrib/parmest/utils/scenario_tree.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/piecewise/__init__.py b/pyomo/contrib/piecewise/__init__.py index 9e15cfd6670..37873c83b3b 100644 --- a/pyomo/contrib/piecewise/__init__.py +++ b/pyomo/contrib/piecewise/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/piecewise/piecewise_linear_expression.py b/pyomo/contrib/piecewise/piecewise_linear_expression.py index ea1d95b0f51..ddcb7c6a42f 100644 --- a/pyomo/contrib/piecewise/piecewise_linear_expression.py +++ b/pyomo/contrib/piecewise/piecewise_linear_expression.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/piecewise/piecewise_linear_function.py b/pyomo/contrib/piecewise/piecewise_linear_function.py index 6d4fa658f88..66ca02ad125 100644 --- a/pyomo/contrib/piecewise/piecewise_linear_function.py +++ b/pyomo/contrib/piecewise/piecewise_linear_function.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/piecewise/tests/__init__.py b/pyomo/contrib/piecewise/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/piecewise/tests/__init__.py +++ b/pyomo/contrib/piecewise/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/piecewise/tests/common_tests.py b/pyomo/contrib/piecewise/tests/common_tests.py index c77d7064544..23e67474934 100644 --- a/pyomo/contrib/piecewise/tests/common_tests.py +++ b/pyomo/contrib/piecewise/tests/common_tests.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/piecewise/tests/models.py b/pyomo/contrib/piecewise/tests/models.py index be2811a70a4..1a8bef04ad7 100644 --- a/pyomo/contrib/piecewise/tests/models.py +++ b/pyomo/contrib/piecewise/tests/models.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/piecewise/tests/test_inner_repn_gdp.py b/pyomo/contrib/piecewise/tests/test_inner_repn_gdp.py index a0dbd1cca19..27fe43e54d5 100644 --- a/pyomo/contrib/piecewise/tests/test_inner_repn_gdp.py +++ b/pyomo/contrib/piecewise/tests/test_inner_repn_gdp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/piecewise/tests/test_outer_repn_gdp.py b/pyomo/contrib/piecewise/tests/test_outer_repn_gdp.py index edc5d9d3d95..5ee18875cb9 100644 --- a/pyomo/contrib/piecewise/tests/test_outer_repn_gdp.py +++ b/pyomo/contrib/piecewise/tests/test_outer_repn_gdp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/piecewise/tests/test_piecewise_linear_function.py b/pyomo/contrib/piecewise/tests/test_piecewise_linear_function.py index e740e5e3384..571601fefbc 100644 --- a/pyomo/contrib/piecewise/tests/test_piecewise_linear_function.py +++ b/pyomo/contrib/piecewise/tests/test_piecewise_linear_function.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/piecewise/tests/test_reduced_inner_repn.py b/pyomo/contrib/piecewise/tests/test_reduced_inner_repn.py index a2d41c04016..b70281c83ed 100644 --- a/pyomo/contrib/piecewise/tests/test_reduced_inner_repn.py +++ b/pyomo/contrib/piecewise/tests/test_reduced_inner_repn.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/piecewise/transform/__init__.py b/pyomo/contrib/piecewise/transform/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/piecewise/transform/__init__.py +++ b/pyomo/contrib/piecewise/transform/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/piecewise/transform/convex_combination.py b/pyomo/contrib/piecewise/transform/convex_combination.py index abfeac27129..21b72bd9e5d 100644 --- a/pyomo/contrib/piecewise/transform/convex_combination.py +++ b/pyomo/contrib/piecewise/transform/convex_combination.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/piecewise/transform/disaggregated_convex_combination.py b/pyomo/contrib/piecewise/transform/disaggregated_convex_combination.py index 44059935e09..0117bf1d045 100644 --- a/pyomo/contrib/piecewise/transform/disaggregated_convex_combination.py +++ b/pyomo/contrib/piecewise/transform/disaggregated_convex_combination.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/piecewise/transform/inner_representation_gdp.py b/pyomo/contrib/piecewise/transform/inner_representation_gdp.py index 627e41aeae9..f0be2d98825 100644 --- a/pyomo/contrib/piecewise/transform/inner_representation_gdp.py +++ b/pyomo/contrib/piecewise/transform/inner_representation_gdp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/piecewise/transform/multiple_choice.py b/pyomo/contrib/piecewise/transform/multiple_choice.py index 97dc8e9d2b3..9291afa8862 100644 --- a/pyomo/contrib/piecewise/transform/multiple_choice.py +++ b/pyomo/contrib/piecewise/transform/multiple_choice.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/piecewise/transform/outer_representation_gdp.py b/pyomo/contrib/piecewise/transform/outer_representation_gdp.py index 04cd01e1246..7c81619430a 100644 --- a/pyomo/contrib/piecewise/transform/outer_representation_gdp.py +++ b/pyomo/contrib/piecewise/transform/outer_representation_gdp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/piecewise/transform/piecewise_to_gdp_transformation.py b/pyomo/contrib/piecewise/transform/piecewise_to_gdp_transformation.py index ed4902ae6d5..2e056c47a15 100644 --- a/pyomo/contrib/piecewise/transform/piecewise_to_gdp_transformation.py +++ b/pyomo/contrib/piecewise/transform/piecewise_to_gdp_transformation.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/piecewise/transform/piecewise_to_mip_visitor.py b/pyomo/contrib/piecewise/transform/piecewise_to_mip_visitor.py index e3347cf206a..fae95a564bf 100644 --- a/pyomo/contrib/piecewise/transform/piecewise_to_mip_visitor.py +++ b/pyomo/contrib/piecewise/transform/piecewise_to_mip_visitor.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/piecewise/transform/reduced_inner_representation_gdp.py b/pyomo/contrib/piecewise/transform/reduced_inner_representation_gdp.py index b89852530d9..5c7dfa895ab 100644 --- a/pyomo/contrib/piecewise/transform/reduced_inner_representation_gdp.py +++ b/pyomo/contrib/piecewise/transform/reduced_inner_representation_gdp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/__init__.py b/pyomo/contrib/preprocessing/__init__.py index 40d38e74d23..6458b7a6e71 100644 --- a/pyomo/contrib/preprocessing/__init__.py +++ b/pyomo/contrib/preprocessing/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/plugins/__init__.py b/pyomo/contrib/preprocessing/plugins/__init__.py index ae5dfe31682..62f5a40c6a9 100644 --- a/pyomo/contrib/preprocessing/plugins/__init__.py +++ b/pyomo/contrib/preprocessing/plugins/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/plugins/bounds_to_vars.py b/pyomo/contrib/preprocessing/plugins/bounds_to_vars.py index 33eaa731816..8cc17296ac3 100644 --- a/pyomo/contrib/preprocessing/plugins/bounds_to_vars.py +++ b/pyomo/contrib/preprocessing/plugins/bounds_to_vars.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/plugins/constraint_tightener.py b/pyomo/contrib/preprocessing/plugins/constraint_tightener.py index 7c5495e72b8..73851bce618 100644 --- a/pyomo/contrib/preprocessing/plugins/constraint_tightener.py +++ b/pyomo/contrib/preprocessing/plugins/constraint_tightener.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/plugins/deactivate_trivial_constraints.py b/pyomo/contrib/preprocessing/plugins/deactivate_trivial_constraints.py index a91e0a292f2..59e475e9ba1 100644 --- a/pyomo/contrib/preprocessing/plugins/deactivate_trivial_constraints.py +++ b/pyomo/contrib/preprocessing/plugins/deactivate_trivial_constraints.py @@ -2,7 +2,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/plugins/detect_fixed_vars.py b/pyomo/contrib/preprocessing/plugins/detect_fixed_vars.py index bafbec7b8bd..e48914e0a91 100644 --- a/pyomo/contrib/preprocessing/plugins/detect_fixed_vars.py +++ b/pyomo/contrib/preprocessing/plugins/detect_fixed_vars.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/plugins/equality_propagate.py b/pyomo/contrib/preprocessing/plugins/equality_propagate.py index 03e2e11dadb..357a556fcb2 100644 --- a/pyomo/contrib/preprocessing/plugins/equality_propagate.py +++ b/pyomo/contrib/preprocessing/plugins/equality_propagate.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/plugins/induced_linearity.py b/pyomo/contrib/preprocessing/plugins/induced_linearity.py index 6378c94e44e..ba291070644 100644 --- a/pyomo/contrib/preprocessing/plugins/induced_linearity.py +++ b/pyomo/contrib/preprocessing/plugins/induced_linearity.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/plugins/init_vars.py b/pyomo/contrib/preprocessing/plugins/init_vars.py index 7469722cf23..a81d898d52c 100644 --- a/pyomo/contrib/preprocessing/plugins/init_vars.py +++ b/pyomo/contrib/preprocessing/plugins/init_vars.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/plugins/int_to_binary.py b/pyomo/contrib/preprocessing/plugins/int_to_binary.py index 6a08dab9645..e1f7f98a81b 100644 --- a/pyomo/contrib/preprocessing/plugins/int_to_binary.py +++ b/pyomo/contrib/preprocessing/plugins/int_to_binary.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/plugins/remove_zero_terms.py b/pyomo/contrib/preprocessing/plugins/remove_zero_terms.py index 256c94d4b7a..ca2052fa471 100644 --- a/pyomo/contrib/preprocessing/plugins/remove_zero_terms.py +++ b/pyomo/contrib/preprocessing/plugins/remove_zero_terms.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/plugins/strip_bounds.py b/pyomo/contrib/preprocessing/plugins/strip_bounds.py index 51704bc9d58..196de64e405 100644 --- a/pyomo/contrib/preprocessing/plugins/strip_bounds.py +++ b/pyomo/contrib/preprocessing/plugins/strip_bounds.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/plugins/var_aggregator.py b/pyomo/contrib/preprocessing/plugins/var_aggregator.py index 651c0ecf7e0..d862f167fd7 100644 --- a/pyomo/contrib/preprocessing/plugins/var_aggregator.py +++ b/pyomo/contrib/preprocessing/plugins/var_aggregator.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/plugins/zero_sum_propagator.py b/pyomo/contrib/preprocessing/plugins/zero_sum_propagator.py index 16c6614cb3b..df6867719d2 100644 --- a/pyomo/contrib/preprocessing/plugins/zero_sum_propagator.py +++ b/pyomo/contrib/preprocessing/plugins/zero_sum_propagator.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/tests/__init__.py b/pyomo/contrib/preprocessing/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/preprocessing/tests/__init__.py +++ b/pyomo/contrib/preprocessing/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/tests/test_bounds_to_vars_xfrm.py b/pyomo/contrib/preprocessing/tests/test_bounds_to_vars_xfrm.py index 534ba11d22f..0df9dd2462d 100644 --- a/pyomo/contrib/preprocessing/tests/test_bounds_to_vars_xfrm.py +++ b/pyomo/contrib/preprocessing/tests/test_bounds_to_vars_xfrm.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/tests/test_constraint_tightener.py b/pyomo/contrib/preprocessing/tests/test_constraint_tightener.py index 808eb688087..acb939552f8 100644 --- a/pyomo/contrib/preprocessing/tests/test_constraint_tightener.py +++ b/pyomo/contrib/preprocessing/tests/test_constraint_tightener.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/tests/test_deactivate_trivial_constraints.py b/pyomo/contrib/preprocessing/tests/test_deactivate_trivial_constraints.py index 0c89b0d7d86..9e26aab8b77 100644 --- a/pyomo/contrib/preprocessing/tests/test_deactivate_trivial_constraints.py +++ b/pyomo/contrib/preprocessing/tests/test_deactivate_trivial_constraints.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/tests/test_detect_fixed_vars.py b/pyomo/contrib/preprocessing/tests/test_detect_fixed_vars.py index f18ac5c3b8a..a67291dc69f 100644 --- a/pyomo/contrib/preprocessing/tests/test_detect_fixed_vars.py +++ b/pyomo/contrib/preprocessing/tests/test_detect_fixed_vars.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/tests/test_equality_propagate.py b/pyomo/contrib/preprocessing/tests/test_equality_propagate.py index a2c00a15e72..6b12f464710 100644 --- a/pyomo/contrib/preprocessing/tests/test_equality_propagate.py +++ b/pyomo/contrib/preprocessing/tests/test_equality_propagate.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/tests/test_induced_linearity.py b/pyomo/contrib/preprocessing/tests/test_induced_linearity.py index c2c24c33f14..4853cb838df 100644 --- a/pyomo/contrib/preprocessing/tests/test_induced_linearity.py +++ b/pyomo/contrib/preprocessing/tests/test_induced_linearity.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/tests/test_init_vars.py b/pyomo/contrib/preprocessing/tests/test_init_vars.py index 6ddf859e930..a90d39af91c 100644 --- a/pyomo/contrib/preprocessing/tests/test_init_vars.py +++ b/pyomo/contrib/preprocessing/tests/test_init_vars.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/tests/test_int_to_binary.py b/pyomo/contrib/preprocessing/tests/test_int_to_binary.py index bb75a075592..8aa244212ed 100644 --- a/pyomo/contrib/preprocessing/tests/test_int_to_binary.py +++ b/pyomo/contrib/preprocessing/tests/test_int_to_binary.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/tests/test_strip_bounds.py b/pyomo/contrib/preprocessing/tests/test_strip_bounds.py index 4eec0cf7434..f36ff4e9f52 100644 --- a/pyomo/contrib/preprocessing/tests/test_strip_bounds.py +++ b/pyomo/contrib/preprocessing/tests/test_strip_bounds.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/tests/test_var_aggregator.py b/pyomo/contrib/preprocessing/tests/test_var_aggregator.py index b3630225402..6f6d02f2180 100644 --- a/pyomo/contrib/preprocessing/tests/test_var_aggregator.py +++ b/pyomo/contrib/preprocessing/tests/test_var_aggregator.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/tests/test_zero_sum_propagate.py b/pyomo/contrib/preprocessing/tests/test_zero_sum_propagate.py index ce88b8ca86e..41ece8e804f 100644 --- a/pyomo/contrib/preprocessing/tests/test_zero_sum_propagate.py +++ b/pyomo/contrib/preprocessing/tests/test_zero_sum_propagate.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/tests/test_zero_term_removal.py b/pyomo/contrib/preprocessing/tests/test_zero_term_removal.py index abe79034ec2..c5b7477c8f6 100644 --- a/pyomo/contrib/preprocessing/tests/test_zero_term_removal.py +++ b/pyomo/contrib/preprocessing/tests/test_zero_term_removal.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/preprocessing/util.py b/pyomo/contrib/preprocessing/util.py index ffc72f46902..13f3e5dd18c 100644 --- a/pyomo/contrib/preprocessing/util.py +++ b/pyomo/contrib/preprocessing/util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/__init__.py b/pyomo/contrib/pynumero/__init__.py index 9364a552999..39ee2197cbf 100644 --- a/pyomo/contrib/pynumero/__init__.py +++ b/pyomo/contrib/pynumero/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/algorithms/__init__.py b/pyomo/contrib/pynumero/algorithms/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/pynumero/algorithms/__init__.py +++ b/pyomo/contrib/pynumero/algorithms/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/algorithms/solvers/__init__.py b/pyomo/contrib/pynumero/algorithms/solvers/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/__init__.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py b/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py index cedbf430a12..9d24c0dd562 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/algorithms/solvers/implicit_functions.py b/pyomo/contrib/pynumero/algorithms/solvers/implicit_functions.py index e0bc0170d33..e40580c1161 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/implicit_functions.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/implicit_functions.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/algorithms/solvers/pyomo_ext_cyipopt.py b/pyomo/contrib/pynumero/algorithms/solvers/pyomo_ext_cyipopt.py index b234d2f0890..16c5a19a5c6 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/pyomo_ext_cyipopt.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/pyomo_ext_cyipopt.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/algorithms/solvers/scipy_solvers.py b/pyomo/contrib/pynumero/algorithms/solvers/scipy_solvers.py index 53f657c984f..ec1f106b73c 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/scipy_solvers.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/scipy_solvers.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/algorithms/solvers/square_solver_base.py b/pyomo/contrib/pynumero/algorithms/solvers/square_solver_base.py index c4a33d97611..1be3032c358 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/square_solver_base.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/square_solver_base.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/algorithms/solvers/tests/__init__.py b/pyomo/contrib/pynumero/algorithms/solvers/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/tests/__init__.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_interfaces.py b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_interfaces.py index 119c4604f19..88d4df1e17d 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_interfaces.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_interfaces.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py index 7ead30117cb..e9da31097a0 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_implicit_functions.py b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_implicit_functions.py index 04d4ed321f1..3a13c1a7598 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_implicit_functions.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_implicit_functions.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_pyomo_ext_cyipopt.py b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_pyomo_ext_cyipopt.py index 82a37873d5f..0036a6b3623 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_pyomo_ext_cyipopt.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_pyomo_ext_cyipopt.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_scipy_solvers.py b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_scipy_solvers.py index 6636dc3d6e2..33b58f17887 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_scipy_solvers.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_scipy_solvers.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/asl.py b/pyomo/contrib/pynumero/asl.py index a28741fb230..55ecc7fd0ee 100644 --- a/pyomo/contrib/pynumero/asl.py +++ b/pyomo/contrib/pynumero/asl.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/build.py b/pyomo/contrib/pynumero/build.py index 08b5c512ab7..bb8443640d5 100644 --- a/pyomo/contrib/pynumero/build.py +++ b/pyomo/contrib/pynumero/build.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/dependencies.py b/pyomo/contrib/pynumero/dependencies.py index d386bbc3dda..9e2088ffa0a 100644 --- a/pyomo/contrib/pynumero/dependencies.py +++ b/pyomo/contrib/pynumero/dependencies.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/__init__.py b/pyomo/contrib/pynumero/examples/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/pynumero/examples/__init__.py +++ b/pyomo/contrib/pynumero/examples/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/callback/__init__.py b/pyomo/contrib/pynumero/examples/callback/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/pynumero/examples/callback/__init__.py +++ b/pyomo/contrib/pynumero/examples/callback/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/callback/cyipopt_callback.py b/pyomo/contrib/pynumero/examples/callback/cyipopt_callback.py index 58367e0bc5a..f66374f6213 100644 --- a/pyomo/contrib/pynumero/examples/callback/cyipopt_callback.py +++ b/pyomo/contrib/pynumero/examples/callback/cyipopt_callback.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/callback/cyipopt_callback_halt.py b/pyomo/contrib/pynumero/examples/callback/cyipopt_callback_halt.py index 55138c99318..9e88f8d4964 100644 --- a/pyomo/contrib/pynumero/examples/callback/cyipopt_callback_halt.py +++ b/pyomo/contrib/pynumero/examples/callback/cyipopt_callback_halt.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/callback/cyipopt_functor_callback.py b/pyomo/contrib/pynumero/examples/callback/cyipopt_functor_callback.py index b52897d58b1..4befc816e1b 100644 --- a/pyomo/contrib/pynumero/examples/callback/cyipopt_functor_callback.py +++ b/pyomo/contrib/pynumero/examples/callback/cyipopt_functor_callback.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/callback/reactor_design.py b/pyomo/contrib/pynumero/examples/callback/reactor_design.py index 98fbc93ee58..3d9e19a446e 100644 --- a/pyomo/contrib/pynumero/examples/callback/reactor_design.py +++ b/pyomo/contrib/pynumero/examples/callback/reactor_design.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/__init__.py b/pyomo/contrib/pynumero/examples/external_grey_box/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/__init__.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/__init__.py b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/__init__.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/generate_data.py b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/generate_data.py index 3af10a465b7..65bb2c82de8 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/generate_data.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/generate_data.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/models.py b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/models.py index a7962f5634d..c6560b4f9c5 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/models.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/models.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/perform_estimation.py b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/perform_estimation.py index 9a18c7fb54b..142b47f8172 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/param_est/perform_estimation.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/param_est/perform_estimation.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/react_example/__init__.py b/pyomo/contrib/pynumero/examples/external_grey_box/react_example/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/react_example/__init__.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/react_example/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/react_example/maximize_cb_outputs.py b/pyomo/contrib/pynumero/examples/external_grey_box/react_example/maximize_cb_outputs.py index 9f683b146fe..e6afd8995a2 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/react_example/maximize_cb_outputs.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/react_example/maximize_cb_outputs.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/react_example/maximize_cb_ratio_residuals.py b/pyomo/contrib/pynumero/examples/external_grey_box/react_example/maximize_cb_ratio_residuals.py index 26d70c7921e..415b58bee54 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/react_example/maximize_cb_ratio_residuals.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/react_example/maximize_cb_ratio_residuals.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_outputs.py b/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_outputs.py index 6e6c997880b..ef8b2783237 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_outputs.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_outputs.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_residuals.py b/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_residuals.py index 69a79425750..bc5a2ca4ce4 100644 --- a/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_residuals.py +++ b/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_residuals.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/feasibility.py b/pyomo/contrib/pynumero/examples/feasibility.py index 94baabb7bec..59e4edcc9ec 100644 --- a/pyomo/contrib/pynumero/examples/feasibility.py +++ b/pyomo/contrib/pynumero/examples/feasibility.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/mumps_example.py b/pyomo/contrib/pynumero/examples/mumps_example.py index 7f96bfce4ae..588ce58bc12 100644 --- a/pyomo/contrib/pynumero/examples/mumps_example.py +++ b/pyomo/contrib/pynumero/examples/mumps_example.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/nlp_interface.py b/pyomo/contrib/pynumero/examples/nlp_interface.py index 730e0fbda47..556b8ec0713 100644 --- a/pyomo/contrib/pynumero/examples/nlp_interface.py +++ b/pyomo/contrib/pynumero/examples/nlp_interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/nlp_interface_2.py b/pyomo/contrib/pynumero/examples/nlp_interface_2.py index ecd63d28c49..4a288a178b1 100644 --- a/pyomo/contrib/pynumero/examples/nlp_interface_2.py +++ b/pyomo/contrib/pynumero/examples/nlp_interface_2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/parallel_matvec.py b/pyomo/contrib/pynumero/examples/parallel_matvec.py index 78095fe1acd..cd77bcfabc9 100644 --- a/pyomo/contrib/pynumero/examples/parallel_matvec.py +++ b/pyomo/contrib/pynumero/examples/parallel_matvec.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/parallel_vector_ops.py b/pyomo/contrib/pynumero/examples/parallel_vector_ops.py index 83e40a342db..fe49ff29e59 100644 --- a/pyomo/contrib/pynumero/examples/parallel_vector_ops.py +++ b/pyomo/contrib/pynumero/examples/parallel_vector_ops.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/sensitivity.py b/pyomo/contrib/pynumero/examples/sensitivity.py index a3927d637b3..0bb0fb3a740 100644 --- a/pyomo/contrib/pynumero/examples/sensitivity.py +++ b/pyomo/contrib/pynumero/examples/sensitivity.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/sqp.py b/pyomo/contrib/pynumero/examples/sqp.py index 15ad62670f2..925cab4c20b 100644 --- a/pyomo/contrib/pynumero/examples/sqp.py +++ b/pyomo/contrib/pynumero/examples/sqp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/tests/__init__.py b/pyomo/contrib/pynumero/examples/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/pynumero/examples/tests/__init__.py +++ b/pyomo/contrib/pynumero/examples/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py b/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py index 167b0601f7a..1f45f26d43b 100644 --- a/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py +++ b/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/tests/test_examples.py b/pyomo/contrib/pynumero/examples/tests/test_examples.py index d4a5313908c..d1494bab557 100644 --- a/pyomo/contrib/pynumero/examples/tests/test_examples.py +++ b/pyomo/contrib/pynumero/examples/tests/test_examples.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/examples/tests/test_mpi_examples.py b/pyomo/contrib/pynumero/examples/tests/test_mpi_examples.py index 554305f23c9..1ee02bb70ca 100644 --- a/pyomo/contrib/pynumero/examples/tests/test_mpi_examples.py +++ b/pyomo/contrib/pynumero/examples/tests/test_mpi_examples.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/exceptions.py b/pyomo/contrib/pynumero/exceptions.py index dc2167d75d2..6b46dd2d9a7 100644 --- a/pyomo/contrib/pynumero/exceptions.py +++ b/pyomo/contrib/pynumero/exceptions.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/__init__.py b/pyomo/contrib/pynumero/interfaces/__init__.py index debe453e175..e2de0dd25cc 100644 --- a/pyomo/contrib/pynumero/interfaces/__init__.py +++ b/pyomo/contrib/pynumero/interfaces/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/ampl_nlp.py b/pyomo/contrib/pynumero/interfaces/ampl_nlp.py index f5bd56696cf..c19d252667d 100644 --- a/pyomo/contrib/pynumero/interfaces/ampl_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/ampl_nlp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py index fc9c45c6d1a..7845a4c189e 100644 --- a/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py +++ b/pyomo/contrib/pynumero/interfaces/cyipopt_interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 642fd3bf310..7e42f161bee 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/external_pyomo_model.py b/pyomo/contrib/pynumero/interfaces/external_pyomo_model.py index d0e6c21fa64..bae3e0b8159 100644 --- a/pyomo/contrib/pynumero/interfaces/external_pyomo_model.py +++ b/pyomo/contrib/pynumero/interfaces/external_pyomo_model.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/nlp.py b/pyomo/contrib/pynumero/interfaces/nlp.py index 95c05f06a61..20b3a5e4938 100644 --- a/pyomo/contrib/pynumero/interfaces/nlp.py +++ b/pyomo/contrib/pynumero/interfaces/nlp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/nlp_projections.py b/pyomo/contrib/pynumero/interfaces/nlp_projections.py index 3f4e8a88c60..4be3cd28dd5 100644 --- a/pyomo/contrib/pynumero/interfaces/nlp_projections.py +++ b/pyomo/contrib/pynumero/interfaces/nlp_projections.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py index 945e9a05f51..e6ed40e9974 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py index 8017c642854..f9014ab29c0 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py @@ -1,6 +1,6 @@ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/tests/__init__.py b/pyomo/contrib/pynumero/interfaces/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/__init__.py +++ b/pyomo/contrib/pynumero/interfaces/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/tests/compare_utils.py b/pyomo/contrib/pynumero/interfaces/tests/compare_utils.py index d30cfb8f56a..8296ea2d1af 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/compare_utils.py +++ b/pyomo/contrib/pynumero/interfaces/tests/compare_utils.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/tests/external_grey_box_models.py b/pyomo/contrib/pynumero/interfaces/tests/external_grey_box_models.py index d7ec499eaf9..b81731b209e 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/external_grey_box_models.py +++ b/pyomo/contrib/pynumero/interfaces/tests/external_grey_box_models.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_cyipopt_interface.py b/pyomo/contrib/pynumero/interfaces/tests/test_cyipopt_interface.py index f28b7b9b549..bbcd6d4f26d 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_cyipopt_interface.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_cyipopt_interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_dynamic_model.py b/pyomo/contrib/pynumero/interfaces/tests/test_dynamic_model.py index ddd56afb5b4..5b8a8d688dd 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_dynamic_model.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_dynamic_model.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_external_asl_function.py b/pyomo/contrib/pynumero/interfaces/tests/test_external_asl_function.py index 88a4024aeeb..9ca0aef4187 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_external_asl_function.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_external_asl_function.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py b/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py index 58e08a409f0..0fc342c4e40 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_external_grey_box_model.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_block.py b/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_block.py index 7e250b9194e..913d3055c9c 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_block.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_block.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_model.py b/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_model.py index 390d0b6fe63..9773fa7e4a8 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_model.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_model.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_nlp.py b/pyomo/contrib/pynumero/interfaces/tests/test_nlp.py index 38d44473a67..4f735e06de7 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_nlp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_nlp_projections.py b/pyomo/contrib/pynumero/interfaces/tests/test_nlp_projections.py index 7bf693b1eb6..2fada5f679a 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_nlp_projections.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_nlp_projections.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/tests/test_pyomo_grey_box_nlp.py index 52536dd9c06..ecadf40e5cf 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_pyomo_grey_box_nlp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_utils.py b/pyomo/contrib/pynumero/interfaces/tests/test_utils.py index dafe89ca2c7..474d26836b9 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_utils.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_utils.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/interfaces/utils.py b/pyomo/contrib/pynumero/interfaces/utils.py index c7bd04eb002..2aa30fc5946 100644 --- a/pyomo/contrib/pynumero/interfaces/utils.py +++ b/pyomo/contrib/pynumero/interfaces/utils.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/intrinsic.py b/pyomo/contrib/pynumero/intrinsic.py index 5a2dccb64e7..84675cc4c02 100644 --- a/pyomo/contrib/pynumero/intrinsic.py +++ b/pyomo/contrib/pynumero/intrinsic.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/linalg/__init__.py b/pyomo/contrib/pynumero/linalg/__init__.py index 09bccd7449b..c1d9ff38825 100644 --- a/pyomo/contrib/pynumero/linalg/__init__.py +++ b/pyomo/contrib/pynumero/linalg/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/linalg/base.py b/pyomo/contrib/pynumero/linalg/base.py index 7f3d1ffa115..21565b052a5 100644 --- a/pyomo/contrib/pynumero/linalg/base.py +++ b/pyomo/contrib/pynumero/linalg/base.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/linalg/ma27.py b/pyomo/contrib/pynumero/linalg/ma27.py index 21c137e837b..40a7d0e1064 100644 --- a/pyomo/contrib/pynumero/linalg/ma27.py +++ b/pyomo/contrib/pynumero/linalg/ma27.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/linalg/ma27_interface.py b/pyomo/contrib/pynumero/linalg/ma27_interface.py index d974cfc1263..42ac6e73154 100644 --- a/pyomo/contrib/pynumero/linalg/ma27_interface.py +++ b/pyomo/contrib/pynumero/linalg/ma27_interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/linalg/ma57.py b/pyomo/contrib/pynumero/linalg/ma57.py index 1be6c8abcf7..baaa3f34100 100644 --- a/pyomo/contrib/pynumero/linalg/ma57.py +++ b/pyomo/contrib/pynumero/linalg/ma57.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/linalg/ma57_interface.py b/pyomo/contrib/pynumero/linalg/ma57_interface.py index dcd47795256..93004406612 100644 --- a/pyomo/contrib/pynumero/linalg/ma57_interface.py +++ b/pyomo/contrib/pynumero/linalg/ma57_interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/linalg/mumps_interface.py b/pyomo/contrib/pynumero/linalg/mumps_interface.py index baab5562716..8735994f16c 100644 --- a/pyomo/contrib/pynumero/linalg/mumps_interface.py +++ b/pyomo/contrib/pynumero/linalg/mumps_interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/linalg/scipy_interface.py b/pyomo/contrib/pynumero/linalg/scipy_interface.py index a5a53690eb0..025cc539245 100644 --- a/pyomo/contrib/pynumero/linalg/scipy_interface.py +++ b/pyomo/contrib/pynumero/linalg/scipy_interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/linalg/tests/__init__.py b/pyomo/contrib/pynumero/linalg/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/pynumero/linalg/tests/__init__.py +++ b/pyomo/contrib/pynumero/linalg/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/linalg/tests/test_linear_solvers.py b/pyomo/contrib/pynumero/linalg/tests/test_linear_solvers.py index d5025042d95..d2fa955434c 100644 --- a/pyomo/contrib/pynumero/linalg/tests/test_linear_solvers.py +++ b/pyomo/contrib/pynumero/linalg/tests/test_linear_solvers.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/linalg/tests/test_ma27.py b/pyomo/contrib/pynumero/linalg/tests/test_ma27.py index 5a02871306a..979be6f747a 100644 --- a/pyomo/contrib/pynumero/linalg/tests/test_ma27.py +++ b/pyomo/contrib/pynumero/linalg/tests/test_ma27.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/linalg/tests/test_ma57.py b/pyomo/contrib/pynumero/linalg/tests/test_ma57.py index 86dbbd3ca50..de245172f96 100644 --- a/pyomo/contrib/pynumero/linalg/tests/test_ma57.py +++ b/pyomo/contrib/pynumero/linalg/tests/test_ma57.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/linalg/tests/test_mumps_interface.py b/pyomo/contrib/pynumero/linalg/tests/test_mumps_interface.py index 9b0aba96be1..8e5b924fb65 100644 --- a/pyomo/contrib/pynumero/linalg/tests/test_mumps_interface.py +++ b/pyomo/contrib/pynumero/linalg/tests/test_mumps_interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/linalg/utils.py b/pyomo/contrib/pynumero/linalg/utils.py index 2b7a9e99142..adec9ae5f35 100644 --- a/pyomo/contrib/pynumero/linalg/utils.py +++ b/pyomo/contrib/pynumero/linalg/utils.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/plugins.py b/pyomo/contrib/pynumero/plugins.py index 06bb0a5a059..c6890cbbb4d 100644 --- a/pyomo/contrib/pynumero/plugins.py +++ b/pyomo/contrib/pynumero/plugins.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/sparse/__init__.py b/pyomo/contrib/pynumero/sparse/__init__.py index e72d1cd7b2d..ee8196566db 100644 --- a/pyomo/contrib/pynumero/sparse/__init__.py +++ b/pyomo/contrib/pynumero/sparse/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/sparse/base_block.py b/pyomo/contrib/pynumero/sparse/base_block.py index 4f2ae385a7e..0b923ce6efb 100644 --- a/pyomo/contrib/pynumero/sparse/base_block.py +++ b/pyomo/contrib/pynumero/sparse/base_block.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/sparse/block_matrix.py b/pyomo/contrib/pynumero/sparse/block_matrix.py index 97e090fec4c..ba7ed4f085b 100644 --- a/pyomo/contrib/pynumero/sparse/block_matrix.py +++ b/pyomo/contrib/pynumero/sparse/block_matrix.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/sparse/block_vector.py b/pyomo/contrib/pynumero/sparse/block_vector.py index 00733a71752..2b529736935 100644 --- a/pyomo/contrib/pynumero/sparse/block_vector.py +++ b/pyomo/contrib/pynumero/sparse/block_vector.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/sparse/mpi_block_matrix.py b/pyomo/contrib/pynumero/sparse/mpi_block_matrix.py index ee045464dec..28a39b4e2eb 100644 --- a/pyomo/contrib/pynumero/sparse/mpi_block_matrix.py +++ b/pyomo/contrib/pynumero/sparse/mpi_block_matrix.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/sparse/mpi_block_vector.py b/pyomo/contrib/pynumero/sparse/mpi_block_vector.py index 0f57f0eb41e..be8091c9597 100644 --- a/pyomo/contrib/pynumero/sparse/mpi_block_vector.py +++ b/pyomo/contrib/pynumero/sparse/mpi_block_vector.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/sparse/tests/__init__.py b/pyomo/contrib/pynumero/sparse/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/pynumero/sparse/tests/__init__.py +++ b/pyomo/contrib/pynumero/sparse/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/sparse/tests/test_block_matrix.py b/pyomo/contrib/pynumero/sparse/tests/test_block_matrix.py index 7402881a285..48c1d3dc77e 100644 --- a/pyomo/contrib/pynumero/sparse/tests/test_block_matrix.py +++ b/pyomo/contrib/pynumero/sparse/tests/test_block_matrix.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py b/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py index 780a8bc2609..610d41a09a4 100644 --- a/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py +++ b/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/sparse/tests/test_intrinsics.py b/pyomo/contrib/pynumero/sparse/tests/test_intrinsics.py index 0768442c2c4..ef0a5142849 100644 --- a/pyomo/contrib/pynumero/sparse/tests/test_intrinsics.py +++ b/pyomo/contrib/pynumero/sparse/tests/test_intrinsics.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_matrix.py b/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_matrix.py index 1415636c50d..917b4433120 100644 --- a/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_matrix.py +++ b/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_matrix.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py b/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py index cd37b7543a2..c28c524823a 100644 --- a/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py +++ b/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pynumero/src/AmplInterface.cpp b/pyomo/contrib/pynumero/src/AmplInterface.cpp index 26053a9611b..805955f7671 100644 --- a/pyomo/contrib/pynumero/src/AmplInterface.cpp +++ b/pyomo/contrib/pynumero/src/AmplInterface.cpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/pynumero/src/AmplInterface.hpp b/pyomo/contrib/pynumero/src/AmplInterface.hpp index 259cf88d895..bedc6d4f669 100644 --- a/pyomo/contrib/pynumero/src/AmplInterface.hpp +++ b/pyomo/contrib/pynumero/src/AmplInterface.hpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/pynumero/src/AssertUtils.hpp b/pyomo/contrib/pynumero/src/AssertUtils.hpp index ba2e5dc887f..061442eb6e9 100644 --- a/pyomo/contrib/pynumero/src/AssertUtils.hpp +++ b/pyomo/contrib/pynumero/src/AssertUtils.hpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/pynumero/src/ma27Interface.cpp b/pyomo/contrib/pynumero/src/ma27Interface.cpp index 29ffef73938..4816e1274e3 100644 --- a/pyomo/contrib/pynumero/src/ma27Interface.cpp +++ b/pyomo/contrib/pynumero/src/ma27Interface.cpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/pynumero/src/ma57Interface.cpp b/pyomo/contrib/pynumero/src/ma57Interface.cpp index a0fb60edcc8..fa9cf4e6811 100644 --- a/pyomo/contrib/pynumero/src/ma57Interface.cpp +++ b/pyomo/contrib/pynumero/src/ma57Interface.cpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/pynumero/src/tests/simple_test.cpp b/pyomo/contrib/pynumero/src/tests/simple_test.cpp index 30255912c1f..9f39fbbd8ff 100644 --- a/pyomo/contrib/pynumero/src/tests/simple_test.cpp +++ b/pyomo/contrib/pynumero/src/tests/simple_test.cpp @@ -1,7 +1,7 @@ /**___________________________________________________________________________ * * Pyomo: Python Optimization Modeling Objects - * Copyright (c) 2008-2022 + * 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 diff --git a/pyomo/contrib/pynumero/tests/__init__.py b/pyomo/contrib/pynumero/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/pynumero/tests/__init__.py +++ b/pyomo/contrib/pynumero/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pyros/__init__.py b/pyomo/contrib/pyros/__init__.py index 8ecd8ee7478..4e134ef1166 100644 --- a/pyomo/contrib/pyros/__init__.py +++ b/pyomo/contrib/pyros/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index a4b1785a987..abf02809396 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 829184fc70c..475eb424c0b 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index 61615652a01..45b652447ff 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index e37d3325a57..084b0442ae6 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pyros/solve_data.py b/pyomo/contrib/pyros/solve_data.py index 3ee22af9749..bc6c071c9a3 100644 --- a/pyomo/contrib/pyros/solve_data.py +++ b/pyomo/contrib/pyros/solve_data.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pyros/tests/__init__.py b/pyomo/contrib/pyros/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/pyros/tests/__init__.py +++ b/pyomo/contrib/pyros/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index c49c131fdf8..fc215f86a7c 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pyros/uncertainty_sets.py b/pyomo/contrib/pyros/uncertainty_sets.py index 7e13026b1e9..17b51be709b 100644 --- a/pyomo/contrib/pyros/uncertainty_sets.py +++ b/pyomo/contrib/pyros/uncertainty_sets.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index c5a80d27102..97fa42c32e9 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/satsolver/__init__.py b/pyomo/contrib/satsolver/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/satsolver/__init__.py +++ b/pyomo/contrib/satsolver/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/satsolver/satsolver.py b/pyomo/contrib/satsolver/satsolver.py index 50e471253e2..b5004d6a611 100644 --- a/pyomo/contrib/satsolver/satsolver.py +++ b/pyomo/contrib/satsolver/satsolver.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/satsolver/test_satsolver.py b/pyomo/contrib/satsolver/test_satsolver.py index 7ac7aaff03f..f19f172f7b2 100644 --- a/pyomo/contrib/satsolver/test_satsolver.py +++ b/pyomo/contrib/satsolver/test_satsolver.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/sensitivity_toolbox/__init__.py b/pyomo/contrib/sensitivity_toolbox/__init__.py index feb094b6b76..a20cbc389d7 100644 --- a/pyomo/contrib/sensitivity_toolbox/__init__.py +++ b/pyomo/contrib/sensitivity_toolbox/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 @@ -12,7 +12,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/sensitivity_toolbox/examples/HIV_Transmission.py b/pyomo/contrib/sensitivity_toolbox/examples/HIV_Transmission.py index 2c8996c95ca..8d43dea26b2 100755 --- a/pyomo/contrib/sensitivity_toolbox/examples/HIV_Transmission.py +++ b/pyomo/contrib/sensitivity_toolbox/examples/HIV_Transmission.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/sensitivity_toolbox/examples/__init__.py b/pyomo/contrib/sensitivity_toolbox/examples/__init__.py index d67dc03be6c..a408b878891 100644 --- a/pyomo/contrib/sensitivity_toolbox/examples/__init__.py +++ b/pyomo/contrib/sensitivity_toolbox/examples/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 @@ -12,7 +12,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/sensitivity_toolbox/examples/feedbackController.py b/pyomo/contrib/sensitivity_toolbox/examples/feedbackController.py index 1112a0c82b3..d973bedf5ba 100644 --- a/pyomo/contrib/sensitivity_toolbox/examples/feedbackController.py +++ b/pyomo/contrib/sensitivity_toolbox/examples/feedbackController.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/sensitivity_toolbox/examples/parameter.py b/pyomo/contrib/sensitivity_toolbox/examples/parameter.py index 3ed1628f2c2..85d31d3303e 100644 --- a/pyomo/contrib/sensitivity_toolbox/examples/parameter.py +++ b/pyomo/contrib/sensitivity_toolbox/examples/parameter.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/sensitivity_toolbox/examples/parameter_kaug.py b/pyomo/contrib/sensitivity_toolbox/examples/parameter_kaug.py index f54e7903442..c5e61307046 100644 --- a/pyomo/contrib/sensitivity_toolbox/examples/parameter_kaug.py +++ b/pyomo/contrib/sensitivity_toolbox/examples/parameter_kaug.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/sensitivity_toolbox/examples/rangeInequality.py b/pyomo/contrib/sensitivity_toolbox/examples/rangeInequality.py index 39e4d26f695..b06cc8390d2 100644 --- a/pyomo/contrib/sensitivity_toolbox/examples/rangeInequality.py +++ b/pyomo/contrib/sensitivity_toolbox/examples/rangeInequality.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/sensitivity_toolbox/examples/rooney_biegler.py b/pyomo/contrib/sensitivity_toolbox/examples/rooney_biegler.py index 350860a0b50..3efb20bd44b 100644 --- a/pyomo/contrib/sensitivity_toolbox/examples/rooney_biegler.py +++ b/pyomo/contrib/sensitivity_toolbox/examples/rooney_biegler.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/sensitivity_toolbox/k_aug.py b/pyomo/contrib/sensitivity_toolbox/k_aug.py index e7ccb4960a5..a7fc10569fe 100644 --- a/pyomo/contrib/sensitivity_toolbox/k_aug.py +++ b/pyomo/contrib/sensitivity_toolbox/k_aug.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 @@ -12,7 +12,7 @@ # ______________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/sensitivity_toolbox/sens.py b/pyomo/contrib/sensitivity_toolbox/sens.py index 43279c7bc5e..a3d69b2c7b1 100644 --- a/pyomo/contrib/sensitivity_toolbox/sens.py +++ b/pyomo/contrib/sensitivity_toolbox/sens.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 @@ -12,7 +12,7 @@ # ______________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/sensitivity_toolbox/tests/__init__.py b/pyomo/contrib/sensitivity_toolbox/tests/__init__.py index 5aecf9868db..53f447ece43 100644 --- a/pyomo/contrib/sensitivity_toolbox/tests/__init__.py +++ b/pyomo/contrib/sensitivity_toolbox/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 @@ -12,7 +12,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/sensitivity_toolbox/tests/test_k_aug_interface.py b/pyomo/contrib/sensitivity_toolbox/tests/test_k_aug_interface.py index cb219f4f403..e941656a392 100644 --- a/pyomo/contrib/sensitivity_toolbox/tests/test_k_aug_interface.py +++ b/pyomo/contrib/sensitivity_toolbox/tests/test_k_aug_interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 @@ -12,7 +12,7 @@ # ____________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/sensitivity_toolbox/tests/test_sens.py b/pyomo/contrib/sensitivity_toolbox/tests/test_sens.py index 76d180ae422..69cf0303987 100644 --- a/pyomo/contrib/sensitivity_toolbox/tests/test_sens.py +++ b/pyomo/contrib/sensitivity_toolbox/tests/test_sens.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 @@ -12,7 +12,7 @@ # ____________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/sensitivity_toolbox/tests/test_sens_unit.py b/pyomo/contrib/sensitivity_toolbox/tests/test_sens_unit.py index d6da0d814e8..9f4bcb2b497 100644 --- a/pyomo/contrib/sensitivity_toolbox/tests/test_sens_unit.py +++ b/pyomo/contrib/sensitivity_toolbox/tests/test_sens_unit.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 @@ -12,7 +12,7 @@ # ____________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/simplemodel/__init__.py b/pyomo/contrib/simplemodel/__init__.py index 4fa4fa2dd16..f2f4922223e 100644 --- a/pyomo/contrib/simplemodel/__init__.py +++ b/pyomo/contrib/simplemodel/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/trustregion/TRF.py b/pyomo/contrib/trustregion/TRF.py index 45e60df7658..6d2cf863d69 100644 --- a/pyomo/contrib/trustregion/TRF.py +++ b/pyomo/contrib/trustregion/TRF.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/trustregion/__init__.py b/pyomo/contrib/trustregion/__init__.py index 62ba0892686..38b30839be3 100644 --- a/pyomo/contrib/trustregion/__init__.py +++ b/pyomo/contrib/trustregion/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/trustregion/examples/__init__.py b/pyomo/contrib/trustregion/examples/__init__.py index 62ba0892686..38b30839be3 100644 --- a/pyomo/contrib/trustregion/examples/__init__.py +++ b/pyomo/contrib/trustregion/examples/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/trustregion/examples/example1.py b/pyomo/contrib/trustregion/examples/example1.py index 19965ff1cb2..66df26d143f 100755 --- a/pyomo/contrib/trustregion/examples/example1.py +++ b/pyomo/contrib/trustregion/examples/example1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/trustregion/examples/example2.py b/pyomo/contrib/trustregion/examples/example2.py index 0c506eb6891..ad648855410 100644 --- a/pyomo/contrib/trustregion/examples/example2.py +++ b/pyomo/contrib/trustregion/examples/example2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/trustregion/filter.py b/pyomo/contrib/trustregion/filter.py index 2f0b20ee8f8..7e647a7f0c5 100644 --- a/pyomo/contrib/trustregion/filter.py +++ b/pyomo/contrib/trustregion/filter.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/trustregion/interface.py b/pyomo/contrib/trustregion/interface.py index f68f2fdb308..b459e7cfa17 100644 --- a/pyomo/contrib/trustregion/interface.py +++ b/pyomo/contrib/trustregion/interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/trustregion/plugins.py b/pyomo/contrib/trustregion/plugins.py index 59a11986f3c..d4ed22b9d2f 100644 --- a/pyomo/contrib/trustregion/plugins.py +++ b/pyomo/contrib/trustregion/plugins.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/trustregion/tests/__init__.py b/pyomo/contrib/trustregion/tests/__init__.py index 62ba0892686..38b30839be3 100644 --- a/pyomo/contrib/trustregion/tests/__init__.py +++ b/pyomo/contrib/trustregion/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/trustregion/tests/test_TRF.py b/pyomo/contrib/trustregion/tests/test_TRF.py index e14a784b4af..e2b2b2b64ad 100644 --- a/pyomo/contrib/trustregion/tests/test_TRF.py +++ b/pyomo/contrib/trustregion/tests/test_TRF.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/trustregion/tests/test_examples.py b/pyomo/contrib/trustregion/tests/test_examples.py index a954b0851c7..5451cca5961 100644 --- a/pyomo/contrib/trustregion/tests/test_examples.py +++ b/pyomo/contrib/trustregion/tests/test_examples.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/trustregion/tests/test_filter.py b/pyomo/contrib/trustregion/tests/test_filter.py index 1b89d8d5cd1..18e833685f8 100644 --- a/pyomo/contrib/trustregion/tests/test_filter.py +++ b/pyomo/contrib/trustregion/tests/test_filter.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/trustregion/tests/test_interface.py b/pyomo/contrib/trustregion/tests/test_interface.py index a7e6457a5ca..148caceddd1 100644 --- a/pyomo/contrib/trustregion/tests/test_interface.py +++ b/pyomo/contrib/trustregion/tests/test_interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/trustregion/tests/test_util.py b/pyomo/contrib/trustregion/tests/test_util.py index 3054c2c2bd5..bdc91744e61 100644 --- a/pyomo/contrib/trustregion/tests/test_util.py +++ b/pyomo/contrib/trustregion/tests/test_util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/trustregion/util.py b/pyomo/contrib/trustregion/util.py index f27420a2bee..ff3f218fc27 100644 --- a/pyomo/contrib/trustregion/util.py +++ b/pyomo/contrib/trustregion/util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/viewer/__init__.py b/pyomo/contrib/viewer/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/viewer/__init__.py +++ b/pyomo/contrib/viewer/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/viewer/model_browser.py b/pyomo/contrib/viewer/model_browser.py index 8379518a4cf..5887a577ba0 100644 --- a/pyomo/contrib/viewer/model_browser.py +++ b/pyomo/contrib/viewer/model_browser.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/viewer/model_select.py b/pyomo/contrib/viewer/model_select.py index 3c6c4ccdf17..e9c82740708 100644 --- a/pyomo/contrib/viewer/model_select.py +++ b/pyomo/contrib/viewer/model_select.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/viewer/pyomo_viewer.py b/pyomo/contrib/viewer/pyomo_viewer.py index a8fec745af4..6a24e12aa61 100644 --- a/pyomo/contrib/viewer/pyomo_viewer.py +++ b/pyomo/contrib/viewer/pyomo_viewer.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/viewer/qt.py b/pyomo/contrib/viewer/qt.py index 150fa3560f6..2715d275758 100644 --- a/pyomo/contrib/viewer/qt.py +++ b/pyomo/contrib/viewer/qt.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/viewer/report.py b/pyomo/contrib/viewer/report.py index 6f212b2fbc3..f83a53c608d 100644 --- a/pyomo/contrib/viewer/report.py +++ b/pyomo/contrib/viewer/report.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/viewer/residual_table.py b/pyomo/contrib/viewer/residual_table.py index 73cf73847e5..94e8902848f 100644 --- a/pyomo/contrib/viewer/residual_table.py +++ b/pyomo/contrib/viewer/residual_table.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/viewer/tests/__init__.py b/pyomo/contrib/viewer/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/viewer/tests/__init__.py +++ b/pyomo/contrib/viewer/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/viewer/tests/test_data_model_item.py b/pyomo/contrib/viewer/tests/test_data_model_item.py index f3e7aaf9513..781ca25508a 100644 --- a/pyomo/contrib/viewer/tests/test_data_model_item.py +++ b/pyomo/contrib/viewer/tests/test_data_model_item.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/viewer/tests/test_data_model_tree.py b/pyomo/contrib/viewer/tests/test_data_model_tree.py index db745aee9ca..d517c91b353 100644 --- a/pyomo/contrib/viewer/tests/test_data_model_tree.py +++ b/pyomo/contrib/viewer/tests/test_data_model_tree.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/viewer/tests/test_qt.py b/pyomo/contrib/viewer/tests/test_qt.py index 38a022b6668..e71921500f9 100644 --- a/pyomo/contrib/viewer/tests/test_qt.py +++ b/pyomo/contrib/viewer/tests/test_qt.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/viewer/tests/test_report.py b/pyomo/contrib/viewer/tests/test_report.py index b496e2294ff..88044490a77 100644 --- a/pyomo/contrib/viewer/tests/test_report.py +++ b/pyomo/contrib/viewer/tests/test_report.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/viewer/ui.py b/pyomo/contrib/viewer/ui.py index 8a621534b31..374af8a26f0 100644 --- a/pyomo/contrib/viewer/ui.py +++ b/pyomo/contrib/viewer/ui.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/viewer/ui_data.py b/pyomo/contrib/viewer/ui_data.py index 8bbaac14e13..c716cfeedf6 100644 --- a/pyomo/contrib/viewer/ui_data.py +++ b/pyomo/contrib/viewer/ui_data.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/__init__.py b/pyomo/core/__init__.py index b119c6357d0..bce79faacc5 100644 --- a/pyomo/core/__init__.py +++ b/pyomo/core/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/PyomoModel.py b/pyomo/core/base/PyomoModel.py index 055f6f8450a..759b17c9a79 100644 --- a/pyomo/core/base/PyomoModel.py +++ b/pyomo/core/base/PyomoModel.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/__init__.py b/pyomo/core/base/__init__.py index f7815f1676b..4bbd0c9dc44 100644 --- a/pyomo/core/base/__init__.py +++ b/pyomo/core/base/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/action.py b/pyomo/core/base/action.py index b54beab8584..f929c4b38ff 100644 --- a/pyomo/core/base/action.py +++ b/pyomo/core/base/action.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 89e872ebbe5..6d4da04de06 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/blockutil.py b/pyomo/core/base/blockutil.py index 21e6ac4db90..fc763da8b98 100644 --- a/pyomo/core/base/blockutil.py +++ b/pyomo/core/base/blockutil.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/boolean_var.py b/pyomo/core/base/boolean_var.py index 1945045abdd..238540ed125 100644 --- a/pyomo/core/base/boolean_var.py +++ b/pyomo/core/base/boolean_var.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/check.py b/pyomo/core/base/check.py index 0e9d8e889b2..cbf2a99e7a3 100644 --- a/pyomo/core/base/check.py +++ b/pyomo/core/base/check.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index bb855bd6f8d..d9ef6911b6f 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/component_namer.py b/pyomo/core/base/component_namer.py index 17d46c12fae..c2fa01f6ad5 100644 --- a/pyomo/core/base/component_namer.py +++ b/pyomo/core/base/component_namer.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/component_order.py b/pyomo/core/base/component_order.py index 0685571ccb0..8e69baa0972 100644 --- a/pyomo/core/base/component_order.py +++ b/pyomo/core/base/component_order.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/componentuid.py b/pyomo/core/base/componentuid.py index 89f7e5f8320..2075aa197dc 100644 --- a/pyomo/core/base/componentuid.py +++ b/pyomo/core/base/componentuid.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/config.py b/pyomo/core/base/config.py index 4c6cc06f90c..14c00522673 100644 --- a/pyomo/core/base/config.py +++ b/pyomo/core/base/config.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/connector.py b/pyomo/core/base/connector.py index f3d4833b837..8dfee45236e 100644 --- a/pyomo/core/base/connector.py +++ b/pyomo/core/base/connector.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index e391b4a5605..f675a80333a 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/disable_methods.py b/pyomo/core/base/disable_methods.py index 61d63d0a385..ff8eb98487a 100644 --- a/pyomo/core/base/disable_methods.py +++ b/pyomo/core/base/disable_methods.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/enums.py b/pyomo/core/base/enums.py index ddcc66fdc4e..31f2212a661 100644 --- a/pyomo/core/base/enums.py +++ b/pyomo/core/base/enums.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/expression.py b/pyomo/core/base/expression.py index 780bc17c8a3..c8e4136be88 100644 --- a/pyomo/core/base/expression.py +++ b/pyomo/core/base/expression.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/external.py b/pyomo/core/base/external.py index 93fb69e8cf7..c1f7ef32a80 100644 --- a/pyomo/core/base/external.py +++ b/pyomo/core/base/external.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/global_set.py b/pyomo/core/base/global_set.py index f4d97403308..6defb426a74 100644 --- a/pyomo/core/base/global_set.py +++ b/pyomo/core/base/global_set.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/indexed_component.py b/pyomo/core/base/indexed_component.py index 86b210331bb..ac9773c630b 100644 --- a/pyomo/core/base/indexed_component.py +++ b/pyomo/core/base/indexed_component.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/indexed_component_slice.py b/pyomo/core/base/indexed_component_slice.py index 9779711a19b..208aee142e4 100644 --- a/pyomo/core/base/indexed_component_slice.py +++ b/pyomo/core/base/indexed_component_slice.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/initializer.py b/pyomo/core/base/initializer.py index 991feb0450d..c87a4236abe 100644 --- a/pyomo/core/base/initializer.py +++ b/pyomo/core/base/initializer.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/instance2dat.py b/pyomo/core/base/instance2dat.py index b11c0c18e11..4dab6435187 100644 --- a/pyomo/core/base/instance2dat.py +++ b/pyomo/core/base/instance2dat.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/label.py b/pyomo/core/base/label.py index b642b834146..4ed61773a7e 100644 --- a/pyomo/core/base/label.py +++ b/pyomo/core/base/label.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/logical_constraint.py b/pyomo/core/base/logical_constraint.py index 6d553c66fed..6c2d1b036f9 100644 --- a/pyomo/core/base/logical_constraint.py +++ b/pyomo/core/base/logical_constraint.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/matrix_constraint.py b/pyomo/core/base/matrix_constraint.py index 0c55dbc15d3..adc9742302e 100644 --- a/pyomo/core/base/matrix_constraint.py +++ b/pyomo/core/base/matrix_constraint.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/misc.py b/pyomo/core/base/misc.py index cf37ad48fea..926d4e576f4 100644 --- a/pyomo/core/base/misc.py +++ b/pyomo/core/base/misc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/numvalue.py b/pyomo/core/base/numvalue.py index 11d45228bf5..75bceef7ebb 100644 --- a/pyomo/core/base/numvalue.py +++ b/pyomo/core/base/numvalue.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/objective.py b/pyomo/core/base/objective.py index 7fb495f3e5b..3021a35525d 100644 --- a/pyomo/core/base/objective.py +++ b/pyomo/core/base/objective.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/param.py b/pyomo/core/base/param.py index ea4290d880d..734df5fb24e 100644 --- a/pyomo/core/base/param.py +++ b/pyomo/core/base/param.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/piecewise.py b/pyomo/core/base/piecewise.py index ef2fb9eefae..b6ae66ac093 100644 --- a/pyomo/core/base/piecewise.py +++ b/pyomo/core/base/piecewise.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/plugin.py b/pyomo/core/base/plugin.py index 4ecb12d86a6..8c44af2dd61 100644 --- a/pyomo/core/base/plugin.py +++ b/pyomo/core/base/plugin.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/range.py b/pyomo/core/base/range.py index 9df4828f550..acb004e3b10 100644 --- a/pyomo/core/base/range.py +++ b/pyomo/core/base/range.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/rangeset.py b/pyomo/core/base/rangeset.py index 18dedb84c34..27693548c1d 100644 --- a/pyomo/core/base/rangeset.py +++ b/pyomo/core/base/rangeset.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/reference.py b/pyomo/core/base/reference.py index 79ae83b97be..e2166e41e80 100644 --- a/pyomo/core/base/reference.py +++ b/pyomo/core/base/reference.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index d820ae8d933..89099fb54e8 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/set_types.py b/pyomo/core/base/set_types.py index db9fe0f796c..80c8a41ff2e 100644 --- a/pyomo/core/base/set_types.py +++ b/pyomo/core/base/set_types.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/sets.py b/pyomo/core/base/sets.py index cbaad33c0b8..f2ae44be459 100644 --- a/pyomo/core/base/sets.py +++ b/pyomo/core/base/sets.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/sos.py b/pyomo/core/base/sos.py index 98cc9d28c8f..32265df6686 100644 --- a/pyomo/core/base/sos.py +++ b/pyomo/core/base/sos.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index 160ae20f116..67ab0b74215 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/symbol_map.py b/pyomo/core/base/symbol_map.py index e4e7f9d781c..189cce7646a 100644 --- a/pyomo/core/base/symbol_map.py +++ b/pyomo/core/base/symbol_map.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/symbolic.py b/pyomo/core/base/symbolic.py index 3fa5c168207..c1ee08dd584 100644 --- a/pyomo/core/base/symbolic.py +++ b/pyomo/core/base/symbolic.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/template_expr.py b/pyomo/core/base/template_expr.py index f8ff345a1e5..c3697be7eb0 100644 --- a/pyomo/core/base/template_expr.py +++ b/pyomo/core/base/template_expr.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/transformation.py b/pyomo/core/base/transformation.py index 70d89af3798..31f5a251553 100644 --- a/pyomo/core/base/transformation.py +++ b/pyomo/core/base/transformation.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/units_container.py b/pyomo/core/base/units_container.py index dd6bb75aec9..1bf25ffdead 100644 --- a/pyomo/core/base/units_container.py +++ b/pyomo/core/base/units_container.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/util.py b/pyomo/core/base/util.py index 867a303395b..6a3885cedfb 100644 --- a/pyomo/core/base/util.py +++ b/pyomo/core/base/util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/base/var.py b/pyomo/core/base/var.py index f54cea98a9e..d84eabcc76b 100644 --- a/pyomo/core/base/var.py +++ b/pyomo/core/base/var.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/beta/__init__.py b/pyomo/core/beta/__init__.py index d07668534c8..a2d51d0b23e 100644 --- a/pyomo/core/beta/__init__.py +++ b/pyomo/core/beta/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/beta/dict_objects.py b/pyomo/core/beta/dict_objects.py index c987d0946a3..a698fcbb717 100644 --- a/pyomo/core/beta/dict_objects.py +++ b/pyomo/core/beta/dict_objects.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/beta/list_objects.py b/pyomo/core/beta/list_objects.py index 2c42dfa57c8..f2ccf0d37aa 100644 --- a/pyomo/core/beta/list_objects.py +++ b/pyomo/core/beta/list_objects.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/__init__.py b/pyomo/core/expr/__init__.py index a03578de957..5efb5026c65 100644 --- a/pyomo/core/expr/__init__.py +++ b/pyomo/core/expr/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/base.py b/pyomo/core/expr/base.py index b74bbff4e3c..f506956e478 100644 --- a/pyomo/core/expr/base.py +++ b/pyomo/core/expr/base.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/boolean_value.py b/pyomo/core/expr/boolean_value.py index b9c8ece29c8..002ec91be9d 100644 --- a/pyomo/core/expr/boolean_value.py +++ b/pyomo/core/expr/boolean_value.py @@ -2,7 +2,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/calculus/__init__.py b/pyomo/core/expr/calculus/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/core/expr/calculus/__init__.py +++ b/pyomo/core/expr/calculus/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/calculus/derivatives.py b/pyomo/core/expr/calculus/derivatives.py index c9787b0e309..ecfdce02fd4 100644 --- a/pyomo/core/expr/calculus/derivatives.py +++ b/pyomo/core/expr/calculus/derivatives.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/calculus/diff_with_pyomo.py b/pyomo/core/expr/calculus/diff_with_pyomo.py index 0e3ba3cc2b2..fe3eddf1490 100644 --- a/pyomo/core/expr/calculus/diff_with_pyomo.py +++ b/pyomo/core/expr/calculus/diff_with_pyomo.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/calculus/diff_with_sympy.py b/pyomo/core/expr/calculus/diff_with_sympy.py index 32cf60547ec..ab62fa3c307 100644 --- a/pyomo/core/expr/calculus/diff_with_sympy.py +++ b/pyomo/core/expr/calculus/diff_with_sympy.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/cnf_walker.py b/pyomo/core/expr/cnf_walker.py index a7bf61bef5a..7b2081e5d36 100644 --- a/pyomo/core/expr/cnf_walker.py +++ b/pyomo/core/expr/cnf_walker.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/compare.py b/pyomo/core/expr/compare.py index ec8d56896b8..790bc30aaee 100644 --- a/pyomo/core/expr/compare.py +++ b/pyomo/core/expr/compare.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/current.py b/pyomo/core/expr/current.py index 0a2ff01c82a..1209dac0310 100644 --- a/pyomo/core/expr/current.py +++ b/pyomo/core/expr/current.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/expr_common.py b/pyomo/core/expr/expr_common.py index daf86c7afc8..88065e37a2c 100644 --- a/pyomo/core/expr/expr_common.py +++ b/pyomo/core/expr/expr_common.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/expr_errors.py b/pyomo/core/expr/expr_errors.py index e33a6cbbbd7..b0ad816d725 100644 --- a/pyomo/core/expr/expr_errors.py +++ b/pyomo/core/expr/expr_errors.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/logical_expr.py b/pyomo/core/expr/logical_expr.py index 48daa79a5b3..9519b02a43b 100644 --- a/pyomo/core/expr/logical_expr.py +++ b/pyomo/core/expr/logical_expr.py @@ -2,7 +2,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/ndarray.py b/pyomo/core/expr/ndarray.py index fcbe5477a08..41514c91153 100644 --- a/pyomo/core/expr/ndarray.py +++ b/pyomo/core/expr/ndarray.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/numeric_expr.py b/pyomo/core/expr/numeric_expr.py index 0a300474790..c1199ffdcad 100644 --- a/pyomo/core/expr/numeric_expr.py +++ b/pyomo/core/expr/numeric_expr.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/numvalue.py b/pyomo/core/expr/numvalue.py index 6c605b080a3..8cc20648eb4 100644 --- a/pyomo/core/expr/numvalue.py +++ b/pyomo/core/expr/numvalue.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/relational_expr.py b/pyomo/core/expr/relational_expr.py index 6e4831d5c0c..c80fdd4930a 100644 --- a/pyomo/core/expr/relational_expr.py +++ b/pyomo/core/expr/relational_expr.py @@ -2,7 +2,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/symbol_map.py b/pyomo/core/expr/symbol_map.py index ab497c217a8..ebcf9b2953e 100644 --- a/pyomo/core/expr/symbol_map.py +++ b/pyomo/core/expr/symbol_map.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/sympy_tools.py b/pyomo/core/expr/sympy_tools.py index 7b494a610cd..48bd542be0f 100644 --- a/pyomo/core/expr/sympy_tools.py +++ b/pyomo/core/expr/sympy_tools.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/taylor_series.py b/pyomo/core/expr/taylor_series.py index 467b1faa679..2658dd36ff5 100644 --- a/pyomo/core/expr/taylor_series.py +++ b/pyomo/core/expr/taylor_series.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/template_expr.py b/pyomo/core/expr/template_expr.py index fd6294f2289..f65a1f2b9b0 100644 --- a/pyomo/core/expr/template_expr.py +++ b/pyomo/core/expr/template_expr.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index f1cd3b7bde6..6a9b7955281 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/__init__.py b/pyomo/core/kernel/__init__.py index 28a329109fc..ffe0beee080 100644 --- a/pyomo/core/kernel/__init__.py +++ b/pyomo/core/kernel/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/base.py b/pyomo/core/kernel/base.py index 2c0af56bc10..d599c76f6a1 100644 --- a/pyomo/core/kernel/base.py +++ b/pyomo/core/kernel/base.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/block.py b/pyomo/core/kernel/block.py index fd779578fc4..8ba332e5545 100644 --- a/pyomo/core/kernel/block.py +++ b/pyomo/core/kernel/block.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/component_map.py b/pyomo/core/kernel/component_map.py index 501854ad972..5b5b6e9a6f2 100644 --- a/pyomo/core/kernel/component_map.py +++ b/pyomo/core/kernel/component_map.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/component_set.py b/pyomo/core/kernel/component_set.py index b0eb3507347..969b8b86372 100644 --- a/pyomo/core/kernel/component_set.py +++ b/pyomo/core/kernel/component_set.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/conic.py b/pyomo/core/kernel/conic.py index 730c072d1b7..1bb5f1b6ce8 100644 --- a/pyomo/core/kernel/conic.py +++ b/pyomo/core/kernel/conic.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/constraint.py b/pyomo/core/kernel/constraint.py index 7c7969cb025..6aa4abc4bfe 100644 --- a/pyomo/core/kernel/constraint.py +++ b/pyomo/core/kernel/constraint.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/container_utils.py b/pyomo/core/kernel/container_utils.py index 7f3329aadb3..e197d0162b5 100644 --- a/pyomo/core/kernel/container_utils.py +++ b/pyomo/core/kernel/container_utils.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/dict_container.py b/pyomo/core/kernel/dict_container.py index b86d9c5b8f2..ae23044f8ed 100644 --- a/pyomo/core/kernel/dict_container.py +++ b/pyomo/core/kernel/dict_container.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/expression.py b/pyomo/core/kernel/expression.py index b375a6a89fc..a477ff9d0e3 100644 --- a/pyomo/core/kernel/expression.py +++ b/pyomo/core/kernel/expression.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/heterogeneous_container.py b/pyomo/core/kernel/heterogeneous_container.py index 43846673838..4783a2d3ec6 100644 --- a/pyomo/core/kernel/heterogeneous_container.py +++ b/pyomo/core/kernel/heterogeneous_container.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/homogeneous_container.py b/pyomo/core/kernel/homogeneous_container.py index 22a70e1edff..edec98e9736 100644 --- a/pyomo/core/kernel/homogeneous_container.py +++ b/pyomo/core/kernel/homogeneous_container.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/list_container.py b/pyomo/core/kernel/list_container.py index 05116797f3a..d60b0c7678d 100644 --- a/pyomo/core/kernel/list_container.py +++ b/pyomo/core/kernel/list_container.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/matrix_constraint.py b/pyomo/core/kernel/matrix_constraint.py index 1dc0fa7ddc3..ac0ec8e832d 100644 --- a/pyomo/core/kernel/matrix_constraint.py +++ b/pyomo/core/kernel/matrix_constraint.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/objective.py b/pyomo/core/kernel/objective.py index c25c86d3c09..9aa8e3315ef 100644 --- a/pyomo/core/kernel/objective.py +++ b/pyomo/core/kernel/objective.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/parameter.py b/pyomo/core/kernel/parameter.py index 1d22072435d..d4dd6336c69 100644 --- a/pyomo/core/kernel/parameter.py +++ b/pyomo/core/kernel/parameter.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/piecewise_library/__init__.py b/pyomo/core/kernel/piecewise_library/__init__.py index d275b52367e..c4d2a751632 100644 --- a/pyomo/core/kernel/piecewise_library/__init__.py +++ b/pyomo/core/kernel/piecewise_library/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/piecewise_library/transforms.py b/pyomo/core/kernel/piecewise_library/transforms.py index f00e57c199d..bc6cb0f51ad 100644 --- a/pyomo/core/kernel/piecewise_library/transforms.py +++ b/pyomo/core/kernel/piecewise_library/transforms.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/piecewise_library/transforms_nd.py b/pyomo/core/kernel/piecewise_library/transforms_nd.py index f1ea67e8d4b..2c4c8a1f1f2 100644 --- a/pyomo/core/kernel/piecewise_library/transforms_nd.py +++ b/pyomo/core/kernel/piecewise_library/transforms_nd.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/piecewise_library/util.py b/pyomo/core/kernel/piecewise_library/util.py index e65502b1a12..23975d87596 100644 --- a/pyomo/core/kernel/piecewise_library/util.py +++ b/pyomo/core/kernel/piecewise_library/util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/register_numpy_types.py b/pyomo/core/kernel/register_numpy_types.py index 5f7812354d9..86877be2230 100644 --- a/pyomo/core/kernel/register_numpy_types.py +++ b/pyomo/core/kernel/register_numpy_types.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/set_types.py b/pyomo/core/kernel/set_types.py index efe5965946a..5915f0d64b3 100644 --- a/pyomo/core/kernel/set_types.py +++ b/pyomo/core/kernel/set_types.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/sos.py b/pyomo/core/kernel/sos.py index cb8d8ea4930..1845343f526 100644 --- a/pyomo/core/kernel/sos.py +++ b/pyomo/core/kernel/sos.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/suffix.py b/pyomo/core/kernel/suffix.py index 77079364703..56e13a371a3 100644 --- a/pyomo/core/kernel/suffix.py +++ b/pyomo/core/kernel/suffix.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/tuple_container.py b/pyomo/core/kernel/tuple_container.py index f717fe0350a..83aab49e5db 100644 --- a/pyomo/core/kernel/tuple_container.py +++ b/pyomo/core/kernel/tuple_container.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/kernel/variable.py b/pyomo/core/kernel/variable.py index ff54bcb2fca..61324b3dc0f 100644 --- a/pyomo/core/kernel/variable.py +++ b/pyomo/core/kernel/variable.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/plugins/__init__.py b/pyomo/core/plugins/__init__.py index f763881c50c..23407cd77ef 100644 --- a/pyomo/core/plugins/__init__.py +++ b/pyomo/core/plugins/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/plugins/transform/__init__.py b/pyomo/core/plugins/transform/__init__.py index 7d37c706542..21e762047ca 100644 --- a/pyomo/core/plugins/transform/__init__.py +++ b/pyomo/core/plugins/transform/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/plugins/transform/add_slack_vars.py b/pyomo/core/plugins/transform/add_slack_vars.py index 6906b033aab..6b5096d315c 100644 --- a/pyomo/core/plugins/transform/add_slack_vars.py +++ b/pyomo/core/plugins/transform/add_slack_vars.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/plugins/transform/discrete_vars.py b/pyomo/core/plugins/transform/discrete_vars.py index cfb1c5e144f..35729e76517 100644 --- a/pyomo/core/plugins/transform/discrete_vars.py +++ b/pyomo/core/plugins/transform/discrete_vars.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/plugins/transform/eliminate_fixed_vars.py b/pyomo/core/plugins/transform/eliminate_fixed_vars.py index 1048b957e08..9312035b8c8 100644 --- a/pyomo/core/plugins/transform/eliminate_fixed_vars.py +++ b/pyomo/core/plugins/transform/eliminate_fixed_vars.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/plugins/transform/equality_transform.py b/pyomo/core/plugins/transform/equality_transform.py index e0cc463e238..a1a1b72f146 100644 --- a/pyomo/core/plugins/transform/equality_transform.py +++ b/pyomo/core/plugins/transform/equality_transform.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/plugins/transform/expand_connectors.py b/pyomo/core/plugins/transform/expand_connectors.py index 8fe14318669..8c02f3e5698 100644 --- a/pyomo/core/plugins/transform/expand_connectors.py +++ b/pyomo/core/plugins/transform/expand_connectors.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/plugins/transform/hierarchy.py b/pyomo/core/plugins/transform/hierarchy.py index a7667fc028a..86338d17f88 100644 --- a/pyomo/core/plugins/transform/hierarchy.py +++ b/pyomo/core/plugins/transform/hierarchy.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/plugins/transform/logical_to_linear.py b/pyomo/core/plugins/transform/logical_to_linear.py index f2c609348e5..7aa541a5fdd 100644 --- a/pyomo/core/plugins/transform/logical_to_linear.py +++ b/pyomo/core/plugins/transform/logical_to_linear.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/plugins/transform/model.py b/pyomo/core/plugins/transform/model.py index 99c1d21c9a0..db8376afd29 100644 --- a/pyomo/core/plugins/transform/model.py +++ b/pyomo/core/plugins/transform/model.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/plugins/transform/nonnegative_transform.py b/pyomo/core/plugins/transform/nonnegative_transform.py index b32b7b1efc0..d123e68cb2e 100644 --- a/pyomo/core/plugins/transform/nonnegative_transform.py +++ b/pyomo/core/plugins/transform/nonnegative_transform.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/plugins/transform/radix_linearization.py b/pyomo/core/plugins/transform/radix_linearization.py index b7ff3375a76..c67e556d60c 100644 --- a/pyomo/core/plugins/transform/radix_linearization.py +++ b/pyomo/core/plugins/transform/radix_linearization.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/plugins/transform/relax_integrality.py b/pyomo/core/plugins/transform/relax_integrality.py index 06dd2faba77..40cf74ddbcc 100644 --- a/pyomo/core/plugins/transform/relax_integrality.py +++ b/pyomo/core/plugins/transform/relax_integrality.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/plugins/transform/scaling.py b/pyomo/core/plugins/transform/scaling.py index 0883455f9de..ad894b31fde 100644 --- a/pyomo/core/plugins/transform/scaling.py +++ b/pyomo/core/plugins/transform/scaling.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/plugins/transform/standard_form.py b/pyomo/core/plugins/transform/standard_form.py index 54df13fc49d..ffc382a2cf7 100644 --- a/pyomo/core/plugins/transform/standard_form.py +++ b/pyomo/core/plugins/transform/standard_form.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/plugins/transform/util.py b/pyomo/core/plugins/transform/util.py index bba8adfbc0f..9719b1f38d9 100644 --- a/pyomo/core/plugins/transform/util.py +++ b/pyomo/core/plugins/transform/util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/pyomoobject.py b/pyomo/core/pyomoobject.py index 692db444f84..3bf6de37489 100644 --- a/pyomo/core/pyomoobject.py +++ b/pyomo/core/pyomoobject.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/staleflag.py b/pyomo/core/staleflag.py index 7d0dddef0dd..da90032a03c 100644 --- a/pyomo/core/staleflag.py +++ b/pyomo/core/staleflag.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/__init__.py b/pyomo/core/tests/__init__.py index 0dc08cc5aea..761a6e6c44c 100644 --- a/pyomo/core/tests/__init__.py +++ b/pyomo/core/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/data/__init__.py b/pyomo/core/tests/data/__init__.py index 21b3abf0760..a73865ee112 100644 --- a/pyomo/core/tests/data/__init__.py +++ b/pyomo/core/tests/data/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/data/test_odbc_ini.py b/pyomo/core/tests/data/test_odbc_ini.py index e7152181645..43584fe3ca9 100644 --- a/pyomo/core/tests/data/test_odbc_ini.py +++ b/pyomo/core/tests/data/test_odbc_ini.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/diet/__init__.py b/pyomo/core/tests/diet/__init__.py index 3e98344ba07..717247051c4 100644 --- a/pyomo/core/tests/diet/__init__.py +++ b/pyomo/core/tests/diet/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/diet/test_diet.py b/pyomo/core/tests/diet/test_diet.py index d92f0a024ba..9e11907179e 100644 --- a/pyomo/core/tests/diet/test_diet.py +++ b/pyomo/core/tests/diet/test_diet.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/examples/__init__.py b/pyomo/core/tests/examples/__init__.py index 602516fcb56..c5ecc4ee437 100644 --- a/pyomo/core/tests/examples/__init__.py +++ b/pyomo/core/tests/examples/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/examples/pmedian.py b/pyomo/core/tests/examples/pmedian.py index 5176f8bad18..c476f01bd17 100644 --- a/pyomo/core/tests/examples/pmedian.py +++ b/pyomo/core/tests/examples/pmedian.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/examples/pmedian1.py b/pyomo/core/tests/examples/pmedian1.py index 5aeec502f7c..8e11383116b 100644 --- a/pyomo/core/tests/examples/pmedian1.py +++ b/pyomo/core/tests/examples/pmedian1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/examples/pmedian2.py b/pyomo/core/tests/examples/pmedian2.py index 8a908f7d661..88a9666fe41 100644 --- a/pyomo/core/tests/examples/pmedian2.py +++ b/pyomo/core/tests/examples/pmedian2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/examples/pmedian4.py b/pyomo/core/tests/examples/pmedian4.py index 98dd90f3e8f..101ee3e7c46 100644 --- a/pyomo/core/tests/examples/pmedian4.py +++ b/pyomo/core/tests/examples/pmedian4.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/examples/test_amplbook2.py b/pyomo/core/tests/examples/test_amplbook2.py index fdb9cc571bf..72e3d2b4599 100644 --- a/pyomo/core/tests/examples/test_amplbook2.py +++ b/pyomo/core/tests/examples/test_amplbook2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/examples/test_kernel_examples.py b/pyomo/core/tests/examples/test_kernel_examples.py index 0434d9127a3..61d0fa2527d 100644 --- a/pyomo/core/tests/examples/test_kernel_examples.py +++ b/pyomo/core/tests/examples/test_kernel_examples.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/examples/test_pyomo.py b/pyomo/core/tests/examples/test_pyomo.py index 64c195c0ab4..2d3a39ebdda 100644 --- a/pyomo/core/tests/examples/test_pyomo.py +++ b/pyomo/core/tests/examples/test_pyomo.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/examples/test_tutorials.py b/pyomo/core/tests/examples/test_tutorials.py index 3a74c1ca142..c8de003007e 100644 --- a/pyomo/core/tests/examples/test_tutorials.py +++ b/pyomo/core/tests/examples/test_tutorials.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/transform/__init__.py b/pyomo/core/tests/transform/__init__.py index df59aa21988..f34c7624e25 100644 --- a/pyomo/core/tests/transform/__init__.py +++ b/pyomo/core/tests/transform/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/transform/test_add_slacks.py b/pyomo/core/tests/transform/test_add_slacks.py index a3698b7d529..7896cab7e88 100644 --- a/pyomo/core/tests/transform/test_add_slacks.py +++ b/pyomo/core/tests/transform/test_add_slacks.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/transform/test_scaling.py b/pyomo/core/tests/transform/test_scaling.py index 1cb4e886956..d0fbfab61bd 100644 --- a/pyomo/core/tests/transform/test_scaling.py +++ b/pyomo/core/tests/transform/test_scaling.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/transform/test_transform.py b/pyomo/core/tests/transform/test_transform.py index 7c3f17fcfec..cd1f26417a7 100644 --- a/pyomo/core/tests/transform/test_transform.py +++ b/pyomo/core/tests/transform/test_transform.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/__init__.py b/pyomo/core/tests/unit/__init__.py index 65e82b81c0c..85ece8d8cd5 100644 --- a/pyomo/core/tests/unit/__init__.py +++ b/pyomo/core/tests/unit/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/kernel/__init__.py b/pyomo/core/tests/unit/kernel/__init__.py index ff387efbd03..e5231e0f859 100644 --- a/pyomo/core/tests/unit/kernel/__init__.py +++ b/pyomo/core/tests/unit/kernel/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/kernel/test_block.py b/pyomo/core/tests/unit/kernel/test_block.py index a22ed4fb4b5..b21771653bb 100644 --- a/pyomo/core/tests/unit/kernel/test_block.py +++ b/pyomo/core/tests/unit/kernel/test_block.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/kernel/test_component_map.py b/pyomo/core/tests/unit/kernel/test_component_map.py index 6d19743c3fe..3fb8b99a9a3 100644 --- a/pyomo/core/tests/unit/kernel/test_component_map.py +++ b/pyomo/core/tests/unit/kernel/test_component_map.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/kernel/test_component_set.py b/pyomo/core/tests/unit/kernel/test_component_set.py index 30f2cf72716..38f17a702c1 100644 --- a/pyomo/core/tests/unit/kernel/test_component_set.py +++ b/pyomo/core/tests/unit/kernel/test_component_set.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/kernel/test_conic.py b/pyomo/core/tests/unit/kernel/test_conic.py index 352976a2410..ccfbcca7e1f 100644 --- a/pyomo/core/tests/unit/kernel/test_conic.py +++ b/pyomo/core/tests/unit/kernel/test_conic.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/kernel/test_constraint.py b/pyomo/core/tests/unit/kernel/test_constraint.py index f2f219cc66f..97832dd8bca 100644 --- a/pyomo/core/tests/unit/kernel/test_constraint.py +++ b/pyomo/core/tests/unit/kernel/test_constraint.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/kernel/test_dict_container.py b/pyomo/core/tests/unit/kernel/test_dict_container.py index e6b6f8d7aab..6ae25362bb2 100644 --- a/pyomo/core/tests/unit/kernel/test_dict_container.py +++ b/pyomo/core/tests/unit/kernel/test_dict_container.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/kernel/test_expression.py b/pyomo/core/tests/unit/kernel/test_expression.py index 85f8c331a46..39d3eaa463c 100644 --- a/pyomo/core/tests/unit/kernel/test_expression.py +++ b/pyomo/core/tests/unit/kernel/test_expression.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/kernel/test_kernel.py b/pyomo/core/tests/unit/kernel/test_kernel.py index fbff295881a..b34bcdeaadb 100644 --- a/pyomo/core/tests/unit/kernel/test_kernel.py +++ b/pyomo/core/tests/unit/kernel/test_kernel.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/kernel/test_list_container.py b/pyomo/core/tests/unit/kernel/test_list_container.py index 9e3ada739b2..a4641f83295 100644 --- a/pyomo/core/tests/unit/kernel/test_list_container.py +++ b/pyomo/core/tests/unit/kernel/test_list_container.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/kernel/test_matrix_constraint.py b/pyomo/core/tests/unit/kernel/test_matrix_constraint.py index c986e5eda96..24a2915f224 100644 --- a/pyomo/core/tests/unit/kernel/test_matrix_constraint.py +++ b/pyomo/core/tests/unit/kernel/test_matrix_constraint.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/kernel/test_objective.py b/pyomo/core/tests/unit/kernel/test_objective.py index f60ff9bdb49..810218f1dc2 100644 --- a/pyomo/core/tests/unit/kernel/test_objective.py +++ b/pyomo/core/tests/unit/kernel/test_objective.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/kernel/test_parameter.py b/pyomo/core/tests/unit/kernel/test_parameter.py index 04dc08f095f..469ed9fbe8c 100644 --- a/pyomo/core/tests/unit/kernel/test_parameter.py +++ b/pyomo/core/tests/unit/kernel/test_parameter.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/kernel/test_piecewise.py b/pyomo/core/tests/unit/kernel/test_piecewise.py index 2c236c0dd12..3d9cf66e39c 100644 --- a/pyomo/core/tests/unit/kernel/test_piecewise.py +++ b/pyomo/core/tests/unit/kernel/test_piecewise.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/kernel/test_sos.py b/pyomo/core/tests/unit/kernel/test_sos.py index 9410425d405..b1cb67a96f8 100644 --- a/pyomo/core/tests/unit/kernel/test_sos.py +++ b/pyomo/core/tests/unit/kernel/test_sos.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/kernel/test_suffix.py b/pyomo/core/tests/unit/kernel/test_suffix.py index c4c75278d50..2a73888c2d3 100644 --- a/pyomo/core/tests/unit/kernel/test_suffix.py +++ b/pyomo/core/tests/unit/kernel/test_suffix.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/kernel/test_tuple_container.py b/pyomo/core/tests/unit/kernel/test_tuple_container.py index 0b45c36b299..c016c5fc789 100644 --- a/pyomo/core/tests/unit/kernel/test_tuple_container.py +++ b/pyomo/core/tests/unit/kernel/test_tuple_container.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/kernel/test_variable.py b/pyomo/core/tests/unit/kernel/test_variable.py index e360240f3b2..181eb15c972 100644 --- a/pyomo/core/tests/unit/kernel/test_variable.py +++ b/pyomo/core/tests/unit/kernel/test_variable.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_action.py b/pyomo/core/tests/unit/test_action.py index 5db6f165854..3481c90a021 100644 --- a/pyomo/core/tests/unit/test_action.py +++ b/pyomo/core/tests/unit/test_action.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_block.py b/pyomo/core/tests/unit/test_block.py index f68850d9421..f60a758eb62 100644 --- a/pyomo/core/tests/unit/test_block.py +++ b/pyomo/core/tests/unit/test_block.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_block_model.py b/pyomo/core/tests/unit/test_block_model.py index ed751e96fc5..b4cf34e7516 100644 --- a/pyomo/core/tests/unit/test_block_model.py +++ b/pyomo/core/tests/unit/test_block_model.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_bounds.py b/pyomo/core/tests/unit/test_bounds.py index c2c6a69bdd2..23554f555c9 100644 --- a/pyomo/core/tests/unit/test_bounds.py +++ b/pyomo/core/tests/unit/test_bounds.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_check.py b/pyomo/core/tests/unit/test_check.py index 5b2d5408fd5..e61e3998fb7 100644 --- a/pyomo/core/tests/unit/test_check.py +++ b/pyomo/core/tests/unit/test_check.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_compare.py b/pyomo/core/tests/unit/test_compare.py index 8b8538a8656..f80753bdb61 100644 --- a/pyomo/core/tests/unit/test_compare.py +++ b/pyomo/core/tests/unit/test_compare.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_component.py b/pyomo/core/tests/unit/test_component.py index b4408fe8c54..175c4c47d46 100644 --- a/pyomo/core/tests/unit/test_component.py +++ b/pyomo/core/tests/unit/test_component.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_componentuid.py b/pyomo/core/tests/unit/test_componentuid.py index 1c9b3c444bf..2893e737136 100644 --- a/pyomo/core/tests/unit/test_componentuid.py +++ b/pyomo/core/tests/unit/test_componentuid.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_con.py b/pyomo/core/tests/unit/test_con.py index bd90972fee2..6ed19c1bcfd 100644 --- a/pyomo/core/tests/unit/test_con.py +++ b/pyomo/core/tests/unit/test_con.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_concrete.py b/pyomo/core/tests/unit/test_concrete.py index a9bd75f05c7..9083c5cf7f9 100644 --- a/pyomo/core/tests/unit/test_concrete.py +++ b/pyomo/core/tests/unit/test_concrete.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_connector.py b/pyomo/core/tests/unit/test_connector.py index 1dde9f3af24..78799d13d0b 100644 --- a/pyomo/core/tests/unit/test_connector.py +++ b/pyomo/core/tests/unit/test_connector.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_deprecation.py b/pyomo/core/tests/unit/test_deprecation.py index 9adf2de26cd..7d718a4bd2a 100644 --- a/pyomo/core/tests/unit/test_deprecation.py +++ b/pyomo/core/tests/unit/test_deprecation.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_derivs.py b/pyomo/core/tests/unit/test_derivs.py index 7db284cb29a..6a4fc6814b3 100644 --- a/pyomo/core/tests/unit/test_derivs.py +++ b/pyomo/core/tests/unit/test_derivs.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_dict_objects.py b/pyomo/core/tests/unit/test_dict_objects.py index 7d3244f4d86..8260f1ae320 100644 --- a/pyomo/core/tests/unit/test_dict_objects.py +++ b/pyomo/core/tests/unit/test_dict_objects.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_disable_methods.py b/pyomo/core/tests/unit/test_disable_methods.py index 4d6595e5fe8..618752aee85 100644 --- a/pyomo/core/tests/unit/test_disable_methods.py +++ b/pyomo/core/tests/unit/test_disable_methods.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_enums.py b/pyomo/core/tests/unit/test_enums.py index 8f342e55188..cce908a87de 100644 --- a/pyomo/core/tests/unit/test_enums.py +++ b/pyomo/core/tests/unit/test_enums.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_expr_misc.py b/pyomo/core/tests/unit/test_expr_misc.py index 4ec53521d6b..f4fd7556117 100644 --- a/pyomo/core/tests/unit/test_expr_misc.py +++ b/pyomo/core/tests/unit/test_expr_misc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_expression.py b/pyomo/core/tests/unit/test_expression.py index 8dca0062dd0..acf5abb2626 100644 --- a/pyomo/core/tests/unit/test_expression.py +++ b/pyomo/core/tests/unit/test_expression.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_external.py b/pyomo/core/tests/unit/test_external.py index 96c05b6b0b8..1d4a59647c1 100644 --- a/pyomo/core/tests/unit/test_external.py +++ b/pyomo/core/tests/unit/test_external.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_indexed.py b/pyomo/core/tests/unit/test_indexed.py index 29bf22ceeb1..3480b653ea5 100644 --- a/pyomo/core/tests/unit/test_indexed.py +++ b/pyomo/core/tests/unit/test_indexed.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_indexed_slice.py b/pyomo/core/tests/unit/test_indexed_slice.py index e89c48a6061..babd3f3c46a 100644 --- a/pyomo/core/tests/unit/test_indexed_slice.py +++ b/pyomo/core/tests/unit/test_indexed_slice.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_initializer.py b/pyomo/core/tests/unit/test_initializer.py index b334a6b857b..c0f9ddc9565 100644 --- a/pyomo/core/tests/unit/test_initializer.py +++ b/pyomo/core/tests/unit/test_initializer.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_kernel_register_numpy_types.py b/pyomo/core/tests/unit/test_kernel_register_numpy_types.py index 117de5c5f4c..91a0f571881 100644 --- a/pyomo/core/tests/unit/test_kernel_register_numpy_types.py +++ b/pyomo/core/tests/unit/test_kernel_register_numpy_types.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_labelers.py b/pyomo/core/tests/unit/test_labelers.py index 15c56b5390d..579abfd8b52 100644 --- a/pyomo/core/tests/unit/test_labelers.py +++ b/pyomo/core/tests/unit/test_labelers.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_list_objects.py b/pyomo/core/tests/unit/test_list_objects.py index 442fa97b6d1..3eb2e279964 100644 --- a/pyomo/core/tests/unit/test_list_objects.py +++ b/pyomo/core/tests/unit/test_list_objects.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_logical_constraint.py b/pyomo/core/tests/unit/test_logical_constraint.py index e38a67a39d0..b1f37996018 100644 --- a/pyomo/core/tests/unit/test_logical_constraint.py +++ b/pyomo/core/tests/unit/test_logical_constraint.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_logical_expr_expanded.py b/pyomo/core/tests/unit/test_logical_expr_expanded.py index 0360e9b4783..6468a21e336 100644 --- a/pyomo/core/tests/unit/test_logical_expr_expanded.py +++ b/pyomo/core/tests/unit/test_logical_expr_expanded.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_logical_to_linear.py b/pyomo/core/tests/unit/test_logical_to_linear.py index 22133f22ba2..e777259f8ce 100644 --- a/pyomo/core/tests/unit/test_logical_to_linear.py +++ b/pyomo/core/tests/unit/test_logical_to_linear.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_matrix_constraint.py b/pyomo/core/tests/unit/test_matrix_constraint.py index d9b51de7bf6..993e2a18eb3 100644 --- a/pyomo/core/tests/unit/test_matrix_constraint.py +++ b/pyomo/core/tests/unit/test_matrix_constraint.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_misc.py b/pyomo/core/tests/unit/test_misc.py index 261c94d96bd..440c8807358 100644 --- a/pyomo/core/tests/unit/test_misc.py +++ b/pyomo/core/tests/unit/test_misc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_model.py b/pyomo/core/tests/unit/test_model.py index 95ad17e97f4..9016f9937c0 100644 --- a/pyomo/core/tests/unit/test_model.py +++ b/pyomo/core/tests/unit/test_model.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_mutable.py b/pyomo/core/tests/unit/test_mutable.py index 933ef1fe3dc..d10622d84c0 100644 --- a/pyomo/core/tests/unit/test_mutable.py +++ b/pyomo/core/tests/unit/test_mutable.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_numeric_expr.py b/pyomo/core/tests/unit/test_numeric_expr.py index a4f3295441e..c073ee0f726 100644 --- a/pyomo/core/tests/unit/test_numeric_expr.py +++ b/pyomo/core/tests/unit/test_numeric_expr.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_numeric_expr_api.py b/pyomo/core/tests/unit/test_numeric_expr_api.py index 69cb43f3ad5..4e0af126315 100644 --- a/pyomo/core/tests/unit/test_numeric_expr_api.py +++ b/pyomo/core/tests/unit/test_numeric_expr_api.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_numeric_expr_dispatcher.py b/pyomo/core/tests/unit/test_numeric_expr_dispatcher.py index 3e9e160b1b1..3787f00de47 100644 --- a/pyomo/core/tests/unit/test_numeric_expr_dispatcher.py +++ b/pyomo/core/tests/unit/test_numeric_expr_dispatcher.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_numeric_expr_zerofilter.py b/pyomo/core/tests/unit/test_numeric_expr_zerofilter.py index 3000f644e80..162d664e0f8 100644 --- a/pyomo/core/tests/unit/test_numeric_expr_zerofilter.py +++ b/pyomo/core/tests/unit/test_numeric_expr_zerofilter.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_numpy_expr.py b/pyomo/core/tests/unit/test_numpy_expr.py index 8f58eb29e56..fb81dfe809f 100644 --- a/pyomo/core/tests/unit/test_numpy_expr.py +++ b/pyomo/core/tests/unit/test_numpy_expr.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_numvalue.py b/pyomo/core/tests/unit/test_numvalue.py index 74df1d29522..eceab3a42d9 100644 --- a/pyomo/core/tests/unit/test_numvalue.py +++ b/pyomo/core/tests/unit/test_numvalue.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_obj.py b/pyomo/core/tests/unit/test_obj.py index d73bf7d6dfd..3c8a05f7058 100644 --- a/pyomo/core/tests/unit/test_obj.py +++ b/pyomo/core/tests/unit/test_obj.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_param.py b/pyomo/core/tests/unit/test_param.py index 6ba1163e3c3..9bc0c4b2ad2 100644 --- a/pyomo/core/tests/unit/test_param.py +++ b/pyomo/core/tests/unit/test_param.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_pickle.py b/pyomo/core/tests/unit/test_pickle.py index 861704a2f9c..fccc92bbfa2 100644 --- a/pyomo/core/tests/unit/test_pickle.py +++ b/pyomo/core/tests/unit/test_pickle.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_piecewise.py b/pyomo/core/tests/unit/test_piecewise.py index aeb02b82624..af82ef7c06d 100644 --- a/pyomo/core/tests/unit/test_piecewise.py +++ b/pyomo/core/tests/unit/test_piecewise.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_preprocess.py b/pyomo/core/tests/unit/test_preprocess.py index d4c5ae75bb0..ce7924f3ac5 100644 --- a/pyomo/core/tests/unit/test_preprocess.py +++ b/pyomo/core/tests/unit/test_preprocess.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_range.py b/pyomo/core/tests/unit/test_range.py index 8cd1e7ce46c..4b489f50d44 100644 --- a/pyomo/core/tests/unit/test_range.py +++ b/pyomo/core/tests/unit/test_range.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_reference.py b/pyomo/core/tests/unit/test_reference.py index a7a470b1a3b..9865e04985f 100644 --- a/pyomo/core/tests/unit/test_reference.py +++ b/pyomo/core/tests/unit/test_reference.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_relational_expr.py b/pyomo/core/tests/unit/test_relational_expr.py index f55bfff108c..d361bfcc83c 100644 --- a/pyomo/core/tests/unit/test_relational_expr.py +++ b/pyomo/core/tests/unit/test_relational_expr.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index 72231bb08d7..35779691a31 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_sets.py b/pyomo/core/tests/unit/test_sets.py index 90668a28e72..48869397aae 100644 --- a/pyomo/core/tests/unit/test_sets.py +++ b/pyomo/core/tests/unit/test_sets.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_smap.py b/pyomo/core/tests/unit/test_smap.py index 2b9d2f192c0..69448916a04 100644 --- a/pyomo/core/tests/unit/test_smap.py +++ b/pyomo/core/tests/unit/test_smap.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_sos.py b/pyomo/core/tests/unit/test_sos.py index 92a8a5eabaa..cacfcdf5d42 100644 --- a/pyomo/core/tests/unit/test_sos.py +++ b/pyomo/core/tests/unit/test_sos.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_sos_v2.py b/pyomo/core/tests/unit/test_sos_v2.py index 4f4599056b5..996dd10829d 100644 --- a/pyomo/core/tests/unit/test_sos_v2.py +++ b/pyomo/core/tests/unit/test_sos_v2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_suffix.py b/pyomo/core/tests/unit/test_suffix.py index 1ec1af9d919..d2e861cceb5 100644 --- a/pyomo/core/tests/unit/test_suffix.py +++ b/pyomo/core/tests/unit/test_suffix.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_symbol_map.py b/pyomo/core/tests/unit/test_symbol_map.py index 5f6416e2c8d..773e6d335f1 100644 --- a/pyomo/core/tests/unit/test_symbol_map.py +++ b/pyomo/core/tests/unit/test_symbol_map.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_symbolic.py b/pyomo/core/tests/unit/test_symbolic.py index bbac4599363..91887f27bb7 100644 --- a/pyomo/core/tests/unit/test_symbolic.py +++ b/pyomo/core/tests/unit/test_symbolic.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_taylor_series.py b/pyomo/core/tests/unit/test_taylor_series.py index d4fe5291b2d..4b36451d222 100644 --- a/pyomo/core/tests/unit/test_taylor_series.py +++ b/pyomo/core/tests/unit/test_taylor_series.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_template_expr.py b/pyomo/core/tests/unit/test_template_expr.py index 4b4ea494b0e..4f255e3567a 100644 --- a/pyomo/core/tests/unit/test_template_expr.py +++ b/pyomo/core/tests/unit/test_template_expr.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_units.py b/pyomo/core/tests/unit/test_units.py index 809db733cde..bda62835711 100644 --- a/pyomo/core/tests/unit/test_units.py +++ b/pyomo/core/tests/unit/test_units.py @@ -2,7 +2,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_var.py b/pyomo/core/tests/unit/test_var.py index 33e46a79e9b..6b2e92be832 100644 --- a/pyomo/core/tests/unit/test_var.py +++ b/pyomo/core/tests/unit/test_var.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_var_set_bounds.py b/pyomo/core/tests/unit/test_var_set_bounds.py index eb969c2ca73..bae89556ce3 100644 --- a/pyomo/core/tests/unit/test_var_set_bounds.py +++ b/pyomo/core/tests/unit/test_var_set_bounds.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_visitor.py b/pyomo/core/tests/unit/test_visitor.py index 086c57aa560..c968287ff1b 100644 --- a/pyomo/core/tests/unit/test_visitor.py +++ b/pyomo/core/tests/unit/test_visitor.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/test_xfrm_discrete_vars.py b/pyomo/core/tests/unit/test_xfrm_discrete_vars.py index ae630586480..d0e74c8cae3 100644 --- a/pyomo/core/tests/unit/test_xfrm_discrete_vars.py +++ b/pyomo/core/tests/unit/test_xfrm_discrete_vars.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/uninstantiated_model_linear.py b/pyomo/core/tests/unit/uninstantiated_model_linear.py index 387444b7bc5..417f7763d87 100644 --- a/pyomo/core/tests/unit/uninstantiated_model_linear.py +++ b/pyomo/core/tests/unit/uninstantiated_model_linear.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/tests/unit/uninstantiated_model_quadratic.py b/pyomo/core/tests/unit/uninstantiated_model_quadratic.py index 572c6a43a14..350d96a85bb 100644 --- a/pyomo/core/tests/unit/uninstantiated_model_quadratic.py +++ b/pyomo/core/tests/unit/uninstantiated_model_quadratic.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/core/util.py b/pyomo/core/util.py index 3f8a136e07d..4e076c7505b 100644 --- a/pyomo/core/util.py +++ b/pyomo/core/util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/__init__.py b/pyomo/dae/__init__.py index 8d07b184336..5860a129aa2 100644 --- a/pyomo/dae/__init__.py +++ b/pyomo/dae/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/contset.py b/pyomo/dae/contset.py index ee4c9f79e89..94d20723770 100644 --- a/pyomo/dae/contset.py +++ b/pyomo/dae/contset.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/diffvar.py b/pyomo/dae/diffvar.py index 8d75b9ae148..6bb3a8b06f0 100644 --- a/pyomo/dae/diffvar.py +++ b/pyomo/dae/diffvar.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/flatten.py b/pyomo/dae/flatten.py index 595f90b3dc7..927b92c8f7d 100644 --- a/pyomo/dae/flatten.py +++ b/pyomo/dae/flatten.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/initialization.py b/pyomo/dae/initialization.py index c10ccb023d1..97928026de2 100644 --- a/pyomo/dae/initialization.py +++ b/pyomo/dae/initialization.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/integral.py b/pyomo/dae/integral.py index 302e50a007d..34a34fdcd9c 100644 --- a/pyomo/dae/integral.py +++ b/pyomo/dae/integral.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/misc.py b/pyomo/dae/misc.py index 9b867bcfff4..3e09a055577 100644 --- a/pyomo/dae/misc.py +++ b/pyomo/dae/misc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/plugins/__init__.py b/pyomo/dae/plugins/__init__.py index 96ab91b0ac0..681112dd970 100644 --- a/pyomo/dae/plugins/__init__.py +++ b/pyomo/dae/plugins/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/plugins/colloc.py b/pyomo/dae/plugins/colloc.py index 7f86e8bc2e2..81f1e4dd7ea 100644 --- a/pyomo/dae/plugins/colloc.py +++ b/pyomo/dae/plugins/colloc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/plugins/finitedifference.py b/pyomo/dae/plugins/finitedifference.py index 71bb2ffc9b6..6557a14e562 100644 --- a/pyomo/dae/plugins/finitedifference.py +++ b/pyomo/dae/plugins/finitedifference.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/set_utils.py b/pyomo/dae/set_utils.py index 981954189b3..d7a1d9517d9 100644 --- a/pyomo/dae/set_utils.py +++ b/pyomo/dae/set_utils.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/simulator.py b/pyomo/dae/simulator.py index 149c42ca6b4..f9121dbc0cc 100644 --- a/pyomo/dae/simulator.py +++ b/pyomo/dae/simulator.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/tests/__init__.py b/pyomo/dae/tests/__init__.py index 12bdccd0ef4..4638923595a 100644 --- a/pyomo/dae/tests/__init__.py +++ b/pyomo/dae/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/tests/test_colloc.py b/pyomo/dae/tests/test_colloc.py index 0786903f12e..e7e6b20d660 100644 --- a/pyomo/dae/tests/test_colloc.py +++ b/pyomo/dae/tests/test_colloc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/tests/test_contset.py b/pyomo/dae/tests/test_contset.py index ce13d53dfd5..e5f11b90e27 100644 --- a/pyomo/dae/tests/test_contset.py +++ b/pyomo/dae/tests/test_contset.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/tests/test_diffvar.py b/pyomo/dae/tests/test_diffvar.py index 718781d5916..279a7e02680 100644 --- a/pyomo/dae/tests/test_diffvar.py +++ b/pyomo/dae/tests/test_diffvar.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/tests/test_finite_diff.py b/pyomo/dae/tests/test_finite_diff.py index adca8bf6a15..a1b842feccf 100644 --- a/pyomo/dae/tests/test_finite_diff.py +++ b/pyomo/dae/tests/test_finite_diff.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/tests/test_flatten.py b/pyomo/dae/tests/test_flatten.py index a6ea824c3ef..7037cd79c96 100644 --- a/pyomo/dae/tests/test_flatten.py +++ b/pyomo/dae/tests/test_flatten.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/tests/test_initialization.py b/pyomo/dae/tests/test_initialization.py index 390b6ecc59e..8407ad2b2a4 100644 --- a/pyomo/dae/tests/test_initialization.py +++ b/pyomo/dae/tests/test_initialization.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/tests/test_integral.py b/pyomo/dae/tests/test_integral.py index 77d6d4dd8a9..933bd97d7b4 100644 --- a/pyomo/dae/tests/test_integral.py +++ b/pyomo/dae/tests/test_integral.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/tests/test_misc.py b/pyomo/dae/tests/test_misc.py index 11c4e44b7b0..48c1e48418d 100644 --- a/pyomo/dae/tests/test_misc.py +++ b/pyomo/dae/tests/test_misc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/tests/test_set_utils.py b/pyomo/dae/tests/test_set_utils.py index fa592e05181..8877dadf798 100644 --- a/pyomo/dae/tests/test_set_utils.py +++ b/pyomo/dae/tests/test_set_utils.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/tests/test_simulator.py b/pyomo/dae/tests/test_simulator.py index e79bc7b23b6..76316b5571e 100644 --- a/pyomo/dae/tests/test_simulator.py +++ b/pyomo/dae/tests/test_simulator.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dae/utilities.py b/pyomo/dae/utilities.py index e48c66e003d..ae4018a122e 100644 --- a/pyomo/dae/utilities.py +++ b/pyomo/dae/utilities.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dataportal/DataPortal.py b/pyomo/dataportal/DataPortal.py index 8eb577af013..24a9c847d48 100644 --- a/pyomo/dataportal/DataPortal.py +++ b/pyomo/dataportal/DataPortal.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dataportal/TableData.py b/pyomo/dataportal/TableData.py index 1d428967449..b7fb98d596a 100644 --- a/pyomo/dataportal/TableData.py +++ b/pyomo/dataportal/TableData.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dataportal/__init__.py b/pyomo/dataportal/__init__.py index ca82614ef2a..ece0ac039f6 100644 --- a/pyomo/dataportal/__init__.py +++ b/pyomo/dataportal/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dataportal/factory.py b/pyomo/dataportal/factory.py index f1c18dc05c9..e6424be25c4 100644 --- a/pyomo/dataportal/factory.py +++ b/pyomo/dataportal/factory.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dataportal/parse_datacmds.py b/pyomo/dataportal/parse_datacmds.py index be363fdb64b..d9f44405577 100644 --- a/pyomo/dataportal/parse_datacmds.py +++ b/pyomo/dataportal/parse_datacmds.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dataportal/plugins/__init__.py b/pyomo/dataportal/plugins/__init__.py index c3387af9d1e..3a356ee9da8 100644 --- a/pyomo/dataportal/plugins/__init__.py +++ b/pyomo/dataportal/plugins/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dataportal/plugins/csv_table.py b/pyomo/dataportal/plugins/csv_table.py index 6563a89df10..a52c8227695 100644 --- a/pyomo/dataportal/plugins/csv_table.py +++ b/pyomo/dataportal/plugins/csv_table.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dataportal/plugins/datacommands.py b/pyomo/dataportal/plugins/datacommands.py index 068a551d8d2..2da0d44f048 100644 --- a/pyomo/dataportal/plugins/datacommands.py +++ b/pyomo/dataportal/plugins/datacommands.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dataportal/plugins/db_table.py b/pyomo/dataportal/plugins/db_table.py index 682b87ab13e..a39705a6058 100644 --- a/pyomo/dataportal/plugins/db_table.py +++ b/pyomo/dataportal/plugins/db_table.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dataportal/plugins/json_dict.py b/pyomo/dataportal/plugins/json_dict.py index e42c040ad0b..8b41e9a1c7b 100644 --- a/pyomo/dataportal/plugins/json_dict.py +++ b/pyomo/dataportal/plugins/json_dict.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dataportal/plugins/sheet.py b/pyomo/dataportal/plugins/sheet.py index 8672b9917da..773cce81116 100644 --- a/pyomo/dataportal/plugins/sheet.py +++ b/pyomo/dataportal/plugins/sheet.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dataportal/plugins/text.py b/pyomo/dataportal/plugins/text.py index a9b169e27bd..9a86fd4481b 100644 --- a/pyomo/dataportal/plugins/text.py +++ b/pyomo/dataportal/plugins/text.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dataportal/plugins/xml_table.py b/pyomo/dataportal/plugins/xml_table.py index 79245c6d24a..7e10b96312e 100644 --- a/pyomo/dataportal/plugins/xml_table.py +++ b/pyomo/dataportal/plugins/xml_table.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dataportal/process_data.py b/pyomo/dataportal/process_data.py index 5eb15269e0c..f6f20d69f67 100644 --- a/pyomo/dataportal/process_data.py +++ b/pyomo/dataportal/process_data.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dataportal/tests/__init__.py b/pyomo/dataportal/tests/__init__.py index 65e82b81c0c..85ece8d8cd5 100644 --- a/pyomo/dataportal/tests/__init__.py +++ b/pyomo/dataportal/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dataportal/tests/test_dat_parser.py b/pyomo/dataportal/tests/test_dat_parser.py index 0663279875d..43bf216525c 100644 --- a/pyomo/dataportal/tests/test_dat_parser.py +++ b/pyomo/dataportal/tests/test_dat_parser.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/dataportal/tests/test_dataportal.py b/pyomo/dataportal/tests/test_dataportal.py index 3171a118118..8496a8fa3f8 100644 --- a/pyomo/dataportal/tests/test_dataportal.py +++ b/pyomo/dataportal/tests/test_dataportal.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/duality/__init__.py b/pyomo/duality/__init__.py index 7f1c869670d..a08ca813ff8 100644 --- a/pyomo/duality/__init__.py +++ b/pyomo/duality/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/duality/collect.py b/pyomo/duality/collect.py index a8b62cb8dfe..350ca058f82 100644 --- a/pyomo/duality/collect.py +++ b/pyomo/duality/collect.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/duality/lagrangian_dual.py b/pyomo/duality/lagrangian_dual.py index 1b27a3f93d4..96bc3f4a95e 100644 --- a/pyomo/duality/lagrangian_dual.py +++ b/pyomo/duality/lagrangian_dual.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/duality/plugins.py b/pyomo/duality/plugins.py index c8c84153975..0e89857ded1 100644 --- a/pyomo/duality/plugins.py +++ b/pyomo/duality/plugins.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/duality/tests/__init__.py b/pyomo/duality/tests/__init__.py index 0dc08cc5aea..761a6e6c44c 100644 --- a/pyomo/duality/tests/__init__.py +++ b/pyomo/duality/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/duality/tests/test_linear_dual.py b/pyomo/duality/tests/test_linear_dual.py index ba3554bdc50..da8ba7a370c 100644 --- a/pyomo/duality/tests/test_linear_dual.py +++ b/pyomo/duality/tests/test_linear_dual.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/environ/__init__.py b/pyomo/environ/__init__.py index c3fb3ec4a85..ec0cc5878e4 100644 --- a/pyomo/environ/__init__.py +++ b/pyomo/environ/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/environ/tests/__init__.py b/pyomo/environ/tests/__init__.py index b1d721839c7..61e159c169b 100644 --- a/pyomo/environ/tests/__init__.py +++ b/pyomo/environ/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/environ/tests/standalone_minimal_pyomo_driver.py b/pyomo/environ/tests/standalone_minimal_pyomo_driver.py index 88f8e9f8651..80fb5d15121 100644 --- a/pyomo/environ/tests/standalone_minimal_pyomo_driver.py +++ b/pyomo/environ/tests/standalone_minimal_pyomo_driver.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/environ/tests/test_environ.py b/pyomo/environ/tests/test_environ.py index b223ba0e916..9c89fd135d5 100644 --- a/pyomo/environ/tests/test_environ.py +++ b/pyomo/environ/tests/test_environ.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/environ/tests/test_package_layout.py b/pyomo/environ/tests/test_package_layout.py index 0bc8c55113a..4e1574ab158 100644 --- a/pyomo/environ/tests/test_package_layout.py +++ b/pyomo/environ/tests/test_package_layout.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/__init__.py b/pyomo/gdp/__init__.py index 6fc2d4b7351..a18bc03084a 100644 --- a/pyomo/gdp/__init__.py +++ b/pyomo/gdp/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/basic_step.py b/pyomo/gdp/basic_step.py index 69313ac2b1b..56a19e2a0f2 100644 --- a/pyomo/gdp/basic_step.py +++ b/pyomo/gdp/basic_step.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/disjunct.py b/pyomo/gdp/disjunct.py index eca6d93d732..b575ab65c99 100644 --- a/pyomo/gdp/disjunct.py +++ b/pyomo/gdp/disjunct.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/plugins/__init__.py b/pyomo/gdp/plugins/__init__.py index 2edb99bbe1b..875e47e6cc1 100644 --- a/pyomo/gdp/plugins/__init__.py +++ b/pyomo/gdp/plugins/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/plugins/between_steps.py b/pyomo/gdp/plugins/between_steps.py index fad783d595d..8f57164334e 100644 --- a/pyomo/gdp/plugins/between_steps.py +++ b/pyomo/gdp/plugins/between_steps.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/plugins/bigm.py b/pyomo/gdp/plugins/bigm.py index e554d5593ab..bdd353a6136 100644 --- a/pyomo/gdp/plugins/bigm.py +++ b/pyomo/gdp/plugins/bigm.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/plugins/bigm_mixin.py b/pyomo/gdp/plugins/bigm_mixin.py index a4df641c8c6..ad6e6dcad86 100644 --- a/pyomo/gdp/plugins/bigm_mixin.py +++ b/pyomo/gdp/plugins/bigm_mixin.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/plugins/bilinear.py b/pyomo/gdp/plugins/bilinear.py index feacaaddefc..67390801348 100644 --- a/pyomo/gdp/plugins/bilinear.py +++ b/pyomo/gdp/plugins/bilinear.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/plugins/binary_multiplication.py b/pyomo/gdp/plugins/binary_multiplication.py index 5afb661aaa8..ef4239e09dc 100644 --- a/pyomo/gdp/plugins/binary_multiplication.py +++ b/pyomo/gdp/plugins/binary_multiplication.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/plugins/bound_pretransformation.py b/pyomo/gdp/plugins/bound_pretransformation.py index 56a39115f34..7c90c24d869 100644 --- a/pyomo/gdp/plugins/bound_pretransformation.py +++ b/pyomo/gdp/plugins/bound_pretransformation.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/plugins/chull.py b/pyomo/gdp/plugins/chull.py index d226c57aae7..c11d8ea0729 100644 --- a/pyomo/gdp/plugins/chull.py +++ b/pyomo/gdp/plugins/chull.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/plugins/cuttingplane.py b/pyomo/gdp/plugins/cuttingplane.py index 7a6a927a316..6c77a582987 100644 --- a/pyomo/gdp/plugins/cuttingplane.py +++ b/pyomo/gdp/plugins/cuttingplane.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/plugins/fix_disjuncts.py b/pyomo/gdp/plugins/fix_disjuncts.py index d0f59ce87ce..44a9d91d513 100644 --- a/pyomo/gdp/plugins/fix_disjuncts.py +++ b/pyomo/gdp/plugins/fix_disjuncts.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/plugins/gdp_to_mip_transformation.py b/pyomo/gdp/plugins/gdp_to_mip_transformation.py index 0aa5ec163b6..96d97206c97 100644 --- a/pyomo/gdp/plugins/gdp_to_mip_transformation.py +++ b/pyomo/gdp/plugins/gdp_to_mip_transformation.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/plugins/gdp_var_mover.py b/pyomo/gdp/plugins/gdp_var_mover.py index df659670bf4..5402b576368 100644 --- a/pyomo/gdp/plugins/gdp_var_mover.py +++ b/pyomo/gdp/plugins/gdp_var_mover.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index a600ef76bc7..630560b57f0 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index 85fb1e4aa6b..4220caa12c1 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/plugins/partition_disjuncts.py b/pyomo/gdp/plugins/partition_disjuncts.py index fbe25ed3ae1..1a76900047c 100644 --- a/pyomo/gdp/plugins/partition_disjuncts.py +++ b/pyomo/gdp/plugins/partition_disjuncts.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/plugins/transform_current_disjunctive_state.py b/pyomo/gdp/plugins/transform_current_disjunctive_state.py index 338f42c68da..3e20224ec3d 100644 --- a/pyomo/gdp/plugins/transform_current_disjunctive_state.py +++ b/pyomo/gdp/plugins/transform_current_disjunctive_state.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/tests/__init__.py b/pyomo/gdp/tests/__init__.py index c5e495e5aa3..a2a2c61779a 100644 --- a/pyomo/gdp/tests/__init__.py +++ b/pyomo/gdp/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/tests/common_tests.py b/pyomo/gdp/tests/common_tests.py index 4a772a7ae56..e6d38ef1502 100644 --- a/pyomo/gdp/tests/common_tests.py +++ b/pyomo/gdp/tests/common_tests.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/tests/models.py b/pyomo/gdp/tests/models.py index f03a847162b..273bdec7261 100644 --- a/pyomo/gdp/tests/models.py +++ b/pyomo/gdp/tests/models.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/tests/test_basic_step.py b/pyomo/gdp/tests/test_basic_step.py index 631611a2651..7e21c46da92 100644 --- a/pyomo/gdp/tests/test_basic_step.py +++ b/pyomo/gdp/tests/test_basic_step.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/tests/test_bigm.py b/pyomo/gdp/tests/test_bigm.py index 13ffe30f9f0..d518219eabd 100644 --- a/pyomo/gdp/tests/test_bigm.py +++ b/pyomo/gdp/tests/test_bigm.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/tests/test_binary_multiplication.py b/pyomo/gdp/tests/test_binary_multiplication.py index 6b3ba87fa21..5f4c4f90ab6 100644 --- a/pyomo/gdp/tests/test_binary_multiplication.py +++ b/pyomo/gdp/tests/test_binary_multiplication.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/tests/test_bound_pretransformation.py b/pyomo/gdp/tests/test_bound_pretransformation.py index 30ce76b7e31..68db64ce93b 100644 --- a/pyomo/gdp/tests/test_bound_pretransformation.py +++ b/pyomo/gdp/tests/test_bound_pretransformation.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/tests/test_cuttingplane.py b/pyomo/gdp/tests/test_cuttingplane.py index 827eac9aa6a..153e236942d 100644 --- a/pyomo/gdp/tests/test_cuttingplane.py +++ b/pyomo/gdp/tests/test_cuttingplane.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/tests/test_disjunct.py b/pyomo/gdp/tests/test_disjunct.py index 676b49a80cd..d969b245ee7 100644 --- a/pyomo/gdp/tests/test_disjunct.py +++ b/pyomo/gdp/tests/test_disjunct.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/tests/test_fix_disjuncts.py b/pyomo/gdp/tests/test_fix_disjuncts.py index 1b741f7a840..6f01e096e9d 100644 --- a/pyomo/gdp/tests/test_fix_disjuncts.py +++ b/pyomo/gdp/tests/test_fix_disjuncts.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/tests/test_gdp.py b/pyomo/gdp/tests/test_gdp.py index 5c810dcce18..b22a60bc04a 100644 --- a/pyomo/gdp/tests/test_gdp.py +++ b/pyomo/gdp/tests/test_gdp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/tests/test_gdp_reclassification_error.py b/pyomo/gdp/tests/test_gdp_reclassification_error.py index a65ccac2d8f..556dc44eead 100644 --- a/pyomo/gdp/tests/test_gdp_reclassification_error.py +++ b/pyomo/gdp/tests/test_gdp_reclassification_error.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index 09f65765fe6..cf0ce3234af 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/tests/test_mbigm.py b/pyomo/gdp/tests/test_mbigm.py index f067e1da5af..6310cb319e3 100644 --- a/pyomo/gdp/tests/test_mbigm.py +++ b/pyomo/gdp/tests/test_mbigm.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/tests/test_partition_disjuncts.py b/pyomo/gdp/tests/test_partition_disjuncts.py index b050bc5e653..dc5ae9f70ce 100644 --- a/pyomo/gdp/tests/test_partition_disjuncts.py +++ b/pyomo/gdp/tests/test_partition_disjuncts.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/tests/test_reclassify.py b/pyomo/gdp/tests/test_reclassify.py index dcf3470a211..223c28c5c7a 100644 --- a/pyomo/gdp/tests/test_reclassify.py +++ b/pyomo/gdp/tests/test_reclassify.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/tests/test_transform_current_disjunctive_state.py b/pyomo/gdp/tests/test_transform_current_disjunctive_state.py index d257c3db8fb..54d80c910e5 100644 --- a/pyomo/gdp/tests/test_transform_current_disjunctive_state.py +++ b/pyomo/gdp/tests/test_transform_current_disjunctive_state.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/tests/test_util.py b/pyomo/gdp/tests/test_util.py index 90c63717b81..fd555fc2f59 100644 --- a/pyomo/gdp/tests/test_util.py +++ b/pyomo/gdp/tests/test_util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/transformed_disjunct.py b/pyomo/gdp/transformed_disjunct.py index 400f77a31f6..6cf60abf414 100644 --- a/pyomo/gdp/transformed_disjunct.py +++ b/pyomo/gdp/transformed_disjunct.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/gdp/util.py b/pyomo/gdp/util.py index b460a3d691c..343b2fd4f42 100644 --- a/pyomo/gdp/util.py +++ b/pyomo/gdp/util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/kernel/__init__.py b/pyomo/kernel/__init__.py index 6ecea6343cd..289fe83f0e4 100644 --- a/pyomo/kernel/__init__.py +++ b/pyomo/kernel/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/kernel/util.py b/pyomo/kernel/util.py index 5fba6a2c2d9..bdfd0939537 100644 --- a/pyomo/kernel/util.py +++ b/pyomo/kernel/util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/mpec/__init__.py b/pyomo/mpec/__init__.py index 3989fe07b8e..a98ab94dc87 100644 --- a/pyomo/mpec/__init__.py +++ b/pyomo/mpec/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/mpec/complementarity.py b/pyomo/mpec/complementarity.py index df991ce9686..38da643cf38 100644 --- a/pyomo/mpec/complementarity.py +++ b/pyomo/mpec/complementarity.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/mpec/plugins/__init__.py b/pyomo/mpec/plugins/__init__.py index 3317e1ce829..1ff8c316e9b 100644 --- a/pyomo/mpec/plugins/__init__.py +++ b/pyomo/mpec/plugins/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/mpec/plugins/mpec1.py b/pyomo/mpec/plugins/mpec1.py index ad6905158c7..5935569d370 100644 --- a/pyomo/mpec/plugins/mpec1.py +++ b/pyomo/mpec/plugins/mpec1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/mpec/plugins/mpec2.py b/pyomo/mpec/plugins/mpec2.py index d019424ea4b..89d6c0814b2 100644 --- a/pyomo/mpec/plugins/mpec2.py +++ b/pyomo/mpec/plugins/mpec2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/mpec/plugins/mpec3.py b/pyomo/mpec/plugins/mpec3.py index d681c305a2d..1b7eb58b021 100644 --- a/pyomo/mpec/plugins/mpec3.py +++ b/pyomo/mpec/plugins/mpec3.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/mpec/plugins/mpec4.py b/pyomo/mpec/plugins/mpec4.py index 5b32886711a..fa3e37b16fe 100644 --- a/pyomo/mpec/plugins/mpec4.py +++ b/pyomo/mpec/plugins/mpec4.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/mpec/plugins/pathampl.py b/pyomo/mpec/plugins/pathampl.py index 7875251c04b..23b1b393ef3 100644 --- a/pyomo/mpec/plugins/pathampl.py +++ b/pyomo/mpec/plugins/pathampl.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/mpec/plugins/solver1.py b/pyomo/mpec/plugins/solver1.py index 0ac1af85522..02659844f1c 100644 --- a/pyomo/mpec/plugins/solver1.py +++ b/pyomo/mpec/plugins/solver1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/mpec/plugins/solver2.py b/pyomo/mpec/plugins/solver2.py index 491c8122d2e..5f5b6922e6f 100644 --- a/pyomo/mpec/plugins/solver2.py +++ b/pyomo/mpec/plugins/solver2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/mpec/tests/__init__.py b/pyomo/mpec/tests/__init__.py index c5e495e5aa3..a2a2c61779a 100644 --- a/pyomo/mpec/tests/__init__.py +++ b/pyomo/mpec/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/mpec/tests/test_complementarity.py b/pyomo/mpec/tests/test_complementarity.py index 1eb0385c3e5..545104364cf 100644 --- a/pyomo/mpec/tests/test_complementarity.py +++ b/pyomo/mpec/tests/test_complementarity.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/mpec/tests/test_minlp.py b/pyomo/mpec/tests/test_minlp.py index 367a57b817e..965906f4235 100644 --- a/pyomo/mpec/tests/test_minlp.py +++ b/pyomo/mpec/tests/test_minlp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/mpec/tests/test_nlp.py b/pyomo/mpec/tests/test_nlp.py index be5234136a1..a87d4ad2b09 100644 --- a/pyomo/mpec/tests/test_nlp.py +++ b/pyomo/mpec/tests/test_nlp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/mpec/tests/test_path.py b/pyomo/mpec/tests/test_path.py index 5dd7178acf5..0501d19d2ac 100644 --- a/pyomo/mpec/tests/test_path.py +++ b/pyomo/mpec/tests/test_path.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/neos/__init__.py b/pyomo/neos/__init__.py index 73ac0c51216..7d18535e753 100644 --- a/pyomo/neos/__init__.py +++ b/pyomo/neos/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/neos/kestrel.py b/pyomo/neos/kestrel.py index 44734294eb4..8959a81bd0f 100644 --- a/pyomo/neos/kestrel.py +++ b/pyomo/neos/kestrel.py @@ -2,7 +2,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/neos/plugins/NEOS.py b/pyomo/neos/plugins/NEOS.py index 85fad42d4b2..84bc51645c0 100644 --- a/pyomo/neos/plugins/NEOS.py +++ b/pyomo/neos/plugins/NEOS.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/neos/plugins/__init__.py b/pyomo/neos/plugins/__init__.py index 323f96e9bdc..75105e87088 100644 --- a/pyomo/neos/plugins/__init__.py +++ b/pyomo/neos/plugins/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/neos/plugins/kestrel_plugin.py b/pyomo/neos/plugins/kestrel_plugin.py index 49fb3809622..fecb98e0084 100644 --- a/pyomo/neos/plugins/kestrel_plugin.py +++ b/pyomo/neos/plugins/kestrel_plugin.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/neos/tests/__init__.py b/pyomo/neos/tests/__init__.py index 1cf642c0eac..83603e3d8ba 100644 --- a/pyomo/neos/tests/__init__.py +++ b/pyomo/neos/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/neos/tests/model_min_lp.py b/pyomo/neos/tests/model_min_lp.py index 56e1b124cd4..eacf0451c94 100644 --- a/pyomo/neos/tests/model_min_lp.py +++ b/pyomo/neos/tests/model_min_lp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/neos/tests/test_neos.py b/pyomo/neos/tests/test_neos.py index c43869e65cc..a4c4e9e6367 100644 --- a/pyomo/neos/tests/test_neos.py +++ b/pyomo/neos/tests/test_neos.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/network/__init__.py b/pyomo/network/__init__.py index 097471102be..6ccfb64f79c 100644 --- a/pyomo/network/__init__.py +++ b/pyomo/network/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/network/arc.py b/pyomo/network/arc.py index ff1874b0274..24efd0b25bf 100644 --- a/pyomo/network/arc.py +++ b/pyomo/network/arc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/network/decomposition.py b/pyomo/network/decomposition.py index ae306766ae0..da7e8950395 100644 --- a/pyomo/network/decomposition.py +++ b/pyomo/network/decomposition.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/network/foqus_graph.py b/pyomo/network/foqus_graph.py index e6fc34aaf62..e4cf3b92014 100644 --- a/pyomo/network/foqus_graph.py +++ b/pyomo/network/foqus_graph.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/network/plugins/__init__.py b/pyomo/network/plugins/__init__.py index 5e9677d2bc4..ab3cde23daa 100644 --- a/pyomo/network/plugins/__init__.py +++ b/pyomo/network/plugins/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/network/plugins/expand_arcs.py b/pyomo/network/plugins/expand_arcs.py index 4f6185d3173..b1f915214eb 100644 --- a/pyomo/network/plugins/expand_arcs.py +++ b/pyomo/network/plugins/expand_arcs.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/network/port.py b/pyomo/network/port.py index 4afb0e23ed0..1472a07224e 100644 --- a/pyomo/network/port.py +++ b/pyomo/network/port.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/network/tests/__init__.py b/pyomo/network/tests/__init__.py index 1eb6d95e148..173fdc4e727 100644 --- a/pyomo/network/tests/__init__.py +++ b/pyomo/network/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/network/tests/test_arc.py b/pyomo/network/tests/test_arc.py index cd340cace7a..f77dff07f2f 100644 --- a/pyomo/network/tests/test_arc.py +++ b/pyomo/network/tests/test_arc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/network/tests/test_decomposition.py b/pyomo/network/tests/test_decomposition.py index 4e4d0231d00..2db310217d0 100644 --- a/pyomo/network/tests/test_decomposition.py +++ b/pyomo/network/tests/test_decomposition.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/network/tests/test_port.py b/pyomo/network/tests/test_port.py index bc9a6fc527f..a417a832015 100644 --- a/pyomo/network/tests/test_port.py +++ b/pyomo/network/tests/test_port.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/network/util.py b/pyomo/network/util.py index be0fa2c84d1..4865218aca8 100644 --- a/pyomo/network/util.py +++ b/pyomo/network/util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/__init__.py b/pyomo/opt/__init__.py index 8c12d3fa201..c78dd0384d2 100644 --- a/pyomo/opt/__init__.py +++ b/pyomo/opt/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/base/__init__.py b/pyomo/opt/base/__init__.py index 9d29efc859d..8d11114dd09 100644 --- a/pyomo/opt/base/__init__.py +++ b/pyomo/opt/base/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/base/convert.py b/pyomo/opt/base/convert.py index 8d8bd78e2ee..a17d1914801 100644 --- a/pyomo/opt/base/convert.py +++ b/pyomo/opt/base/convert.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/base/error.py b/pyomo/opt/base/error.py index aa97469f6d0..b03fafd7037 100644 --- a/pyomo/opt/base/error.py +++ b/pyomo/opt/base/error.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/base/formats.py b/pyomo/opt/base/formats.py index 2acd77b80e4..72c4f5306a7 100644 --- a/pyomo/opt/base/formats.py +++ b/pyomo/opt/base/formats.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/base/opt_config.py b/pyomo/opt/base/opt_config.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/opt/base/opt_config.py +++ b/pyomo/opt/base/opt_config.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/base/problem.py b/pyomo/opt/base/problem.py index 6be1d4d6db6..02748e08b70 100644 --- a/pyomo/opt/base/problem.py +++ b/pyomo/opt/base/problem.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/base/results.py b/pyomo/opt/base/results.py index 68999fae6e4..8b00ec3e14e 100644 --- a/pyomo/opt/base/results.py +++ b/pyomo/opt/base/results.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/base/solvers.py b/pyomo/opt/base/solvers.py index b11e6393b02..cc49349142e 100644 --- a/pyomo/opt/base/solvers.py +++ b/pyomo/opt/base/solvers.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/parallel/__init__.py b/pyomo/opt/parallel/__init__.py index 9820f39afd4..dbfdf2302ca 100644 --- a/pyomo/opt/parallel/__init__.py +++ b/pyomo/opt/parallel/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/parallel/async_solver.py b/pyomo/opt/parallel/async_solver.py index e9806b7125a..d74206e4790 100644 --- a/pyomo/opt/parallel/async_solver.py +++ b/pyomo/opt/parallel/async_solver.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/parallel/local.py b/pyomo/opt/parallel/local.py index a7a80a7d33c..211adf92e5c 100644 --- a/pyomo/opt/parallel/local.py +++ b/pyomo/opt/parallel/local.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/parallel/manager.py b/pyomo/opt/parallel/manager.py index a97f6ae1d27..faa34d5190f 100644 --- a/pyomo/opt/parallel/manager.py +++ b/pyomo/opt/parallel/manager.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/plugins/__init__.py b/pyomo/opt/plugins/__init__.py index 797147f5f69..5ea2490b534 100644 --- a/pyomo/opt/plugins/__init__.py +++ b/pyomo/opt/plugins/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/plugins/driver.py b/pyomo/opt/plugins/driver.py index 23757053beb..c7c7103835c 100644 --- a/pyomo/opt/plugins/driver.py +++ b/pyomo/opt/plugins/driver.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/plugins/res.py b/pyomo/opt/plugins/res.py index 25d25d5feb0..31971ee7d25 100644 --- a/pyomo/opt/plugins/res.py +++ b/pyomo/opt/plugins/res.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/plugins/sol.py b/pyomo/opt/plugins/sol.py index 297b1c87d06..10da469f186 100644 --- a/pyomo/opt/plugins/sol.py +++ b/pyomo/opt/plugins/sol.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/problem/__init__.py b/pyomo/opt/problem/__init__.py index 1b1a5328beb..8199553247d 100644 --- a/pyomo/opt/problem/__init__.py +++ b/pyomo/opt/problem/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/problem/ampl.py b/pyomo/opt/problem/ampl.py index 625c342f005..d128ec94930 100644 --- a/pyomo/opt/problem/ampl.py +++ b/pyomo/opt/problem/ampl.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/results/__init__.py b/pyomo/opt/results/__init__.py index 8b2933adfe0..64a1b42ac86 100644 --- a/pyomo/opt/results/__init__.py +++ b/pyomo/opt/results/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/results/container.py b/pyomo/opt/results/container.py index 98a68048b45..1cdf6fe77ce 100644 --- a/pyomo/opt/results/container.py +++ b/pyomo/opt/results/container.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/results/problem.py b/pyomo/opt/results/problem.py index 71fd748dd81..d39ba204aaf 100644 --- a/pyomo/opt/results/problem.py +++ b/pyomo/opt/results/problem.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/results/results_.py b/pyomo/opt/results/results_.py index 2852bb72e8a..a9b802e2adb 100644 --- a/pyomo/opt/results/results_.py +++ b/pyomo/opt/results/results_.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/results/solution.py b/pyomo/opt/results/solution.py index 0cb8e92e730..2862087cf43 100644 --- a/pyomo/opt/results/solution.py +++ b/pyomo/opt/results/solution.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/results/solver.py b/pyomo/opt/results/solver.py index 5f9ceb3b68e..e2d0cfff605 100644 --- a/pyomo/opt/results/solver.py +++ b/pyomo/opt/results/solver.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/solver/__init__.py b/pyomo/opt/solver/__init__.py index 961d7e0edbd..6da73d408fa 100644 --- a/pyomo/opt/solver/__init__.py +++ b/pyomo/opt/solver/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/solver/ilmcmd.py b/pyomo/opt/solver/ilmcmd.py index d08feab7d9a..efd1096c20f 100644 --- a/pyomo/opt/solver/ilmcmd.py +++ b/pyomo/opt/solver/ilmcmd.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/solver/shellcmd.py b/pyomo/opt/solver/shellcmd.py index 20892000066..58274b572d3 100644 --- a/pyomo/opt/solver/shellcmd.py +++ b/pyomo/opt/solver/shellcmd.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/testing/__init__.py b/pyomo/opt/testing/__init__.py index 5d0d8ebd8d7..37ed419fbe3 100644 --- a/pyomo/opt/testing/__init__.py +++ b/pyomo/opt/testing/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/testing/pyunit.py b/pyomo/opt/testing/pyunit.py index 527b72cec7a..9143714f4e3 100644 --- a/pyomo/opt/testing/pyunit.py +++ b/pyomo/opt/testing/pyunit.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/tests/__init__.py b/pyomo/opt/tests/__init__.py index 65dc8785c9b..b333eb78878 100644 --- a/pyomo/opt/tests/__init__.py +++ b/pyomo/opt/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/tests/base/__init__.py b/pyomo/opt/tests/base/__init__.py index dbebb21e4f1..cde23945b56 100644 --- a/pyomo/opt/tests/base/__init__.py +++ b/pyomo/opt/tests/base/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/tests/base/test_ampl.py b/pyomo/opt/tests/base/test_ampl.py index 1baffcbb0af..d37befcac57 100644 --- a/pyomo/opt/tests/base/test_ampl.py +++ b/pyomo/opt/tests/base/test_ampl.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/tests/base/test_convert.py b/pyomo/opt/tests/base/test_convert.py index f8f0bef0fe4..30a8fb0d1fc 100644 --- a/pyomo/opt/tests/base/test_convert.py +++ b/pyomo/opt/tests/base/test_convert.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/tests/base/test_factory.py b/pyomo/opt/tests/base/test_factory.py index ab2a64a6330..441ba245c5e 100644 --- a/pyomo/opt/tests/base/test_factory.py +++ b/pyomo/opt/tests/base/test_factory.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/tests/base/test_sol.py b/pyomo/opt/tests/base/test_sol.py index ff233b42a43..fada795b925 100644 --- a/pyomo/opt/tests/base/test_sol.py +++ b/pyomo/opt/tests/base/test_sol.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/tests/base/test_soln.py b/pyomo/opt/tests/base/test_soln.py index 0511b3ceb9c..d39baeab15f 100644 --- a/pyomo/opt/tests/base/test_soln.py +++ b/pyomo/opt/tests/base/test_soln.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/tests/base/test_solver.py b/pyomo/opt/tests/base/test_solver.py index 73d6067efe4..8ffc647804d 100644 --- a/pyomo/opt/tests/base/test_solver.py +++ b/pyomo/opt/tests/base/test_solver.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/tests/solver/__init__.py b/pyomo/opt/tests/solver/__init__.py index 4c145a1b507..d27a8ab41d6 100644 --- a/pyomo/opt/tests/solver/__init__.py +++ b/pyomo/opt/tests/solver/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/opt/tests/solver/test_shellcmd.py b/pyomo/opt/tests/solver/test_shellcmd.py index f71fcf07c6d..b6cc264b8f7 100644 --- a/pyomo/opt/tests/solver/test_shellcmd.py +++ b/pyomo/opt/tests/solver/test_shellcmd.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/pysp/__init__.py b/pyomo/pysp/__init__.py index 3fb4abbbd42..bb8a401e45e 100644 --- a/pyomo/pysp/__init__.py +++ b/pyomo/pysp/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/__init__.py b/pyomo/repn/__init__.py index 1b27071c404..842f4750127 100644 --- a/pyomo/repn/__init__.py +++ b/pyomo/repn/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/beta/__init__.py b/pyomo/repn/beta/__init__.py index fd7fac1125a..a75a75ec760 100644 --- a/pyomo/repn/beta/__init__.py +++ b/pyomo/repn/beta/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/beta/matrix.py b/pyomo/repn/beta/matrix.py index ff2d6857bd6..741e54d380c 100644 --- a/pyomo/repn/beta/matrix.py +++ b/pyomo/repn/beta/matrix.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/linear.py b/pyomo/repn/linear.py index 59bc0b58d99..6ab4abfdaf5 100644 --- a/pyomo/repn/linear.py +++ b/pyomo/repn/linear.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/plugins/__init__.py b/pyomo/repn/plugins/__init__.py index 56b221d3129..d3804c55106 100644 --- a/pyomo/repn/plugins/__init__.py +++ b/pyomo/repn/plugins/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/plugins/ampl/__init__.py b/pyomo/repn/plugins/ampl/__init__.py index 493bc06d9c4..d935056c90b 100644 --- a/pyomo/repn/plugins/ampl/__init__.py +++ b/pyomo/repn/plugins/ampl/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/plugins/ampl/ampl_.py b/pyomo/repn/plugins/ampl/ampl_.py index d1a11bf2f38..4cc55cabd51 100644 --- a/pyomo/repn/plugins/ampl/ampl_.py +++ b/pyomo/repn/plugins/ampl/ampl_.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/plugins/baron_writer.py b/pyomo/repn/plugins/baron_writer.py index 0d684fcd1d2..de19b5aad73 100644 --- a/pyomo/repn/plugins/baron_writer.py +++ b/pyomo/repn/plugins/baron_writer.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/plugins/cpxlp.py b/pyomo/repn/plugins/cpxlp.py index cdcb4b42c3b..46e6b6d5265 100644 --- a/pyomo/repn/plugins/cpxlp.py +++ b/pyomo/repn/plugins/cpxlp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/plugins/gams_writer.py b/pyomo/repn/plugins/gams_writer.py index 719839fc8dd..5f94f176762 100644 --- a/pyomo/repn/plugins/gams_writer.py +++ b/pyomo/repn/plugins/gams_writer.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/plugins/lp_writer.py b/pyomo/repn/plugins/lp_writer.py index be718ee696e..627a54e3f68 100644 --- a/pyomo/repn/plugins/lp_writer.py +++ b/pyomo/repn/plugins/lp_writer.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/plugins/mps.py b/pyomo/repn/plugins/mps.py index f40c7666278..ba26783eea1 100644 --- a/pyomo/repn/plugins/mps.py +++ b/pyomo/repn/plugins/mps.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index cda4ee011d3..5c0b505a2be 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/plugins/standard_form.py b/pyomo/repn/plugins/standard_form.py index c72661daaf0..239cd845930 100644 --- a/pyomo/repn/plugins/standard_form.py +++ b/pyomo/repn/plugins/standard_form.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/quadratic.py b/pyomo/repn/quadratic.py index 2d11261de5d..c538d1efc7f 100644 --- a/pyomo/repn/quadratic.py +++ b/pyomo/repn/quadratic.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/standard_aux.py b/pyomo/repn/standard_aux.py index 8704253eca3..628914780a6 100644 --- a/pyomo/repn/standard_aux.py +++ b/pyomo/repn/standard_aux.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/standard_repn.py b/pyomo/repn/standard_repn.py index 53618d3eb50..c1cca42afe4 100644 --- a/pyomo/repn/standard_repn.py +++ b/pyomo/repn/standard_repn.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/__init__.py b/pyomo/repn/tests/__init__.py index 5e413c0132c..a9e1a5bea47 100644 --- a/pyomo/repn/tests/__init__.py +++ b/pyomo/repn/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/__init__.py b/pyomo/repn/tests/ampl/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/repn/tests/ampl/__init__.py +++ b/pyomo/repn/tests/ampl/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/helper.py b/pyomo/repn/tests/ampl/helper.py index eb09afc37cc..2bf2198d20f 100644 --- a/pyomo/repn/tests/ampl/helper.py +++ b/pyomo/repn/tests/ampl/helper.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/nl_diff.py b/pyomo/repn/tests/ampl/nl_diff.py index ecac3967dfe..9fe352ee503 100644 --- a/pyomo/repn/tests/ampl/nl_diff.py +++ b/pyomo/repn/tests/ampl/nl_diff.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/small10_testCase.py b/pyomo/repn/tests/ampl/small10_testCase.py index f51aea76d3e..deb56f92a88 100644 --- a/pyomo/repn/tests/ampl/small10_testCase.py +++ b/pyomo/repn/tests/ampl/small10_testCase.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/small11_testCase.py b/pyomo/repn/tests/ampl/small11_testCase.py index 5874007e13c..11b61805d5e 100644 --- a/pyomo/repn/tests/ampl/small11_testCase.py +++ b/pyomo/repn/tests/ampl/small11_testCase.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/small12_testCase.py b/pyomo/repn/tests/ampl/small12_testCase.py index 63d4ba29cf6..b73a8f528f2 100644 --- a/pyomo/repn/tests/ampl/small12_testCase.py +++ b/pyomo/repn/tests/ampl/small12_testCase.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/small13_testCase.py b/pyomo/repn/tests/ampl/small13_testCase.py index 9814c979cc7..c24185bf8d7 100644 --- a/pyomo/repn/tests/ampl/small13_testCase.py +++ b/pyomo/repn/tests/ampl/small13_testCase.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/small14_testCase.py b/pyomo/repn/tests/ampl/small14_testCase.py index 3d896242243..fb2c2bc6c5e 100644 --- a/pyomo/repn/tests/ampl/small14_testCase.py +++ b/pyomo/repn/tests/ampl/small14_testCase.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/small15_testCase.py b/pyomo/repn/tests/ampl/small15_testCase.py index 8345621cecd..d4d5796aaa5 100644 --- a/pyomo/repn/tests/ampl/small15_testCase.py +++ b/pyomo/repn/tests/ampl/small15_testCase.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/small1_testCase.py b/pyomo/repn/tests/ampl/small1_testCase.py index 00e6dd322ed..06f5ad122d9 100644 --- a/pyomo/repn/tests/ampl/small1_testCase.py +++ b/pyomo/repn/tests/ampl/small1_testCase.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/small2_testCase.py b/pyomo/repn/tests/ampl/small2_testCase.py index 2df3aebb139..8a65779f55e 100644 --- a/pyomo/repn/tests/ampl/small2_testCase.py +++ b/pyomo/repn/tests/ampl/small2_testCase.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/small3_testCase.py b/pyomo/repn/tests/ampl/small3_testCase.py index f11137979b4..999143d9a0c 100644 --- a/pyomo/repn/tests/ampl/small3_testCase.py +++ b/pyomo/repn/tests/ampl/small3_testCase.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/small4_testCase.py b/pyomo/repn/tests/ampl/small4_testCase.py index 08d68c21f50..9736dd9bf3b 100644 --- a/pyomo/repn/tests/ampl/small4_testCase.py +++ b/pyomo/repn/tests/ampl/small4_testCase.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/small5_testCase.py b/pyomo/repn/tests/ampl/small5_testCase.py index 1e976820f9b..1f254b7f04d 100644 --- a/pyomo/repn/tests/ampl/small5_testCase.py +++ b/pyomo/repn/tests/ampl/small5_testCase.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/small6_testCase.py b/pyomo/repn/tests/ampl/small6_testCase.py index da9f1d58f9b..9d309c09fef 100644 --- a/pyomo/repn/tests/ampl/small6_testCase.py +++ b/pyomo/repn/tests/ampl/small6_testCase.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/small7_testCase.py b/pyomo/repn/tests/ampl/small7_testCase.py index 22a75a33394..485962dd211 100644 --- a/pyomo/repn/tests/ampl/small7_testCase.py +++ b/pyomo/repn/tests/ampl/small7_testCase.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/small8_testCase.py b/pyomo/repn/tests/ampl/small8_testCase.py index 554e27c0924..61a3e3ccce7 100644 --- a/pyomo/repn/tests/ampl/small8_testCase.py +++ b/pyomo/repn/tests/ampl/small8_testCase.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/small9_testCase.py b/pyomo/repn/tests/ampl/small9_testCase.py index 3d7af602a88..7cb0913a762 100644 --- a/pyomo/repn/tests/ampl/small9_testCase.py +++ b/pyomo/repn/tests/ampl/small9_testCase.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/test_ampl_comparison.py b/pyomo/repn/tests/ampl/test_ampl_comparison.py index eb5aff329e1..8210bbdd173 100644 --- a/pyomo/repn/tests/ampl/test_ampl_comparison.py +++ b/pyomo/repn/tests/ampl/test_ampl_comparison.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/test_ampl_nl.py b/pyomo/repn/tests/ampl/test_ampl_nl.py index bd58c254bfd..53a2d3cda82 100644 --- a/pyomo/repn/tests/ampl/test_ampl_nl.py +++ b/pyomo/repn/tests/ampl/test_ampl_nl.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/test_ampl_repn.py b/pyomo/repn/tests/ampl/test_ampl_repn.py index cf1a889006e..9c911540eb0 100644 --- a/pyomo/repn/tests/ampl/test_ampl_repn.py +++ b/pyomo/repn/tests/ampl/test_ampl_repn.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 6422a2b0020..8b95fc03bdb 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/ampl/test_suffixes.py b/pyomo/repn/tests/ampl/test_suffixes.py index e73060e7e8c..1372da68bdc 100644 --- a/pyomo/repn/tests/ampl/test_suffixes.py +++ b/pyomo/repn/tests/ampl/test_suffixes.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/baron/__init__.py b/pyomo/repn/tests/baron/__init__.py index 030f46eaca8..c693bb8accd 100644 --- a/pyomo/repn/tests/baron/__init__.py +++ b/pyomo/repn/tests/baron/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/baron/small14a_testCase.py b/pyomo/repn/tests/baron/small14a_testCase.py index 72190756dc7..b2cf5afcb72 100644 --- a/pyomo/repn/tests/baron/small14a_testCase.py +++ b/pyomo/repn/tests/baron/small14a_testCase.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/baron/test_baron.py b/pyomo/repn/tests/baron/test_baron.py index 348ad6036fb..6f22f26cd38 100644 --- a/pyomo/repn/tests/baron/test_baron.py +++ b/pyomo/repn/tests/baron/test_baron.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/baron/test_baron_comparison.py b/pyomo/repn/tests/baron/test_baron_comparison.py index 7c480321624..1b394f6a5b1 100644 --- a/pyomo/repn/tests/baron/test_baron_comparison.py +++ b/pyomo/repn/tests/baron/test_baron_comparison.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/cpxlp/__init__.py b/pyomo/repn/tests/cpxlp/__init__.py index 8ffbfd52054..f216a76f48b 100644 --- a/pyomo/repn/tests/cpxlp/__init__.py +++ b/pyomo/repn/tests/cpxlp/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/cpxlp/test_cpxlp.py b/pyomo/repn/tests/cpxlp/test_cpxlp.py index 28c9043a8de..567c5184517 100644 --- a/pyomo/repn/tests/cpxlp/test_cpxlp.py +++ b/pyomo/repn/tests/cpxlp/test_cpxlp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/cpxlp/test_lpv2.py b/pyomo/repn/tests/cpxlp/test_lpv2.py index 336939a4d7d..fbef24c77c3 100644 --- a/pyomo/repn/tests/cpxlp/test_lpv2.py +++ b/pyomo/repn/tests/cpxlp/test_lpv2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/diffutils.py b/pyomo/repn/tests/diffutils.py index 24188d46c86..c346f8c48b2 100644 --- a/pyomo/repn/tests/diffutils.py +++ b/pyomo/repn/tests/diffutils.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/gams/__init__.py b/pyomo/repn/tests/gams/__init__.py index 8d13c4ffb99..e548666fd72 100644 --- a/pyomo/repn/tests/gams/__init__.py +++ b/pyomo/repn/tests/gams/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/gams/small14a_testCase.py b/pyomo/repn/tests/gams/small14a_testCase.py index c7e3e0805ea..1efdd1baa25 100644 --- a/pyomo/repn/tests/gams/small14a_testCase.py +++ b/pyomo/repn/tests/gams/small14a_testCase.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/gams/test_gams.py b/pyomo/repn/tests/gams/test_gams.py index e6b729e5dfc..e3304e18491 100644 --- a/pyomo/repn/tests/gams/test_gams.py +++ b/pyomo/repn/tests/gams/test_gams.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/gams/test_gams_comparison.py b/pyomo/repn/tests/gams/test_gams_comparison.py index 4e530b10d43..42fa9f71dda 100644 --- a/pyomo/repn/tests/gams/test_gams_comparison.py +++ b/pyomo/repn/tests/gams/test_gams_comparison.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/lp_diff.py b/pyomo/repn/tests/lp_diff.py index 23b24f8b51b..2c119d72c6f 100644 --- a/pyomo/repn/tests/lp_diff.py +++ b/pyomo/repn/tests/lp_diff.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/mps/__init__.py b/pyomo/repn/tests/mps/__init__.py index 1a8a69a1409..effc182aa1c 100644 --- a/pyomo/repn/tests/mps/__init__.py +++ b/pyomo/repn/tests/mps/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/mps/test_mps.py b/pyomo/repn/tests/mps/test_mps.py index 9be45a17870..ff7981b391c 100644 --- a/pyomo/repn/tests/mps/test_mps.py +++ b/pyomo/repn/tests/mps/test_mps.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/nl_diff.py b/pyomo/repn/tests/nl_diff.py index e96d6f6357b..aa2b4519db3 100644 --- a/pyomo/repn/tests/nl_diff.py +++ b/pyomo/repn/tests/nl_diff.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/test_linear.py b/pyomo/repn/tests/test_linear.py index d4f268ae182..6843650d0c2 100644 --- a/pyomo/repn/tests/test_linear.py +++ b/pyomo/repn/tests/test_linear.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/test_quadratic.py b/pyomo/repn/tests/test_quadratic.py index 605c859464a..2d2e4022037 100644 --- a/pyomo/repn/tests/test_quadratic.py +++ b/pyomo/repn/tests/test_quadratic.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/test_standard.py b/pyomo/repn/tests/test_standard.py index b62d18e6eff..6c5a6e3e033 100644 --- a/pyomo/repn/tests/test_standard.py +++ b/pyomo/repn/tests/test_standard.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/test_standard_form.py b/pyomo/repn/tests/test_standard_form.py index d186f28dab8..e24195edfde 100644 --- a/pyomo/repn/tests/test_standard_form.py +++ b/pyomo/repn/tests/test_standard_form.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/tests/test_util.py b/pyomo/repn/tests/test_util.py index c4902a7064d..b5e4cc4facf 100644 --- a/pyomo/repn/tests/test_util.py +++ b/pyomo/repn/tests/test_util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index 634b4d1d640..49cca32eaf9 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/scripting/__init__.py b/pyomo/scripting/__init__.py index a3c2c1bb7ce..7cb5ac652fc 100644 --- a/pyomo/scripting/__init__.py +++ b/pyomo/scripting/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/scripting/commands.py b/pyomo/scripting/commands.py index 7782962c2c1..ef59d64b542 100644 --- a/pyomo/scripting/commands.py +++ b/pyomo/scripting/commands.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/scripting/convert.py b/pyomo/scripting/convert.py index 2f0c0e5b400..997e69ac7c9 100644 --- a/pyomo/scripting/convert.py +++ b/pyomo/scripting/convert.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/scripting/driver_help.py b/pyomo/scripting/driver_help.py index 81970a6b5cc..38d1a4c16bf 100644 --- a/pyomo/scripting/driver_help.py +++ b/pyomo/scripting/driver_help.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/scripting/interface.py b/pyomo/scripting/interface.py index efb97470e43..fca485b279b 100644 --- a/pyomo/scripting/interface.py +++ b/pyomo/scripting/interface.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/scripting/plugins/__init__.py b/pyomo/scripting/plugins/__init__.py index 44e3956f314..86a3100e077 100644 --- a/pyomo/scripting/plugins/__init__.py +++ b/pyomo/scripting/plugins/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/scripting/plugins/build_ext.py b/pyomo/scripting/plugins/build_ext.py index 9ae63cbb8a1..5b4ac836a00 100644 --- a/pyomo/scripting/plugins/build_ext.py +++ b/pyomo/scripting/plugins/build_ext.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/scripting/plugins/convert.py b/pyomo/scripting/plugins/convert.py index 55290ed90ce..ea6742cec56 100644 --- a/pyomo/scripting/plugins/convert.py +++ b/pyomo/scripting/plugins/convert.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/scripting/plugins/download.py b/pyomo/scripting/plugins/download.py index 73a164ee708..eea858a737f 100644 --- a/pyomo/scripting/plugins/download.py +++ b/pyomo/scripting/plugins/download.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/scripting/plugins/extras.py b/pyomo/scripting/plugins/extras.py index 4cf9e623212..2bd1c4a0803 100644 --- a/pyomo/scripting/plugins/extras.py +++ b/pyomo/scripting/plugins/extras.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/scripting/plugins/solve.py b/pyomo/scripting/plugins/solve.py index 69451a04e3c..b2a849e995b 100644 --- a/pyomo/scripting/plugins/solve.py +++ b/pyomo/scripting/plugins/solve.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/scripting/pyomo_command.py b/pyomo/scripting/pyomo_command.py index b652e95372a..8beec41a8b1 100644 --- a/pyomo/scripting/pyomo_command.py +++ b/pyomo/scripting/pyomo_command.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/scripting/pyomo_main.py b/pyomo/scripting/pyomo_main.py index 9acafea0471..6497206fdda 100644 --- a/pyomo/scripting/pyomo_main.py +++ b/pyomo/scripting/pyomo_main.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/scripting/pyomo_parser.py b/pyomo/scripting/pyomo_parser.py index 345d400a1aa..09998085576 100644 --- a/pyomo/scripting/pyomo_parser.py +++ b/pyomo/scripting/pyomo_parser.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/scripting/solve_config.py b/pyomo/scripting/solve_config.py index 3048431d443..7ce3505d045 100644 --- a/pyomo/scripting/solve_config.py +++ b/pyomo/scripting/solve_config.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/scripting/tests/__init__.py b/pyomo/scripting/tests/__init__.py index 88e18b19035..d9146f7eee4 100644 --- a/pyomo/scripting/tests/__init__.py +++ b/pyomo/scripting/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/scripting/tests/test_cmds.py b/pyomo/scripting/tests/test_cmds.py index 960e0d4ada1..9a120c8c175 100644 --- a/pyomo/scripting/tests/test_cmds.py +++ b/pyomo/scripting/tests/test_cmds.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/scripting/util.py b/pyomo/scripting/util.py index 5bc65eb35ae..b2a30ebaecd 100644 --- a/pyomo/scripting/util.py +++ b/pyomo/scripting/util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/__init__.py b/pyomo/solvers/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/solvers/__init__.py +++ b/pyomo/solvers/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/mockmip.py b/pyomo/solvers/mockmip.py index 9497a6dff9d..2c28b7a9be0 100644 --- a/pyomo/solvers/mockmip.py +++ b/pyomo/solvers/mockmip.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/__init__.py b/pyomo/solvers/plugins/__init__.py index 797ed5036bd..2a7bf2fea04 100644 --- a/pyomo/solvers/plugins/__init__.py +++ b/pyomo/solvers/plugins/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/converter/__init__.py b/pyomo/solvers/plugins/converter/__init__.py index b6baf4f6682..56c32f1c8c1 100644 --- a/pyomo/solvers/plugins/converter/__init__.py +++ b/pyomo/solvers/plugins/converter/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/converter/ampl.py b/pyomo/solvers/plugins/converter/ampl.py index b718faf2d21..0798115a448 100644 --- a/pyomo/solvers/plugins/converter/ampl.py +++ b/pyomo/solvers/plugins/converter/ampl.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/converter/glpsol.py b/pyomo/solvers/plugins/converter/glpsol.py index a38892e3cf5..9b404567c4d 100644 --- a/pyomo/solvers/plugins/converter/glpsol.py +++ b/pyomo/solvers/plugins/converter/glpsol.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/converter/model.py b/pyomo/solvers/plugins/converter/model.py index 89a521d1521..817df157bf5 100644 --- a/pyomo/solvers/plugins/converter/model.py +++ b/pyomo/solvers/plugins/converter/model.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/converter/pico.py b/pyomo/solvers/plugins/converter/pico.py index 7fd0d11222b..e5d008da347 100644 --- a/pyomo/solvers/plugins/converter/pico.py +++ b/pyomo/solvers/plugins/converter/pico.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/ASL.py b/pyomo/solvers/plugins/solvers/ASL.py index debcd27f75e..ae7ad82c870 100644 --- a/pyomo/solvers/plugins/solvers/ASL.py +++ b/pyomo/solvers/plugins/solvers/ASL.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/BARON.py b/pyomo/solvers/plugins/solvers/BARON.py index eb5ac0830c5..044cab27b86 100644 --- a/pyomo/solvers/plugins/solvers/BARON.py +++ b/pyomo/solvers/plugins/solvers/BARON.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/CBCplugin.py b/pyomo/solvers/plugins/solvers/CBCplugin.py index 86871dbc1ac..108b142a9e0 100644 --- a/pyomo/solvers/plugins/solvers/CBCplugin.py +++ b/pyomo/solvers/plugins/solvers/CBCplugin.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/CONOPT.py b/pyomo/solvers/plugins/solvers/CONOPT.py index 30e8ada11a1..89ee3848805 100644 --- a/pyomo/solvers/plugins/solvers/CONOPT.py +++ b/pyomo/solvers/plugins/solvers/CONOPT.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/CPLEX.py b/pyomo/solvers/plugins/solvers/CPLEX.py index 9755bc58614..b2b8c5e988d 100644 --- a/pyomo/solvers/plugins/solvers/CPLEX.py +++ b/pyomo/solvers/plugins/solvers/CPLEX.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/GAMS.py b/pyomo/solvers/plugins/solvers/GAMS.py index d0365d49078..e84cbdb441d 100644 --- a/pyomo/solvers/plugins/solvers/GAMS.py +++ b/pyomo/solvers/plugins/solvers/GAMS.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/GLPK.py b/pyomo/solvers/plugins/solvers/GLPK.py index a5b8ad9c019..39948d465f4 100644 --- a/pyomo/solvers/plugins/solvers/GLPK.py +++ b/pyomo/solvers/plugins/solvers/GLPK.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/GUROBI.py b/pyomo/solvers/plugins/solvers/GUROBI.py index e0eddf008af..c8b0912970e 100644 --- a/pyomo/solvers/plugins/solvers/GUROBI.py +++ b/pyomo/solvers/plugins/solvers/GUROBI.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/GUROBI_RUN.py b/pyomo/solvers/plugins/solvers/GUROBI_RUN.py index 2b505adf49c..88f953e18ae 100644 --- a/pyomo/solvers/plugins/solvers/GUROBI_RUN.py +++ b/pyomo/solvers/plugins/solvers/GUROBI_RUN.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/IPOPT.py b/pyomo/solvers/plugins/solvers/IPOPT.py index 611180113c8..deda4314a52 100644 --- a/pyomo/solvers/plugins/solvers/IPOPT.py +++ b/pyomo/solvers/plugins/solvers/IPOPT.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/SCIPAMPL.py b/pyomo/solvers/plugins/solvers/SCIPAMPL.py index 9898b9cdd90..be7415a19ef 100644 --- a/pyomo/solvers/plugins/solvers/SCIPAMPL.py +++ b/pyomo/solvers/plugins/solvers/SCIPAMPL.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/XPRESS.py b/pyomo/solvers/plugins/solvers/XPRESS.py index 7b85aea1266..2c16d971144 100644 --- a/pyomo/solvers/plugins/solvers/XPRESS.py +++ b/pyomo/solvers/plugins/solvers/XPRESS.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/__init__.py b/pyomo/solvers/plugins/solvers/__init__.py index c5fbfa97e42..9b2507d876c 100644 --- a/pyomo/solvers/plugins/solvers/__init__.py +++ b/pyomo/solvers/plugins/solvers/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/cplex_direct.py b/pyomo/solvers/plugins/solvers/cplex_direct.py index 308d3438329..93d8015514e 100644 --- a/pyomo/solvers/plugins/solvers/cplex_direct.py +++ b/pyomo/solvers/plugins/solvers/cplex_direct.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/cplex_persistent.py b/pyomo/solvers/plugins/solvers/cplex_persistent.py index a7fdcc45ade..fd396a8c87f 100644 --- a/pyomo/solvers/plugins/solvers/cplex_persistent.py +++ b/pyomo/solvers/plugins/solvers/cplex_persistent.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/direct_or_persistent_solver.py b/pyomo/solvers/plugins/solvers/direct_or_persistent_solver.py index 09bbfbda70f..c131b8ad10a 100644 --- a/pyomo/solvers/plugins/solvers/direct_or_persistent_solver.py +++ b/pyomo/solvers/plugins/solvers/direct_or_persistent_solver.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/direct_solver.py b/pyomo/solvers/plugins/solvers/direct_solver.py index 4f90a753fe6..3eab658391c 100644 --- a/pyomo/solvers/plugins/solvers/direct_solver.py +++ b/pyomo/solvers/plugins/solvers/direct_solver.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/gurobi_direct.py b/pyomo/solvers/plugins/solvers/gurobi_direct.py index 54ea9111508..1d88eced629 100644 --- a/pyomo/solvers/plugins/solvers/gurobi_direct.py +++ b/pyomo/solvers/plugins/solvers/gurobi_direct.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/gurobi_persistent.py b/pyomo/solvers/plugins/solvers/gurobi_persistent.py index 382cb7c4e6d..4522a2151c3 100644 --- a/pyomo/solvers/plugins/solvers/gurobi_persistent.py +++ b/pyomo/solvers/plugins/solvers/gurobi_persistent.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/mosek_direct.py b/pyomo/solvers/plugins/solvers/mosek_direct.py index 4c0718bfe74..5000a2f35c4 100644 --- a/pyomo/solvers/plugins/solvers/mosek_direct.py +++ b/pyomo/solvers/plugins/solvers/mosek_direct.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/mosek_persistent.py b/pyomo/solvers/plugins/solvers/mosek_persistent.py index 6eaad564781..97f88e0cb9a 100644 --- a/pyomo/solvers/plugins/solvers/mosek_persistent.py +++ b/pyomo/solvers/plugins/solvers/mosek_persistent.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/persistent_solver.py b/pyomo/solvers/plugins/solvers/persistent_solver.py index 141621d0a31..29aa3f2bbf5 100644 --- a/pyomo/solvers/plugins/solvers/persistent_solver.py +++ b/pyomo/solvers/plugins/solvers/persistent_solver.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/pywrapper.py b/pyomo/solvers/plugins/solvers/pywrapper.py index 8f72e630a3d..c3ec2eaf709 100644 --- a/pyomo/solvers/plugins/solvers/pywrapper.py +++ b/pyomo/solvers/plugins/solvers/pywrapper.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/xpress_direct.py b/pyomo/solvers/plugins/solvers/xpress_direct.py index aa5a4ba1b4e..75cf8f921df 100644 --- a/pyomo/solvers/plugins/solvers/xpress_direct.py +++ b/pyomo/solvers/plugins/solvers/xpress_direct.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/plugins/solvers/xpress_persistent.py b/pyomo/solvers/plugins/solvers/xpress_persistent.py index 56024bc0540..513a7fbc257 100644 --- a/pyomo/solvers/plugins/solvers/xpress_persistent.py +++ b/pyomo/solvers/plugins/solvers/xpress_persistent.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/__init__.py b/pyomo/solvers/tests/__init__.py index 42c694b0170..4d8d45da724 100644 --- a/pyomo/solvers/tests/__init__.py +++ b/pyomo/solvers/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/checks/__init__.py b/pyomo/solvers/tests/checks/__init__.py index 03a34303759..ccd3a0f98a4 100644 --- a/pyomo/solvers/tests/checks/__init__.py +++ b/pyomo/solvers/tests/checks/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/checks/test_BARON.py b/pyomo/solvers/tests/checks/test_BARON.py index 897f1e88a42..29c7ffb0148 100644 --- a/pyomo/solvers/tests/checks/test_BARON.py +++ b/pyomo/solvers/tests/checks/test_BARON.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/checks/test_CBCplugin.py b/pyomo/solvers/tests/checks/test_CBCplugin.py index fe01a89bb53..2ea0e55c5f4 100644 --- a/pyomo/solvers/tests/checks/test_CBCplugin.py +++ b/pyomo/solvers/tests/checks/test_CBCplugin.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/checks/test_CPLEXDirect.py b/pyomo/solvers/tests/checks/test_CPLEXDirect.py index 86e03d1024f..400d7ee5f75 100644 --- a/pyomo/solvers/tests/checks/test_CPLEXDirect.py +++ b/pyomo/solvers/tests/checks/test_CPLEXDirect.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/checks/test_CPLEXPersistent.py b/pyomo/solvers/tests/checks/test_CPLEXPersistent.py index d7f00d0f486..91a60eee9dd 100644 --- a/pyomo/solvers/tests/checks/test_CPLEXPersistent.py +++ b/pyomo/solvers/tests/checks/test_CPLEXPersistent.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/checks/test_GAMS.py b/pyomo/solvers/tests/checks/test_GAMS.py index 7aa952a6c69..1eef09819f7 100644 --- a/pyomo/solvers/tests/checks/test_GAMS.py +++ b/pyomo/solvers/tests/checks/test_GAMS.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/checks/test_MOSEKDirect.py b/pyomo/solvers/tests/checks/test_MOSEKDirect.py index 369cc08161a..2cf7034b80a 100644 --- a/pyomo/solvers/tests/checks/test_MOSEKDirect.py +++ b/pyomo/solvers/tests/checks/test_MOSEKDirect.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/checks/test_MOSEKPersistent.py b/pyomo/solvers/tests/checks/test_MOSEKPersistent.py index 6db99919177..a4c0aa21666 100644 --- a/pyomo/solvers/tests/checks/test_MOSEKPersistent.py +++ b/pyomo/solvers/tests/checks/test_MOSEKPersistent.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/checks/test_cbc.py b/pyomo/solvers/tests/checks/test_cbc.py index 0fd6e9f49a1..420de7cc61d 100644 --- a/pyomo/solvers/tests/checks/test_cbc.py +++ b/pyomo/solvers/tests/checks/test_cbc.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/checks/test_cplex.py b/pyomo/solvers/tests/checks/test_cplex.py index 44b82d2ad77..ff5ac5f17e1 100644 --- a/pyomo/solvers/tests/checks/test_cplex.py +++ b/pyomo/solvers/tests/checks/test_cplex.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/checks/test_gurobi.py b/pyomo/solvers/tests/checks/test_gurobi.py index cfd0f077eab..e87685a046c 100644 --- a/pyomo/solvers/tests/checks/test_gurobi.py +++ b/pyomo/solvers/tests/checks/test_gurobi.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/checks/test_gurobi_direct.py b/pyomo/solvers/tests/checks/test_gurobi_direct.py index d9802894c47..1e3a366a37a 100644 --- a/pyomo/solvers/tests/checks/test_gurobi_direct.py +++ b/pyomo/solvers/tests/checks/test_gurobi_direct.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/checks/test_gurobi_persistent.py b/pyomo/solvers/tests/checks/test_gurobi_persistent.py index 9d69c1dd920..a2c089207e5 100644 --- a/pyomo/solvers/tests/checks/test_gurobi_persistent.py +++ b/pyomo/solvers/tests/checks/test_gurobi_persistent.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/checks/test_no_solution_behavior.py b/pyomo/solvers/tests/checks/test_no_solution_behavior.py index 9ba8e86a013..81a2d2bf297 100644 --- a/pyomo/solvers/tests/checks/test_no_solution_behavior.py +++ b/pyomo/solvers/tests/checks/test_no_solution_behavior.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/checks/test_pickle.py b/pyomo/solvers/tests/checks/test_pickle.py index d8551b34740..745320cb4eb 100644 --- a/pyomo/solvers/tests/checks/test_pickle.py +++ b/pyomo/solvers/tests/checks/test_pickle.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/checks/test_writers.py b/pyomo/solvers/tests/checks/test_writers.py index e406e07a4d6..55002c71357 100644 --- a/pyomo/solvers/tests/checks/test_writers.py +++ b/pyomo/solvers/tests/checks/test_writers.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/checks/test_xpress_persistent.py b/pyomo/solvers/tests/checks/test_xpress_persistent.py index abfcf9c0afc..ddae860cd92 100644 --- a/pyomo/solvers/tests/checks/test_xpress_persistent.py +++ b/pyomo/solvers/tests/checks/test_xpress_persistent.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/mip/__init__.py b/pyomo/solvers/tests/mip/__init__.py index c95d27d9497..707a8c4b7e5 100644 --- a/pyomo/solvers/tests/mip/__init__.py +++ b/pyomo/solvers/tests/mip/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/mip/model.py b/pyomo/solvers/tests/mip/model.py index 389151160b8..83c1411fe6c 100644 --- a/pyomo/solvers/tests/mip/model.py +++ b/pyomo/solvers/tests/mip/model.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/mip/test_asl.py b/pyomo/solvers/tests/mip/test_asl.py index 42b77df7d87..6f23a06eff2 100644 --- a/pyomo/solvers/tests/mip/test_asl.py +++ b/pyomo/solvers/tests/mip/test_asl.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/mip/test_convert.py b/pyomo/solvers/tests/mip/test_convert.py index cd916da29f2..962b021c4ae 100644 --- a/pyomo/solvers/tests/mip/test_convert.py +++ b/pyomo/solvers/tests/mip/test_convert.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/mip/test_factory.py b/pyomo/solvers/tests/mip/test_factory.py index 6960a0f8ced..31d47486aa4 100644 --- a/pyomo/solvers/tests/mip/test_factory.py +++ b/pyomo/solvers/tests/mip/test_factory.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/mip/test_ipopt.py b/pyomo/solvers/tests/mip/test_ipopt.py index bccb4f2a27c..38c3b35d8a1 100644 --- a/pyomo/solvers/tests/mip/test_ipopt.py +++ b/pyomo/solvers/tests/mip/test_ipopt.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/mip/test_mip.py b/pyomo/solvers/tests/mip/test_mip.py index 0257e65de20..58cdfe9f7de 100644 --- a/pyomo/solvers/tests/mip/test_mip.py +++ b/pyomo/solvers/tests/mip/test_mip.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/mip/test_qp.py b/pyomo/solvers/tests/mip/test_qp.py index 5d920b9085d..9c5cb5ffbc4 100644 --- a/pyomo/solvers/tests/mip/test_qp.py +++ b/pyomo/solvers/tests/mip/test_qp.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/mip/test_scip.py b/pyomo/solvers/tests/mip/test_scip.py index 7fffdc53c13..01de0d16826 100644 --- a/pyomo/solvers/tests/mip/test_scip.py +++ b/pyomo/solvers/tests/mip/test_scip.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/mip/test_scip_log_data.py b/pyomo/solvers/tests/mip/test_scip_log_data.py index 0dc0825afb3..a0006d69eb7 100644 --- a/pyomo/solvers/tests/mip/test_scip_log_data.py +++ b/pyomo/solvers/tests/mip/test_scip_log_data.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/mip/test_scip_version.py b/pyomo/solvers/tests/mip/test_scip_version.py index c0cc80c0316..f83bed2da32 100644 --- a/pyomo/solvers/tests/mip/test_scip_version.py +++ b/pyomo/solvers/tests/mip/test_scip_version.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/mip/test_solver.py b/pyomo/solvers/tests/mip/test_solver.py index 90a7076cbca..bf3550a001d 100644 --- a/pyomo/solvers/tests/mip/test_solver.py +++ b/pyomo/solvers/tests/mip/test_solver.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/LP_block.py b/pyomo/solvers/tests/models/LP_block.py index 64c866faa9e..37b01dc1c2d 100644 --- a/pyomo/solvers/tests/models/LP_block.py +++ b/pyomo/solvers/tests/models/LP_block.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/LP_compiled.py b/pyomo/solvers/tests/models/LP_compiled.py index 686406e7ec6..960b8730e0c 100644 --- a/pyomo/solvers/tests/models/LP_compiled.py +++ b/pyomo/solvers/tests/models/LP_compiled.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/LP_constant_objective1.py b/pyomo/solvers/tests/models/LP_constant_objective1.py index 306a7a867a2..0c01cd7085f 100644 --- a/pyomo/solvers/tests/models/LP_constant_objective1.py +++ b/pyomo/solvers/tests/models/LP_constant_objective1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/LP_constant_objective2.py b/pyomo/solvers/tests/models/LP_constant_objective2.py index 17da01bf209..07739c1f708 100644 --- a/pyomo/solvers/tests/models/LP_constant_objective2.py +++ b/pyomo/solvers/tests/models/LP_constant_objective2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/LP_duals_maximize.py b/pyomo/solvers/tests/models/LP_duals_maximize.py index 61d827daa62..ed45e4eee29 100644 --- a/pyomo/solvers/tests/models/LP_duals_maximize.py +++ b/pyomo/solvers/tests/models/LP_duals_maximize.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/LP_duals_minimize.py b/pyomo/solvers/tests/models/LP_duals_minimize.py index 77471d0182c..3f97276a61e 100644 --- a/pyomo/solvers/tests/models/LP_duals_minimize.py +++ b/pyomo/solvers/tests/models/LP_duals_minimize.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/LP_inactive_index.py b/pyomo/solvers/tests/models/LP_inactive_index.py index d3fdd5b32ca..5e2b570a1e8 100644 --- a/pyomo/solvers/tests/models/LP_inactive_index.py +++ b/pyomo/solvers/tests/models/LP_inactive_index.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/LP_infeasible1.py b/pyomo/solvers/tests/models/LP_infeasible1.py index 28243574a37..8cba441a6c3 100644 --- a/pyomo/solvers/tests/models/LP_infeasible1.py +++ b/pyomo/solvers/tests/models/LP_infeasible1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/LP_infeasible2.py b/pyomo/solvers/tests/models/LP_infeasible2.py index 383267c0e3c..7f417d9145c 100644 --- a/pyomo/solvers/tests/models/LP_infeasible2.py +++ b/pyomo/solvers/tests/models/LP_infeasible2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/LP_piecewise.py b/pyomo/solvers/tests/models/LP_piecewise.py index f6350b38591..22ee9d08694 100644 --- a/pyomo/solvers/tests/models/LP_piecewise.py +++ b/pyomo/solvers/tests/models/LP_piecewise.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/LP_simple.py b/pyomo/solvers/tests/models/LP_simple.py index 3449a657f79..4f1e6dcbc7e 100644 --- a/pyomo/solvers/tests/models/LP_simple.py +++ b/pyomo/solvers/tests/models/LP_simple.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/LP_trivial_constraints.py b/pyomo/solvers/tests/models/LP_trivial_constraints.py index 096c9e71712..3958f2b4493 100644 --- a/pyomo/solvers/tests/models/LP_trivial_constraints.py +++ b/pyomo/solvers/tests/models/LP_trivial_constraints.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/LP_unbounded.py b/pyomo/solvers/tests/models/LP_unbounded.py index e3173e2ff07..e75977c40ba 100644 --- a/pyomo/solvers/tests/models/LP_unbounded.py +++ b/pyomo/solvers/tests/models/LP_unbounded.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/LP_unique_duals.py b/pyomo/solvers/tests/models/LP_unique_duals.py index 624181eb27d..f5a4df6338d 100644 --- a/pyomo/solvers/tests/models/LP_unique_duals.py +++ b/pyomo/solvers/tests/models/LP_unique_duals.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/LP_unused_vars.py b/pyomo/solvers/tests/models/LP_unused_vars.py index 5e6b40fa4bf..0062fc58463 100644 --- a/pyomo/solvers/tests/models/LP_unused_vars.py +++ b/pyomo/solvers/tests/models/LP_unused_vars.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/MILP_discrete_var_bounds.py b/pyomo/solvers/tests/models/MILP_discrete_var_bounds.py index 8fef69ef76a..22876a7a291 100644 --- a/pyomo/solvers/tests/models/MILP_discrete_var_bounds.py +++ b/pyomo/solvers/tests/models/MILP_discrete_var_bounds.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/MILP_infeasible1.py b/pyomo/solvers/tests/models/MILP_infeasible1.py index 2a0bf1bd188..e95fef92744 100644 --- a/pyomo/solvers/tests/models/MILP_infeasible1.py +++ b/pyomo/solvers/tests/models/MILP_infeasible1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/MILP_simple.py b/pyomo/solvers/tests/models/MILP_simple.py index fb157ea6555..488c7841024 100644 --- a/pyomo/solvers/tests/models/MILP_simple.py +++ b/pyomo/solvers/tests/models/MILP_simple.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/MILP_unbounded.py b/pyomo/solvers/tests/models/MILP_unbounded.py index 364f3ffeb86..c5a166a6141 100644 --- a/pyomo/solvers/tests/models/MILP_unbounded.py +++ b/pyomo/solvers/tests/models/MILP_unbounded.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/MILP_unused_vars.py b/pyomo/solvers/tests/models/MILP_unused_vars.py index 742d0f951a8..b6e06c8db0c 100644 --- a/pyomo/solvers/tests/models/MILP_unused_vars.py +++ b/pyomo/solvers/tests/models/MILP_unused_vars.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/MIQCP_simple.py b/pyomo/solvers/tests/models/MIQCP_simple.py index 46c1293b23c..5946e83fadb 100644 --- a/pyomo/solvers/tests/models/MIQCP_simple.py +++ b/pyomo/solvers/tests/models/MIQCP_simple.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/MIQP_simple.py b/pyomo/solvers/tests/models/MIQP_simple.py index 1d43d96ab8b..6922d6be97d 100644 --- a/pyomo/solvers/tests/models/MIQP_simple.py +++ b/pyomo/solvers/tests/models/MIQP_simple.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/QCP_simple.py b/pyomo/solvers/tests/models/QCP_simple.py index 5f8405f1f00..5f4311a3ab9 100644 --- a/pyomo/solvers/tests/models/QCP_simple.py +++ b/pyomo/solvers/tests/models/QCP_simple.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/QP_constant_objective.py b/pyomo/solvers/tests/models/QP_constant_objective.py index 2769fe07556..6ea34b69f51 100644 --- a/pyomo/solvers/tests/models/QP_constant_objective.py +++ b/pyomo/solvers/tests/models/QP_constant_objective.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/QP_simple.py b/pyomo/solvers/tests/models/QP_simple.py index 5959cf1d8b1..c5f4f40c576 100644 --- a/pyomo/solvers/tests/models/QP_simple.py +++ b/pyomo/solvers/tests/models/QP_simple.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/SOS1_simple.py b/pyomo/solvers/tests/models/SOS1_simple.py index e6156ad5c32..ba3c89e680b 100644 --- a/pyomo/solvers/tests/models/SOS1_simple.py +++ b/pyomo/solvers/tests/models/SOS1_simple.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/SOS2_simple.py b/pyomo/solvers/tests/models/SOS2_simple.py index 4f192773ca4..2062611f8cf 100644 --- a/pyomo/solvers/tests/models/SOS2_simple.py +++ b/pyomo/solvers/tests/models/SOS2_simple.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/__init__.py b/pyomo/solvers/tests/models/__init__.py index c6a550397d5..46a1c96936d 100644 --- a/pyomo/solvers/tests/models/__init__.py +++ b/pyomo/solvers/tests/models/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/models/base.py b/pyomo/solvers/tests/models/base.py index 106e8860145..25442611806 100644 --- a/pyomo/solvers/tests/models/base.py +++ b/pyomo/solvers/tests/models/base.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/__init__.py b/pyomo/solvers/tests/piecewise_linear/__init__.py index bcaa157f6f4..79b33f0d427 100644 --- a/pyomo/solvers/tests/piecewise_linear/__init__.py +++ b/pyomo/solvers/tests/piecewise_linear/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/kernel_problems/concave_var.py b/pyomo/solvers/tests/piecewise_linear/kernel_problems/concave_var.py index 38c840f9ed9..45270d7dc34 100644 --- a/pyomo/solvers/tests/piecewise_linear/kernel_problems/concave_var.py +++ b/pyomo/solvers/tests/piecewise_linear/kernel_problems/concave_var.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/kernel_problems/convex_var.py b/pyomo/solvers/tests/piecewise_linear/kernel_problems/convex_var.py index 3aef735965e..cf28dc044eb 100644 --- a/pyomo/solvers/tests/piecewise_linear/kernel_problems/convex_var.py +++ b/pyomo/solvers/tests/piecewise_linear/kernel_problems/convex_var.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/kernel_problems/piecewise_var.py b/pyomo/solvers/tests/piecewise_linear/kernel_problems/piecewise_var.py index b77566e9d2d..cadbff305e8 100644 --- a/pyomo/solvers/tests/piecewise_linear/kernel_problems/piecewise_var.py +++ b/pyomo/solvers/tests/piecewise_linear/kernel_problems/piecewise_var.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/kernel_problems/step_var.py b/pyomo/solvers/tests/piecewise_linear/kernel_problems/step_var.py index 642181deb7d..1e6e418acf0 100644 --- a/pyomo/solvers/tests/piecewise_linear/kernel_problems/step_var.py +++ b/pyomo/solvers/tests/piecewise_linear/kernel_problems/step_var.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/problems/concave_multi_vararray1.py b/pyomo/solvers/tests/piecewise_linear/problems/concave_multi_vararray1.py index b24f7e1bd72..473b3328660 100644 --- a/pyomo/solvers/tests/piecewise_linear/problems/concave_multi_vararray1.py +++ b/pyomo/solvers/tests/piecewise_linear/problems/concave_multi_vararray1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/problems/concave_multi_vararray2.py b/pyomo/solvers/tests/piecewise_linear/problems/concave_multi_vararray2.py index 24c8beeba34..e6b57a4b652 100644 --- a/pyomo/solvers/tests/piecewise_linear/problems/concave_multi_vararray2.py +++ b/pyomo/solvers/tests/piecewise_linear/problems/concave_multi_vararray2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/problems/concave_var.py b/pyomo/solvers/tests/piecewise_linear/problems/concave_var.py index 4eedf7bdeb9..b225cee4f87 100644 --- a/pyomo/solvers/tests/piecewise_linear/problems/concave_var.py +++ b/pyomo/solvers/tests/piecewise_linear/problems/concave_var.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/problems/concave_vararray.py b/pyomo/solvers/tests/piecewise_linear/problems/concave_vararray.py index be013b62309..727fc33ed80 100644 --- a/pyomo/solvers/tests/piecewise_linear/problems/concave_vararray.py +++ b/pyomo/solvers/tests/piecewise_linear/problems/concave_vararray.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/problems/convex_multi_vararray1.py b/pyomo/solvers/tests/piecewise_linear/problems/convex_multi_vararray1.py index 8d00a99d49d..98f369b8c45 100644 --- a/pyomo/solvers/tests/piecewise_linear/problems/convex_multi_vararray1.py +++ b/pyomo/solvers/tests/piecewise_linear/problems/convex_multi_vararray1.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/problems/convex_multi_vararray2.py b/pyomo/solvers/tests/piecewise_linear/problems/convex_multi_vararray2.py index 2892b759a65..c877bb6b72b 100644 --- a/pyomo/solvers/tests/piecewise_linear/problems/convex_multi_vararray2.py +++ b/pyomo/solvers/tests/piecewise_linear/problems/convex_multi_vararray2.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/problems/convex_var.py b/pyomo/solvers/tests/piecewise_linear/problems/convex_var.py index bb4609be7c9..842ef50515b 100644 --- a/pyomo/solvers/tests/piecewise_linear/problems/convex_var.py +++ b/pyomo/solvers/tests/piecewise_linear/problems/convex_var.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/problems/convex_vararray.py b/pyomo/solvers/tests/piecewise_linear/problems/convex_vararray.py index 140d69dcb1a..087d0977ee0 100644 --- a/pyomo/solvers/tests/piecewise_linear/problems/convex_vararray.py +++ b/pyomo/solvers/tests/piecewise_linear/problems/convex_vararray.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/problems/piecewise_multi_vararray.py b/pyomo/solvers/tests/piecewise_linear/problems/piecewise_multi_vararray.py index 3c587d694e1..56452e0cd19 100644 --- a/pyomo/solvers/tests/piecewise_linear/problems/piecewise_multi_vararray.py +++ b/pyomo/solvers/tests/piecewise_linear/problems/piecewise_multi_vararray.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/problems/piecewise_var.py b/pyomo/solvers/tests/piecewise_linear/problems/piecewise_var.py index 5b18842f81d..60c45a69e80 100644 --- a/pyomo/solvers/tests/piecewise_linear/problems/piecewise_var.py +++ b/pyomo/solvers/tests/piecewise_linear/problems/piecewise_var.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/problems/piecewise_vararray.py b/pyomo/solvers/tests/piecewise_linear/problems/piecewise_vararray.py index d35c308e172..9e53edb0c93 100644 --- a/pyomo/solvers/tests/piecewise_linear/problems/piecewise_vararray.py +++ b/pyomo/solvers/tests/piecewise_linear/problems/piecewise_vararray.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/problems/step_var.py b/pyomo/solvers/tests/piecewise_linear/problems/step_var.py index a0c1062c9d6..59cefdd39c9 100644 --- a/pyomo/solvers/tests/piecewise_linear/problems/step_var.py +++ b/pyomo/solvers/tests/piecewise_linear/problems/step_var.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/problems/step_vararray.py b/pyomo/solvers/tests/piecewise_linear/problems/step_vararray.py index 749df3b6d7f..e4853e666d6 100644 --- a/pyomo/solvers/tests/piecewise_linear/problems/step_vararray.py +++ b/pyomo/solvers/tests/piecewise_linear/problems/step_vararray.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/problems/tester.py b/pyomo/solvers/tests/piecewise_linear/problems/tester.py index 02e04f5052e..56261f7cc38 100644 --- a/pyomo/solvers/tests/piecewise_linear/problems/tester.py +++ b/pyomo/solvers/tests/piecewise_linear/problems/tester.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/test_examples.py b/pyomo/solvers/tests/piecewise_linear/test_examples.py index b151ffd2c0e..3454f62d56b 100644 --- a/pyomo/solvers/tests/piecewise_linear/test_examples.py +++ b/pyomo/solvers/tests/piecewise_linear/test_examples.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/test_piecewise_linear.py b/pyomo/solvers/tests/piecewise_linear/test_piecewise_linear.py index bfa206a987b..48472c2dabf 100644 --- a/pyomo/solvers/tests/piecewise_linear/test_piecewise_linear.py +++ b/pyomo/solvers/tests/piecewise_linear/test_piecewise_linear.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/piecewise_linear/test_piecewise_linear_kernel.py b/pyomo/solvers/tests/piecewise_linear/test_piecewise_linear_kernel.py index 4137d9d3eed..20addb2b1eb 100644 --- a/pyomo/solvers/tests/piecewise_linear/test_piecewise_linear_kernel.py +++ b/pyomo/solvers/tests/piecewise_linear/test_piecewise_linear_kernel.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/solvers.py b/pyomo/solvers/tests/solvers.py index 6bbfe08c7c7..e67df47a0b0 100644 --- a/pyomo/solvers/tests/solvers.py +++ b/pyomo/solvers/tests/solvers.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/tests/testcases.py b/pyomo/solvers/tests/testcases.py index f5920ed6814..6bef40818d9 100644 --- a/pyomo/solvers/tests/testcases.py +++ b/pyomo/solvers/tests/testcases.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/solvers/wrappers.py b/pyomo/solvers/wrappers.py index 3b083f7a14f..ee167ce1cb0 100644 --- a/pyomo/solvers/wrappers.py +++ b/pyomo/solvers/wrappers.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/__init__.py b/pyomo/util/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/util/__init__.py +++ b/pyomo/util/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/blockutil.py b/pyomo/util/blockutil.py index 52befea6ed5..56cc4266017 100644 --- a/pyomo/util/blockutil.py +++ b/pyomo/util/blockutil.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/calc_var_value.py b/pyomo/util/calc_var_value.py index 42d38f2f874..b5e620fea07 100644 --- a/pyomo/util/calc_var_value.py +++ b/pyomo/util/calc_var_value.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/check_units.py b/pyomo/util/check_units.py index be72493af3f..6f95486c8cd 100644 --- a/pyomo/util/check_units.py +++ b/pyomo/util/check_units.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/components.py b/pyomo/util/components.py index 02ef8a30f64..2f1d85a4934 100644 --- a/pyomo/util/components.py +++ b/pyomo/util/components.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/diagnostics.py b/pyomo/util/diagnostics.py index 8bad078ad64..709a483f2ff 100644 --- a/pyomo/util/diagnostics.py +++ b/pyomo/util/diagnostics.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/infeasible.py b/pyomo/util/infeasible.py index 9c8196d1ff4..961d5b35036 100644 --- a/pyomo/util/infeasible.py +++ b/pyomo/util/infeasible.py @@ -2,7 +2,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/model_size.py b/pyomo/util/model_size.py index 9575e327a74..1fdac357368 100644 --- a/pyomo/util/model_size.py +++ b/pyomo/util/model_size.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/report_scaling.py b/pyomo/util/report_scaling.py index 5b4a4df7c84..201319ea92a 100644 --- a/pyomo/util/report_scaling.py +++ b/pyomo/util/report_scaling.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/slices.py b/pyomo/util/slices.py index 0449acb3f2f..53f6d364219 100644 --- a/pyomo/util/slices.py +++ b/pyomo/util/slices.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/subsystems.py b/pyomo/util/subsystems.py index 673781def17..70a0af1b2a7 100644 --- a/pyomo/util/subsystems.py +++ b/pyomo/util/subsystems.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/tests/__init__.py b/pyomo/util/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/util/tests/__init__.py +++ b/pyomo/util/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/tests/test_blockutil.py b/pyomo/util/tests/test_blockutil.py index 06b75bd6b68..dfe4f482fb2 100644 --- a/pyomo/util/tests/test_blockutil.py +++ b/pyomo/util/tests/test_blockutil.py @@ -2,7 +2,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/tests/test_calc_var_value.py b/pyomo/util/tests/test_calc_var_value.py index 91f23dd5a5d..a02d7a7d838 100644 --- a/pyomo/util/tests/test_calc_var_value.py +++ b/pyomo/util/tests/test_calc_var_value.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/tests/test_check_units.py b/pyomo/util/tests/test_check_units.py index d2fb35c4f3b..9cde8d8dbae 100644 --- a/pyomo/util/tests/test_check_units.py +++ b/pyomo/util/tests/test_check_units.py @@ -2,7 +2,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/tests/test_components.py b/pyomo/util/tests/test_components.py index 92eb7dd5ef1..1027815ca6b 100644 --- a/pyomo/util/tests/test_components.py +++ b/pyomo/util/tests/test_components.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/tests/test_infeasible.py b/pyomo/util/tests/test_infeasible.py index cefc129b41e..687a578e5c8 100644 --- a/pyomo/util/tests/test_infeasible.py +++ b/pyomo/util/tests/test_infeasible.py @@ -2,7 +2,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/tests/test_model_size.py b/pyomo/util/tests/test_model_size.py index 417ff7526e8..2380d272a24 100644 --- a/pyomo/util/tests/test_model_size.py +++ b/pyomo/util/tests/test_model_size.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/tests/test_report_scaling.py b/pyomo/util/tests/test_report_scaling.py index b010065d697..2eaed2d0ade 100644 --- a/pyomo/util/tests/test_report_scaling.py +++ b/pyomo/util/tests/test_report_scaling.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/tests/test_slices.py b/pyomo/util/tests/test_slices.py index db66a74b468..992bdc0a332 100644 --- a/pyomo/util/tests/test_slices.py +++ b/pyomo/util/tests/test_slices.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/tests/test_subsystems.py b/pyomo/util/tests/test_subsystems.py index a081b51cee9..87a4fb3cf28 100644 --- a/pyomo/util/tests/test_subsystems.py +++ b/pyomo/util/tests/test_subsystems.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/util/vars_from_expressions.py b/pyomo/util/vars_from_expressions.py index 8866ba980bd..f9b3f1ab8ae 100644 --- a/pyomo/util/vars_from_expressions.py +++ b/pyomo/util/vars_from_expressions.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/version/__init__.py b/pyomo/version/__init__.py index 08bcde304a6..acc92ff6b37 100644 --- a/pyomo/version/__init__.py +++ b/pyomo/version/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/version/info.py b/pyomo/version/info.py index cedb30c2dd4..0db00ac240f 100644 --- a/pyomo/version/info.py +++ b/pyomo/version/info.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/version/tests/__init__.py b/pyomo/version/tests/__init__.py index 9fb4f531a5b..f013ccd3fa3 100644 --- a/pyomo/version/tests/__init__.py +++ b/pyomo/version/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/version/tests/check.py b/pyomo/version/tests/check.py index ab3b45ffc6c..0fca9badb2f 100644 --- a/pyomo/version/tests/check.py +++ b/pyomo/version/tests/check.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/version/tests/test_version.py b/pyomo/version/tests/test_version.py index 253ee53137c..3b39bd71cb1 100644 --- a/pyomo/version/tests/test_version.py +++ b/pyomo/version/tests/test_version.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/scripts/admin/contributors.py b/scripts/admin/contributors.py index fe5d483f16d..ffc02059d6f 100644 --- a/scripts/admin/contributors.py +++ b/scripts/admin/contributors.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/scripts/get_pyomo.py b/scripts/get_pyomo.py index a97c0ba3a00..d90773f2315 100644 --- a/scripts/get_pyomo.py +++ b/scripts/get_pyomo.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/scripts/get_pyomo_extras.py b/scripts/get_pyomo_extras.py index d2aa097154a..6688f3c6dc4 100644 --- a/scripts/get_pyomo_extras.py +++ b/scripts/get_pyomo_extras.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/scripts/performance/compare.py b/scripts/performance/compare.py index 5edef9bfadd..e62440fd6d9 100755 --- a/scripts/performance/compare.py +++ b/scripts/performance/compare.py @@ -2,7 +2,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/scripts/performance/compare_components.py b/scripts/performance/compare_components.py index 1edaa73003b..764b50217ef 100644 --- a/scripts/performance/compare_components.py +++ b/scripts/performance/compare_components.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/scripts/performance/expr_perf.py b/scripts/performance/expr_perf.py index 6f0d246e1f3..9abdd560887 100644 --- a/scripts/performance/expr_perf.py +++ b/scripts/performance/expr_perf.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/scripts/performance/main.py b/scripts/performance/main.py index 10349c0eb73..07dc38a11a7 100755 --- a/scripts/performance/main.py +++ b/scripts/performance/main.py @@ -2,7 +2,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/scripts/performance/simple.py b/scripts/performance/simple.py index bd5ffd99368..c5fb836b64b 100644 --- a/scripts/performance/simple.py +++ b/scripts/performance/simple.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/setup.py b/setup.py index e2d702db010..0bbcb6a8390 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 From 6cfbd21615ce8b82f15a0545e187d8a4d2c5e89a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 15 Feb 2024 14:12:31 -0700 Subject: [PATCH 0992/1204] Add documentation (and fix an import) --- doc/OnlineDocs/developer_reference/index.rst | 1 + pyomo/__future__.py | 46 +++++++++++++++++++- pyomo/contrib/solver/factory.py | 2 +- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/doc/OnlineDocs/developer_reference/index.rst b/doc/OnlineDocs/developer_reference/index.rst index 0f0f636abee..0feb33cdab9 100644 --- a/doc/OnlineDocs/developer_reference/index.rst +++ b/doc/OnlineDocs/developer_reference/index.rst @@ -12,4 +12,5 @@ scripts using Pyomo. config.rst deprecation.rst expressions/index.rst + future.rst solvers.rst diff --git a/pyomo/__future__.py b/pyomo/__future__.py index 7028265b2ad..c614bf6cc04 100644 --- a/pyomo/__future__.py +++ b/pyomo/__future__.py @@ -11,6 +11,25 @@ import pyomo.environ as _environ +__doc__ = """ +Preview capabilities through `pyomo.__future__` +=============================================== + +This module provides a uniform interface for gaining access to future +("preview") capabilities that are either slightly incompatible with the +current official offering, or are still under development with the +intent to replace the current offering. + +Currently supported `__future__` offerings include: + +.. autosummary:: + + solver_factory + +.. autofunction:: solver_factory + +""" + def __getattr__(name): if name in ('solver_factory_v1', 'solver_factory_v2', 'solver_factory_v3'): @@ -23,15 +42,39 @@ def solver_factory(version=None): This allows users to query / set the current implementation of the SolverFactory that should be used throughout Pyomo. Valid options are: - + 1: the original Pyomo SolverFactor 2: the SolverFactory from APPSI 3: the SolverFactory from pyomo.contrib.solver + The current active version can be obtained by calling the method + with no arguments + + .. doctest:: + + >>> from pyomo.__future__ import solver_factory + >>> solver_factory() + 1 + + The active factory can be set either by passing the appropriate + version to this function: + + .. doctest:: + + >>> solver_factory(3) + + + or by importing the "special" name: + + .. doctest:: + + >>> from pyomo.__future__ import solver_factory_v3 + """ import pyomo.opt.base.solvers as _solvers import pyomo.contrib.solver.factory as _contrib import pyomo.contrib.appsi.base as _appsi + versions = { 1: _solvers.LegacySolverFactory, 2: _appsi.SolverFactory, @@ -66,4 +109,5 @@ def solver_factory(version=None): ) return src + solver_factory._active_version = solver_factory() diff --git a/pyomo/contrib/solver/factory.py b/pyomo/contrib/solver/factory.py index 73666ff57e4..52fd9e51236 100644 --- a/pyomo/contrib/solver/factory.py +++ b/pyomo/contrib/solver/factory.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ -from pyomo.opt.base import LegacySolverFactory +from pyomo.opt.base.solvers import LegacySolverFactory from pyomo.common.factory import Factory from pyomo.contrib.solver.base import LegacySolverWrapper From 2c471e43e4e2194a4cdeb9c1b7f7870ee7c19a59 Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 15 Feb 2024 16:39:22 -0500 Subject: [PATCH 0993/1204] Simplify argument resolution --- pyomo/contrib/pyros/config.py | 91 ------------------------ pyomo/contrib/pyros/pyros.py | 23 +++--- pyomo/contrib/pyros/tests/test_config.py | 66 ----------------- pyomo/contrib/pyros/tests/test_grcs.py | 56 +++++---------- 4 files changed, 24 insertions(+), 212 deletions(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index 798e68b157f..749152f234c 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -950,94 +950,3 @@ def pyros_config(): ) return CONFIG - - -def resolve_keyword_arguments(prioritized_kwargs_dicts, func=None): - """ - Resolve the keyword arguments to a callable in the event - the arguments may have been passed in one or more possible - ways. - - A warning-level message is logged (through the default PyROS - logger) in the event an argument is specified in more than one - way. In this case, the value provided through the means with - the highest priority is selected. - - Parameters - ---------- - prioritized_kwargs_dicts : dict - Each entry maps a str to a dict of the keyword arguments - passed via the means described by the str. - Entries of `prioritized_kwargs_dicts` are taken to be - provided in descending order of priority of the means - by which the arguments may have been passed to the callable. - func : callable or None, optional - Callable to which the keyword arguments are/were passed. - Currently, only the `__name__` attribute is used, - for the purpose of logging warning-level messages. - If `None` is passed, then the warning messages - logged are slightly less informative. - - Returns - ------- - resolved_kwargs : dict - Resolved keyword arguments. - """ - # warnings are issued through logger object - default_logger = default_pyros_solver_logger - - # used for warning messages - func_desc = f"passed to {func.__name__}()" if func is not None else "passed" - - # we will loop through the priority dict. initialize: - # - resolved keyword arguments, taking into account the - # priority order and overlap - # - kwarg dicts already processed - # - sequence of kwarg dicts yet to be processed - resolved_kwargs = dict() - prev_prioritized_kwargs_dicts = dict() - remaining_kwargs_dicts = prioritized_kwargs_dicts.copy() - for curr_desc, curr_kwargs in remaining_kwargs_dicts.items(): - overlapping_args = dict() - overlapping_args_set = set() - - for prev_desc, prev_kwargs in prev_prioritized_kwargs_dicts.items(): - # determine overlap between current and previous - # set of kwargs, and remove overlap of current - # and higher priority sets from the result - curr_prev_overlapping_args = ( - set(curr_kwargs.keys()) & set(prev_kwargs.keys()) - ) - overlapping_args_set - if curr_prev_overlapping_args: - # if there is overlap, prepare overlapping args - # for when warning is to be issued - overlapping_args[prev_desc] = curr_prev_overlapping_args - - # update set of args overlapping with higher priority dicts - overlapping_args_set |= curr_prev_overlapping_args - - # ensure kwargs specified in higher priority - # dicts are not overwritten in resolved kwargs - resolved_kwargs.update( - { - kw: val - for kw, val in curr_kwargs.items() - if kw not in overlapping_args_set - } - ) - - # if there are overlaps, log warnings accordingly - # per priority level - for overlap_desc, args_set in overlapping_args.items(): - new_overlapping_args_str = ", ".join(f"{arg!r}" for arg in args_set) - default_logger.warning( - f"Arguments [{new_overlapping_args_str}] passed {curr_desc} " - f"already {func_desc} {overlap_desc}, " - "and will not be overwritten. " - "Consider modifying your arguments to remove the overlap." - ) - - # increment sequence of kwarg dicts already processed - prev_prioritized_kwargs_dicts[curr_desc] = curr_kwargs - - return resolved_kwargs diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 0659ab43a64..314b0c3eac4 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -20,7 +20,7 @@ from pyomo.contrib.pyros.util import time_code from pyomo.common.modeling import unique_component_name from pyomo.opt import SolverFactory -from pyomo.contrib.pyros.config import pyros_config, resolve_keyword_arguments +from pyomo.contrib.pyros.config import pyros_config from pyomo.contrib.pyros.util import ( recast_to_min_obj, add_decision_rule_constraints, @@ -267,22 +267,15 @@ def _resolve_and_validate_pyros_args(self, model, **kwds): ---- This method can be broken down into three steps: - 1. Resolve user arguments based on how they were passed - and order of precedence of the various means by which - they could be passed. - 2. Cast resolved arguments to ConfigDict. Argument-wise + 1. Cast arguments to ConfigDict. Argument-wise validation is performed automatically. - 3. Inter-argument validation. + Note that arguments specified directly take + precedence over arguments specified indirectly + through direct argument 'options'. + 2. Inter-argument validation. """ - options_dict = kwds.pop("options", {}) - resolved_kwds = resolve_keyword_arguments( - prioritized_kwargs_dicts={ - "explicitly": kwds, - "implicitly through argument 'options'": options_dict, - }, - func=self.solve, - ) - config = self.CONFIG(resolved_kwds) + config = self.CONFIG(kwds.pop("options", {})) + config = config(kwds) state_vars = validate_pyros_inputs(model, config) return config, state_vars diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index eaed462a9b3..cc6fde225f3 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -19,7 +19,6 @@ PathLikeOrNone, PositiveIntOrMinusOne, pyros_config, - resolve_keyword_arguments, SolverIterable, SolverResolvable, UncertaintySetDomain, @@ -723,70 +722,5 @@ def test_logger_type(self): standardizer_func(2) -class TestResolveKeywordArguments(unittest.TestCase): - """ - Test keyword argument resolution function works as expected. - """ - - def test_resolve_kwargs_simple_dict(self): - """ - Test resolve kwargs works, simple example - where there is overlap. - """ - explicit_kwargs = dict(arg1=1) - implicit_kwargs_1 = dict(arg1=2, arg2=3) - implicit_kwargs_2 = dict(arg1=4, arg2=4, arg3=5) - - # expected answer - expected_resolved_kwargs = dict(arg1=1, arg2=3, arg3=5) - - # attempt kwargs resolve - with LoggingIntercept(level=logging.WARNING) as LOG: - resolved_kwargs = resolve_keyword_arguments( - prioritized_kwargs_dicts={ - "explicitly": explicit_kwargs, - "implicitly through set 1": implicit_kwargs_1, - "implicitly through set 2": implicit_kwargs_2, - } - ) - - # check kwargs resolved as expected - self.assertEqual( - resolved_kwargs, - expected_resolved_kwargs, - msg="Resolved kwargs do not match expected value.", - ) - - # extract logger warning messages - warning_msgs = LOG.getvalue().split("\n")[:-1] - - self.assertEqual( - len(warning_msgs), 3, msg="Number of warning messages is not as expected." - ) - - # check contents of warning msgs - self.assertRegex( - warning_msgs[0], - expected_regex=( - r"Arguments \['arg1'\] passed implicitly through set 1 " - r"already passed explicitly.*" - ), - ) - self.assertRegex( - warning_msgs[1], - expected_regex=( - r"Arguments \['arg1'\] passed implicitly through set 2 " - r"already passed explicitly.*" - ), - ) - self.assertRegex( - warning_msgs[2], - expected_regex=( - r"Arguments \['arg2'\] passed implicitly through set 2 " - r"already passed implicitly through set 1.*" - ), - ) - - if __name__ == "__main__": unittest.main() diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index a94b4d9d408..59045f3c6b7 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -6409,46 +6409,22 @@ def test_pyros_kwargs_with_overlap(self): global_subsolver = SolverFactory("baron") # Call the PyROS solver - with LoggingIntercept(level=logging.WARNING) as LOG: - results = pyros_solver.solve( - model=m, - first_stage_variables=[m.x1, m.x2], - second_stage_variables=[], - uncertain_params=[m.u1, m.u2], - uncertainty_set=ellipsoid, - local_solver=local_subsolver, - global_solver=global_subsolver, - bypass_local_separation=True, - solve_master_globally=True, - options={ - "objective_focus": ObjectiveType.worst_case, - "solve_master_globally": False, - "max_iter": 1, - "time_limit": 1000, - }, - ) - - # extract warning-level messages. - warning_msgs = LOG.getvalue().split("\n")[:-1] - resolve_kwargs_warning_msgs = [ - msg - for msg in warning_msgs - if msg.startswith("Arguments [") - and "Consider modifying your arguments" in msg - ] - self.assertEqual( - len(resolve_kwargs_warning_msgs), - 1, - msg="Number of warning-level messages not as expected.", - ) - - self.assertRegex( - resolve_kwargs_warning_msgs[0], - expected_regex=( - r"Arguments \['solve_master_globally'\] passed " - r"implicitly through argument 'options' " - r"already passed .*explicitly.*" - ), + results = pyros_solver.solve( + model=m, + first_stage_variables=[m.x1, m.x2], + second_stage_variables=[], + uncertain_params=[m.u1, m.u2], + uncertainty_set=ellipsoid, + local_solver=local_subsolver, + global_solver=global_subsolver, + bypass_local_separation=True, + solve_master_globally=True, + options={ + "objective_focus": ObjectiveType.worst_case, + "solve_master_globally": False, + "max_iter": 1, + "time_limit": 1000, + }, ) # check termination status as expected From d2a3ff14e9d70d118a18a5dca4c728185e8e0a24 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 15 Feb 2024 15:06:02 -0700 Subject: [PATCH 0994/1204] Update documentation; include package needed for sphinx enum tools --- doc/OnlineDocs/developer_reference/solvers.rst | 3 +-- pyomo/contrib/solver/base.py | 13 +++++++++++-- pyomo/contrib/solver/config.py | 2 +- setup.py | 1 + 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index 78344293e39..581d899af50 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -75,7 +75,6 @@ Future Capability Mode model.pprint() - Interface Implementation ------------------------ @@ -88,7 +87,7 @@ All solvers should have the following: .. autoclass:: pyomo.contrib.solver.base.SolverBase :members: -Persistent solvers should also include: +Persistent solvers include additional members as well as other configuration options: .. autoclass:: pyomo.contrib.solver.base.PersistentSolverBase :show-inheritance: diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 1cd9db2baa9..327ad2e01ca 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -19,7 +19,7 @@ from pyomo.core.base.param import _ParamData from pyomo.core.base.block import _BlockData from pyomo.core.base.objective import _GeneralObjectiveData -from pyomo.common.timing import HierarchicalTimer +from pyomo.common.config import document_kwargs_from_configdict from pyomo.common.errors import ApplicationError from pyomo.common.deprecation import deprecation_warning from pyomo.opt.results.results_ import SolverResults as LegacySolverResults @@ -28,7 +28,7 @@ from pyomo.core.base import SymbolMap from pyomo.core.base.label import NumericLabeler from pyomo.core.staleflag import StaleFlagManager -from pyomo.contrib.solver.config import SolverConfig +from pyomo.contrib.solver.config import SolverConfig, PersistentSolverConfig from pyomo.contrib.solver.util import get_objective from pyomo.contrib.solver.results import ( Results, @@ -104,6 +104,7 @@ def __str__(self): # preserve the previous behavior return self.name + @document_kwargs_from_configdict(CONFIG) @abc.abstractmethod def solve(self, model: _BlockData, **kwargs) -> Results: """ @@ -176,6 +177,14 @@ class PersistentSolverBase(SolverBase): Example usage can be seen in the Gurobi interface. """ + CONFIG = PersistentSolverConfig() + + def __init__(self, kwds): + super().__init__(kwds) + + @document_kwargs_from_configdict(CONFIG) + def solve(self, model: _BlockData, **kwargs) -> Results: + super().solve(model, kwargs) def is_persistent(self): """ diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index a1133f93ae4..335307c1bbf 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -64,7 +64,7 @@ def __init__( domain=str, default=None, description="The directory in which generated files should be saved. " - "This replaced the `keepfiles` option.", + "This replaces the `keepfiles` option.", ), ) self.load_solutions: bool = self.declare( diff --git a/setup.py b/setup.py index e2d702db010..27d169af746 100644 --- a/setup.py +++ b/setup.py @@ -253,6 +253,7 @@ def __ne__(self, other): 'sphinx_rtd_theme>0.5', 'sphinxcontrib-jsmath', 'sphinxcontrib-napoleon', + 'enum-tools[sphinx]', 'numpy', # Needed by autodoc for pynumero 'scipy', # Needed by autodoc for pynumero ], From 75da70e77d245866cf865ae7e8fd997769441566 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 15 Feb 2024 15:16:13 -0700 Subject: [PATCH 0995/1204] Fix init; add more descriptive skip messages --- pyomo/contrib/solver/base.py | 5 +- .../solver/tests/solvers/test_solvers.py | 76 +++++++++---------- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 327ad2e01ca..bc4ab725a81 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -179,10 +179,11 @@ class PersistentSolverBase(SolverBase): """ CONFIG = PersistentSolverConfig() - def __init__(self, kwds): - super().__init__(kwds) + def __init__(self, **kwds): + super().__init__(**kwds) @document_kwargs_from_configdict(CONFIG) + @abc.abstractmethod def solve(self, model: _BlockData, **kwargs) -> Results: super().solve(model, kwargs) diff --git a/pyomo/contrib/solver/tests/solvers/test_solvers.py b/pyomo/contrib/solver/tests/solvers/test_solvers.py index 36f3596e890..0393d1adb2e 100644 --- a/pyomo/contrib/solver/tests/solvers/test_solvers.py +++ b/pyomo/contrib/solver/tests/solvers/test_solvers.py @@ -55,7 +55,7 @@ def test_remove_variable_and_objective( # this test is for issue #2888 opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var(bounds=(2, None)) m.obj = pe.Objective(expr=m.x) @@ -75,7 +75,7 @@ def test_remove_variable_and_objective( def test_stale_vars(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -116,7 +116,7 @@ def test_stale_vars(self, name: str, opt_class: Type[SolverBase]): def test_range_constraint(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.obj = pe.Objective(expr=m.x) @@ -137,7 +137,7 @@ def test_range_constraint(self, name: str, opt_class: Type[SolverBase]): def test_reduced_costs(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var(bounds=(-1, 1)) m.y = pe.Var(bounds=(-2, 2)) @@ -159,7 +159,7 @@ def test_reduced_costs(self, name: str, opt_class: Type[SolverBase]): def test_reduced_costs2(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var(bounds=(-1, 1)) m.obj = pe.Objective(expr=m.x) @@ -179,7 +179,7 @@ def test_reduced_costs2(self, name: str, opt_class: Type[SolverBase]): def test_param_changes(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -219,7 +219,7 @@ def test_immutable_param(self, name: str, opt_class: Type[SolverBase]): """ opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -255,7 +255,7 @@ def test_immutable_param(self, name: str, opt_class: Type[SolverBase]): def test_equality(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') if isinstance(opt, ipopt): opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() @@ -293,7 +293,7 @@ def test_equality(self, name: str, opt_class: Type[SolverBase]): def test_linear_expression(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -331,7 +331,7 @@ def test_linear_expression(self, name: str, opt_class: Type[SolverBase]): def test_no_objective(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -362,7 +362,7 @@ def test_no_objective(self, name: str, opt_class: Type[SolverBase]): def test_add_remove_cons(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -416,7 +416,7 @@ def test_add_remove_cons(self, name: str, opt_class: Type[SolverBase]): def test_results_infeasible(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -465,7 +465,7 @@ def test_results_infeasible(self, name: str, opt_class: Type[SolverBase]): def test_duals(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -490,7 +490,7 @@ def test_mutable_quadratic_coefficient( ): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -512,7 +512,7 @@ def test_mutable_quadratic_coefficient( def test_mutable_quadratic_objective(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -542,7 +542,7 @@ def test_fixed_vars(self, name: str, opt_class: Type[SolverBase]): treat_fixed_vars_as_params ) if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.x.fix(0) @@ -580,7 +580,7 @@ def test_fixed_vars_2(self, name: str, opt_class: Type[SolverBase]): if opt.is_persistent(): opt.config.auto_updates.treat_fixed_vars_as_params = True if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.x.fix(0) @@ -618,7 +618,7 @@ def test_fixed_vars_3(self, name: str, opt_class: Type[SolverBase]): if opt.is_persistent(): opt.config.auto_updates.treat_fixed_vars_as_params = True if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -634,7 +634,7 @@ def test_fixed_vars_4(self, name: str, opt_class: Type[SolverBase]): if opt.is_persistent(): opt.config.auto_updates.treat_fixed_vars_as_params = True if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -652,7 +652,7 @@ def test_fixed_vars_4(self, name: str, opt_class: Type[SolverBase]): def test_mutable_param_with_range(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') try: import numpy as np except: @@ -746,7 +746,7 @@ def test_mutable_param_with_range(self, name: str, opt_class: Type[SolverBase]): def test_add_and_remove_vars(self, name: str, opt_class: Type[SolverBase]): opt = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.y = pe.Var(bounds=(-1, None)) m.obj = pe.Objective(expr=m.y) @@ -792,7 +792,7 @@ def test_add_and_remove_vars(self, name: str, opt_class: Type[SolverBase]): def test_exp(self, name: str, opt_class: Type[SolverBase]): opt = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -806,7 +806,7 @@ def test_exp(self, name: str, opt_class: Type[SolverBase]): def test_log(self, name: str, opt_class: Type[SolverBase]): opt = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var(initialize=1) m.y = pe.Var() @@ -820,7 +820,7 @@ def test_log(self, name: str, opt_class: Type[SolverBase]): def test_with_numpy(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -848,7 +848,7 @@ def test_with_numpy(self, name: str, opt_class: Type[SolverBase]): def test_bounds_with_params(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.y = pe.Var() m.p = pe.Param(mutable=True) @@ -880,7 +880,7 @@ def test_bounds_with_params(self, name: str, opt_class: Type[SolverBase]): def test_solution_loader(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var(bounds=(1, None)) m.y = pe.Var() @@ -930,7 +930,7 @@ def test_solution_loader(self, name: str, opt_class: Type[SolverBase]): def test_time_limit(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') from sys import platform if platform == 'win32': @@ -986,7 +986,7 @@ def test_time_limit(self, name: str, opt_class: Type[SolverBase]): def test_objective_changes(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -1050,7 +1050,7 @@ def test_objective_changes(self, name: str, opt_class: Type[SolverBase]): def test_domain(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var(bounds=(1, None), domain=pe.NonNegativeReals) m.obj = pe.Objective(expr=m.x) @@ -1074,7 +1074,7 @@ def test_domain(self, name: str, opt_class: Type[SolverBase]): def test_domain_with_integers(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var(bounds=(-1, None), domain=pe.NonNegativeIntegers) m.obj = pe.Objective(expr=m.x) @@ -1098,7 +1098,7 @@ def test_domain_with_integers(self, name: str, opt_class: Type[SolverBase]): def test_fixed_binaries(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var(domain=pe.Binary) m.y = pe.Var() @@ -1125,7 +1125,7 @@ def test_fixed_binaries(self, name: str, opt_class: Type[SolverBase]): def test_with_gdp(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var(bounds=(-10, 10)) @@ -1156,7 +1156,7 @@ def test_with_gdp(self, name: str, opt_class: Type[SolverBase]): def test_variables_elsewhere(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() @@ -1183,7 +1183,7 @@ def test_variables_elsewhere(self, name: str, opt_class: Type[SolverBase]): def test_variables_elsewhere2(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() @@ -1218,7 +1218,7 @@ def test_variables_elsewhere2(self, name: str, opt_class: Type[SolverBase]): def test_bug_1(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var(bounds=(3, 7)) @@ -1246,7 +1246,7 @@ def test_bug_2(self, name: str, opt_class: Type[SolverBase]): for fixed_var_option in [True, False]: opt: SolverBase = opt_class() if not opt.available(): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') if opt.is_persistent(): opt.config.auto_updates.treat_fixed_vars_as_params = fixed_var_option @@ -1272,7 +1272,7 @@ class TestLegacySolverInterface(unittest.TestCase): def test_param_updates(self, name: str, opt_class: Type[SolverBase]): opt = pe.SolverFactory(name + '_v2') if not opt.available(exception_flag=False): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -1302,7 +1302,7 @@ def test_param_updates(self, name: str, opt_class: Type[SolverBase]): def test_load_solutions(self, name: str, opt_class: Type[SolverBase]): opt = pe.SolverFactory(name + '_v2') if not opt.available(exception_flag=False): - raise unittest.SkipTest + raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pe.ConcreteModel() m.x = pe.Var() m.obj = pe.Objective(expr=m.x) From e7eb1423272e53e984d7ae3ea8541eded92b56e7 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 15 Feb 2024 15:16:36 -0700 Subject: [PATCH 0996/1204] Apply blacl --- pyomo/contrib/solver/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index bc4ab725a81..09c73ab3a9b 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -177,6 +177,7 @@ class PersistentSolverBase(SolverBase): Example usage can be seen in the Gurobi interface. """ + CONFIG = PersistentSolverConfig() def __init__(self, **kwds): From bf4f27018d4948cefc0f44f4e3c39d5ff3848eca Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 15 Feb 2024 15:35:36 -0700 Subject: [PATCH 0997/1204] Small typo; changes enum-tools line --- pyomo/contrib/solver/config.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index 335307c1bbf..d36c7102620 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -89,7 +89,7 @@ def __init__( ConfigValue( domain=bool, default=False, - description="If True, the names given to the solver will reflect the names of the Pyomo components." + description="If True, the names given to the solver will reflect the names of the Pyomo components. " "Cannot be changed after set_instance is called.", ), ) diff --git a/setup.py b/setup.py index 27d169af746..1572910ad89 100644 --- a/setup.py +++ b/setup.py @@ -253,7 +253,7 @@ def __ne__(self, other): 'sphinx_rtd_theme>0.5', 'sphinxcontrib-jsmath', 'sphinxcontrib-napoleon', - 'enum-tools[sphinx]', + 'enum-tools', 'numpy', # Needed by autodoc for pynumero 'scipy', # Needed by autodoc for pynumero ], From eb9b2532cd4045ca0b674a6c5b73503258731b9a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 15 Feb 2024 15:49:19 -0700 Subject: [PATCH 0998/1204] Underscore instead of dash --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1572910ad89..1f0d56c10a7 100644 --- a/setup.py +++ b/setup.py @@ -253,7 +253,7 @@ def __ne__(self, other): 'sphinx_rtd_theme>0.5', 'sphinxcontrib-jsmath', 'sphinxcontrib-napoleon', - 'enum-tools', + 'enum_tools', 'numpy', # Needed by autodoc for pynumero 'scipy', # Needed by autodoc for pynumero ], From f313b0a49c1d8bbb92244029487c8ce57d5e3977 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 15 Feb 2024 15:51:09 -0700 Subject: [PATCH 0999/1204] NFC: apply black --- pyomo/contrib/appsi/plugins.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/pyomo/contrib/appsi/plugins.py b/pyomo/contrib/appsi/plugins.py index cec95337a9b..a765f9a45de 100644 --- a/pyomo/contrib/appsi/plugins.py +++ b/pyomo/contrib/appsi/plugins.py @@ -9,15 +9,13 @@ def load(): SolverFactory.register( name='gurobi', doc='Automated persistent interface to Gurobi' )(Gurobi) - SolverFactory.register( - name='cplex', doc='Automated persistent interface to Cplex' - )(Cplex) - SolverFactory.register( - name='ipopt', doc='Automated persistent interface to Ipopt' - )(Ipopt) - SolverFactory.register( - name='cbc', doc='Automated persistent interface to Cbc' - )(Cbc) - SolverFactory.register( - name='highs', doc='Automated persistent interface to Highs' - )(Highs) + SolverFactory.register(name='cplex', doc='Automated persistent interface to Cplex')( + Cplex + ) + SolverFactory.register(name='ipopt', doc='Automated persistent interface to Ipopt')( + Ipopt + ) + SolverFactory.register(name='cbc', doc='Automated persistent interface to Cbc')(Cbc) + SolverFactory.register(name='highs', doc='Automated persistent interface to Highs')( + Highs + ) From 74971722ca3bd9a8e1aa3d2d8549373a77a0ba6f Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 15 Feb 2024 15:58:03 -0700 Subject: [PATCH 1000/1204] NFC: update copyright on new file (missed by #3139) --- pyomo/__future__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/__future__.py b/pyomo/__future__.py index c614bf6cc04..235143592f1 100644 --- a/pyomo/__future__.py +++ b/pyomo/__future__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 From cfbab706bd472a37f70830d3a3f8371f1be1ada0 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 15 Feb 2024 15:59:22 -0700 Subject: [PATCH 1001/1204] Add in two more deps --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 1f0d56c10a7..dbb5a68ceb2 100644 --- a/setup.py +++ b/setup.py @@ -253,6 +253,8 @@ def __ne__(self, other): 'sphinx_rtd_theme>0.5', 'sphinxcontrib-jsmath', 'sphinxcontrib-napoleon', + 'sphinx-toolbox>=2.16.0', + 'sphinx-jinja2-compat>=0.1.1', 'enum_tools', 'numpy', # Needed by autodoc for pynumero 'scipy', # Needed by autodoc for pynumero From 0330e095f681d368a0bb50213bf4347575248b6b Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 15 Feb 2024 16:06:03 -0700 Subject: [PATCH 1002/1204] NFC: fix doc formatting --- pyomo/__future__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/__future__.py b/pyomo/__future__.py index 235143592f1..0dc22cca0a7 100644 --- a/pyomo/__future__.py +++ b/pyomo/__future__.py @@ -43,9 +43,9 @@ def solver_factory(version=None): This allows users to query / set the current implementation of the SolverFactory that should be used throughout Pyomo. Valid options are: - 1: the original Pyomo SolverFactor - 2: the SolverFactory from APPSI - 3: the SolverFactory from pyomo.contrib.solver + - ``1``: the original Pyomo SolverFactor + - ``2``: the SolverFactory from APPSI + - ``3``: the SolverFactory from pyomo.contrib.solver The current active version can be obtained by calling the method with no arguments From b3c4b66bf0b78aa8a69d7bb30f9c7991dee1f147 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 15 Feb 2024 16:07:55 -0700 Subject: [PATCH 1003/1204] Add missing doc file --- doc/OnlineDocs/developer_reference/future.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 doc/OnlineDocs/developer_reference/future.rst diff --git a/doc/OnlineDocs/developer_reference/future.rst b/doc/OnlineDocs/developer_reference/future.rst new file mode 100644 index 00000000000..531c0fdb5c6 --- /dev/null +++ b/doc/OnlineDocs/developer_reference/future.rst @@ -0,0 +1,3 @@ + +.. automodule:: pyomo.__future__ + :noindex: From f45201a3209a52979f43168c2af5ddaa36bf3ab6 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 15 Feb 2024 16:15:41 -0700 Subject: [PATCH 1004/1204] NFC: additional doc formatting --- pyomo/__future__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/__future__.py b/pyomo/__future__.py index 0dc22cca0a7..a2e08ccf291 100644 --- a/pyomo/__future__.py +++ b/pyomo/__future__.py @@ -12,15 +12,15 @@ import pyomo.environ as _environ __doc__ = """ -Preview capabilities through `pyomo.__future__` -=============================================== +Preview capabilities through ``pyomo.__future__`` +================================================= This module provides a uniform interface for gaining access to future ("preview") capabilities that are either slightly incompatible with the current official offering, or are still under development with the intent to replace the current offering. -Currently supported `__future__` offerings include: +Currently supported ``__future__`` offerings include: .. autosummary:: From 9157b8c048a26aef8b3637f979a16d01c3768cf1 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 15 Feb 2024 16:29:59 -0700 Subject: [PATCH 1005/1204] Update documentation to reflect the new preview page --- .../developer_reference/solvers.rst | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index 581d899af50..db9fe307b18 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -13,20 +13,23 @@ New Interface Usage ------------------- The new interfaces have two modes: backwards compatible and future capability. -To use the backwards compatible version, simply use the ``SolverFactory`` -as usual and replace the solver name with the new version. Currently, the new -versions available are: +The future capability mode can be accessed directly or by switching the default +``SolverFactory`` version (see :doc:`future`). Currently, the new versions +available are: .. list-table:: Available Redesigned Solvers - :widths: 25 25 + :widths: 25 25 25 :header-rows: 1 * - Solver - - ``SolverFactory`` Name + - ``SolverFactory``([1]) Name + - ``SolverFactory``([3]) Name * - ipopt - ``ipopt_v2`` - * - GUROBI + - ``ipopt`` + * - Gurobi - ``gurobi_v2`` + - ``gurobi`` Backwards Compatible Mode ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -52,8 +55,12 @@ Backwards Compatible Mode Future Capability Mode ^^^^^^^^^^^^^^^^^^^^^^ +There are multiple ways to utilize the future compatibility mode: direct import +or changed ``SolverFactory`` version. + .. code-block:: python + # Direct import import pyomo.environ as pyo from pyomo.contrib.solver.util import assert_optimal_termination from pyomo.contrib.solver.ipopt import ipopt @@ -74,6 +81,29 @@ Future Capability Mode status.display() model.pprint() +Changing the ``SolverFactory`` version: + +.. code-block:: python + + # Change SolverFactory version + import pyomo.environ as pyo + from pyomo.contrib.solver.util import assert_optimal_termination + from pyomo.__future__ import solver_factory_v3 + + model = pyo.ConcreteModel() + model.x = pyo.Var(initialize=1.5) + model.y = pyo.Var(initialize=1.5) + + def rosenbrock(model): + return (1.0 - model.x) ** 2 + 100.0 * (model.y - model.x**2) ** 2 + + model.obj = pyo.Objective(rule=rosenbrock, sense=pyo.minimize) + + status = pyo.SolverFactory('ipopt').solve(model) + assert_optimal_termination(status) + # Displays important results information; only available in future capability mode + status.display() + model.pprint() Interface Implementation ------------------------ From 8e56d4e0acdb36821bd96f39624f88e21f01fd93 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 15 Feb 2024 16:41:52 -0700 Subject: [PATCH 1006/1204] Doc formatting fix --- doc/OnlineDocs/developer_reference/solvers.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index db9fe307b18..f0f2a574331 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -22,8 +22,8 @@ available are: :header-rows: 1 * - Solver - - ``SolverFactory``([1]) Name - - ``SolverFactory``([3]) Name + - ``SolverFactory`` ([1]) Name + - ``SolverFactory`` ([3]) Name * - ipopt - ``ipopt_v2`` - ``ipopt`` From ae28ceea6f4b255242d58847e0c92bba5536a87d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 16 Feb 2024 07:29:27 -0700 Subject: [PATCH 1007/1204] Update copyright year on all solver files --- pyomo/contrib/solver/__init__.py | 2 +- pyomo/contrib/solver/base.py | 2 +- pyomo/contrib/solver/config.py | 2 +- pyomo/contrib/solver/factory.py | 2 +- pyomo/contrib/solver/gurobi.py | 2 +- pyomo/contrib/solver/ipopt.py | 2 +- pyomo/contrib/solver/plugins.py | 2 +- pyomo/contrib/solver/results.py | 2 +- pyomo/contrib/solver/sol_reader.py | 2 +- pyomo/contrib/solver/solution.py | 2 +- pyomo/contrib/solver/tests/__init__.py | 2 +- pyomo/contrib/solver/tests/solvers/__init__.py | 2 +- pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py | 2 +- pyomo/contrib/solver/tests/solvers/test_ipopt.py | 2 +- pyomo/contrib/solver/tests/solvers/test_solvers.py | 2 +- pyomo/contrib/solver/tests/unit/__init__.py | 2 +- pyomo/contrib/solver/tests/unit/sol_files/__init__.py | 2 +- pyomo/contrib/solver/tests/unit/test_base.py | 2 +- pyomo/contrib/solver/tests/unit/test_config.py | 2 +- pyomo/contrib/solver/tests/unit/test_results.py | 2 +- pyomo/contrib/solver/tests/unit/test_sol_reader.py | 2 +- pyomo/contrib/solver/tests/unit/test_solution.py | 2 +- pyomo/contrib/solver/tests/unit/test_util.py | 2 +- pyomo/contrib/solver/util.py | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) diff --git a/pyomo/contrib/solver/__init__.py b/pyomo/contrib/solver/__init__.py index e3eafa991cc..2dc73091ea2 100644 --- a/pyomo/contrib/solver/__init__.py +++ b/pyomo/contrib/solver/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 09c73ab3a9b..a60e770e660 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index d36c7102620..d13e1caf81d 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/factory.py b/pyomo/contrib/solver/factory.py index 52fd9e51236..91ce92a9dee 100644 --- a/pyomo/contrib/solver/factory.py +++ b/pyomo/contrib/solver/factory.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/gurobi.py b/pyomo/contrib/solver/gurobi.py index 919e7ae3995..c1b02c08ef9 100644 --- a/pyomo/contrib/solver/gurobi.py +++ b/pyomo/contrib/solver/gurobi.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index f70cbb5f194..ff809a146c1 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/plugins.py b/pyomo/contrib/solver/plugins.py index 7d984d10eaa..cb089200100 100644 --- a/pyomo/contrib/solver/plugins.py +++ b/pyomo/contrib/solver/plugins.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/results.py b/pyomo/contrib/solver/results.py index e80bad126a1..b330773e4f3 100644 --- a/pyomo/contrib/solver/results.py +++ b/pyomo/contrib/solver/results.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/sol_reader.py b/pyomo/contrib/solver/sol_reader.py index c4497516de2..2817dab4516 100644 --- a/pyomo/contrib/solver/sol_reader.py +++ b/pyomo/contrib/solver/sol_reader.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/solution.py b/pyomo/contrib/solver/solution.py index d4069b5b5a1..31792a76dfe 100644 --- a/pyomo/contrib/solver/solution.py +++ b/pyomo/contrib/solver/solution.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/tests/__init__.py b/pyomo/contrib/solver/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/solver/tests/__init__.py +++ b/pyomo/contrib/solver/tests/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/tests/solvers/__init__.py b/pyomo/contrib/solver/tests/solvers/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/solver/tests/solvers/__init__.py +++ b/pyomo/contrib/solver/tests/solvers/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py b/pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py index d4c0078a0df..f2dd79619b4 100644 --- a/pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py +++ b/pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/tests/solvers/test_ipopt.py b/pyomo/contrib/solver/tests/solvers/test_ipopt.py index 627d502629c..2886045055c 100644 --- a/pyomo/contrib/solver/tests/solvers/test_ipopt.py +++ b/pyomo/contrib/solver/tests/solvers/test_ipopt.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/tests/solvers/test_solvers.py b/pyomo/contrib/solver/tests/solvers/test_solvers.py index 0393d1adb2e..e5af2ada170 100644 --- a/pyomo/contrib/solver/tests/solvers/test_solvers.py +++ b/pyomo/contrib/solver/tests/solvers/test_solvers.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/tests/unit/__init__.py b/pyomo/contrib/solver/tests/unit/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/solver/tests/unit/__init__.py +++ b/pyomo/contrib/solver/tests/unit/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/tests/unit/sol_files/__init__.py b/pyomo/contrib/solver/tests/unit/sol_files/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/solver/tests/unit/sol_files/__init__.py +++ b/pyomo/contrib/solver/tests/unit/sol_files/__init__.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/tests/unit/test_base.py b/pyomo/contrib/solver/tests/unit/test_base.py index b8d5c79fc0f..5fecd012cda 100644 --- a/pyomo/contrib/solver/tests/unit/test_base.py +++ b/pyomo/contrib/solver/tests/unit/test_base.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/tests/unit/test_config.py b/pyomo/contrib/solver/tests/unit/test_config.py index f28dd5fcedf..354cfd8a37a 100644 --- a/pyomo/contrib/solver/tests/unit/test_config.py +++ b/pyomo/contrib/solver/tests/unit/test_config.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/tests/unit/test_results.py b/pyomo/contrib/solver/tests/unit/test_results.py index 7b9de32bc00..2d8f6460448 100644 --- a/pyomo/contrib/solver/tests/unit/test_results.py +++ b/pyomo/contrib/solver/tests/unit/test_results.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/tests/unit/test_sol_reader.py b/pyomo/contrib/solver/tests/unit/test_sol_reader.py index 0ab94dfc4ac..d5602945e07 100644 --- a/pyomo/contrib/solver/tests/unit/test_sol_reader.py +++ b/pyomo/contrib/solver/tests/unit/test_sol_reader.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/tests/unit/test_solution.py b/pyomo/contrib/solver/tests/unit/test_solution.py index 7a18344d4cb..a5ee8a9e391 100644 --- a/pyomo/contrib/solver/tests/unit/test_solution.py +++ b/pyomo/contrib/solver/tests/unit/test_solution.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/tests/unit/test_util.py b/pyomo/contrib/solver/tests/unit/test_util.py index ab8a778067f..f2e8ee707f4 100644 --- a/pyomo/contrib/solver/tests/unit/test_util.py +++ b/pyomo/contrib/solver/tests/unit/test_util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 diff --git a/pyomo/contrib/solver/util.py b/pyomo/contrib/solver/util.py index af856eab7e2..d104022692e 100644 --- a/pyomo/contrib/solver/util.py +++ b/pyomo/contrib/solver/util.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 From 23bdbf7b76f674b5d437a3136387e28a84844086 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 16 Feb 2024 07:37:30 -0700 Subject: [PATCH 1008/1204] Add hidden doctest to reset solver factory --- pyomo/__future__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyomo/__future__.py b/pyomo/__future__.py index a2e08ccf291..87b1d4e77b3 100644 --- a/pyomo/__future__.py +++ b/pyomo/__future__.py @@ -70,6 +70,11 @@ def solver_factory(version=None): >>> from pyomo.__future__ import solver_factory_v3 + .. doctest:: + :hide: + + >>> from pyomo.__future__ import solver_factory_v1 + """ import pyomo.opt.base.solvers as _solvers import pyomo.contrib.solver.factory as _contrib From d405bcb4ed379cd0f61c4dd0f7901019ff742810 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 16 Feb 2024 09:01:05 -0700 Subject: [PATCH 1009/1204] Add missing dep for windows/conda enum_tools --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- doc/OnlineDocs/developer_reference/solvers.rst | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index e5513d25975..77f47b505ff 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -75,7 +75,7 @@ jobs: python: 3.9 TARGET: win PYENV: conda - PACKAGES: glpk pytest-qt + PACKAGES: glpk pytest-qt filelock - os: ubuntu-latest python: '3.11' diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index c5028606c17..87d6aa4d7a8 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -76,7 +76,7 @@ jobs: - os: windows-latest TARGET: win PYENV: conda - PACKAGES: glpk pytest-qt + PACKAGES: glpk pytest-qt filelock - os: ubuntu-latest python: '3.11' diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index f0f2a574331..5f6f3fc547b 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -22,8 +22,8 @@ available are: :header-rows: 1 * - Solver - - ``SolverFactory`` ([1]) Name - - ``SolverFactory`` ([3]) Name + - ``SolverFactory`` (v1) Name + - ``SolverFactory`` (v3) Name * - ipopt - ``ipopt_v2`` - ``ipopt`` From a3f1f826bbf186975751a0f28cd1e46152a4ee9c Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 16 Feb 2024 12:04:43 -0500 Subject: [PATCH 1010/1204] Extend range of support of `common.config.Path` --- pyomo/common/config.py | 2 +- pyomo/common/tests/test_config.py | 125 ++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 1 deletion(-) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index 15f15872fc6..000cd76de80 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -454,7 +454,7 @@ def __init__(self, basePath=None, expandPath=None): self.expandPath = expandPath def __call__(self, path): - path = str(path) + path = os.fsdecode(path) _expand = self.expandPath if _expand is None: _expand = not Path.SuppressPathExpansion diff --git a/pyomo/common/tests/test_config.py b/pyomo/common/tests/test_config.py index 1b732d86c0a..fd70e36397e 100644 --- a/pyomo/common/tests/test_config.py +++ b/pyomo/common/tests/test_config.py @@ -454,6 +454,17 @@ def norm(x): x = cwd[:2] + x return x.replace('/', os.path.sep) + class ExamplePathLike: + def __init__(self, path_str_or_bytes): + self.path = path_str_or_bytes + + def __fspath__(self): + return self.path + + def __str__(self): + path_str = str(self.path) + return f"{type(self).__name__}({path_str})" + cwd = os.getcwd() + os.path.sep c = ConfigDict() @@ -462,12 +473,30 @@ def norm(x): c.a = "/a/b/c" self.assertTrue(os.path.sep in c.a) self.assertEqual(c.a, norm('/a/b/c')) + c.a = b"/a/b/c" + self.assertTrue(os.path.sep in c.a) + self.assertEqual(c.a, norm('/a/b/c')) + c.a = ExamplePathLike("/a/b/c") + self.assertTrue(os.path.sep in c.a) + self.assertEqual(c.a, norm('/a/b/c')) c.a = "a/b/c" self.assertTrue(os.path.sep in c.a) self.assertEqual(c.a, norm(cwd + 'a/b/c')) + c.a = b'a/b/c' + self.assertTrue(os.path.sep in c.a) + self.assertEqual(c.a, norm(cwd + 'a/b/c')) + c.a = ExamplePathLike('a/b/c') + self.assertTrue(os.path.sep in c.a) + self.assertEqual(c.a, norm(cwd + 'a/b/c')) c.a = "${CWD}/a/b/c" self.assertTrue(os.path.sep in c.a) self.assertEqual(c.a, norm(cwd + 'a/b/c')) + c.a = b'${CWD}/a/b/c' + self.assertTrue(os.path.sep in c.a) + self.assertEqual(c.a, norm(cwd + 'a/b/c')) + c.a = ExamplePathLike('${CWD}/a/b/c') + self.assertTrue(os.path.sep in c.a) + self.assertEqual(c.a, norm(cwd + 'a/b/c')) c.a = None self.assertIs(c.a, None) @@ -476,12 +505,30 @@ def norm(x): c.b = "/a/b/c" self.assertTrue(os.path.sep in c.b) self.assertEqual(c.b, norm('/a/b/c')) + c.b = b"/a/b/c" + self.assertTrue(os.path.sep in c.b) + self.assertEqual(c.b, norm('/a/b/c')) + c.b = ExamplePathLike("/a/b/c") + self.assertTrue(os.path.sep in c.b) + self.assertEqual(c.b, norm('/a/b/c')) c.b = "a/b/c" self.assertTrue(os.path.sep in c.b) self.assertEqual(c.b, norm(cwd + 'rel/path/a/b/c')) + c.b = b"a/b/c" + self.assertTrue(os.path.sep in c.b) + self.assertEqual(c.b, norm(cwd + 'rel/path/a/b/c')) + c.b = ExamplePathLike("a/b/c") + self.assertTrue(os.path.sep in c.b) + self.assertEqual(c.b, norm(cwd + "rel/path/a/b/c")) c.b = "${CWD}/a/b/c" self.assertTrue(os.path.sep in c.b) self.assertEqual(c.b, norm(cwd + 'a/b/c')) + c.b = b"${CWD}/a/b/c" + self.assertTrue(os.path.sep in c.b) + self.assertEqual(c.b, norm(cwd + 'a/b/c')) + c.b = ExamplePathLike("${CWD}/a/b/c") + self.assertTrue(os.path.sep in c.b) + self.assertEqual(c.b, norm(cwd + 'a/b/c')) c.b = None self.assertIs(c.b, None) @@ -490,12 +537,30 @@ def norm(x): c.c = "/a/b/c" self.assertTrue(os.path.sep in c.c) self.assertEqual(c.c, norm('/a/b/c')) + c.c = b"/a/b/c" + self.assertTrue(os.path.sep in c.c) + self.assertEqual(c.c, norm('/a/b/c')) + c.c = ExamplePathLike("/a/b/c") + self.assertTrue(os.path.sep in c.c) + self.assertEqual(c.c, norm('/a/b/c')) c.c = "a/b/c" self.assertTrue(os.path.sep in c.c) self.assertEqual(c.c, norm('/my/dir/a/b/c')) + c.c = b"a/b/c" + self.assertTrue(os.path.sep in c.c) + self.assertEqual(c.c, norm('/my/dir/a/b/c')) + c.c = ExamplePathLike("a/b/c") + self.assertTrue(os.path.sep in c.c) + self.assertEqual(c.c, norm("/my/dir/a/b/c")) c.c = "${CWD}/a/b/c" self.assertTrue(os.path.sep in c.c) self.assertEqual(c.c, norm(cwd + 'a/b/c')) + c.c = b"${CWD}/a/b/c" + self.assertTrue(os.path.sep in c.c) + self.assertEqual(c.c, norm(cwd + 'a/b/c')) + c.c = ExamplePathLike("${CWD}/a/b/c") + self.assertTrue(os.path.sep in c.c) + self.assertEqual(c.c, norm(cwd + 'a/b/c')) c.c = None self.assertIs(c.c, None) @@ -505,12 +570,30 @@ def norm(x): c.d = "/a/b/c" self.assertTrue(os.path.sep in c.d) self.assertEqual(c.d, norm('/a/b/c')) + c.d = b"/a/b/c" + self.assertTrue(os.path.sep in c.d) + self.assertEqual(c.d, norm('/a/b/c')) + c.d = ExamplePathLike("/a/b/c") + self.assertTrue(os.path.sep in c.d) + self.assertEqual(c.d, norm('/a/b/c')) c.d = "a/b/c" self.assertTrue(os.path.sep in c.d) self.assertEqual(c.d, norm(cwd + 'a/b/c')) + c.d = b"a/b/c" + self.assertTrue(os.path.sep in c.d) + self.assertEqual(c.d, norm(cwd + 'a/b/c')) + c.d = ExamplePathLike("a/b/c") + self.assertTrue(os.path.sep in c.d) + self.assertEqual(c.d, norm(cwd + 'a/b/c')) c.d = "${CWD}/a/b/c" self.assertTrue(os.path.sep in c.d) self.assertEqual(c.d, norm(cwd + 'a/b/c')) + c.d = b"${CWD}/a/b/c" + self.assertTrue(os.path.sep in c.d) + self.assertEqual(c.d, norm(cwd + 'a/b/c')) + c.d = ExamplePathLike("${CWD}/a/b/c") + self.assertTrue(os.path.sep in c.d) + self.assertEqual(c.d, norm(cwd + 'a/b/c')) c.d_base = '/my/dir' c.d = "/a/b/c" @@ -527,12 +610,30 @@ def norm(x): c.d = "/a/b/c" self.assertTrue(os.path.sep in c.d) self.assertEqual(c.d, norm('/a/b/c')) + c.d = b"/a/b/c" + self.assertTrue(os.path.sep in c.d) + self.assertEqual(c.d, norm('/a/b/c')) + c.d = ExamplePathLike("/a/b/c") + self.assertTrue(os.path.sep in c.d) + self.assertEqual(c.d, norm('/a/b/c')) c.d = "a/b/c" self.assertTrue(os.path.sep in c.d) self.assertEqual(c.d, norm(cwd + 'rel/path/a/b/c')) + c.d = b"a/b/c" + self.assertTrue(os.path.sep in c.d) + self.assertEqual(c.d, norm(cwd + 'rel/path/a/b/c')) + c.d = ExamplePathLike("a/b/c") + self.assertTrue(os.path.sep in c.d) + self.assertEqual(c.d, norm(cwd + 'rel/path/a/b/c')) c.d = "${CWD}/a/b/c" self.assertTrue(os.path.sep in c.d) self.assertEqual(c.d, norm(cwd + 'a/b/c')) + c.d = b"${CWD}/a/b/c" + self.assertTrue(os.path.sep in c.d) + self.assertEqual(c.d, norm(cwd + 'a/b/c')) + c.d = ExamplePathLike("${CWD}/a/b/c") + self.assertTrue(os.path.sep in c.d) + self.assertEqual(c.d, norm(cwd + 'a/b/c')) try: Path.SuppressPathExpansion = True @@ -540,14 +641,38 @@ def norm(x): self.assertTrue('/' in c.d) self.assertTrue('\\' not in c.d) self.assertEqual(c.d, '/a/b/c') + c.d = b"/a/b/c" + self.assertTrue('/' in c.d) + self.assertTrue('\\' not in c.d) + self.assertEqual(c.d, '/a/b/c') + c.d = ExamplePathLike("/a/b/c") + self.assertTrue('/' in c.d) + self.assertTrue('\\' not in c.d) + self.assertEqual(c.d, '/a/b/c') c.d = "a/b/c" self.assertTrue('/' in c.d) self.assertTrue('\\' not in c.d) self.assertEqual(c.d, 'a/b/c') + c.d = b"a/b/c" + self.assertTrue('/' in c.d) + self.assertTrue('\\' not in c.d) + self.assertEqual(c.d, 'a/b/c') + c.d = ExamplePathLike("a/b/c") + self.assertTrue('/' in c.d) + self.assertTrue('\\' not in c.d) + self.assertEqual(c.d, 'a/b/c') c.d = "${CWD}/a/b/c" self.assertTrue('/' in c.d) self.assertTrue('\\' not in c.d) self.assertEqual(c.d, "${CWD}/a/b/c") + c.d = b"${CWD}/a/b/c" + self.assertTrue('/' in c.d) + self.assertTrue('\\' not in c.d) + self.assertEqual(c.d, "${CWD}/a/b/c") + c.d = ExamplePathLike("${CWD}/a/b/c") + self.assertTrue('/' in c.d) + self.assertTrue('\\' not in c.d) + self.assertEqual(c.d, "${CWD}/a/b/c") finally: Path.SuppressPathExpansion = False From e2965167b214c01f199065f15688b35dcd642eb2 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 16 Feb 2024 13:05:43 -0500 Subject: [PATCH 1011/1204] Add `IsInstance` domain validator to `common.config` --- pyomo/common/config.py | 40 +++++++++++++++++++++++++++++++ pyomo/common/tests/test_config.py | 37 ++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index 000cd76de80..baf07b39fdf 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -302,6 +302,46 @@ def domain_name(self): return f'InEnum[{self._domain.__name__}]' +class IsInstance(object): + def __init__(self, *bases): + assert bases + self.baseClasses = bases + + @staticmethod + def _fullname(klass): + """ + Get full name of class, including appropriate module qualifier. + """ + module_name = klass.__module__ + module_qual = "" if module_name == "builtins" else f"{module_name}." + return f"{module_qual}{klass.__name__}" + + def __call__(self, obj): + if isinstance(obj, self.baseClasses): + return obj + if len(self.baseClasses) > 1: + class_names = ", ".join( + f"{self._fullname(kls)!r}" for kls in self.baseClasses + ) + msg = ( + "Expected an instance of one of these types: " + f"{class_names}, but received value {obj!r} of type " + f"{self._fullname(type(obj))!r}" + ) + else: + msg = ( + f"Expected an instance of " + f"{self._fullname(self.baseClasses[0])!r}, " + f"but received value {obj!r} of type {self._fullname(type(obj))!r}" + ) + raise ValueError(msg) + + def domain_name(self): + return ( + f"IsInstance({', '.join(self._fullname(kls) for kls in self.baseClasses)})" + ) + + class ListOf(object): """Domain validator for lists of a specified type diff --git a/pyomo/common/tests/test_config.py b/pyomo/common/tests/test_config.py index fd70e36397e..19d4bfbe7e8 100644 --- a/pyomo/common/tests/test_config.py +++ b/pyomo/common/tests/test_config.py @@ -60,6 +60,7 @@ def yaml_load(arg): NonPositiveFloat, NonNegativeFloat, In, + IsInstance, ListOf, Module, Path, @@ -448,6 +449,42 @@ class TestEnum(enum.Enum): with self.assertRaisesRegex(ValueError, '.*invalid value'): cfg.enum = 'ITEM_THREE' + def test_IsInstance(self): + c = ConfigDict() + c.declare("val", ConfigValue(None, IsInstance(int))) + c.val = 1 + self.assertEqual(c.val, 1) + exc_str = ( + "Expected an instance of 'int', but received value 2.4 of type 'float'" + ) + with self.assertRaisesRegex(ValueError, exc_str): + c.val = 2.4 + + class TestClass: + def __repr__(self): + return f"{TestClass.__name__}()" + + c.declare("val2", ConfigValue(None, IsInstance(TestClass))) + testinst = TestClass() + c.val2 = testinst + self.assertEqual(c.val2, testinst) + exc_str = ( + r"Expected an instance of '.*\.TestClass', " + "but received value 2.4 of type 'float'" + ) + with self.assertRaisesRegex(ValueError, exc_str): + c.val2 = 2.4 + + c.declare("val3", ConfigValue(None, IsInstance(int, str))) + c.val3 = 2 + self.assertEqual(c.val3, 2) + exc_str = ( + r"Expected an instance of one of these types: 'int', 'str'" + r", but received value TestClass\(\) of type '.*\.TestClass'" + ) + with self.assertRaisesRegex(ValueError, exc_str): + c.val3 = TestClass() + def test_Path(self): def norm(x): if cwd[1] == ':' and x[0] == '/': From 02796dd0cebd1c6ec6ae9d120cb7b85f296b4139 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 16 Feb 2024 13:21:13 -0500 Subject: [PATCH 1012/1204] Add `IsInstance` domain validator for type checking --- pyomo/common/config.py | 10 ++++++++++ pyomo/common/tests/test_config.py | 11 +++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index baf07b39fdf..d812363bdec 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -303,6 +303,15 @@ def domain_name(self): class IsInstance(object): + """ + Domain validator for type checking. + + Parameters + ---------- + *bases : tuple of type + Valid types. + """ + def __init__(self, *bases): assert bases self.baseClasses = bases @@ -749,6 +758,7 @@ def from_enum_or_string(cls, arg): NonNegativeFloat In InEnum + IsInstance ListOf Module Path diff --git a/pyomo/common/tests/test_config.py b/pyomo/common/tests/test_config.py index 19d4bfbe7e8..b5acf51ba46 100644 --- a/pyomo/common/tests/test_config.py +++ b/pyomo/common/tests/test_config.py @@ -475,15 +475,18 @@ def __repr__(self): with self.assertRaisesRegex(ValueError, exc_str): c.val2 = 2.4 - c.declare("val3", ConfigValue(None, IsInstance(int, str))) + c.declare("val3", ConfigValue(None, IsInstance(int, TestClass))) + self.assertRegex( + c.get("val3").domain_name(), r"IsInstance\(int, .*\.TestClass\)" + ) c.val3 = 2 self.assertEqual(c.val3, 2) exc_str = ( - r"Expected an instance of one of these types: 'int', 'str'" - r", but received value TestClass\(\) of type '.*\.TestClass'" + r"Expected an instance of one of these types: 'int', '.*\.TestClass'" + r", but received value 2.4 of type 'float'" ) with self.assertRaisesRegex(ValueError, exc_str): - c.val3 = TestClass() + c.val3 = 2.4 def test_Path(self): def norm(x): From 832a789cd0c8858d7c0c6616419288bacf794643 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 16 Feb 2024 11:29:36 -0700 Subject: [PATCH 1013/1204] Add minimal example for presolve and scaling to docs --- .../developer_reference/solvers.rst | 39 +++++++++++++++++++ pyomo/contrib/solver/ipopt.py | 25 +++++++++--- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index 5f6f3fc547b..8c8c9e5b8ee 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -105,6 +105,45 @@ Changing the ``SolverFactory`` version: status.display() model.pprint() +Linear Presolve and Scaling +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The new interface will allow for direct manipulation of linear presolve and scaling +options for certain solvers. Currently, these options are only available for +``ipopt``. + +.. autoclass:: pyomo.contrib.solver.ipopt.ipopt + :members: solve + +The ``writer_config`` configuration option can be used to manipulate presolve +and scaling options: + +.. code-block:: python + + >>> from pyomo.contrib.solver.ipopt import ipopt + >>> opt = ipopt() + >>> opt.config.writer_config.display() + + show_section_timing: false + skip_trivial_constraints: true + file_determinism: FileDeterminism.ORDERED + symbolic_solver_labels: false + scale_model: true + export_nonlinear_variables: None + row_order: None + column_order: None + export_defined_variables: true + linear_presolve: true + +Note that, by default, both ``linear_presolve`` and ``scale_model`` are enabled. +Users can manipulate ``linear_presolve`` and ``scale_model`` to their preferred +states by changing their values. + +.. code-block:: python + + >>> opt.config.writer_config.linear_presolve = False + + Interface Implementation ------------------------ diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index ff809a146c1..edea4e693b4 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -17,7 +17,12 @@ from typing import Mapping, Optional, Sequence from pyomo.common import Executable -from pyomo.common.config import ConfigValue, NonNegativeFloat +from pyomo.common.config import ( + ConfigValue, + NonNegativeFloat, + document_kwargs_from_configdict, + ConfigDict, +) from pyomo.common.errors import PyomoException from pyomo.common.tempfiles import TempfileManager from pyomo.common.timing import HierarchicalTimer @@ -65,11 +70,20 @@ def __init__( visibility=visibility, ) - self.executable = self.declare( - 'executable', ConfigValue(default=Executable('ipopt')) + self.executable: Executable = self.declare( + 'executable', + ConfigValue( + default=Executable('ipopt'), + description="Preferred executable for ipopt. Defaults to searching the " + "``PATH`` for the first available ``ipopt``.", + ), ) - self.writer_config = self.declare( - 'writer_config', ConfigValue(default=NLWriter.CONFIG()) + self.writer_config: ConfigDict = self.declare( + 'writer_config', + ConfigValue( + default=NLWriter.CONFIG(), + description="For the manipulation of NL writer options.", + ), ) @@ -270,6 +284,7 @@ def _create_command_line(self, basename: str, config: ipoptConfig, opt_file: boo cmd.append(str(k) + '=' + str(val)) return cmd + @document_kwargs_from_configdict(CONFIG) def solve(self, model, **kwds): # Begin time tracking start_timestamp = datetime.datetime.now(datetime.timezone.utc) From f43a49e105a14d04756e1d510e1fbf8aa5a4e138 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 16 Feb 2024 14:51:59 -0700 Subject: [PATCH 1014/1204] Remove __all__ from modules --- pyomo/common/_command.py | 2 -- pyomo/contrib/pynumero/interfaces/ampl_nlp.py | 4 +--- pyomo/contrib/pynumero/interfaces/nlp.py | 3 +-- .../contrib/pynumero/interfaces/pyomo_nlp.py | 3 --- pyomo/contrib/pynumero/sparse/block_matrix.py | 2 -- pyomo/contrib/pynumero/sparse/block_vector.py | 2 -- .../pynumero/sparse/mpi_block_matrix.py | 2 -- .../pynumero/sparse/mpi_block_vector.py | 2 -- pyomo/core/base/PyomoModel.py | 7 ++---- pyomo/core/base/action.py | 5 ++-- pyomo/core/base/block.py | 14 ----------- pyomo/core/base/blockutil.py | 2 -- pyomo/core/base/check.py | 2 -- pyomo/core/base/component_order.py | 3 --- pyomo/core/base/connector.py | 3 --- pyomo/core/base/constraint.py | 10 -------- pyomo/core/base/expression.py | 5 +--- pyomo/core/base/external.py | 2 -- pyomo/core/base/indexed_component.py | 8 +------ pyomo/core/base/instance2dat.py | 2 -- pyomo/core/base/label.py | 11 --------- pyomo/core/base/logical_constraint.py | 3 --- pyomo/core/base/misc.py | 4 ---- pyomo/core/base/objective.py | 10 -------- pyomo/core/base/param.py | 2 -- pyomo/core/base/piecewise.py | 3 --- pyomo/core/base/plugin.py | 24 ------------------- pyomo/core/base/rangeset.py | 2 -- pyomo/core/base/sets.py | 2 -- pyomo/core/base/sos.py | 2 -- pyomo/core/base/suffix.py | 2 -- pyomo/core/base/var.py | 5 ---- pyomo/core/beta/dict_objects.py | 2 -- pyomo/core/beta/list_objects.py | 2 -- pyomo/core/expr/__init__.py | 8 ------- pyomo/core/expr/numvalue.py | 17 ------------- pyomo/core/util.py | 12 ---------- pyomo/dae/contset.py | 1 - pyomo/dae/diffvar.py | 2 -- pyomo/dae/integral.py | 2 -- pyomo/dae/simulator.py | 12 ++++------ pyomo/dataportal/DataPortal.py | 2 -- pyomo/dataportal/TableData.py | 2 -- pyomo/dataportal/factory.py | 2 -- pyomo/dataportal/parse_datacmds.py | 2 -- pyomo/network/arc.py | 2 -- pyomo/network/decomposition.py | 2 -- pyomo/network/port.py | 2 -- pyomo/opt/base/convert.py | 2 -- pyomo/opt/base/formats.py | 5 ---- pyomo/opt/base/problem.py | 2 -- pyomo/opt/base/results.py | 2 -- pyomo/opt/base/solvers.py | 5 +--- pyomo/opt/parallel/async_solver.py | 3 --- pyomo/opt/parallel/local.py | 3 --- pyomo/opt/parallel/manager.py | 10 -------- pyomo/opt/problem/ampl.py | 2 -- pyomo/opt/results/container.py | 17 ++----------- pyomo/opt/results/problem.py | 2 -- pyomo/opt/results/results_.py | 4 +--- pyomo/opt/results/solution.py | 2 -- pyomo/opt/results/solver.py | 8 ------- pyomo/opt/solver/ilmcmd.py | 2 -- pyomo/opt/solver/shellcmd.py | 2 -- pyomo/opt/testing/pyunit.py | 3 --- pyomo/repn/beta/matrix.py | 6 ----- pyomo/repn/plugins/ampl/ampl_.py | 2 -- pyomo/repn/standard_aux.py | 3 --- pyomo/repn/standard_repn.py | 3 --- pyomo/scripting/convert.py | 2 -- pyomo/scripting/pyomo_parser.py | 2 -- pyomo/solvers/plugins/solvers/CBCplugin.py | 2 -- pyomo/solvers/tests/solvers.py | 2 -- pyomo/util/blockutil.py | 2 -- 74 files changed, 16 insertions(+), 307 deletions(-) diff --git a/pyomo/common/_command.py b/pyomo/common/_command.py index 0777155a557..ad521659aa7 100644 --- a/pyomo/common/_command.py +++ b/pyomo/common/_command.py @@ -13,8 +13,6 @@ Management of Pyomo commands """ -__all__ = ['pyomo_command', 'get_pyomo_commands'] - import logging logger = logging.getLogger('pyomo.common') diff --git a/pyomo/contrib/pynumero/interfaces/ampl_nlp.py b/pyomo/contrib/pynumero/interfaces/ampl_nlp.py index c19d252667d..30258b3e685 100644 --- a/pyomo/contrib/pynumero/interfaces/ampl_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/ampl_nlp.py @@ -27,10 +27,8 @@ from pyomo.common.deprecation import deprecated from pyomo.contrib.pynumero.interfaces.nlp import ExtendedNLP -__all__ = ['AslNLP', 'AmplNLP'] - -# ToDo: need to add support for modifying bounds. +# TODO: need to add support for modifying bounds. # support for changing variable bounds seems possible. # support for changing inequality bounds would require more work. (this is less frequent?) # TODO: check performance impacts of caching - memory and computational time. diff --git a/pyomo/contrib/pynumero/interfaces/nlp.py b/pyomo/contrib/pynumero/interfaces/nlp.py index 20b3a5e4938..d6571086429 100644 --- a/pyomo/contrib/pynumero/interfaces/nlp.py +++ b/pyomo/contrib/pynumero/interfaces/nlp.py @@ -50,9 +50,8 @@ .. rubric:: Contents """ -import abc -__all__ = ['NLP'] +import abc class NLP(object, metaclass=abc.ABCMeta): diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py index f9014ab29c0..51edd09311a 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py @@ -28,9 +28,6 @@ from .external_grey_box import ExternalGreyBoxBlock -__all__ = ['PyomoNLP'] - - # TODO: There are todos in the code below class PyomoNLP(AslNLP): def __init__(self, pyomo_model, nl_file_options=None): diff --git a/pyomo/contrib/pynumero/sparse/block_matrix.py b/pyomo/contrib/pynumero/sparse/block_matrix.py index ba7ed4f085b..02ad584928b 100644 --- a/pyomo/contrib/pynumero/sparse/block_matrix.py +++ b/pyomo/contrib/pynumero/sparse/block_matrix.py @@ -31,8 +31,6 @@ import logging import warnings -__all__ = ['BlockMatrix', 'NotFullyDefinedBlockMatrixError'] - logger = logging.getLogger(__name__) diff --git a/pyomo/contrib/pynumero/sparse/block_vector.py b/pyomo/contrib/pynumero/sparse/block_vector.py index 2b529736935..b636dd74203 100644 --- a/pyomo/contrib/pynumero/sparse/block_vector.py +++ b/pyomo/contrib/pynumero/sparse/block_vector.py @@ -27,8 +27,6 @@ from ..dependencies import numpy as np from .base_block import BaseBlockVector -__all__ = ['BlockVector', 'NotFullyDefinedBlockVectorError'] - class NotFullyDefinedBlockVectorError(Exception): pass diff --git a/pyomo/contrib/pynumero/sparse/mpi_block_matrix.py b/pyomo/contrib/pynumero/sparse/mpi_block_matrix.py index 28a39b4e2eb..d32adebce0e 100644 --- a/pyomo/contrib/pynumero/sparse/mpi_block_matrix.py +++ b/pyomo/contrib/pynumero/sparse/mpi_block_matrix.py @@ -32,8 +32,6 @@ from scipy.sparse import coo_matrix import operator -__all__ = ['MPIBlockMatrix'] - def assert_block_structure(mat: MPIBlockMatrix): if mat.has_undefined_row_sizes() or mat.has_undefined_col_sizes(): diff --git a/pyomo/contrib/pynumero/sparse/mpi_block_vector.py b/pyomo/contrib/pynumero/sparse/mpi_block_vector.py index be8091c9597..89cf136a5f7 100644 --- a/pyomo/contrib/pynumero/sparse/mpi_block_vector.py +++ b/pyomo/contrib/pynumero/sparse/mpi_block_vector.py @@ -17,8 +17,6 @@ import numpy as np import operator -__all__ = ['MPIBlockVector'] - def assert_block_structure(vec): if vec.has_none: diff --git a/pyomo/core/base/PyomoModel.py b/pyomo/core/base/PyomoModel.py index 759b17c9a79..ba7823c642a 100644 --- a/pyomo/core/base/PyomoModel.py +++ b/pyomo/core/base/PyomoModel.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['Model', 'ConcreteModel', 'AbstractModel', 'global_option'] - import logging import sys from weakref import ref as weakref_ref @@ -20,7 +18,7 @@ from pyomo.common import timing from pyomo.common.collections import Bunch from pyomo.common.dependencies import pympler, pympler_available -from pyomo.common.deprecation import deprecated, deprecation_warning +from pyomo.common.deprecation import deprecated from pyomo.common.gc_manager import PauseGC from pyomo.common.log import is_debug_set from pyomo.common.numeric_types import value @@ -34,11 +32,10 @@ from pyomo.core.base.block import ScalarBlock from pyomo.core.base.set import Set from pyomo.core.base.componentuid import ComponentUID -from pyomo.core.base.transformation import TransformationFactory from pyomo.core.base.label import CNameLabeler, CuidLabeler from pyomo.dataportal.DataPortal import DataPortal -from pyomo.opt.results import SolverResults, Solution, SolverStatus, UndefinedData +from pyomo.opt.results import Solution, SolverStatus, UndefinedData from contextlib import nullcontext from io import StringIO diff --git a/pyomo/core/base/action.py b/pyomo/core/base/action.py index f929c4b38ff..d24d94fe05a 100644 --- a/pyomo/core/base/action.py +++ b/pyomo/core/base/action.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['BuildAction'] - import logging import types @@ -24,7 +22,8 @@ @ModelComponentFactory.register( - "A component that performs arbitrary actions during model construction. The action rule is applied to every index value." + "A component that performs arbitrary actions during model construction. " + "The action rule is applied to every index value." ) class BuildAction(IndexedComponent): """A build action, which executes a rule for all valid indices. diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 190a820fbfe..48353078fca 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -9,20 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = [ - 'Block', - 'TraversalStrategy', - 'SortComponents', - 'active_components', - 'components', - 'active_components_data', - 'components_data', - 'SimpleBlock', - 'ScalarBlock', -] - import copy -import enum import logging import sys import weakref @@ -41,7 +28,6 @@ from pyomo.common.formatting import StreamIndenter from pyomo.common.gc_manager import PauseGC from pyomo.common.log import is_debug_set -from pyomo.common.sorting import sorted_robust from pyomo.common.timing import ConstructionTimer from pyomo.core.base.component import ( Component, diff --git a/pyomo/core/base/blockutil.py b/pyomo/core/base/blockutil.py index fc763da8b98..d91a5c85ac2 100644 --- a/pyomo/core/base/blockutil.py +++ b/pyomo/core/base/blockutil.py @@ -12,8 +12,6 @@ # the purpose of this file is to collect all utility methods that compute # attributes of blocks, based on their contents. -__all__ = ['has_discrete_variables'] - from pyomo.common import deprecated from pyomo.core.base import Var diff --git a/pyomo/core/base/check.py b/pyomo/core/base/check.py index cbf2a99e7a3..485d1a73b6b 100644 --- a/pyomo/core/base/check.py +++ b/pyomo/core/base/check.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['BuildCheck'] - import logging import types diff --git a/pyomo/core/base/component_order.py b/pyomo/core/base/component_order.py index 8e69baa0972..9244828cbe5 100644 --- a/pyomo/core/base/component_order.py +++ b/pyomo/core/base/component_order.py @@ -9,9 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ - -__all__ = ['items', 'display_items', 'display_name'] - from pyomo.core.base.set import Set, RangeSet from pyomo.core.base.param import Param from pyomo.core.base.var import Var diff --git a/pyomo/core/base/connector.py b/pyomo/core/base/connector.py index 8dfee45236e..435a2c2fccb 100644 --- a/pyomo/core/base/connector.py +++ b/pyomo/core/base/connector.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['Connector'] - import logging import sys from weakref import ref as weakref_ref @@ -26,7 +24,6 @@ from pyomo.core.base.global_set import UnindexedComponent_index from pyomo.core.base.indexed_component import IndexedComponent from pyomo.core.base.misc import apply_indexed_rule -from pyomo.core.base.transformation import TransformationFactory logger = logging.getLogger('pyomo.core') diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index 21da457edf6..c67236656be 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -9,18 +9,8 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = [ - 'Constraint', - '_ConstraintData', - 'ConstraintList', - 'simple_constraint_rule', - 'simple_constraintlist_rule', -] - -import io import sys import logging -import math from weakref import ref as weakref_ref from pyomo.common.pyomo_typing import overload diff --git a/pyomo/core/base/expression.py b/pyomo/core/base/expression.py index 3695f95b0be..3ce998b62a4 100644 --- a/pyomo/core/base/expression.py +++ b/pyomo/core/base/expression.py @@ -9,15 +9,13 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['Expression', '_ExpressionData'] - import sys import logging from weakref import ref as weakref_ref from pyomo.common.pyomo_typing import overload from pyomo.common.log import is_debug_set -from pyomo.common.deprecation import deprecated, RenamedClass +from pyomo.common.deprecation import RenamedClass from pyomo.common.modeling import NOTSET from pyomo.common.formatting import tabular_writer from pyomo.common.timing import ConstructionTimer @@ -32,7 +30,6 @@ from pyomo.core.base.component import ComponentData, ModelComponentFactory from pyomo.core.base.global_set import UnindexedComponent_index from pyomo.core.base.indexed_component import IndexedComponent, UnindexedComponent_set -from pyomo.core.base.misc import apply_indexed_rule from pyomo.core.expr.numvalue import as_numeric from pyomo.core.base.initializer import Initializer diff --git a/pyomo/core/base/external.py b/pyomo/core/base/external.py index c1f7ef32a80..3c0038d745d 100644 --- a/pyomo/core/base/external.py +++ b/pyomo/core/base/external.py @@ -43,8 +43,6 @@ from pyomo.core.base.component import Component from pyomo.core.base.units_container import units -__all__ = ('ExternalFunction',) - logger = logging.getLogger('pyomo.core') nan = float('nan') diff --git a/pyomo/core/base/indexed_component.py b/pyomo/core/base/indexed_component.py index e87cafa0606..abb29580960 100644 --- a/pyomo/core/base/indexed_component.py +++ b/pyomo/core/base/indexed_component.py @@ -9,16 +9,11 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['IndexedComponent', 'ActiveIndexedComponent'] - -import enum import inspect import logging import sys import textwrap -from copy import deepcopy - import pyomo.core.expr as EXPR import pyomo.core.base as BASE from pyomo.core.base.indexed_component_slice import IndexedComponent_slice @@ -32,9 +27,8 @@ from pyomo.common import DeveloperError from pyomo.common.autoslots import fast_deepcopy from pyomo.common.collections import ComponentSet -from pyomo.common.dependencies import numpy as np, numpy_available from pyomo.common.deprecation import deprecated, deprecation_warning -from pyomo.common.errors import DeveloperError, TemplateExpressionError +from pyomo.common.errors import TemplateExpressionError from pyomo.common.modeling import NOTSET from pyomo.common.numeric_types import native_types from pyomo.common.sorting import sorted_robust diff --git a/pyomo/core/base/instance2dat.py b/pyomo/core/base/instance2dat.py index 4dab6435187..5cd690b7ece 100644 --- a/pyomo/core/base/instance2dat.py +++ b/pyomo/core/base/instance2dat.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['instance2dat'] - import types from pyomo.core.base import Set, Param, value diff --git a/pyomo/core/base/label.py b/pyomo/core/base/label.py index 4ed61773a7e..e22c1283138 100644 --- a/pyomo/core/base/label.py +++ b/pyomo/core/base/label.py @@ -9,17 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = [ - 'CounterLabeler', - 'NumericLabeler', - 'CNameLabeler', - 'TextLabeler', - 'AlphaNumericTextLabeler', - 'NameLabeler', - 'CuidLabeler', - 'ShortNameLabeler', -] - import re from pyomo.common.deprecation import deprecated diff --git a/pyomo/core/base/logical_constraint.py b/pyomo/core/base/logical_constraint.py index 9a6e9d552d0..f32d727931a 100644 --- a/pyomo/core/base/logical_constraint.py +++ b/pyomo/core/base/logical_constraint.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['LogicalConstraint', '_LogicalConstraintData', 'LogicalConstraintList'] - import inspect import sys import logging @@ -22,7 +20,6 @@ from pyomo.common.modeling import NOTSET from pyomo.common.timing import ConstructionTimer -from pyomo.core.base.constraint import Constraint from pyomo.core.expr.boolean_value import as_boolean, BooleanConstant from pyomo.core.expr.numvalue import native_types, native_logical_types from pyomo.core.base.component import ActiveComponentData, ModelComponentFactory diff --git a/pyomo/core/base/misc.py b/pyomo/core/base/misc.py index 926d4e576f4..456a4531e30 100644 --- a/pyomo/core/base/misc.py +++ b/pyomo/core/base/misc.py @@ -9,14 +9,10 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['display'] - import logging import sys -import types from pyomo.common.deprecation import relocated_module_attribute -from pyomo.core.expr import native_numeric_types logger = logging.getLogger('pyomo.core') diff --git a/pyomo/core/base/objective.py b/pyomo/core/base/objective.py index f0e60a00e85..fcc63755f2b 100644 --- a/pyomo/core/base/objective.py +++ b/pyomo/core/base/objective.py @@ -9,16 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ( - 'Objective', - 'simple_objective_rule', - '_ObjectiveData', - 'minimize', - 'maximize', - 'simple_objectivelist_rule', - 'ObjectiveList', -) - import sys import logging from weakref import ref as weakref_ref diff --git a/pyomo/core/base/param.py b/pyomo/core/base/param.py index 88e7ca98de7..03d700140e8 100644 --- a/pyomo/core/base/param.py +++ b/pyomo/core/base/param.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['Param'] - import sys import types import logging diff --git a/pyomo/core/base/piecewise.py b/pyomo/core/base/piecewise.py index b6ae66ac093..7817a61b2f2 100644 --- a/pyomo/core/base/piecewise.py +++ b/pyomo/core/base/piecewise.py @@ -32,9 +32,6 @@ *) piecewise for functions of the form y = f(x1,x2,...) """ - -__all__ = ['Piecewise'] - import logging import math import itertools diff --git a/pyomo/core/base/plugin.py b/pyomo/core/base/plugin.py index 8c44af2dd61..062e9f9fb85 100644 --- a/pyomo/core/base/plugin.py +++ b/pyomo/core/base/plugin.py @@ -21,30 +21,6 @@ calling_frame=inspect.currentframe().f_back, ) -__all__ = [ - 'pyomo_callback', - 'IPyomoExpression', - 'ExpressionFactory', - 'ExpressionRegistration', - 'IPyomoPresolver', - 'IPyomoPresolveAction', - 'IParamRepresentation', - 'ParamRepresentationFactory', - 'IPyomoScriptPreprocess', - 'IPyomoScriptCreateModel', - 'IPyomoScriptCreateDataPortal', - 'IPyomoScriptModifyInstance', - 'IPyomoScriptPrintModel', - 'IPyomoScriptPrintInstance', - 'IPyomoScriptSaveInstance', - 'IPyomoScriptPrintResults', - 'IPyomoScriptSaveResults', - 'IPyomoScriptPostprocess', - 'ModelComponentFactory', - 'Transformation', - 'TransformationFactory', -] - from pyomo.core.base.component import ModelComponentFactory from pyomo.core.base.transformation import ( Transformation, diff --git a/pyomo/core/base/rangeset.py b/pyomo/core/base/rangeset.py index 27693548c1d..32e41698aab 100644 --- a/pyomo/core/base/rangeset.py +++ b/pyomo/core/base/rangeset.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['RangeSet'] - from .set import RangeSet from pyomo.common.deprecation import deprecation_warning diff --git a/pyomo/core/base/sets.py b/pyomo/core/base/sets.py index f2ae44be459..ca693cf7d8b 100644 --- a/pyomo/core/base/sets.py +++ b/pyomo/core/base/sets.py @@ -13,8 +13,6 @@ # . rename 'filter' to something else # . confirm that filtering is efficient -__all__ = ['Set', 'set_options', 'simple_set_rule', 'SetOf'] - from .set import ( process_setarg, set_options, diff --git a/pyomo/core/base/sos.py b/pyomo/core/base/sos.py index 32265df6686..6b8586c9b49 100644 --- a/pyomo/core/base/sos.py +++ b/pyomo/core/base/sos.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['SOSConstraint'] - import sys import logging diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index 67ab0b74215..0c27eee060f 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ('Suffix', 'active_export_suffix_generator', 'active_import_suffix_generator') - import enum import logging diff --git a/pyomo/core/base/var.py b/pyomo/core/base/var.py index 67b6e1a28d7..d03fd0b677f 100644 --- a/pyomo/core/base/var.py +++ b/pyomo/core/base/var.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['Var', '_VarData', '_GeneralVarData', 'VarList', 'SimpleVar', 'ScalarVar'] - import logging import sys from pyomo.common.pyomo_typing import overload @@ -29,7 +27,6 @@ value, is_potentially_variable, native_numeric_types, - native_types, ) from pyomo.core.base.component import ComponentData, ModelComponentFactory from pyomo.core.base.global_set import UnindexedComponent_index @@ -44,7 +41,6 @@ DefaultInitializer, BoundInitializer, ) -from pyomo.core.base.misc import apply_indexed_rule from pyomo.core.base.set import ( Reals, Binary, @@ -54,7 +50,6 @@ integer_global_set_ids, ) from pyomo.core.base.units_container import units -from pyomo.core.base.util import is_functor logger = logging.getLogger('pyomo.core') diff --git a/pyomo/core/beta/dict_objects.py b/pyomo/core/beta/dict_objects.py index a698fcbb717..a8298b08e63 100644 --- a/pyomo/core/beta/dict_objects.py +++ b/pyomo/core/beta/dict_objects.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = () - import logging from weakref import ref as weakref_ref diff --git a/pyomo/core/beta/list_objects.py b/pyomo/core/beta/list_objects.py index f2ccf0d37aa..f53997fed17 100644 --- a/pyomo/core/beta/list_objects.py +++ b/pyomo/core/beta/list_objects.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = () - import logging from weakref import ref as weakref_ref diff --git a/pyomo/core/expr/__init__.py b/pyomo/core/expr/__init__.py index 5efb5026c65..b0ad2ac4892 100644 --- a/pyomo/core/expr/__init__.py +++ b/pyomo/core/expr/__init__.py @@ -9,14 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -# -# The definition of __all__ is a bit funky here, because we want to -# expose symbols in pyomo.core.expr.current that are not included in -# pyomo.core.expr. The idea is that pyomo.core.expr provides symbols -# that are used by general users, but pyomo.core.expr.current provides -# symbols that are used by developers. -# - from . import ( numvalue, visitor, diff --git a/pyomo/core/expr/numvalue.py b/pyomo/core/expr/numvalue.py index 8cc20648eb4..f3ea76c305c 100644 --- a/pyomo/core/expr/numvalue.py +++ b/pyomo/core/expr/numvalue.py @@ -9,21 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ( - 'value', - 'is_constant', - 'is_fixed', - 'is_variable_type', - 'is_potentially_variable', - 'NumericValue', - 'ZeroConstant', - 'native_numeric_types', - 'native_types', - 'nonpyomo_leaf_types', - 'polynomial_degree', -) - -import collections import sys import logging @@ -34,7 +19,6 @@ ) from pyomo.core.expr.expr_common import ExpressionType from pyomo.core.expr.numeric_expr import NumericValue -import pyomo.common.numeric_types as _numeric_types # TODO: update Pyomo to import these objects from common.numeric_types # (and not from here) @@ -48,7 +32,6 @@ check_if_numeric_type, value, ) -from pyomo.core.pyomoobject import PyomoObject relocated_module_attribute( 'native_boolean_types', diff --git a/pyomo/core/util.py b/pyomo/core/util.py index 4e076c7505b..e4a70aea05a 100644 --- a/pyomo/core/util.py +++ b/pyomo/core/util.py @@ -13,24 +13,12 @@ # Utility functions # -__all__ = [ - 'sum_product', - 'summation', - 'dot_product', - 'sequence', - 'prod', - 'quicksum', - 'target_list', -] - from pyomo.common.deprecation import deprecation_warning from pyomo.core.expr.numvalue import native_numeric_types from pyomo.core.expr.numeric_expr import ( mutable_expression, - nonlinear_expression, NPV_SumExpression, ) -import pyomo.core.expr as EXPR from pyomo.core.base.var import Var from pyomo.core.base.expression import Expression from pyomo.core.base.component import _ComponentBase diff --git a/pyomo/dae/contset.py b/pyomo/dae/contset.py index 94d20723770..9b4f11714df 100644 --- a/pyomo/dae/contset.py +++ b/pyomo/dae/contset.py @@ -17,7 +17,6 @@ from pyomo.core.base.component import ModelComponentFactory logger = logging.getLogger('pyomo.dae') -__all__ = ['ContinuousSet'] @ModelComponentFactory.register( diff --git a/pyomo/dae/diffvar.py b/pyomo/dae/diffvar.py index 6bb3a8b06f0..b921107957f 100644 --- a/pyomo/dae/diffvar.py +++ b/pyomo/dae/diffvar.py @@ -16,8 +16,6 @@ from pyomo.core.base.var import Var from pyomo.dae.contset import ContinuousSet -__all__ = ('DerivativeVar', 'DAE_Error') - def create_access_function(var): """ diff --git a/pyomo/dae/integral.py b/pyomo/dae/integral.py index 34a34fdcd9c..41114296a93 100644 --- a/pyomo/dae/integral.py +++ b/pyomo/dae/integral.py @@ -21,8 +21,6 @@ from pyomo.dae.contset import ContinuousSet from pyomo.dae.diffvar import DAE_Error -__all__ = ('Integral',) - @ModelComponentFactory.register("Integral Expression in a DAE model.") class Integral(Expression): diff --git a/pyomo/dae/simulator.py b/pyomo/dae/simulator.py index f9121dbc0cc..72ba0c7331d 100644 --- a/pyomo/dae/simulator.py +++ b/pyomo/dae/simulator.py @@ -17,20 +17,14 @@ # the U.S. Government retains certain rights in this software. # This software is distributed under the BSD License. # _________________________________________________________________________ -from pyomo.core.base import Constraint, Param, value, Suffix, Block +import logging +from pyomo.core.base import Constraint, Param, value, Suffix, Block from pyomo.dae import ContinuousSet, DerivativeVar from pyomo.dae.diffvar import DAE_Error - import pyomo.core.expr as EXPR from pyomo.core.expr.numvalue import native_numeric_types from pyomo.core.expr.template_expr import IndexTemplate, _GetItemIndexer - -import logging - -__all__ = ('Simulator',) -logger = logging.getLogger('pyomo.core') - from pyomo.common.dependencies import ( numpy as np, numpy_available, @@ -39,6 +33,8 @@ attempt_import, ) +logger = logging.getLogger('pyomo.core') + casadi_intrinsic = {} diff --git a/pyomo/dataportal/DataPortal.py b/pyomo/dataportal/DataPortal.py index 24a9c847d48..457bb1aacee 100644 --- a/pyomo/dataportal/DataPortal.py +++ b/pyomo/dataportal/DataPortal.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['DataPortal'] - import logging from pyomo.common.log import is_debug_set from pyomo.dataportal.factory import DataManagerFactory, UnknownDataManager diff --git a/pyomo/dataportal/TableData.py b/pyomo/dataportal/TableData.py index b7fb98d596a..f1500d09f9b 100644 --- a/pyomo/dataportal/TableData.py +++ b/pyomo/dataportal/TableData.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['TableData'] - from pyomo.common.collections import Bunch from pyomo.dataportal.process_data import _process_data diff --git a/pyomo/dataportal/factory.py b/pyomo/dataportal/factory.py index e6424be25c4..479769137e2 100644 --- a/pyomo/dataportal/factory.py +++ b/pyomo/dataportal/factory.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['DataManagerFactory', 'UnknownDataManager'] - import logging from pyomo.common import Factory from pyomo.common.plugin_base import PluginError diff --git a/pyomo/dataportal/parse_datacmds.py b/pyomo/dataportal/parse_datacmds.py index d9f44405577..60e2f2c0acb 100644 --- a/pyomo/dataportal/parse_datacmds.py +++ b/pyomo/dataportal/parse_datacmds.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['parse_data_commands'] - import bisect import sys import logging diff --git a/pyomo/network/arc.py b/pyomo/network/arc.py index 1aa2b88edb6..42b7c6ea075 100644 --- a/pyomo/network/arc.py +++ b/pyomo/network/arc.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['Arc'] - from pyomo.network.port import Port from pyomo.core.base.component import ActiveComponentData, ModelComponentFactory from pyomo.core.base.indexed_component import ( diff --git a/pyomo/network/decomposition.py b/pyomo/network/decomposition.py index da7e8950395..1ffb6a710ff 100644 --- a/pyomo/network/decomposition.py +++ b/pyomo/network/decomposition.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['SequentialDecomposition'] - from pyomo.network import Port, Arc from pyomo.network.foqus_graph import FOQUSGraph from pyomo.core import ( diff --git a/pyomo/network/port.py b/pyomo/network/port.py index 63ed8b097b1..26822d4fee9 100644 --- a/pyomo/network/port.py +++ b/pyomo/network/port.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['Port'] - import logging, sys from weakref import ref as weakref_ref diff --git a/pyomo/opt/base/convert.py b/pyomo/opt/base/convert.py index a17d1914801..28ad6727d3e 100644 --- a/pyomo/opt/base/convert.py +++ b/pyomo/opt/base/convert.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['convert_problem'] - import copy import os diff --git a/pyomo/opt/base/formats.py b/pyomo/opt/base/formats.py index 72c4f5306a7..6e9d3958f48 100644 --- a/pyomo/opt/base/formats.py +++ b/pyomo/opt/base/formats.py @@ -9,11 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -# -# The formats that are supported by Pyomo -# -__all__ = ['ProblemFormat', 'ResultsFormat', 'guess_format'] - import enum diff --git a/pyomo/opt/base/problem.py b/pyomo/opt/base/problem.py index 02748e08b70..804a97e2e4c 100644 --- a/pyomo/opt/base/problem.py +++ b/pyomo/opt/base/problem.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ["AbstractProblemWriter", "WriterFactory", "BranchDirection"] - from pyomo.common import Factory diff --git a/pyomo/opt/base/results.py b/pyomo/opt/base/results.py index 8b00ec3e14e..ea295a66315 100644 --- a/pyomo/opt/base/results.py +++ b/pyomo/opt/base/results.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['AbstractResultsReader', 'ReaderFactory'] - from pyomo.common import Factory diff --git a/pyomo/opt/base/solvers.py b/pyomo/opt/base/solvers.py index cc49349142e..68e719e3862 100644 --- a/pyomo/opt/base/solvers.py +++ b/pyomo/opt/base/solvers.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ('OptSolver', 'SolverFactory', 'UnknownSolver', 'check_available_solvers') - import re import sys import time @@ -18,12 +16,11 @@ import shlex from pyomo.common import Factory -from pyomo.common.config import ConfigDict from pyomo.common.errors import ApplicationError from pyomo.common.collections import Bunch from pyomo.opt.base.convert import convert_problem -from pyomo.opt.base.formats import ResultsFormat, ProblemFormat +from pyomo.opt.base.formats import ResultsFormat import pyomo.opt.base.results logger = logging.getLogger('pyomo.opt') diff --git a/pyomo/opt/parallel/async_solver.py b/pyomo/opt/parallel/async_solver.py index d74206e4790..74e222e2241 100644 --- a/pyomo/opt/parallel/async_solver.py +++ b/pyomo/opt/parallel/async_solver.py @@ -9,9 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ - -__all__ = ['AsynchronousSolverManager', 'SolverManagerFactory'] - from pyomo.common import Factory from pyomo.opt.parallel.manager import AsynchronousActionManager diff --git a/pyomo/opt/parallel/local.py b/pyomo/opt/parallel/local.py index 211adf92e5c..e130ea0407f 100644 --- a/pyomo/opt/parallel/local.py +++ b/pyomo/opt/parallel/local.py @@ -9,9 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ - -__all__ = () - import time from pyomo.common.collections import OrderedDict diff --git a/pyomo/opt/parallel/manager.py b/pyomo/opt/parallel/manager.py index faa34d5190f..203c348e119 100644 --- a/pyomo/opt/parallel/manager.py +++ b/pyomo/opt/parallel/manager.py @@ -9,16 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ - -__all__ = [ - 'ActionManagerError', - 'ActionHandle', - 'AsynchronousActionManager', - 'ActionStatus', - 'FailedActionHandle', - 'solve_all_instances', -] - import enum diff --git a/pyomo/opt/problem/ampl.py b/pyomo/opt/problem/ampl.py index d128ec94930..ed107cace60 100644 --- a/pyomo/opt/problem/ampl.py +++ b/pyomo/opt/problem/ampl.py @@ -14,8 +14,6 @@ can be optimized with the Acro COLIN optimizers. """ -__all__ = ['AmplModel'] - import os from pyomo.opt.base import ProblemFormat, convert_problem, guess_format diff --git a/pyomo/opt/results/container.py b/pyomo/opt/results/container.py index 1cdf6fe77ce..ec4cb3c1c53 100644 --- a/pyomo/opt/results/container.py +++ b/pyomo/opt/results/container.py @@ -9,25 +9,12 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = [ - 'UndefinedData', - 'undefined', - 'ignore', - 'ScalarData', - 'ListContainer', - 'MapContainer', - 'default_print_options', - 'ScalarType', -] - import copy - -from math import inf -from pyomo.common.collections import Bunch - import enum from io import StringIO +from math import inf +from pyomo.common.collections import Bunch class ScalarType(str, enum.Enum): int = 'int' diff --git a/pyomo/opt/results/problem.py b/pyomo/opt/results/problem.py index d39ba204aaf..98f749f3aeb 100644 --- a/pyomo/opt/results/problem.py +++ b/pyomo/opt/results/problem.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['ProblemInformation', 'ProblemSense'] - import enum from pyomo.opt.results.container import MapContainer diff --git a/pyomo/opt/results/results_.py b/pyomo/opt/results/results_.py index a9b802e2adb..0a045550517 100644 --- a/pyomo/opt/results/results_.py +++ b/pyomo/opt/results/results_.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['SolverResults'] - import math import sys import copy @@ -18,7 +16,7 @@ import logging import os.path -from pyomo.common.dependencies import yaml, yaml_load_args, yaml_available +from pyomo.common.dependencies import yaml, yaml_load_args import pyomo.opt from pyomo.opt.results.container import undefined, ignore, ListContainer, MapContainer import pyomo.opt.results.solution diff --git a/pyomo/opt/results/solution.py b/pyomo/opt/results/solution.py index 2862087cf43..6dcd348ea72 100644 --- a/pyomo/opt/results/solution.py +++ b/pyomo/opt/results/solution.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['SolutionStatus', 'Solution'] - import math import enum from pyomo.opt.results.container import MapContainer, ListContainer, ignore diff --git a/pyomo/opt/results/solver.py b/pyomo/opt/results/solver.py index e2d0cfff605..d4cf46c38a9 100644 --- a/pyomo/opt/results/solver.py +++ b/pyomo/opt/results/solver.py @@ -9,14 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = [ - 'SolverInformation', - 'SolverStatus', - 'TerminationCondition', - 'check_optimal_termination', - 'assert_optimal_termination', -] - import enum from pyomo.opt.results.container import MapContainer, ScalarType diff --git a/pyomo/opt/solver/ilmcmd.py b/pyomo/opt/solver/ilmcmd.py index efd1096c20f..c956b2ed42f 100644 --- a/pyomo/opt/solver/ilmcmd.py +++ b/pyomo/opt/solver/ilmcmd.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['ILMLicensedSystemCallSolver'] - import re import sys import os diff --git a/pyomo/opt/solver/shellcmd.py b/pyomo/opt/solver/shellcmd.py index 58274b572d3..94117779237 100644 --- a/pyomo/opt/solver/shellcmd.py +++ b/pyomo/opt/solver/shellcmd.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['SystemCallSolver'] - import os import sys import time diff --git a/pyomo/opt/testing/pyunit.py b/pyomo/opt/testing/pyunit.py index 9143714f4e3..bb96806d520 100644 --- a/pyomo/opt/testing/pyunit.py +++ b/pyomo/opt/testing/pyunit.py @@ -9,9 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ - -__all__ = ['TestCase'] - import sys import os import re diff --git a/pyomo/repn/beta/matrix.py b/pyomo/repn/beta/matrix.py index 741e54d380c..916b0daf755 100644 --- a/pyomo/repn/beta/matrix.py +++ b/pyomo/repn/beta/matrix.py @@ -9,12 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ( - "_LinearConstraintData", - "MatrixConstraint", - "compile_block_linear_constraints", -) - import time import logging import array diff --git a/pyomo/repn/plugins/ampl/ampl_.py b/pyomo/repn/plugins/ampl/ampl_.py index 4cc55cabd51..f422a085a3c 100644 --- a/pyomo/repn/plugins/ampl/ampl_.py +++ b/pyomo/repn/plugins/ampl/ampl_.py @@ -13,8 +13,6 @@ # AMPL Problem Writer Plugin # -__all__ = ['ProblemWriter_nl'] - import itertools import logging import operator diff --git a/pyomo/repn/standard_aux.py b/pyomo/repn/standard_aux.py index 628914780a6..403320c462c 100644 --- a/pyomo/repn/standard_aux.py +++ b/pyomo/repn/standard_aux.py @@ -10,9 +10,6 @@ # ___________________________________________________________________________ -__all__ = ['compute_standard_repn'] - - from pyomo.repn.standard_repn import ( preprocess_block_constraints, preprocess_block_objectives, diff --git a/pyomo/repn/standard_repn.py b/pyomo/repn/standard_repn.py index c1cca42afe4..8700872f04f 100644 --- a/pyomo/repn/standard_repn.py +++ b/pyomo/repn/standard_repn.py @@ -10,9 +10,6 @@ # ___________________________________________________________________________ -__all__ = ['StandardRepn', 'generate_standard_repn'] - - import sys import logging import itertools diff --git a/pyomo/scripting/convert.py b/pyomo/scripting/convert.py index 997e69ac7c9..20f9ef6d382 100644 --- a/pyomo/scripting/convert.py +++ b/pyomo/scripting/convert.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['pyomo2lp', 'pyomo2nl', 'pyomo2dakota'] - import os import sys diff --git a/pyomo/scripting/pyomo_parser.py b/pyomo/scripting/pyomo_parser.py index 09998085576..9294d46f85e 100644 --- a/pyomo/scripting/pyomo_parser.py +++ b/pyomo/scripting/pyomo_parser.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['add_subparser', 'get_parser', 'subparsers'] - import argparse import sys diff --git a/pyomo/solvers/plugins/solvers/CBCplugin.py b/pyomo/solvers/plugins/solvers/CBCplugin.py index 108b142a9e0..eb6c2c2e1bd 100644 --- a/pyomo/solvers/plugins/solvers/CBCplugin.py +++ b/pyomo/solvers/plugins/solvers/CBCplugin.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['CBC', 'MockCBC'] - import os import re import time diff --git a/pyomo/solvers/tests/solvers.py b/pyomo/solvers/tests/solvers.py index e67df47a0b0..918a801ae37 100644 --- a/pyomo/solvers/tests/solvers.py +++ b/pyomo/solvers/tests/solvers.py @@ -9,8 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -__all__ = ['test_solver_cases'] - import logging from pyomo.common.collections import Bunch diff --git a/pyomo/util/blockutil.py b/pyomo/util/blockutil.py index 56cc4266017..9f043e64ab7 100644 --- a/pyomo/util/blockutil.py +++ b/pyomo/util/blockutil.py @@ -12,8 +12,6 @@ # the purpose of this file is to collect all utility methods that compute # attributes of blocks, based on their contents. -__all__ = ['has_discrete_variables'] - import logging from pyomo.core import Var, Constraint, TraversalStrategy From db5fdf02d7e876ae3bd690ab85c032ebf9b46d26 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 16 Feb 2024 14:53:59 -0700 Subject: [PATCH 1015/1204] Fix import; apply black --- pyomo/core/expr/numvalue.py | 1 + pyomo/core/util.py | 5 +---- pyomo/opt/results/container.py | 1 + 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pyomo/core/expr/numvalue.py b/pyomo/core/expr/numvalue.py index f3ea76c305c..3a4359af2f9 100644 --- a/pyomo/core/expr/numvalue.py +++ b/pyomo/core/expr/numvalue.py @@ -32,6 +32,7 @@ check_if_numeric_type, value, ) +from pyomo.core.pyomoobject import PyomoObject relocated_module_attribute( 'native_boolean_types', diff --git a/pyomo/core/util.py b/pyomo/core/util.py index e4a70aea05a..f337b487cef 100644 --- a/pyomo/core/util.py +++ b/pyomo/core/util.py @@ -15,10 +15,7 @@ from pyomo.common.deprecation import deprecation_warning from pyomo.core.expr.numvalue import native_numeric_types -from pyomo.core.expr.numeric_expr import ( - mutable_expression, - NPV_SumExpression, -) +from pyomo.core.expr.numeric_expr import mutable_expression, NPV_SumExpression from pyomo.core.base.var import Var from pyomo.core.base.expression import Expression from pyomo.core.base.component import _ComponentBase diff --git a/pyomo/opt/results/container.py b/pyomo/opt/results/container.py index ec4cb3c1c53..4bbaf44edf7 100644 --- a/pyomo/opt/results/container.py +++ b/pyomo/opt/results/container.py @@ -16,6 +16,7 @@ from pyomo.common.collections import Bunch + class ScalarType(str, enum.Enum): int = 'int' time = 'time' From ec7c8e6a3b417b42880d36528c0d75462594497b Mon Sep 17 00:00:00 2001 From: lukasbiton Date: Fri, 16 Feb 2024 22:12:45 +0000 Subject: [PATCH 1016/1204] error msg more explicit wrt different interfaces --- pyomo/contrib/appsi/solvers/highs.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index a9a23682355..3612b9d5014 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -680,9 +680,11 @@ def _postsolve(self, timer: HierarchicalTimer): self.load_vars() else: raise RuntimeError( - 'A feasible solution was not found, so no solution can be loaded.' - 'Please set opt.config.load_solution=False and check ' - 'results.termination_condition and ' + 'A feasible solution was not found, so no solution can be loaded. ' + 'If using the appsi.solvers.Highs interface, you can ' + 'set opt.config.load_solution=False. If using the environ.SolverFactory ' + 'interface, you can set opt.solve(model, load_solutions = False). ' + 'Then you can check results.termination_condition and ' 'results.best_feasible_objective before loading a solution.' ) timer.stop('load solution') From 5b74de426559b5c39876e1de38e2721a9e997d92 Mon Sep 17 00:00:00 2001 From: lukasbiton Date: Fri, 16 Feb 2024 22:21:40 +0000 Subject: [PATCH 1017/1204] align new error message for all appsi solvers --- pyomo/contrib/appsi/solvers/cbc.py | 8 +++++--- pyomo/contrib/appsi/solvers/cplex.py | 8 +++++--- pyomo/contrib/appsi/solvers/gurobi.py | 8 +++++--- pyomo/contrib/appsi/solvers/ipopt.py | 8 +++++--- pyomo/contrib/appsi/solvers/wntr.py | 8 +++++--- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index 2c522af864d..7f04ffbfce7 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -411,9 +411,11 @@ def _check_and_escape_options(): if cp.returncode != 0: if self.config.load_solution: raise RuntimeError( - 'A feasible solution was not found, so no solution can be loaded.' - 'Please set opt.config.load_solution=False and check ' - 'results.termination_condition and ' + 'A feasible solution was not found, so no solution can be loaded. ' + 'If using the appsi.solvers.Cbc interface, you can ' + 'set opt.config.load_solution=False. If using the environ.SolverFactory ' + 'interface, you can set opt.solve(model, load_solutions = False). ' + 'Then you can check results.termination_condition and ' 'results.best_feasible_objective before loading a solution.' ) results = Results() diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 1d7147f16e8..1b7ab5000d2 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -341,9 +341,11 @@ def _postsolve(self, timer: HierarchicalTimer, solve_time): if config.load_solution: if cpxprob.solution.get_solution_type() == cpxprob.solution.type.none: raise RuntimeError( - 'A feasible solution was not found, so no solution can be loades. ' - 'Please set opt.config.load_solution=False and check ' - 'results.termination_condition and ' + 'A feasible solution was not found, so no solution can be loaded. ' + 'If using the appsi.solvers.Cplex interface, you can ' + 'set opt.config.load_solution=False. If using the environ.SolverFactory ' + 'interface, you can set opt.solve(model, load_solutions = False). ' + 'Then you can check results.termination_condition and ' 'results.best_feasible_objective before loading a solution.' ) else: diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index aa233ef77d6..1e18862e3bd 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -946,9 +946,11 @@ def _postsolve(self, timer: HierarchicalTimer): self.load_vars() else: raise RuntimeError( - 'A feasible solution was not found, so no solution can be loaded.' - 'Please set opt.config.load_solution=False and check ' - 'results.termination_condition and ' + 'A feasible solution was not found, so no solution can be loaded. ' + 'If using the appsi.solvers.Gurobi interface, you can ' + 'set opt.config.load_solution=False. If using the environ.SolverFactory ' + 'interface, you can set opt.solve(model, load_solutions = False). ' + 'Then you can check results.termination_condition and ' 'results.best_feasible_objective before loading a solution.' ) timer.stop('load solution') diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index d7a786e6c2c..29e74f81c98 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -421,9 +421,11 @@ def _parse_sol(self): results.best_feasible_objective = value(obj_expr_evaluated) elif self.config.load_solution: raise RuntimeError( - 'A feasible solution was not found, so no solution can be loaded.' - 'Please set opt.config.load_solution=False and check ' - 'results.termination_condition and ' + 'A feasible solution was not found, so no solution can be loaded. ' + 'If using the appsi.solvers.Ipopt interface, you can ' + 'set opt.config.load_solution=False. If using the environ.SolverFactory ' + 'interface, you can set opt.solve(model, load_solutions = False). ' + 'Then you can check results.termination_condition and ' 'results.best_feasible_objective before loading a solution.' ) diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py index e1835b810b0..00c0598c687 100644 --- a/pyomo/contrib/appsi/solvers/wntr.py +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -169,9 +169,11 @@ def _solve(self, timer: HierarchicalTimer): timer.stop('load solution') else: raise RuntimeError( - 'A feasible solution was not found, so no solution can be loaded.' - 'Please set opt.config.load_solution=False and check ' - 'results.termination_condition and ' + 'A feasible solution was not found, so no solution can be loaded. ' + 'If using the appsi.solvers.Wntr interface, you can ' + 'set opt.config.load_solution=False. If using the environ.SolverFactory ' + 'interface, you can set opt.solve(model, load_solutions = False). ' + 'Then you can check results.termination_condition and ' 'results.best_feasible_objective before loading a solution.' ) return results From 120c9a4c8fea118e6fbf5a1a9ace5b93f6ce127f Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 16 Feb 2024 17:22:03 -0500 Subject: [PATCH 1018/1204] Incorporate updated interfaces of `common.config` --- pyomo/contrib/pyros/config.py | 59 +---------- pyomo/contrib/pyros/tests/test_config.py | 119 ----------------------- pyomo/contrib/pyros/uncertainty_sets.py | 42 -------- 3 files changed, 4 insertions(+), 216 deletions(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index 749152f234c..a7ca41d095f 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -4,13 +4,13 @@ from collections.abc import Iterable import logging -import os from pyomo.common.collections import ComponentSet from pyomo.common.config import ( ConfigDict, ConfigValue, In, + IsInstance, NonNegativeFloat, InEnum, Path, @@ -20,7 +20,7 @@ from pyomo.core.base.param import Param, _ParamData from pyomo.opt import SolverFactory from pyomo.contrib.pyros.util import ObjectiveType, setup_pyros_logger -from pyomo.contrib.pyros.uncertainty_sets import UncertaintySetDomain +from pyomo.contrib.pyros.uncertainty_sets import UncertaintySet default_pyros_solver_logger = setup_pyros_logger() @@ -93,57 +93,6 @@ def domain_name(self): return "positive int or -1" -class PathLikeOrNone: - """ - Validator for path-like objects. - - This interface is a wrapper around the domain validator - ``common.config.Path``, and extends the domain of interest to - to include: - - None - - objects following the Python ``os.PathLike`` protocol. - - Parameters - ---------- - **config_path_kwargs : dict - Keyword arguments to ``common.config.Path``. - """ - - def __init__(self, **config_path_kwargs): - """Initialize self (see class docstring).""" - self.config_path = Path(**config_path_kwargs) - - def __call__(self, path): - """ - Cast path to expanded string representation. - - Parameters - ---------- - path : None str, bytes, or path-like - Object to be cast. - - Returns - ------- - None - If obj is None. - str - String representation of path-like object. - """ - if path is None: - return path - - # prevent common.config.Path from invoking - # str() on the path-like object - path_str = os.fsdecode(path) - - # standardize path str as necessary - return self.config_path(path_str) - - def domain_name(self): - """Return str briefly describing domain encompassed by self.""" - return "str, bytes, path-like or None" - - def mutable_param_validator(param_obj): """ Check that Param-like object has attribute `mutable=True`. @@ -637,7 +586,7 @@ def pyros_config(): "uncertainty_set", ConfigValue( default=None, - domain=UncertaintySetDomain(), + domain=IsInstance(UncertaintySet), description=( """ Uncertainty set against which the @@ -871,7 +820,7 @@ def pyros_config(): "subproblem_file_directory", ConfigValue( default=None, - domain=PathLikeOrNone(), + domain=Path(), description=( """ Directory to which to export subproblems not successfully diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index cc6fde225f3..76b9114b9e6 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -3,11 +3,9 @@ """ import logging -import os import unittest from pyomo.core.base import ConcreteModel, Var, _VarData -from pyomo.common.config import Path from pyomo.common.log import LoggingIntercept from pyomo.common.errors import ApplicationError from pyomo.core.base.param import Param, _ParamData @@ -16,12 +14,10 @@ mutable_param_validator, LoggerType, SolverNotResolvable, - PathLikeOrNone, PositiveIntOrMinusOne, pyros_config, SolverIterable, SolverResolvable, - UncertaintySetDomain, ) from pyomo.contrib.pyros.util import ObjectiveType from pyomo.opt import SolverFactory, SolverResults @@ -275,34 +271,6 @@ def test_standardizer_valid_mutable_params(self): ) -class TestUncertaintySetDomain(unittest.TestCase): - """ - Test domain validator for uncertainty set arguments. - """ - - @unittest.skipUnless(numpy_available, "Numpy is not available.") - def test_uncertainty_set_domain_valid_set(self): - """ - Test validator works for valid argument. - """ - standardizer_func = UncertaintySetDomain() - bset = BoxSet([[0, 1]]) - self.assertIs( - bset, - standardizer_func(bset), - msg="Output of uncertainty set domain not as expected.", - ) - - def test_uncertainty_set_domain_invalid_type(self): - """ - Test validator works for valid argument. - """ - standardizer_func = UncertaintySetDomain() - exc_str = "Expected an .*UncertaintySet object.*received object 2" - with self.assertRaisesRegex(ValueError, exc_str): - standardizer_func(2) - - AVAILABLE_SOLVER_TYPE_NAME = "available_pyros_test_solver" @@ -580,93 +548,6 @@ def test_config_objective_focus(self): config.objective_focus = invalid_focus -class TestPathLikeOrNone(unittest.TestCase): - """ - Test interface for validating path-like arguments. - """ - - def test_none_valid(self): - """ - Test `None` is valid. - """ - standardizer_func = PathLikeOrNone() - - self.assertIs( - standardizer_func(None), - None, - msg="Output of `PathLikeOrNone` standardizer not as expected.", - ) - - def test_str_bytes_path_like_valid(self): - """ - Check path-like validator handles str, bytes, and path-like - inputs correctly. - """ - - class ExamplePathLike(os.PathLike): - """ - Path-like class for testing. Key feature: __fspath__ - and __str__ return different outputs. - """ - - def __init__(self, path_str_or_bytes): - self.path = path_str_or_bytes - - def __fspath__(self): - return self.path - - def __str__(self): - path_str = os.fsdecode(self.path) - return f"{type(self).__name__}({path_str})" - - path_standardization_func = PathLikeOrNone() - - # construct path arguments of different type - path_as_str = "example_output_dir/" - path_as_bytes = os.fsencode(path_as_str) - path_like_from_str = ExamplePathLike(path_as_str) - path_like_from_bytes = ExamplePathLike(path_as_bytes) - - # for all possible arguments, output should be - # the str returned by ``common.config.Path`` when - # string representation of the path is input. - expected_output = Path()(path_as_str) - - # check output is as expected in all cases - self.assertEqual( - path_standardization_func(path_as_str), - expected_output, - msg=( - "Path-like validator output from str input " - "does not match expected value." - ), - ) - self.assertEqual( - path_standardization_func(path_as_bytes), - expected_output, - msg=( - "Path-like validator output from bytes input " - "does not match expected value." - ), - ) - self.assertEqual( - path_standardization_func(path_like_from_str), - expected_output, - msg=( - "Path-like validator output from path-like input " - "derived from str does not match expected value." - ), - ) - self.assertEqual( - path_standardization_func(path_like_from_bytes), - expected_output, - msg=( - "Path-like validator output from path-like input " - "derived from bytes does not match expected value." - ), - ) - - class TestPositiveIntOrMinusOne(unittest.TestCase): """ Test validator for -1 or positive int works as expected. diff --git a/pyomo/contrib/pyros/uncertainty_sets.py b/pyomo/contrib/pyros/uncertainty_sets.py index 179f986fdac..028a9f38da1 100644 --- a/pyomo/contrib/pyros/uncertainty_sets.py +++ b/pyomo/contrib/pyros/uncertainty_sets.py @@ -283,48 +283,6 @@ def generate_shape_str(shape, required_shape): ) -class UncertaintySetDomain: - """ - Domain validator for uncertainty set argument. - """ - - def __call__(self, obj): - """ - Type validate uncertainty set object. - - Parameters - ---------- - obj : object - Object to validate. - - Returns - ------- - obj : object - Object that was passed, provided type validation successful. - - Raises - ------ - ValueError - If type validation failed. - """ - if not isinstance(obj, UncertaintySet): - raise ValueError( - f"Expected an {UncertaintySet.__name__} object, " - f"instead received object {obj}" - ) - return obj - - def domain_name(self): - """ - Domain name of self. - """ - return UncertaintySet.__name__ - - -# maintain compatibility with prior versions -uncertainty_sets = UncertaintySetDomain() - - def column(matrix, i): # Get column i of a given multi-dimensional list return [row[i] for row in matrix] From d8b0ba354cb14d3b538dbcdbc41860b694d3afba Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 16 Feb 2024 15:42:23 -0700 Subject: [PATCH 1019/1204] bug --- pyomo/contrib/solver/gurobi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/solver/gurobi.py b/pyomo/contrib/solver/gurobi.py index c1b02c08ef9..c55565e20fb 100644 --- a/pyomo/contrib/solver/gurobi.py +++ b/pyomo/contrib/solver/gurobi.py @@ -239,7 +239,7 @@ class Gurobi(PersistentSolverUtils, PersistentSolverBase): def __init__(self, **kwds): PersistentSolverUtils.__init__(self) PersistentSolverBase.__init__(self, **kwds) - self._num_instances += 1 + Gurobi._num_instances += 1 self._solver_model = None self._symbol_map = SymbolMap() self._labeler = None @@ -310,8 +310,8 @@ def release_license(self): def __del__(self): if not python_is_shutting_down(): - self._num_instances -= 1 - if self._num_instances == 0: + Gurobi._num_instances -= 1 + if Gurobi._num_instances == 0: self.release_license() def version(self): From 350c3c37fa61691f250199ed8574bacd34fb569c Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Sat, 17 Feb 2024 13:19:21 -0700 Subject: [PATCH 1020/1204] Working rewrite of hull that handles nested correctly, many changes to tests because of this --- pyomo/gdp/plugins/hull.py | 386 ++++++++++++++++---------------- pyomo/gdp/tests/common_tests.py | 69 ++++-- pyomo/gdp/tests/models.py | 2 +- pyomo/gdp/tests/test_hull.py | 212 ++++++++++-------- 4 files changed, 366 insertions(+), 303 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index c7a005bb4ea..e880d599366 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -50,6 +50,7 @@ _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 logger = logging.getLogger('pyomo.gdp.hull') @@ -224,10 +225,11 @@ def _get_user_defined_local_vars(self, targets): if t.ctype is Disjunct or isinstance(t, _DisjunctData): # first look beneath where we are (there could be Blocks on this # disjunct) - for b in t.component_data_objects(Block, descend_into=Block, - active=True, - sort=SortComponents.deterministic - ): + for b in t.component_data_objects( + Block, descend_into=Block, + active=True, + sort=SortComponents.deterministic + ): if b not in seen_blocks: self._collect_local_vars_from_block(b, user_defined_local_vars) seen_blocks.add(b) @@ -282,6 +284,7 @@ def _apply_to_impl(self, instance, **kwds): # nested GDPs, we will introduce variables that need disaggregating into # parent Disjuncts as we transform their child Disjunctions. preprocessed_targets = gdp_tree.reverse_topological_sort() + # Get all LocalVars from Suffixes ahead of time local_vars_by_disjunct = self._get_user_defined_local_vars( preprocessed_targets) @@ -303,15 +306,7 @@ def _add_transformation_block(self, to_block): return transBlock, new_block transBlock.lbub = Set(initialize=['lb', 'ub', 'eq']) - # Map between disaggregated variables and their - # originals - transBlock._disaggregatedVarMap = { - 'srcVar': ComponentMap(), - 'disaggregatedVar': ComponentMap(), - } - # Map between disaggregated variables and their lb*indicator <= var <= - # ub*indicator constraints - transBlock._bigMConstraintMap = ComponentMap() + # We will store all of the disaggregation constraints for any # Disjunctions we transform onto this block here. transBlock.disaggregationConstraints = Constraint(NonNegativeIntegers) @@ -329,6 +324,7 @@ def _add_transformation_block(self, to_block): def _transform_disjunctionData(self, obj, index, parent_disjunct, local_vars_by_disjunct): + print("Transforming Disjunction %s" % obj) # Hull reformulation doesn't work if this is an OR constraint. So if # xor is false, give up if not obj.xor: @@ -338,8 +334,8 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, "Must be an XOR!" % obj.name ) # collect the Disjuncts we are going to transform now because we will - # change their active status when we transform them, but still need this - # list after the fact. + # 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] # We put *all* transformed things on the parent Block of this @@ -357,19 +353,23 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, # We first go through and collect all the variables that we are going to # disaggregate. We do this in its own pass because we want to know all - # the Disjuncts that each Var appears in. + # the Disjuncts that each Var appears in since that will tell us exactly + # which diaggregated variables we need. var_order = ComponentSet() disjuncts_var_appears_in = ComponentMap() + # For each disjunct in the disjunction, we will store a list of Vars + # that need a disaggregated counterpart in that disjunct. + disjunct_disaggregated_var_map = {} for disjunct in active_disjuncts: # create the key for each disjunct now - transBlock._disaggregatedVarMap['disaggregatedVar'][ - disjunct - ] = ComponentMap() - for cons in disjunct.component_data_objects( - Constraint, - active=True, - sort=SortComponents.deterministic, - descend_into=Block, + disjunct_disaggregated_var_map[disjunct] = ComponentMap() + for var in get_vars_from_components( + disjunct, + Constraint, + include_fixed=not self._config.assume_fixed_vars_permanent, + active=True, + sort=SortComponents.deterministic, + descend_into=Block ): # [ESJ 02/14/2020] By default, we disaggregate fixed variables # on the philosophy that fixing is not a promise for the future @@ -378,21 +378,20 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, # with their transformed model. However, the user may have set # assume_fixed_vars_permanent to True in which case we will skip # them - for var in EXPR.identify_variables( - cons.body, include_fixed=not - self._config.assume_fixed_vars_permanent): - # Note that, because ComponentSets are ordered, we will - # eventually disaggregate the vars in a deterministic order - # (the order that we found them) - if var not in var_order: - var_order.add(var) - disjuncts_var_appears_in[var] = ComponentSet([disjunct]) - else: - disjuncts_var_appears_in[var].add(disjunct) + + # Note that, because ComponentSets are ordered, we will + # eventually disaggregate the vars in a deterministic order + # (the order that we found them) + if var not in var_order: + var_order.add(var) + disjuncts_var_appears_in[var] = ComponentSet([disjunct]) + else: + disjuncts_var_appears_in[var].add(disjunct) - # We will disaggregate all variables that are not explicitly declared as - # being local. We have marked our own disaggregated variables as local, - # so they will not be re-disaggregated. + # Now, we will disaggregate all variables that are not explicitly + # declared as being local. If we are moving up in a nested tree, we have + # marked our own disaggregated variables as local, so they will not be + # re-disaggregated. vars_to_disaggregate = {disj: ComponentSet() for disj in obj.disjuncts} all_vars_to_disaggregate = ComponentSet() # We will ignore variables declared as local in a Disjunct that don't @@ -407,27 +406,22 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, if self._generate_debug_messages: logger.debug( "Assuming '%s' is not a local var since it is" - "used in multiple disjuncts." - % var.getname(fully_qualified=True) + "used in multiple disjuncts." % var.name ) for disj in disjuncts: vars_to_disaggregate[disj].add(var) all_vars_to_disaggregate.add(var) - else: # disjuncts is a set of length 1 + else: # var only appears in one disjunct disjunct = next(iter(disjuncts)) + # We check if the user declared it as local if disjunct in local_vars_by_disjunct: if var in local_vars_by_disjunct[disjunct]: local_vars[disjunct].add(var) - else: - # It's not declared local to this Disjunct, so we - # disaggregate - vars_to_disaggregate[disjunct].add(var) - all_vars_to_disaggregate.add(var) - else: - # The user didn't declare any local vars for this - # Disjunct, so we know we're disaggregating it - vars_to_disaggregate[disjunct].add(var) - all_vars_to_disaggregate.add(var) + continue + # It's not declared local to this Disjunct, so we + # disaggregate + vars_to_disaggregate[disjunct].add(var) + all_vars_to_disaggregate.add(var) # Now that we know who we need to disaggregate, we will do it # while we also transform the disjuncts. @@ -438,22 +432,22 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, or_expr = 0 for disjunct in obj.disjuncts: or_expr += disjunct.indicator_var.get_associated_binary() - if obj.active: + if disjunct.active: self._transform_disjunct( - disjunct, - transBlock, - vars_to_disaggregate[disjunct], - local_vars[disjunct], - parent_local_var_list, - local_vars_by_disjunct[parent_disjunct] + obj=disjunct, + transBlock=transBlock, + vars_to_disaggregate=vars_to_disaggregate[disjunct], + local_vars=local_vars[disjunct], + parent_local_var_suffix=parent_local_var_list, + 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]) - # add the reaggregation constraints - i = 0 + # Now add the reaggregation constraints for var in all_vars_to_disaggregate: # There are two cases here: Either the var appeared in every # disjunct in the disjunction, or it didn't. If it did, there's @@ -465,8 +459,9 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, # create one more disaggregated var idx = len(disaggregatedVars) disaggregated_var = disaggregatedVars[idx] - # mark this as local because we won't re-disaggregate if this is - # a nested disjunction + print("Creating extra disaggregated var: '%s'" % disaggregated_var) + # mark this as local because we won't re-disaggregate it if this + # is a nested disjunction if parent_local_var_list is not None: parent_local_var_list.append(disaggregated_var) local_vars_by_disjunct[parent_disjunct].add(disaggregated_var) @@ -475,14 +470,34 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, for disj in disjuncts_var_appears_in[var] ) self._declare_disaggregated_var_bounds( - var, - disaggregated_var, - obj, - disaggregated_var_bounds, - (idx, 'lb'), - (idx, 'ub'), - var_free, + original_var=var, + disaggregatedVar=disaggregated_var, + disjunct=obj, + bigmConstraint=disaggregated_var_bounds, + lb_idx=(idx, 'lb'), + ub_idx=(idx, 'ub'), + var_free_indicator=var_free + ) + # Update mappings: + var_info = var.parent_block().private_data() + if 'disaggregated_var_map' not in var_info: + var_info['disaggregated_var_map'] = ComponentMap() + disaggregated_var_map = var_info['disaggregated_var_map'] + dis_var_info = disaggregated_var.parent_block().private_data() + if 'original_var_map' not in dis_var_info: + dis_var_info['original_var_map'] = ComponentMap() + original_var_map = dis_var_info['original_var_map'] + if 'bigm_constraint_map' not in dis_var_info: + dis_var_info['bigm_constraint_map'] = ComponentMap() + bigm_constraint_map = dis_var_info['bigm_constraint_map'] + + if disaggregated_var not in bigm_constraint_map: + bigm_constraint_map[disaggregated_var] = {} + bigm_constraint_map[disaggregated_var][obj] = ( + Reference(disaggregated_var_bounds[idx, :]) ) + original_var_map[disaggregated_var] = var + # For every Disjunct the Var does not appear in, we want to map # that this new variable is its disaggreggated variable. for disj in active_disjuncts: @@ -493,38 +508,28 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, disj._transformation_block is not None and disj not in disjuncts_var_appears_in[var] ): - relaxationBlock = disj._transformation_block().parent_block() - relaxationBlock._bigMConstraintMap[disaggregated_var] = ( - Reference(disaggregated_var_bounds[idx, :]) - ) - relaxationBlock._disaggregatedVarMap['srcVar'][ - disaggregated_var - ] = var - relaxationBlock._disaggregatedVarMap[ - 'disaggregatedVar'][disj][ - var - ] = disaggregated_var + if not disj in disaggregated_var_map: + disaggregated_var_map[disj] = ComponentMap() + disaggregated_var_map[disj][var] = disaggregated_var + # start the expression for the reaggregation constraint with + # this var disaggregatedExpr = disaggregated_var else: disaggregatedExpr = 0 for disjunct in disjuncts_var_appears_in[var]: - # We know this Disjunct was active, so it has been transformed now. - disaggregatedVar = ( - disjunct._transformation_block() - .parent_block() - ._disaggregatedVarMap['disaggregatedVar'][disjunct][var] - ) - disaggregatedExpr += disaggregatedVar + disaggregatedExpr += disjunct_disaggregated_var_map[disjunct][var] cons_idx = len(disaggregationConstraint) # We always aggregate to the original var. If this is nested, this - # constraint will be transformed again. + # constraint will be transformed again. (And if it turns out + # everything in it is local, then that transformation won't actually + # change the mathematical expression, so it's okay. disaggregationConstraint.add(cons_idx, var == disaggregatedExpr) # and update the map so that we can find this later. We index by # variable and the particular disjunction because there is a # different one for each disjunction - if disaggregationConstraintMap.get(var) is not None: + if var in disaggregationConstraintMap: disaggregationConstraintMap[var][obj] = disaggregationConstraint[ cons_idx ] @@ -532,14 +537,13 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, thismap = disaggregationConstraintMap[var] = ComponentMap() thismap[obj] = disaggregationConstraint[cons_idx] - i += 1 - # deactivate for the writers obj.deactivate() def _transform_disjunct(self, obj, transBlock, vars_to_disaggregate, local_vars, - parent_local_var_suffix, parent_disjunct_local_vars): - print("\nTransforming '%s'" % obj.name) + parent_local_var_suffix, parent_disjunct_local_vars, + disjunct_disaggregated_var_map): + print("\nTransforming Disjunct '%s'" % obj.name) relaxationBlock = self._get_disjunct_transformation_block(obj, transBlock) # Put the disaggregated variables all on their own block so that we can @@ -565,7 +569,7 @@ def _transform_disjunct(self, obj, transBlock, vars_to_disaggregate, local_vars, if parent_local_var_suffix is not None: parent_local_var_suffix.append(disaggregatedVar) # Record that it's local for our own bookkeeping in case we're in a - # nested situation in *this* transformation + # nested tree in *this* transformation parent_disjunct_local_vars.add(disaggregatedVar) # add the bigm constraint @@ -574,17 +578,26 @@ def _transform_disjunct(self, obj, transBlock, vars_to_disaggregate, local_vars, disaggregatedVarName + "_bounds", bigmConstraint ) - print("Adding bounds constraints for '%s'" % var) + print("Adding bounds constraints for '%s', the disaggregated var " + "corresponding to Var '%s' on Disjunct '%s'" % + (disaggregatedVar, var, obj)) self._declare_disaggregated_var_bounds( - var, - disaggregatedVar, - obj, - bigmConstraint, - 'lb', - 'ub', - obj.indicator_var.get_associated_binary(), - transBlock, + original_var=var, + disaggregatedVar=disaggregatedVar, + disjunct=obj, + bigmConstraint=bigmConstraint, + lb_idx='lb', + ub_idx='ub', + var_free_indicator=obj.indicator_var.get_associated_binary(), ) + # update the bigm constraint mappings + data_dict = disaggregatedVar.parent_block().private_data() + if 'bigm_constraint_map' not in data_dict: + data_dict['bigm_constraint_map'] = ComponentMap() + if disaggregatedVar not in data_dict['bigm_constraint_map']: + data_dict['bigm_constraint_map'][disaggregatedVar] = {} + data_dict['bigm_constraint_map'][disaggregatedVar][obj] = bigmConstraint + disjunct_disaggregated_var_map[obj][var] = disaggregatedVar for var in local_vars: # we don't need to disaggregate, i.e., we can use this Var, but we @@ -600,35 +613,30 @@ def _transform_disjunct(self, obj, transBlock, vars_to_disaggregate, local_vars, relaxationBlock.add_component(conName, bigmConstraint) parent_block = var.parent_block() - disaggregated_var_map = self._get_disaggregated_var_map(parent_block) print("Adding bounds constraints for local var '%s'" % var) - # TODO: This gets mapped in a place where we can't find it if we ask - # for it from the local var itself. self._declare_disaggregated_var_bounds( - var, - var, - obj, - bigmConstraint, - 'lb', - 'ub', - obj.indicator_var.get_associated_binary(), - disaggregated_var_map, + original_var=var, + disaggregatedVar=var, + disjunct=obj, + bigmConstraint=bigmConstraint, + lb_idx='lb', + ub_idx='ub', + var_free_indicator=obj.indicator_var.get_associated_binary(), ) - - var_substitute_map = dict( - (id(v), newV) - for v, newV in transBlock._disaggregatedVarMap['disaggregatedVar'][ - obj - ].items() - ) - zero_substitute_map = dict( - (id(v), ZeroConstant) - for v, newV in transBlock._disaggregatedVarMap['disaggregatedVar'][ - obj - ].items() - ) - zero_substitute_map.update((id(v), ZeroConstant) for v in local_vars) + # update the bigm constraint mappings + data_dict = var.parent_block().private_data() + if 'bigm_constraint_map' not in data_dict: + data_dict['bigm_constraint_map'] = ComponentMap() + if var not in data_dict['bigm_constraint_map']: + data_dict['bigm_constraint_map'][var] = {} + data_dict['bigm_constraint_map'][var][obj] = bigmConstraint + disjunct_disaggregated_var_map[obj][var] = var + + var_substitute_map = dict((id(v), newV) for v, newV in + disjunct_disaggregated_var_map[obj].items() ) + zero_substitute_map = dict((id(v), ZeroConstant) for v, newV in + disjunct_disaggregated_var_map[obj].items() ) # Transform each component within this disjunct self._transform_block_components( @@ -650,7 +658,6 @@ def _declare_disaggregated_var_bounds( lb_idx, ub_idx, var_free_indicator, - disaggregated_var_map, ): lb = original_var.lb ub = original_var.ub @@ -669,19 +676,24 @@ def _declare_disaggregated_var_bounds( if ub: bigmConstraint.add(ub_idx, disaggregatedVar <= ub * var_free_indicator) + original_var_info = original_var.parent_block().private_data() + if 'disaggregated_var_map' not in original_var_info: + original_var_info['disaggregated_var_map'] = ComponentMap() + disaggregated_var_map = original_var_info['disaggregated_var_map'] + + disaggregated_var_info = disaggregatedVar.parent_block().private_data() + if 'original_var_map' not in disaggregated_var_info: + disaggregated_var_info['original_var_map'] = ComponentMap() + original_var_map = disaggregated_var_info['original_var_map'] + # store the mappings from variables to their disaggregated selves on # the transformation block - disaggregated_var_map['disaggregatedVar'][disjunct][ - original_var] = disaggregatedVar - disaggregated_var_map['srcVar'][disaggregatedVar] = original_var - bigMConstraintMap[disaggregatedVar] = bigmConstraint - - # if transBlock is not None: - # transBlock._disaggregatedVarMap['disaggregatedVar'][disjunct][ - # original_var - # ] = disaggregatedVar - # transBlock._disaggregatedVarMap['srcVar'][disaggregatedVar] = original_var - # transBlock._bigMConstraintMap[disaggregatedVar] = bigmConstraint + if disjunct not in disaggregated_var_map: + disaggregated_var_map[disjunct] = ComponentMap() + print("DISAGGREGATED VAR MAP (%s, %s) : %s" % (disjunct, original_var, + disaggregatedVar)) + disaggregated_var_map[disjunct][original_var] = disaggregatedVar + original_var_map[disaggregatedVar] = original_var def _get_local_var_list(self, parent_disjunct): # Add or retrieve Suffix from parent_disjunct so that, if this is @@ -903,17 +915,19 @@ def get_disaggregated_var(self, v, disjunct, raise_exception=True): """ if disjunct._transformation_block is None: raise GDP_Error("Disjunct '%s' has not been transformed" % disjunct.name) - transBlock = disjunct._transformation_block().parent_block() - try: - return transBlock._disaggregatedVarMap['disaggregatedVar'][disjunct][v] - except: - if raise_exception: - logger.error( - "It does not appear '%s' is a " - "variable that appears in disjunct '%s'" % (v.name, disjunct.name) - ) - raise - return none + msg = ("It does not appear '%s' is a " + "variable that appears in disjunct '%s'" % (v.name, disjunct.name)) + var_map = v.parent_block().private_data() + if 'disaggregated_var_map' in var_map: + try: + return var_map['disaggregated_var_map'][disjunct][v] + except: + if raise_exception: + logger.error(msg) + raise + elif raise_exception: + raise GDP_Error(msg) + return None def get_src_var(self, disaggregated_var): """ @@ -927,23 +941,13 @@ def get_src_var(self, disaggregated_var): (and so appears on a transformation block of some Disjunct) """ - msg = ( + var_map = disaggregated_var.parent_block().private_data() + if 'original_var_map' in var_map: + if disaggregated_var in var_map['original_var_map']: + return var_map['original_var_map'][disaggregated_var] + raise GDP_Error( "'%s' does not appear to be a " - "disaggregated variable" % disaggregated_var.name - ) - # We always put a dictionary called '_disaggregatedVarMap' on the parent - # block of the variable. If it's not there, then this probably isn't a - # disaggregated Var (or if it is it's a developer error). Similarly, if - # the var isn't in the dictionary, if we're doing what we should, then - # it's not a disaggregated var. - transBlock = disaggregated_var.parent_block() - if not hasattr(transBlock, '_disaggregatedVarMap'): - raise GDP_Error(msg) - try: - return transBlock._disaggregatedVarMap['srcVar'][disaggregated_var] - except: - logger.error(msg) - raise + "disaggregated variable" % disaggregated_var.name) # retrieves the disaggregation constraint for original_var resulting from # transforming disjunction @@ -990,7 +994,7 @@ def get_disaggregation_constraint(self, original_var, disjunction, cons = self.get_transformed_constraints(cons)[0] return cons - def get_var_bounds_constraint(self, v): + def get_var_bounds_constraint(self, v, disjunct=None): """ Returns the IndexedConstraint which sets a disaggregated variable to be within its bounds when its Disjunct is active and to @@ -1002,36 +1006,32 @@ def get_var_bounds_constraint(self, v): v: a Var that was created by the hull transformation as a disaggregated variable (and so appears on a transformation block of some Disjunct) + disjunct: (For nested Disjunctions) Which Disjunct in the + hierarchy the bounds Constraint should correspond to. + Optional since for non-nested models this can be inferred. """ - msg = ( + info = v.parent_block().private_data() + if 'bigm_constraint_map' in info: + if v in info['bigm_constraint_map']: + if len(info['bigm_constraint_map'][v]) == 1: + # Not nested, or it's at the top layer, so we're fine. + return list(info['bigm_constraint_map'][v].values())[0] + elif disjunct is not None: + # This is nested, so we need to walk up to find the active ones + return info['bigm_constraint_map'][v][disjunct] + else: + raise ValueError( + "It appears that the variable '%s' appears " + "within a nested GDP hierarchy, and no " + "'disjunct' argument was specified. Please " + "specify for which Disjunct the bounds " + "constraint for '%s' should be returned." + % (v, v)) + raise GDP_Error( "Either '%s' is not a disaggregated variable, or " "the disjunction that disaggregates it has not " "been properly transformed." % v.name ) - # This can only go well if v is a disaggregated var - transBlock = v.parent_block() - if not hasattr(transBlock, '_bigMConstraintMap'): - try: - transBlock = transBlock.parent_block().parent_block() - except: - logger.error(msg) - raise - try: - cons = transBlock._bigMConstraintMap[v] - except: - logger.error(msg) - raise - transformed_cons = {key: con for key, con in cons.items()} - def is_active(cons): - return all(c.active for c in cons.values()) - while not is_active(transformed_cons): - if 'lb' in transformed_cons: - transformed_cons['lb'] = self.get_transformed_constraints( - transformed_cons['lb'])[0] - if 'ub' in transformed_cons: - transformed_cons['ub'] = self.get_transformed_constraints( - transformed_cons['ub'])[0] - return transformed_cons def get_transformed_constraints(self, cons): cons = super().get_transformed_constraints(cons) diff --git a/pyomo/gdp/tests/common_tests.py b/pyomo/gdp/tests/common_tests.py index c5e750e4f08..bef05a78cf6 100644 --- a/pyomo/gdp/tests/common_tests.py +++ b/pyomo/gdp/tests/common_tests.py @@ -697,32 +697,29 @@ def check_indexedDisj_only_targets_transformed(self, transformation): trans.get_transformed_constraints(m.disjunct1[1, 0].c)[0] .parent_block() .parent_block(), - disjBlock[2], + disjBlock[0], ) self.assertIs( trans.get_transformed_constraints(m.disjunct1[1, 1].c)[0].parent_block(), - disjBlock[3], + disjBlock[1], ) # In the disaggregated var bounds self.assertIs( trans.get_transformed_constraints(m.disjunct1[2, 0].c)[0] .parent_block() .parent_block(), - disjBlock[0], + disjBlock[2], ) self.assertIs( trans.get_transformed_constraints(m.disjunct1[2, 1].c)[0].parent_block(), - disjBlock[1], + disjBlock[3], ) # This relies on the disjunctions being transformed in the same order # every time. These are the mappings between the indices of the original # disjuncts and the indices on the indexed block on the transformation # block. - if transformation == 'bigm': - pairs = [((1, 0), 0), ((1, 1), 1), ((2, 0), 2), ((2, 1), 3)] - elif transformation == 'hull': - pairs = [((2, 0), 0), ((2, 1), 1), ((1, 0), 2), ((1, 1), 3)] + pairs = [((1, 0), 0), ((1, 1), 1), ((2, 0), 2), ((2, 1), 3)] for i, j in pairs: self.assertIs(trans.get_src_disjunct(disjBlock[j]), m.disjunct1[i]) @@ -1731,35 +1728,69 @@ def check_transformation_blocks_nestedDisjunctions(self, m, transformation): # This is a much more comprehensive test that doesn't depend on # transformation Block structure, so just reuse it: hull = TransformationFactory('gdp.hull') - d3 = hull.get_disaggregated_var(m.d1.d3.indicator_var, m.d1) - d4 = hull.get_disaggregated_var(m.d1.d4.indicator_var, m.d1) + d3 = hull.get_disaggregated_var(m.d1.d3.binary_indicator_var, m.d1) + d4 = hull.get_disaggregated_var(m.d1.d4.binary_indicator_var, m.d1) self.check_transformed_model_nestedDisjuncts(m, d3, d4) - # check the disaggregated indicator var bound constraints too - cons = hull.get_var_bounds_constraint(d3) + # Check the 4 constraints that are unique to the case where we didn't + # declare d1.d3 and d1.d4 as local + d32 = hull.get_disaggregated_var(m.d1.d3.binary_indicator_var, m.d2) + d42 = hull.get_disaggregated_var(m.d1.d4.binary_indicator_var, m.d2) + # check the additional disaggregated indicator var bound constraints + cons = hull.get_var_bounds_constraint(d32) self.assertEqual(len(cons), 1) check_obj_in_active_tree(self, cons['ub']) cons_expr = self.simplify_leq_cons(cons['ub']) + # Note that this comes out as d32 <= 1 - d1.ind_var because it's the + # "extra" disaggregated var that gets created when it need to be + # disaggregated for d1, but it's not used in d2 assertExpressionsEqual( self, cons_expr, - d3 - m.d1.binary_indicator_var <= 0.0 + d32 + m.d1.binary_indicator_var - 1 <= 0.0 ) - cons = hull.get_var_bounds_constraint(d4) + cons = hull.get_var_bounds_constraint(d42) self.assertEqual(len(cons), 1) check_obj_in_active_tree(self, cons['ub']) cons_expr = self.simplify_leq_cons(cons['ub']) + # Note that this comes out as d42 <= 1 - d1.ind_var because it's the + # "extra" disaggregated var that gets created when it need to be + # disaggregated for d1, but it's not used in d2 + assertExpressionsEqual( + self, + cons_expr, + d42 + m.d1.binary_indicator_var - 1 <= 0.0 + ) + # check the aggregation constraints for the disaggregated indicator vars + cons = hull.get_disaggregation_constraint(m.d1.d3.binary_indicator_var, + m.disj) + check_obj_in_active_tree(self, cons) + cons_expr = self.simplify_cons(cons) + assertExpressionsEqual( + self, + cons_expr, + m.d1.d3.binary_indicator_var - d32 - d3 == 0.0 + ) + cons = hull.get_disaggregation_constraint(m.d1.d4.binary_indicator_var, + m.disj) + check_obj_in_active_tree(self, cons) + cons_expr = self.simplify_cons(cons) assertExpressionsEqual( self, cons_expr, - d4 - m.d1.binary_indicator_var <= 0.0 + m.d1.d4.binary_indicator_var - d42 - d4 == 0.0 ) - num_cons = len(m.component_data_objects(Constraint, - active=True, - descend_into=Block)) - self.assertEqual(num_cons, 10) + num_cons = len(list(m.component_data_objects(Constraint, + active=True, + descend_into=Block))) + # 30 total constraints in transformed model minus 10 trivial bounds + # (lower bounds of 0) gives us 20 constraints total: + self.assertEqual(num_cons, 20) + # (And this is 4 more than we test in + # self.check_transformed_model_nestedDisjuncts, so that's comforting + # too.) def check_nested_disjunction_target(self, transformation): diff --git a/pyomo/gdp/tests/models.py b/pyomo/gdp/tests/models.py index a52f08b790e..3477d182241 100644 --- a/pyomo/gdp/tests/models.py +++ b/pyomo/gdp/tests/models.py @@ -463,7 +463,7 @@ def makeNestedDisjunctions(): (makeNestedDisjunctions_NestedDisjuncts is a much simpler model. All this adds is that it has a nested disjunction on a DisjunctData as well - as on a SimpleDisjunct. So mostly it exists for historical reasons.) + as on a ScalarDisjunct. So mostly it exists for historical reasons.) """ m = ConcreteModel() m.x = Var(bounds=(-9, 9)) diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index 436367b3a89..b3bfbaaf8da 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -412,13 +412,11 @@ def test_error_for_or(self): ) def check_disaggregation_constraint(self, cons, var, disvar1, disvar2): - repn = generate_standard_repn(cons.body) - self.assertEqual(cons.lower, 0) - self.assertEqual(cons.upper, 0) - self.assertEqual(len(repn.linear_vars), 3) - ct.check_linear_coef(self, repn, var, 1) - ct.check_linear_coef(self, repn, disvar1, -1) - ct.check_linear_coef(self, repn, disvar2, -1) + assertExpressionsEqual( + self, + cons.expr, + var == disvar1 + disvar2 + ) def test_disaggregation_constraint(self): m = models.makeTwoTermDisj_Nonlinear() @@ -430,8 +428,8 @@ def test_disaggregation_constraint(self): self.check_disaggregation_constraint( hull.get_disaggregation_constraint(m.w, m.disjunction), m.w, - disjBlock[1].disaggregatedVars.w, transBlock._disaggregatedVars[1], + disjBlock[1].disaggregatedVars.w, ) self.check_disaggregation_constraint( hull.get_disaggregation_constraint(m.x, m.disjunction), @@ -442,8 +440,8 @@ def test_disaggregation_constraint(self): self.check_disaggregation_constraint( hull.get_disaggregation_constraint(m.y, m.disjunction), m.y, - disjBlock[0].disaggregatedVars.y, transBlock._disaggregatedVars[0], + disjBlock[0].disaggregatedVars.y, ) def test_xor_constraint_mapping(self): @@ -672,17 +670,38 @@ def test_global_vars_local_to_a_disjunction_disaggregated(self): self.assertIs(hull.get_src_var(x), m.disj1.x) # there is a spare x on disjunction1's block - x2 = m.disjunction1.algebraic_constraint.parent_block()._disaggregatedVars[2] + x2 = m.disjunction1.algebraic_constraint.parent_block()._disaggregatedVars[0] self.assertIs(hull.get_disaggregated_var(m.disj1.x, m.disj2), x2) self.assertIs(hull.get_src_var(x2), m.disj1.x) + # What really matters is that the above matches this: + agg_cons = hull.get_disaggregation_constraint(m.disj1.x, m.disjunction1) + assertExpressionsEqual( + self, + agg_cons.expr, + m.disj1.x == x2 + hull.get_disaggregated_var(m.disj1.x, m.disj1) + ) # and both a spare x and y on disjunction2's block - x2 = m.disjunction2.algebraic_constraint.parent_block()._disaggregatedVars[0] - y1 = m.disjunction2.algebraic_constraint.parent_block()._disaggregatedVars[1] + x2 = m.disjunction2.algebraic_constraint.parent_block()._disaggregatedVars[1] + y1 = m.disjunction2.algebraic_constraint.parent_block()._disaggregatedVars[2] self.assertIs(hull.get_disaggregated_var(m.disj1.x, m.disj4), x2) self.assertIs(hull.get_src_var(x2), m.disj1.x) self.assertIs(hull.get_disaggregated_var(m.disj1.y, m.disj3), y1) self.assertIs(hull.get_src_var(y1), m.disj1.y) + # and again what really matters is that these align with the + # disaggregation constraints: + agg_cons = hull.get_disaggregation_constraint(m.disj1.x, m.disjunction2) + assertExpressionsEqual( + self, + agg_cons.expr, + m.disj1.x == x2 + hull.get_disaggregated_var(m.disj1.x, m.disj3) + ) + agg_cons = hull.get_disaggregation_constraint(m.disj1.y, m.disjunction2) + assertExpressionsEqual( + self, + agg_cons.expr, + m.disj1.y == y1 + hull.get_disaggregated_var(m.disj1.y, m.disj4) + ) def check_name_collision_disaggregated_vars(self, m, disj): hull = TransformationFactory('gdp.hull') @@ -1105,7 +1124,7 @@ def check_trans_block_disjunctions_of_disjunct_datas(self, m): self.assertEqual(len(transBlock1.relaxedDisjuncts), 4) hull = TransformationFactory('gdp.hull') - firstTerm2 = transBlock1.relaxedDisjuncts[0] + firstTerm2 = transBlock1.relaxedDisjuncts[2] self.assertIs(firstTerm2, m.firstTerm[2].transformation_block) self.assertIsInstance(firstTerm2.disaggregatedVars.component("x"), Var) constraints = hull.get_transformed_constraints(m.firstTerm[2].cons) @@ -1119,7 +1138,7 @@ def check_trans_block_disjunctions_of_disjunct_datas(self, m): self.assertIs(cons.parent_block(), firstTerm2) self.assertEqual(len(cons), 2) - secondTerm2 = transBlock1.relaxedDisjuncts[1] + secondTerm2 = transBlock1.relaxedDisjuncts[3] self.assertIs(secondTerm2, m.secondTerm[2].transformation_block) self.assertIsInstance(secondTerm2.disaggregatedVars.component("x"), Var) constraints = hull.get_transformed_constraints(m.secondTerm[2].cons) @@ -1133,7 +1152,7 @@ def check_trans_block_disjunctions_of_disjunct_datas(self, m): self.assertIs(cons.parent_block(), secondTerm2) self.assertEqual(len(cons), 2) - firstTerm1 = transBlock1.relaxedDisjuncts[2] + firstTerm1 = transBlock1.relaxedDisjuncts[0] self.assertIs(firstTerm1, m.firstTerm[1].transformation_block) self.assertIsInstance(firstTerm1.disaggregatedVars.component("x"), Var) self.assertTrue(firstTerm1.disaggregatedVars.x.is_fixed()) @@ -1151,7 +1170,7 @@ def check_trans_block_disjunctions_of_disjunct_datas(self, m): self.assertIs(cons.parent_block(), firstTerm1) self.assertEqual(len(cons), 2) - secondTerm1 = transBlock1.relaxedDisjuncts[3] + secondTerm1 = transBlock1.relaxedDisjuncts[1] self.assertIs(secondTerm1, m.secondTerm[1].transformation_block) self.assertIsInstance(secondTerm1.disaggregatedVars.component("x"), Var) constraints = hull.get_transformed_constraints(m.secondTerm[1].cons) @@ -1379,9 +1398,8 @@ def test_deactivated_disjunct_leaves_nested_disjuncts_active(self): ct.check_deactivated_disjunct_leaves_nested_disjunct_active(self, 'hull') def test_mappings_between_disjunctions_and_xors(self): - # This test is nearly identical to the one in bigm, but because of - # different transformation orders, the name conflict gets resolved in - # the opposite way. + # Tests that the XOR constraints are put on the parent block of the + # disjunction, and checks the mappings. m = models.makeNestedDisjunctions() transform = TransformationFactory('gdp.hull') transform.apply_to(m) @@ -1390,8 +1408,10 @@ def test_mappings_between_disjunctions_and_xors(self): disjunctionPairs = [ (m.disjunction, transBlock.disjunction_xor), - (m.disjunct[1].innerdisjunction[0], transBlock.innerdisjunction_xor[0]), - (m.simpledisjunct.innerdisjunction, transBlock.innerdisjunction_xor_4), + (m.disjunct[1].innerdisjunction[0], + m.disjunct[1].innerdisjunction[0].algebraic_constraint.parent_block().innerdisjunction_xor[0]), + (m.simpledisjunct.innerdisjunction, + m.simpledisjunct.innerdisjunction.algebraic_constraint.parent_block().innerdisjunction_xor), ] # check disjunction mappings @@ -1568,24 +1588,23 @@ def test_transformed_model_nestedDisjuncts(self): m.d1.d4.binary_indicator_var) # Last, check that there aren't things we weren't expecting - all_cons = list(m.component_data_objects(Constraint, active=True, descend_into=Block)) - num_cons = len(all_cons) - - for idx, cons in enumerate(all_cons): - print(idx) - print(cons.name) - print(cons.expr) - print("") # 2 disaggregation constraints for x 0,3 - # + 6 bounds constraints for x 6,8,9,13,14,16 These are dumb: 10,14,16 + # + 6 bounds constraints for x 6,8,9,13,14,16 # + 2 bounds constraints for inner indicator vars 11, 12 # + 2 exactly-one constraints 1,4 # + 4 transformed constraints 2,5,7,15 - self.assertEqual(num_cons, 16) + self.assertEqual(len(all_cons), 16) def check_transformed_model_nestedDisjuncts(self, m, d3, d4): + # This function checks all of the 16 constraint expressions from + # transforming models.makeNestedDisjunction_NestedDisjuncts when + # declaring the inner indicator vars (d3 and d4) as local. Note that it + # also is a correct test for the case where the inner indicator vars are + # *not* declared as local, but not a complete one, since there are + # additional constraints in that case (see + # check_transformation_blocks_nestedDisjunctions in common_tests.py). hull = TransformationFactory('gdp.hull') transBlock = m._pyomo_gdp_hull_reformulation self.assertTrue(transBlock.active) @@ -1654,7 +1673,7 @@ def check_transformed_model_nestedDisjuncts(self, m, d3, d4): assertExpressionsEqual( self, cons_expr, - 1.2*m.d1.d3.binary_indicator_var - x_d3 <= 0.0 + 1.2*d3 - x_d3 <= 0.0 ) cons = hull.get_transformed_constraints(m.d1.d4.c) @@ -1665,7 +1684,7 @@ def check_transformed_model_nestedDisjuncts(self, m, d3, d4): assertExpressionsEqual( self, cons_expr, - 1.3*m.d1.d4.binary_indicator_var - x_d4 <= 0.0 + 1.3*d4 - x_d4 <= 0.0 ) cons = hull.get_transformed_constraints(m.d1.c) @@ -1711,41 +1730,69 @@ def check_transformed_model_nestedDisjuncts(self, m, d3, d4): cons_expr, x_d2 - 2*m.d2.binary_indicator_var <= 0.0 ) - cons = hull.get_var_bounds_constraint(x_d3) + cons = hull.get_var_bounds_constraint(x_d3, m.d1.d3) # the lb is trivial in this case, so we just have 1 self.assertEqual(len(cons), 1) - ct.check_obj_in_active_tree(self, cons['ub']) - cons_expr = self.simplify_leq_cons(cons['ub']) + # And we know it has actually been transformed again, so get that one + cons = hull.get_transformed_constraints(cons['ub']) + self.assertEqual(len(cons), 1) + ub = cons[0] + ct.check_obj_in_active_tree(self, ub) + cons_expr = self.simplify_leq_cons(ub) assertExpressionsEqual( self, cons_expr, x_d3 - 2*d3 <= 0.0 ) - cons = hull.get_var_bounds_constraint(x_d4) + cons = hull.get_var_bounds_constraint(x_d4, m.d1.d4) # the lb is trivial in this case, so we just have 1 self.assertEqual(len(cons), 1) - ct.check_obj_in_active_tree(self, cons['ub']) - cons_expr = self.simplify_leq_cons(cons['ub']) + # And we know it has actually been transformed again, so get that one + cons = hull.get_transformed_constraints(cons['ub']) + self.assertEqual(len(cons), 1) + ub = cons[0] + ct.check_obj_in_active_tree(self, ub) + cons_expr = self.simplify_leq_cons(ub) assertExpressionsEqual( self, cons_expr, x_d4 - 2*d4 <= 0.0 ) + cons = hull.get_var_bounds_constraint(x_d3, m.d1) + self.assertEqual(len(cons), 1) + ub = cons['ub'] + ct.check_obj_in_active_tree(self, ub) + cons_expr = self.simplify_leq_cons(ub) + assertExpressionsEqual( + self, + cons_expr, + x_d3 - 2*m.d1.binary_indicator_var <= 0.0 + ) + cons = hull.get_var_bounds_constraint(x_d4, m.d1) + self.assertEqual(len(cons), 1) + ub = cons['ub'] + ct.check_obj_in_active_tree(self, ub) + cons_expr = self.simplify_leq_cons(ub) + assertExpressionsEqual( + self, + cons_expr, + x_d4 - 2*m.d1.binary_indicator_var <= 0.0 + ) # Bounds constraints for local vars - cons = hull.get_var_bounds_constraint(m.d1.d3.binary_indicator_var) + cons = hull.get_var_bounds_constraint(d3) ct.check_obj_in_active_tree(self, cons['ub']) assertExpressionsEqual( self, cons['ub'].expr, - m.d1.d3.binary_indicator_var <= m.d1.binary_indicator_var + d3 <= m.d1.binary_indicator_var ) - cons = hull.get_var_bounds_constraint(m.d1.d4.binary_indicator_var) + cons = hull.get_var_bounds_constraint(d4) ct.check_obj_in_active_tree(self, cons['ub']) assertExpressionsEqual( self, cons['ub'].expr, - m.d1.d4.binary_indicator_var <= m.d1.binary_indicator_var + d4 <= m.d1.binary_indicator_var ) @unittest.skipIf(not linear_solvers, "No linear solver available") @@ -1853,6 +1900,9 @@ def d_r(e): e.c1 = Constraint(expr=e.lambdas[1] + e.lambdas[2] == 1) e.c2 = Constraint(expr=m.x == 2 * e.lambdas[1] + 3 * e.lambdas[2]) + d.LocalVars = Suffix(direction=Suffix.LOCAL) + d.LocalVars[d] = [d.d_l.indicator_var.get_associated_binary(), + d.d_r.indicator_var.get_associated_binary()] d.inner_disj = Disjunction(expr=[d.d_l, d.d_r]) m.disj = Disjunction(expr=[m.d_l, m.d_r]) @@ -1875,28 +1925,29 @@ def d_r(e): cons = hull.get_transformed_constraints(d.c1) self.assertEqual(len(cons), 1) convex_combo = cons[0] + convex_combo_expr = self.simplify_cons(convex_combo) assertExpressionsEqual( self, - convex_combo.expr, - lambda1 + lambda2 - (1 - d.indicator_var.get_associated_binary()) * 0.0 - == d.indicator_var.get_associated_binary(), + convex_combo_expr, + lambda1 + lambda2 - d.indicator_var.get_associated_binary() + == 0.0, ) cons = hull.get_transformed_constraints(d.c2) self.assertEqual(len(cons), 1) get_x = cons[0] + get_x_expr = self.simplify_cons(get_x) assertExpressionsEqual( self, - get_x.expr, - x - - (2 * lambda1 + 3 * lambda2) - - (1 - d.indicator_var.get_associated_binary()) * 0.0 - == 0.0 * d.indicator_var.get_associated_binary(), + get_x_expr, + x - 2 * lambda1 - 3 * lambda2 + == 0.0, ) cons = hull.get_disaggregation_constraint(m.x, m.disj) assertExpressionsEqual(self, cons.expr, m.x == x1 + x2) cons = hull.get_disaggregation_constraint(m.x, m.d_r.inner_disj) - assertExpressionsEqual(self, cons.expr, x2 == x3 + x4) + cons_expr = self.simplify_cons(cons) + assertExpressionsEqual(self, cons_expr, x2 - x3 - x4 == 0.0) def test_nested_with_var_that_does_not_appear_in_every_disjunct(self): m = ConcreteModel() @@ -1949,7 +2000,7 @@ def test_nested_with_var_that_does_not_appear_in_every_disjunct(self): x_c3 == 0.0) def simplify_cons(self, cons): - visitor = LinearRepnVisitor({}, {}, {}) + visitor = LinearRepnVisitor({}, {}, {}, None) lb = cons.lower ub = cons.upper self.assertEqual(cons.lb, cons.ub) @@ -1958,7 +2009,7 @@ def simplify_cons(self, cons): return repn.to_expression(visitor) == lb def simplify_leq_cons(self, cons): - visitor = LinearRepnVisitor({}, {}, {}) + visitor = LinearRepnVisitor({}, {}, {}, None) self.assertIsNone(cons.lower) ub = cons.upper repn = visitor.walk_expression(cons.body) @@ -2294,20 +2345,13 @@ def test_mapping_method_errors(self): hull = TransformationFactory('gdp.hull') hull.apply_to(m) - log = StringIO() - with LoggingIntercept(log, 'pyomo.gdp.hull', logging.ERROR): - self.assertRaisesRegex( - AttributeError, - "'NoneType' object has no attribute 'parent_block'", - hull.get_var_bounds_constraint, - m.w, - ) - self.assertRegex( - log.getvalue(), + with self.assertRaisesRegex( + GDP_Error, ".*Either 'w' is not a disaggregated variable, " "or the disjunction that disaggregates it has " "not been properly transformed.", - ) + ): + hull.get_var_bounds_constraint(m.w) log = StringIO() with LoggingIntercept(log, 'pyomo.gdp.hull', logging.ERROR): @@ -2328,36 +2372,24 @@ def test_mapping_method_errors(self): r"Disjunction 'disjunction'", ) - log = StringIO() - with LoggingIntercept(log, 'pyomo.gdp.hull', logging.ERROR): - self.assertRaisesRegex( - AttributeError, - "'NoneType' object has no attribute 'parent_block'", - hull.get_src_var, - m.w, - ) - self.assertRegex( - log.getvalue(), ".*'w' does not appear to be a disaggregated variable" - ) + with self.assertRaisesRegex( + GDP_Error, + ".*'w' does not appear to be a disaggregated variable" + ): + hull.get_src_var(m.w,) - log = StringIO() - with LoggingIntercept(log, 'pyomo.gdp.hull', logging.ERROR): - self.assertRaisesRegex( - KeyError, - r".*_pyomo_gdp_hull_reformulation.relaxedDisjuncts\[1\]." - r"disaggregatedVars.w", - hull.get_disaggregated_var, - m.d[1].transformation_block.disaggregatedVars.w, - m.d[1], - ) - self.assertRegex( - log.getvalue(), + with self.assertRaisesRegex( + GDP_Error, r".*It does not appear " r"'_pyomo_gdp_hull_reformulation." r"relaxedDisjuncts\[1\].disaggregatedVars.w' " r"is a variable that appears in disjunct " - r"'d\[1\]'", - ) + r"'d\[1\]'" + ): + hull.get_disaggregated_var( + m.d[1].transformation_block.disaggregatedVars.w, + m.d[1], + ) m.random_disjunction = Disjunction(expr=[m.w == 2, m.w >= 7]) self.assertRaisesRegex( From 0ff74b63eed54196534b04ee57c607c041aa5b3f Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Sat, 17 Feb 2024 13:21:04 -0700 Subject: [PATCH 1021/1204] Black has opinions --- pyomo/gdp/plugins/hull.py | 116 +++++++++++++---------- pyomo/gdp/tests/common_tests.py | 28 ++---- pyomo/gdp/tests/test_hull.py | 163 ++++++++++++-------------------- 3 files changed, 133 insertions(+), 174 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index e880d599366..9fcac6a8f4e 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -57,6 +57,7 @@ from pytest import set_trace + @TransformationFactory.register( 'gdp.hull', doc="Relax disjunctive model by forming the hull reformulation." ) @@ -226,9 +227,10 @@ def _get_user_defined_local_vars(self, targets): # first look beneath where we are (there could be Blocks on this # disjunct) for b in t.component_data_objects( - Block, descend_into=Block, - active=True, - sort=SortComponents.deterministic + Block, + descend_into=Block, + active=True, + sort=SortComponents.deterministic, ): if b not in seen_blocks: self._collect_local_vars_from_block(b, user_defined_local_vars) @@ -237,8 +239,9 @@ def _get_user_defined_local_vars(self, targets): blk = t while blk is not None: if blk not in seen_blocks: - self._collect_local_vars_from_block(blk, - user_defined_local_vars) + self._collect_local_vars_from_block( + blk, user_defined_local_vars + ) seen_blocks.add(blk) blk = blk.parent_block() return user_defined_local_vars @@ -285,16 +288,12 @@ def _apply_to_impl(self, instance, **kwds): # parent Disjuncts as we transform their child Disjunctions. preprocessed_targets = gdp_tree.reverse_topological_sort() # Get all LocalVars from Suffixes ahead of time - local_vars_by_disjunct = self._get_user_defined_local_vars( - preprocessed_targets) + local_vars_by_disjunct = self._get_user_defined_local_vars(preprocessed_targets) for t in preprocessed_targets: if t.ctype is Disjunction: self._transform_disjunctionData( - t, - t.index(), - gdp_tree.parent(t), - local_vars_by_disjunct + t, t.index(), gdp_tree.parent(t), local_vars_by_disjunct ) # We skip disjuncts now, because we need information from the # disjunctions to transform them (which variables to disaggregate), @@ -322,8 +321,9 @@ def _add_transformation_block(self, to_block): return transBlock, True - def _transform_disjunctionData(self, obj, index, parent_disjunct, - local_vars_by_disjunct): + def _transform_disjunctionData( + self, obj, index, parent_disjunct, local_vars_by_disjunct + ): print("Transforming Disjunction %s" % obj) # Hull reformulation doesn't work if this is an OR constraint. So if # xor is false, give up @@ -364,12 +364,12 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, # create the key for each disjunct now disjunct_disaggregated_var_map[disjunct] = ComponentMap() for var in get_vars_from_components( - disjunct, - Constraint, - include_fixed=not self._config.assume_fixed_vars_permanent, - active=True, - sort=SortComponents.deterministic, - descend_into=Block + disjunct, + Constraint, + include_fixed=not self._config.assume_fixed_vars_permanent, + active=True, + sort=SortComponents.deterministic, + descend_into=Block, ): # [ESJ 02/14/2020] By default, we disaggregate fixed variables # on the philosophy that fixing is not a promise for the future @@ -378,7 +378,7 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, # with their transformed model. However, the user may have set # assume_fixed_vars_permanent to True in which case we will skip # them - + # Note that, because ComponentSets are ordered, we will # eventually disaggregate the vars in a deterministic order # (the order that we found them) @@ -411,7 +411,7 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, for disj in disjuncts: vars_to_disaggregate[disj].add(var) all_vars_to_disaggregate.add(var) - else: # var only appears in one disjunct + else: # var only appears in one disjunct disjunct = next(iter(disjuncts)) # We check if the user declared it as local if disjunct in local_vars_by_disjunct: @@ -440,7 +440,7 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, local_vars=local_vars[disjunct], parent_local_var_suffix=parent_local_var_list, parent_disjunct_local_vars=local_vars_by_disjunct[parent_disjunct], - disjunct_disaggregated_var_map=disjunct_disaggregated_var_map + 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 @@ -476,7 +476,7 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, bigmConstraint=disaggregated_var_bounds, lb_idx=(idx, 'lb'), ub_idx=(idx, 'ub'), - var_free_indicator=var_free + var_free_indicator=var_free, ) # Update mappings: var_info = var.parent_block().private_data() @@ -493,8 +493,8 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, if disaggregated_var not in bigm_constraint_map: bigm_constraint_map[disaggregated_var] = {} - bigm_constraint_map[disaggregated_var][obj] = ( - Reference(disaggregated_var_bounds[idx, :]) + bigm_constraint_map[disaggregated_var][obj] = Reference( + disaggregated_var_bounds[idx, :] ) original_var_map[disaggregated_var] = var @@ -540,9 +540,16 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, # deactivate for the writers obj.deactivate() - def _transform_disjunct(self, obj, transBlock, vars_to_disaggregate, local_vars, - parent_local_var_suffix, parent_disjunct_local_vars, - disjunct_disaggregated_var_map): + def _transform_disjunct( + self, + obj, + transBlock, + vars_to_disaggregate, + local_vars, + parent_local_var_suffix, + parent_disjunct_local_vars, + disjunct_disaggregated_var_map, + ): print("\nTransforming Disjunct '%s'" % obj.name) relaxationBlock = self._get_disjunct_transformation_block(obj, transBlock) @@ -578,9 +585,11 @@ def _transform_disjunct(self, obj, transBlock, vars_to_disaggregate, local_vars, disaggregatedVarName + "_bounds", bigmConstraint ) - print("Adding bounds constraints for '%s', the disaggregated var " - "corresponding to Var '%s' on Disjunct '%s'" % - (disaggregatedVar, var, obj)) + print( + "Adding bounds constraints for '%s', the disaggregated var " + "corresponding to Var '%s' on Disjunct '%s'" + % (disaggregatedVar, var, obj) + ) self._declare_disaggregated_var_bounds( original_var=var, disaggregatedVar=disaggregatedVar, @@ -633,10 +642,13 @@ def _transform_disjunct(self, obj, transBlock, vars_to_disaggregate, local_vars, data_dict['bigm_constraint_map'][var][obj] = bigmConstraint disjunct_disaggregated_var_map[obj][var] = var - var_substitute_map = dict((id(v), newV) for v, newV in - disjunct_disaggregated_var_map[obj].items() ) - zero_substitute_map = dict((id(v), ZeroConstant) for v, newV in - disjunct_disaggregated_var_map[obj].items() ) + var_substitute_map = dict( + (id(v), newV) for v, newV in disjunct_disaggregated_var_map[obj].items() + ) + zero_substitute_map = dict( + (id(v), ZeroConstant) + for v, newV in disjunct_disaggregated_var_map[obj].items() + ) # Transform each component within this disjunct self._transform_block_components( @@ -690,8 +702,10 @@ def _declare_disaggregated_var_bounds( # the transformation block if disjunct not in disaggregated_var_map: disaggregated_var_map[disjunct] = ComponentMap() - print("DISAGGREGATED VAR MAP (%s, %s) : %s" % (disjunct, original_var, - disaggregatedVar)) + print( + "DISAGGREGATED VAR MAP (%s, %s) : %s" + % (disjunct, original_var, disaggregatedVar) + ) disaggregated_var_map[disjunct][original_var] = disaggregatedVar original_var_map[disaggregatedVar] = original_var @@ -915,8 +929,10 @@ def get_disaggregated_var(self, v, disjunct, raise_exception=True): """ if disjunct._transformation_block is None: raise GDP_Error("Disjunct '%s' has not been transformed" % disjunct.name) - msg = ("It does not appear '%s' is a " - "variable that appears in disjunct '%s'" % (v.name, disjunct.name)) + msg = ( + "It does not appear '%s' is a " + "variable that appears in disjunct '%s'" % (v.name, disjunct.name) + ) var_map = v.parent_block().private_data() if 'disaggregated_var_map' in var_map: try: @@ -947,12 +963,14 @@ def get_src_var(self, disaggregated_var): return var_map['original_var_map'][disaggregated_var] raise GDP_Error( "'%s' does not appear to be a " - "disaggregated variable" % disaggregated_var.name) + "disaggregated variable" % disaggregated_var.name + ) # retrieves the disaggregation constraint for original_var resulting from # transforming disjunction - def get_disaggregation_constraint(self, original_var, disjunction, - raise_exception=True): + def get_disaggregation_constraint( + self, original_var, disjunction, raise_exception=True + ): """ Returns the disaggregation (re-aggregation?) constraint (which links the disaggregated variables to their original) @@ -976,11 +994,9 @@ def get_disaggregation_constraint(self, original_var, disjunction, ) try: - cons = ( - transBlock - .parent_block() - ._disaggregationConstraintMap[original_var][disjunction] - ) + cons = transBlock.parent_block()._disaggregationConstraintMap[original_var][ + disjunction + ] except: if raise_exception: logger.error( @@ -1006,7 +1022,7 @@ def get_var_bounds_constraint(self, v, disjunct=None): v: a Var that was created by the hull transformation as a disaggregated variable (and so appears on a transformation block of some Disjunct) - disjunct: (For nested Disjunctions) Which Disjunct in the + disjunct: (For nested Disjunctions) Which Disjunct in the hierarchy the bounds Constraint should correspond to. Optional since for non-nested models this can be inferred. """ @@ -1025,8 +1041,8 @@ def get_var_bounds_constraint(self, v, disjunct=None): "within a nested GDP hierarchy, and no " "'disjunct' argument was specified. Please " "specify for which Disjunct the bounds " - "constraint for '%s' should be returned." - % (v, v)) + "constraint for '%s' should be returned." % (v, v) + ) raise GDP_Error( "Either '%s' is not a disaggregated variable, or " "the disjunction that disaggregates it has not " diff --git a/pyomo/gdp/tests/common_tests.py b/pyomo/gdp/tests/common_tests.py index bef05a78cf6..e63742bb1a8 100644 --- a/pyomo/gdp/tests/common_tests.py +++ b/pyomo/gdp/tests/common_tests.py @@ -1745,9 +1745,7 @@ def check_transformation_blocks_nestedDisjunctions(self, m, transformation): # "extra" disaggregated var that gets created when it need to be # disaggregated for d1, but it's not used in d2 assertExpressionsEqual( - self, - cons_expr, - d32 + m.d1.binary_indicator_var - 1 <= 0.0 + self, cons_expr, d32 + m.d1.binary_indicator_var - 1 <= 0.0 ) cons = hull.get_var_bounds_constraint(d42) @@ -1758,33 +1756,25 @@ def check_transformation_blocks_nestedDisjunctions(self, m, transformation): # "extra" disaggregated var that gets created when it need to be # disaggregated for d1, but it's not used in d2 assertExpressionsEqual( - self, - cons_expr, - d42 + m.d1.binary_indicator_var - 1 <= 0.0 + self, cons_expr, d42 + m.d1.binary_indicator_var - 1 <= 0.0 ) # check the aggregation constraints for the disaggregated indicator vars - cons = hull.get_disaggregation_constraint(m.d1.d3.binary_indicator_var, - m.disj) + cons = hull.get_disaggregation_constraint(m.d1.d3.binary_indicator_var, m.disj) check_obj_in_active_tree(self, cons) cons_expr = self.simplify_cons(cons) assertExpressionsEqual( - self, - cons_expr, - m.d1.d3.binary_indicator_var - d32 - d3 == 0.0 + self, cons_expr, m.d1.d3.binary_indicator_var - d32 - d3 == 0.0 ) - cons = hull.get_disaggregation_constraint(m.d1.d4.binary_indicator_var, - m.disj) + cons = hull.get_disaggregation_constraint(m.d1.d4.binary_indicator_var, m.disj) check_obj_in_active_tree(self, cons) cons_expr = self.simplify_cons(cons) assertExpressionsEqual( - self, - cons_expr, - m.d1.d4.binary_indicator_var - d42 - d4 == 0.0 + self, cons_expr, m.d1.d4.binary_indicator_var - d42 - d4 == 0.0 ) - num_cons = len(list(m.component_data_objects(Constraint, - active=True, - descend_into=Block))) + num_cons = len( + list(m.component_data_objects(Constraint, active=True, descend_into=Block)) + ) # 30 total constraints in transformed model minus 10 trivial bounds # (lower bounds of 0) gives us 20 constraints total: self.assertEqual(num_cons, 20) diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index b3bfbaaf8da..aef119c0f1e 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -412,11 +412,7 @@ def test_error_for_or(self): ) def check_disaggregation_constraint(self, cons, var, disvar1, disvar2): - assertExpressionsEqual( - self, - cons.expr, - var == disvar1 + disvar2 - ) + assertExpressionsEqual(self, cons.expr, var == disvar1 + disvar2) def test_disaggregation_constraint(self): m = models.makeTwoTermDisj_Nonlinear() @@ -678,7 +674,7 @@ def test_global_vars_local_to_a_disjunction_disaggregated(self): assertExpressionsEqual( self, agg_cons.expr, - m.disj1.x == x2 + hull.get_disaggregated_var(m.disj1.x, m.disj1) + m.disj1.x == x2 + hull.get_disaggregated_var(m.disj1.x, m.disj1), ) # and both a spare x and y on disjunction2's block @@ -694,13 +690,13 @@ def test_global_vars_local_to_a_disjunction_disaggregated(self): assertExpressionsEqual( self, agg_cons.expr, - m.disj1.x == x2 + hull.get_disaggregated_var(m.disj1.x, m.disj3) + m.disj1.x == x2 + hull.get_disaggregated_var(m.disj1.x, m.disj3), ) agg_cons = hull.get_disaggregation_constraint(m.disj1.y, m.disjunction2) assertExpressionsEqual( self, agg_cons.expr, - m.disj1.y == y1 + hull.get_disaggregated_var(m.disj1.y, m.disj4) + m.disj1.y == y1 + hull.get_disaggregated_var(m.disj1.y, m.disj4), ) def check_name_collision_disaggregated_vars(self, m, disj): @@ -1408,10 +1404,17 @@ def test_mappings_between_disjunctions_and_xors(self): disjunctionPairs = [ (m.disjunction, transBlock.disjunction_xor), - (m.disjunct[1].innerdisjunction[0], - m.disjunct[1].innerdisjunction[0].algebraic_constraint.parent_block().innerdisjunction_xor[0]), - (m.simpledisjunct.innerdisjunction, - m.simpledisjunct.innerdisjunction.algebraic_constraint.parent_block().innerdisjunction_xor), + ( + m.disjunct[1].innerdisjunction[0], + m.disjunct[1] + .innerdisjunction[0] + .algebraic_constraint.parent_block() + .innerdisjunction_xor[0], + ), + ( + m.simpledisjunct.innerdisjunction, + m.simpledisjunct.innerdisjunction.algebraic_constraint.parent_block().innerdisjunction_xor, + ), ] # check disjunction mappings @@ -1578,18 +1581,20 @@ def test_transformed_model_nestedDisjuncts(self): m.LocalVars[m.d1] = [ m.d1.binary_indicator_var, m.d1.d3.binary_indicator_var, - m.d1.d4.binary_indicator_var + m.d1.d4.binary_indicator_var, ] - + hull = TransformationFactory('gdp.hull') hull.apply_to(m) - self.check_transformed_model_nestedDisjuncts(m, m.d1.d3.binary_indicator_var, - m.d1.d4.binary_indicator_var) + self.check_transformed_model_nestedDisjuncts( + m, m.d1.d3.binary_indicator_var, m.d1.d4.binary_indicator_var + ) # Last, check that there aren't things we weren't expecting - all_cons = list(m.component_data_objects(Constraint, active=True, - descend_into=Block)) + all_cons = list( + m.component_data_objects(Constraint, active=True, descend_into=Block) + ) # 2 disaggregation constraints for x 0,3 # + 6 bounds constraints for x 6,8,9,13,14,16 # + 2 bounds constraints for inner indicator vars 11, 12 @@ -1614,9 +1619,7 @@ def check_transformed_model_nestedDisjuncts(self, m, d3, d4): self.assertIsInstance(xor, Constraint) ct.check_obj_in_active_tree(self, xor) assertExpressionsEqual( - self, - xor.expr, - m.d1.binary_indicator_var + m.d2.binary_indicator_var == 1 + self, xor.expr, m.d1.binary_indicator_var + m.d2.binary_indicator_var == 1 ) self.assertIs(xor, m.disj.algebraic_constraint) self.assertIs(m.disj, hull.get_src_disjunction(xor)) @@ -1630,11 +1633,7 @@ def check_transformed_model_nestedDisjuncts(self, m, d3, d4): ct.check_obj_in_active_tree(self, xor) xor_expr = self.simplify_cons(xor) assertExpressionsEqual( - self, - xor_expr, - d3 + - d4 - - m.d1.binary_indicator_var == 0.0 + self, xor_expr, d3 + d4 - m.d1.binary_indicator_var == 0.0 ) # check disaggregation constraints @@ -1649,20 +1648,12 @@ def check_transformed_model_nestedDisjuncts(self, m, d3, d4): cons = hull.get_disaggregation_constraint(m.x, m.d1.disj2) ct.check_obj_in_active_tree(self, cons) cons_expr = self.simplify_cons(cons) - assertExpressionsEqual( - self, - cons_expr, - x_d1 - x_d3 - x_d4 == 0.0 - ) + assertExpressionsEqual(self, cons_expr, x_d1 - x_d3 - x_d4 == 0.0) # Outer disjunction cons = hull.get_disaggregation_constraint(m.x, m.disj) ct.check_obj_in_active_tree(self, cons) cons_expr = self.simplify_cons(cons) - assertExpressionsEqual( - self, - cons_expr, - m.x - x_d1 - x_d2 == 0.0 - ) + assertExpressionsEqual(self, cons_expr, m.x - x_d1 - x_d2 == 0.0) ## Transformed constraints cons = hull.get_transformed_constraints(m.d1.d3.c) @@ -1670,32 +1661,22 @@ def check_transformed_model_nestedDisjuncts(self, m, d3, d4): cons = cons[0] ct.check_obj_in_active_tree(self, cons) cons_expr = self.simplify_leq_cons(cons) - assertExpressionsEqual( - self, - cons_expr, - 1.2*d3 - x_d3 <= 0.0 - ) + assertExpressionsEqual(self, cons_expr, 1.2 * d3 - x_d3 <= 0.0) cons = hull.get_transformed_constraints(m.d1.d4.c) self.assertEqual(len(cons), 1) cons = cons[0] ct.check_obj_in_active_tree(self, cons) cons_expr = self.simplify_leq_cons(cons) - assertExpressionsEqual( - self, - cons_expr, - 1.3*d4 - x_d4 <= 0.0 - ) - + assertExpressionsEqual(self, cons_expr, 1.3 * d4 - x_d4 <= 0.0) + cons = hull.get_transformed_constraints(m.d1.c) self.assertEqual(len(cons), 1) cons = cons[0] ct.check_obj_in_active_tree(self, cons) cons_expr = self.simplify_leq_cons(cons) assertExpressionsEqual( - self, - cons_expr, - 1.0*m.d1.binary_indicator_var - x_d1 <= 0.0 + self, cons_expr, 1.0 * m.d1.binary_indicator_var - x_d1 <= 0.0 ) cons = hull.get_transformed_constraints(m.d2.c) @@ -1704,9 +1685,7 @@ def check_transformed_model_nestedDisjuncts(self, m, d3, d4): ct.check_obj_in_active_tree(self, cons) cons_expr = self.simplify_leq_cons(cons) assertExpressionsEqual( - self, - cons_expr, - 1.1*m.d2.binary_indicator_var - x_d2 <= 0.0 + self, cons_expr, 1.1 * m.d2.binary_indicator_var - x_d2 <= 0.0 ) ## Bounds constraints @@ -1716,9 +1695,7 @@ def check_transformed_model_nestedDisjuncts(self, m, d3, d4): ct.check_obj_in_active_tree(self, cons['ub']) cons_expr = self.simplify_leq_cons(cons['ub']) assertExpressionsEqual( - self, - cons_expr, - x_d1 - 2*m.d1.binary_indicator_var <= 0.0 + self, cons_expr, x_d1 - 2 * m.d1.binary_indicator_var <= 0.0 ) cons = hull.get_var_bounds_constraint(x_d2) # the lb is trivial in this case, so we just have 1 @@ -1726,9 +1703,7 @@ def check_transformed_model_nestedDisjuncts(self, m, d3, d4): ct.check_obj_in_active_tree(self, cons['ub']) cons_expr = self.simplify_leq_cons(cons['ub']) assertExpressionsEqual( - self, - cons_expr, - x_d2 - 2*m.d2.binary_indicator_var <= 0.0 + self, cons_expr, x_d2 - 2 * m.d2.binary_indicator_var <= 0.0 ) cons = hull.get_var_bounds_constraint(x_d3, m.d1.d3) # the lb is trivial in this case, so we just have 1 @@ -1739,11 +1714,7 @@ def check_transformed_model_nestedDisjuncts(self, m, d3, d4): ub = cons[0] ct.check_obj_in_active_tree(self, ub) cons_expr = self.simplify_leq_cons(ub) - assertExpressionsEqual( - self, - cons_expr, - x_d3 - 2*d3 <= 0.0 - ) + assertExpressionsEqual(self, cons_expr, x_d3 - 2 * d3 <= 0.0) cons = hull.get_var_bounds_constraint(x_d4, m.d1.d4) # the lb is trivial in this case, so we just have 1 self.assertEqual(len(cons), 1) @@ -1753,20 +1724,14 @@ def check_transformed_model_nestedDisjuncts(self, m, d3, d4): ub = cons[0] ct.check_obj_in_active_tree(self, ub) cons_expr = self.simplify_leq_cons(ub) - assertExpressionsEqual( - self, - cons_expr, - x_d4 - 2*d4 <= 0.0 - ) + assertExpressionsEqual(self, cons_expr, x_d4 - 2 * d4 <= 0.0) cons = hull.get_var_bounds_constraint(x_d3, m.d1) self.assertEqual(len(cons), 1) ub = cons['ub'] ct.check_obj_in_active_tree(self, ub) cons_expr = self.simplify_leq_cons(ub) assertExpressionsEqual( - self, - cons_expr, - x_d3 - 2*m.d1.binary_indicator_var <= 0.0 + self, cons_expr, x_d3 - 2 * m.d1.binary_indicator_var <= 0.0 ) cons = hull.get_var_bounds_constraint(x_d4, m.d1) self.assertEqual(len(cons), 1) @@ -1774,26 +1739,16 @@ def check_transformed_model_nestedDisjuncts(self, m, d3, d4): ct.check_obj_in_active_tree(self, ub) cons_expr = self.simplify_leq_cons(ub) assertExpressionsEqual( - self, - cons_expr, - x_d4 - 2*m.d1.binary_indicator_var <= 0.0 + self, cons_expr, x_d4 - 2 * m.d1.binary_indicator_var <= 0.0 ) # Bounds constraints for local vars cons = hull.get_var_bounds_constraint(d3) ct.check_obj_in_active_tree(self, cons['ub']) - assertExpressionsEqual( - self, - cons['ub'].expr, - d3 <= m.d1.binary_indicator_var - ) + assertExpressionsEqual(self, cons['ub'].expr, d3 <= m.d1.binary_indicator_var) cons = hull.get_var_bounds_constraint(d4) ct.check_obj_in_active_tree(self, cons['ub']) - assertExpressionsEqual( - self, - cons['ub'].expr, - d4 <= m.d1.binary_indicator_var - ) + assertExpressionsEqual(self, cons['ub'].expr, d4 <= m.d1.binary_indicator_var) @unittest.skipIf(not linear_solvers, "No linear solver available") def test_solve_nested_model(self): @@ -1804,8 +1759,8 @@ def test_solve_nested_model(self): m.LocalVars[m.d1] = [ m.d1.binary_indicator_var, m.d1.d3.binary_indicator_var, - m.d1.d4.binary_indicator_var - ] + m.d1.d4.binary_indicator_var, + ] hull = TransformationFactory('gdp.hull') m_hull = hull.create_using(m) @@ -1901,8 +1856,10 @@ def d_r(e): e.c2 = Constraint(expr=m.x == 2 * e.lambdas[1] + 3 * e.lambdas[2]) d.LocalVars = Suffix(direction=Suffix.LOCAL) - d.LocalVars[d] = [d.d_l.indicator_var.get_associated_binary(), - d.d_r.indicator_var.get_associated_binary()] + d.LocalVars[d] = [ + d.d_l.indicator_var.get_associated_binary(), + d.d_r.indicator_var.get_associated_binary(), + ] d.inner_disj = Disjunction(expr=[d.d_l, d.d_r]) m.disj = Disjunction(expr=[m.d_l, m.d_r]) @@ -1929,18 +1886,14 @@ def d_r(e): assertExpressionsEqual( self, convex_combo_expr, - lambda1 + lambda2 - d.indicator_var.get_associated_binary() - == 0.0, + lambda1 + lambda2 - d.indicator_var.get_associated_binary() == 0.0, ) cons = hull.get_transformed_constraints(d.c2) self.assertEqual(len(cons), 1) get_x = cons[0] get_x_expr = self.simplify_cons(get_x) assertExpressionsEqual( - self, - get_x_expr, - x - 2 * lambda1 - 3 * lambda2 - == 0.0, + self, get_x_expr, x - 2 * lambda1 - 3 * lambda2 == 0.0 ) cons = hull.get_disaggregation_constraint(m.x, m.disj) @@ -1996,8 +1949,9 @@ def test_nested_with_var_that_does_not_appear_in_every_disjunct(self): assertExpressionsEqual(self, x_cons_parent.expr, m.x == x_p1 + x_p2) x_cons_child = hull.get_disaggregation_constraint(m.x, m.parent1.disjunction) x_cons_child_expr = self.simplify_cons(x_cons_child) - assertExpressionsEqual(self, x_cons_child_expr, x_p1 - x_c1 - x_c2 - - x_c3 == 0.0) + assertExpressionsEqual( + self, x_cons_child_expr, x_p1 - x_c1 - x_c2 - x_c3 == 0.0 + ) def simplify_cons(self, cons): visitor = LinearRepnVisitor({}, {}, {}, None) @@ -2065,8 +2019,9 @@ def test_nested_with_var_that_skips_a_level(self): self.assertTrue(cons.active) cons_expr = self.simplify_cons(cons) assertExpressionsEqual(self, cons_expr, m.x - x_y1 - x_y2 == 0.0) - cons = hull.get_disaggregation_constraint(m.y, m.y1.z1.disjunction, - raise_exception=False) + cons = hull.get_disaggregation_constraint( + m.y, m.y1.z1.disjunction, raise_exception=False + ) self.assertIsNone(cons) cons = hull.get_disaggregation_constraint(m.y, m.y1.disjunction) self.assertTrue(cons.active) @@ -2373,10 +2328,9 @@ def test_mapping_method_errors(self): ) with self.assertRaisesRegex( - GDP_Error, - ".*'w' does not appear to be a disaggregated variable" + GDP_Error, ".*'w' does not appear to be a disaggregated variable" ): - hull.get_src_var(m.w,) + hull.get_src_var(m.w) with self.assertRaisesRegex( GDP_Error, @@ -2384,11 +2338,10 @@ def test_mapping_method_errors(self): r"'_pyomo_gdp_hull_reformulation." r"relaxedDisjuncts\[1\].disaggregatedVars.w' " r"is a variable that appears in disjunct " - r"'d\[1\]'" + r"'d\[1\]'", ): hull.get_disaggregated_var( - m.d[1].transformation_block.disaggregatedVars.w, - m.d[1], + m.d[1].transformation_block.disaggregatedVars.w, m.d[1] ) m.random_disjunction = Disjunction(expr=[m.w == 2, m.w >= 7]) From c9eca76fae1b319b456aa014e7a49b36e213ce8c Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Sat, 17 Feb 2024 13:22:21 -0700 Subject: [PATCH 1022/1204] Removing debugging --- pyomo/gdp/plugins/hull.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 9fcac6a8f4e..53dffc7a18e 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -324,7 +324,6 @@ def _add_transformation_block(self, to_block): def _transform_disjunctionData( self, obj, index, parent_disjunct, local_vars_by_disjunct ): - print("Transforming Disjunction %s" % obj) # Hull reformulation doesn't work if this is an OR constraint. So if # xor is false, give up if not obj.xor: @@ -459,7 +458,6 @@ def _transform_disjunctionData( # create one more disaggregated var idx = len(disaggregatedVars) disaggregated_var = disaggregatedVars[idx] - print("Creating extra disaggregated var: '%s'" % disaggregated_var) # mark this as local because we won't re-disaggregate it if this # is a nested disjunction if parent_local_var_list is not None: @@ -550,7 +548,6 @@ def _transform_disjunct( parent_disjunct_local_vars, disjunct_disaggregated_var_map, ): - print("\nTransforming Disjunct '%s'" % obj.name) relaxationBlock = self._get_disjunct_transformation_block(obj, transBlock) # Put the disaggregated variables all on their own block so that we can @@ -585,11 +582,6 @@ def _transform_disjunct( disaggregatedVarName + "_bounds", bigmConstraint ) - print( - "Adding bounds constraints for '%s', the disaggregated var " - "corresponding to Var '%s' on Disjunct '%s'" - % (disaggregatedVar, var, obj) - ) self._declare_disaggregated_var_bounds( original_var=var, disaggregatedVar=disaggregatedVar, @@ -623,7 +615,6 @@ def _transform_disjunct( parent_block = var.parent_block() - print("Adding bounds constraints for local var '%s'" % var) self._declare_disaggregated_var_bounds( original_var=var, disaggregatedVar=var, @@ -702,10 +693,6 @@ def _declare_disaggregated_var_bounds( # the transformation block if disjunct not in disaggregated_var_map: disaggregated_var_map[disjunct] = ComponentMap() - print( - "DISAGGREGATED VAR MAP (%s, %s) : %s" - % (disjunct, original_var, disaggregatedVar) - ) disaggregated_var_map[disjunct][original_var] = disaggregatedVar original_var_map[disaggregatedVar] = original_var From f12b76290f74ea257a210ae3deeb5af7fd91fe08 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Sat, 17 Feb 2024 13:24:23 -0700 Subject: [PATCH 1023/1204] Removing more debugging --- pyomo/gdp/plugins/hull.py | 2 -- pyomo/gdp/tests/test_hull.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 53dffc7a18e..12665eef340 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -55,8 +55,6 @@ logger = logging.getLogger('pyomo.gdp.hull') -from pytest import set_trace - @TransformationFactory.register( 'gdp.hull', doc="Relax disjunctive model by forming the hull reformulation." diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index aef119c0f1e..e45a7543e25 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -52,8 +52,6 @@ import os from os.path import abspath, dirname, join -##DEBUG -from pytest import set_trace currdir = dirname(abspath(__file__)) from filecmp import cmp From fed34aedb900160a94261fe448d3e562b8a21fc0 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Sat, 17 Feb 2024 13:32:03 -0700 Subject: [PATCH 1024/1204] NFC: updating docstring and removing comments --- pyomo/gdp/plugins/hull.py | 36 +++++------------------------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 12665eef340..8813cc25137 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -80,19 +80,11 @@ class Hull_Reformulation(GDP_to_MIP_Transformation): list of blocks and Disjunctions [default: the instance] The transformation will create a new Block with a unique - name beginning "_pyomo_gdp_hull_reformulation". - The block will have a dictionary "_disaggregatedVarMap: - 'srcVar': ComponentMap(:), - 'disaggregatedVar': ComponentMap(:) - - It will also have a ComponentMap "_bigMConstraintMap": - - : - - Last, it will contain an indexed Block named "relaxedDisjuncts", - which will hold the relaxed disjuncts. This block is indexed by - an integer indicating the order in which the disjuncts were relaxed. - Each block has a dictionary "_constraintMap": + name beginning "_pyomo_gdp_hull_reformulation". It will contain an + indexed Block named "relaxedDisjuncts" that will hold the relaxed + disjuncts. This block is indexed by an integer indicating the order + in which the disjuncts were relaxed. Each block has a dictionary + "_constraintMap": 'srcConstraints': ComponentMap(: ), @@ -108,7 +100,6 @@ class Hull_Reformulation(GDP_to_MIP_Transformation): The _pyomo_gdp_hull_reformulation block will have a ComponentMap "_disaggregationConstraintMap": :ComponentMap(: ) - """ CONFIG = cfg.ConfigDict('gdp.hull') @@ -244,23 +235,6 @@ def _get_user_defined_local_vars(self, targets): blk = blk.parent_block() return user_defined_local_vars - # def _get_local_vars_from_suffixes(self, block, local_var_dict): - # # You can specify suffixes on any block (disjuncts included). This - # # method starts from a Disjunct (presumably) and checks for a LocalVar - # # suffixes going both up and down the tree, adding them into the - # # dictionary that is the second argument. - - # # first look beneath where we are (there could be Blocks on this - # # disjunct) - # for b in block.component_data_objects( - # Block, descend_into=Block, active=True, sort=SortComponents.deterministic - # ): - # self._collect_local_vars_from_block(b, local_var_dict) - # # now traverse upwards and get what's above - # while block is not None: - # self._collect_local_vars_from_block(block, local_var_dict) - # block = block.parent_block() - def _apply_to(self, instance, **kwds): try: self._apply_to_impl(instance, **kwds) From 9caba527f53b101429edd0471fb70647e5df94fb Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Sat, 17 Feb 2024 20:31:58 -0700 Subject: [PATCH 1025/1204] Updating baselines because I changed the transformation order --- pyomo/gdp/tests/jobshop_large_hull.lp | 1778 ++++++++++++------------- pyomo/gdp/tests/jobshop_small_hull.lp | 122 +- 2 files changed, 950 insertions(+), 950 deletions(-) diff --git a/pyomo/gdp/tests/jobshop_large_hull.lp b/pyomo/gdp/tests/jobshop_large_hull.lp index df3833bdee3..ee8ee0a73d2 100644 --- a/pyomo/gdp/tests/jobshop_large_hull.lp +++ b/pyomo/gdp/tests/jobshop_large_hull.lp @@ -42,87 +42,87 @@ c_u_Feas(G)_: <= -17 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(0)_: -+1 t(G) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(G)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(G)_ ++1 t(B) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(B)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(B)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(1)_: -+1 t(F) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(F)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(F)_ ++1 t(A) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(A)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(A)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(2)_: -+1 t(G) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(G)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(G)_ ++1 t(B) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(B)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(B)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(3)_: -+1 t(E) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(E)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(E)_ ++1 t(A) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(A)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(A)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(4)_: -+1 t(G) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(G)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(G)_ ++1 t(A) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(A)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(A)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(5)_: -+1 t(E) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(E)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(E)_ ++1 t(C) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(C)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(C)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(6)_: -+1 t(F) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)_disaggregatedVars__t(F)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)_disaggregatedVars__t(F)_ ++1 t(D) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)_disaggregatedVars__t(D)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)_disaggregatedVars__t(D)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(7)_: -+1 t(E) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)_disaggregatedVars__t(E)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)_disaggregatedVars__t(E)_ ++1 t(A) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)_disaggregatedVars__t(A)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)_disaggregatedVars__t(A)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(8)_: -+1 t(G) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)_disaggregatedVars__t(G)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)_disaggregatedVars__t(G)_ ++1 t(E) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)_disaggregatedVars__t(E)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)_disaggregatedVars__t(E)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(9)_: -+1 t(D) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)_disaggregatedVars__t(D)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)_disaggregatedVars__t(D)_ ++1 t(A) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)_disaggregatedVars__t(A)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)_disaggregatedVars__t(A)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(10)_: -+1 t(G) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)_disaggregatedVars__t(G)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)_disaggregatedVars__t(G)_ ++1 t(E) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)_disaggregatedVars__t(E)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)_disaggregatedVars__t(E)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(11)_: -+1 t(D) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)_disaggregatedVars__t(D)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)_disaggregatedVars__t(D)_ ++1 t(A) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)_disaggregatedVars__t(A)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)_disaggregatedVars__t(A)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(12)_: -+1 t(F) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)_disaggregatedVars__t(F)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)_disaggregatedVars__t(F)_ ++1 t(A) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)_disaggregatedVars__t(A)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)_disaggregatedVars__t(A)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(13)_: -+1 t(D) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)_disaggregatedVars__t(D)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)_disaggregatedVars__t(D)_ ++1 t(F) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)_disaggregatedVars__t(F)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)_disaggregatedVars__t(F)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(14)_: @@ -132,81 +132,81 @@ c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(14)_: = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(15)_: -+1 t(D) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(14)_disaggregatedVars__t(D)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(15)_disaggregatedVars__t(D)_ ++1 t(A) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(14)_disaggregatedVars__t(A)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(15)_disaggregatedVars__t(A)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(16)_: -+1 t(E) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)_disaggregatedVars__t(E)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)_disaggregatedVars__t(E)_ ++1 t(G) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)_disaggregatedVars__t(G)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)_disaggregatedVars__t(G)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(17)_: -+1 t(D) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)_disaggregatedVars__t(D)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)_disaggregatedVars__t(D)_ ++1 t(A) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)_disaggregatedVars__t(A)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)_disaggregatedVars__t(A)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(18)_: -+1 t(E) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(E)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(E)_ ++1 t(B) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(B)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(B)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(19)_: -+1 t(D) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(D)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(D)_ ++1 t(C) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(C)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(C)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(20)_: -+1 t(G) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(G)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(G)_ ++1 t(B) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(B)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(B)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(21)_: -+1 t(C) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(C)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(C)_ ++1 t(D) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(D)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(D)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(22)_: -+1 t(G) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)_disaggregatedVars__t(G)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)_disaggregatedVars__t(G)_ ++1 t(D) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)_disaggregatedVars__t(D)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)_disaggregatedVars__t(D)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(23)_: -+1 t(C) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)_disaggregatedVars__t(C)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)_disaggregatedVars__t(C)_ ++1 t(B) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)_disaggregatedVars__t(B)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)_disaggregatedVars__t(B)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(24)_: -+1 t(F) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(F)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(F)_ ++1 t(B) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(B)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(B)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(25)_: -+1 t(C) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(C)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(C)_ ++1 t(E) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(E)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(E)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(26)_: -+1 t(F) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)_disaggregatedVars__t(F)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)_disaggregatedVars__t(F)_ ++1 t(E) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)_disaggregatedVars__t(E)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)_disaggregatedVars__t(E)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(27)_: -+1 t(C) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)_disaggregatedVars__t(C)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)_disaggregatedVars__t(C)_ ++1 t(B) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)_disaggregatedVars__t(B)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)_disaggregatedVars__t(B)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(28)_: @@ -216,33 +216,33 @@ c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(28)_: = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(29)_: -+1 t(C) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(28)_disaggregatedVars__t(C)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(29)_disaggregatedVars__t(C)_ ++1 t(B) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(28)_disaggregatedVars__t(B)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(29)_disaggregatedVars__t(B)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(30)_: -+1 t(D) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)_disaggregatedVars__t(D)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)_disaggregatedVars__t(D)_ ++1 t(F) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)_disaggregatedVars__t(F)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)_disaggregatedVars__t(F)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(31)_: -+1 t(C) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)_disaggregatedVars__t(C)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)_disaggregatedVars__t(C)_ ++1 t(B) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)_disaggregatedVars__t(B)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)_disaggregatedVars__t(B)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(32)_: -+1 t(D) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(D)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(D)_ ++1 t(B) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(B)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(B)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(33)_: -+1 t(C) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(C)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(C)_ ++1 t(G) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(G)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(G)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(34)_: @@ -258,27 +258,27 @@ c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(35)_: = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(36)_: -+1 t(G) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)_disaggregatedVars__t(G)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)_disaggregatedVars__t(G)_ ++1 t(D) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)_disaggregatedVars__t(D)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)_disaggregatedVars__t(D)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(37)_: -+1 t(B) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)_disaggregatedVars__t(B)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)_disaggregatedVars__t(B)_ ++1 t(C) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)_disaggregatedVars__t(C)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)_disaggregatedVars__t(C)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(38)_: -+1 t(F) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)_disaggregatedVars__t(F)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)_disaggregatedVars__t(F)_ ++1 t(D) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)_disaggregatedVars__t(D)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)_disaggregatedVars__t(D)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(39)_: -+1 t(B) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)_disaggregatedVars__t(B)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)_disaggregatedVars__t(B)_ ++1 t(C) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)_disaggregatedVars__t(C)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)_disaggregatedVars__t(C)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(40)_: @@ -288,81 +288,81 @@ c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(40)_: = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(41)_: -+1 t(B) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(40)_disaggregatedVars__t(B)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(41)_disaggregatedVars__t(B)_ ++1 t(C) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(40)_disaggregatedVars__t(C)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(41)_disaggregatedVars__t(C)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(42)_: -+1 t(E) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(E)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(E)_ ++1 t(C) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(C)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(C)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(43)_: -+1 t(B) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(B)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(B)_ ++1 t(F) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(F)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(F)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(44)_: -+1 t(E) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)_disaggregatedVars__t(E)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)_disaggregatedVars__t(E)_ ++1 t(F) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)_disaggregatedVars__t(F)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)_disaggregatedVars__t(F)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(45)_: -+1 t(B) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)_disaggregatedVars__t(B)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)_disaggregatedVars__t(B)_ ++1 t(C) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)_disaggregatedVars__t(C)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)_disaggregatedVars__t(C)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(46)_: -+1 t(D) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)_disaggregatedVars__t(D)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)_disaggregatedVars__t(D)_ ++1 t(G) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)_disaggregatedVars__t(G)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)_disaggregatedVars__t(G)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(47)_: -+1 t(B) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)_disaggregatedVars__t(B)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)_disaggregatedVars__t(B)_ ++1 t(C) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)_disaggregatedVars__t(C)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)_disaggregatedVars__t(C)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(48)_: -+1 t(D) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)_disaggregatedVars__t(D)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)_disaggregatedVars__t(D)_ ++1 t(G) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)_disaggregatedVars__t(G)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)_disaggregatedVars__t(G)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(49)_: -+1 t(B) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)_disaggregatedVars__t(B)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)_disaggregatedVars__t(B)_ ++1 t(C) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)_disaggregatedVars__t(C)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)_disaggregatedVars__t(C)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(50)_: -+1 t(C) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(C)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(C)_ ++1 t(D) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(D)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(D)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(51)_: -+1 t(B) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(B)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(B)_ ++1 t(E) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(E)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(E)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(52)_: -+1 t(G) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)_disaggregatedVars__t(G)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)_disaggregatedVars__t(G)_ ++1 t(E) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)_disaggregatedVars__t(E)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)_disaggregatedVars__t(E)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(53)_: -+1 t(A) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)_disaggregatedVars__t(A)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)_disaggregatedVars__t(A)_ ++1 t(D) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)_disaggregatedVars__t(D)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)_disaggregatedVars__t(D)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(54)_: @@ -372,9 +372,9 @@ c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(54)_: = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(55)_: -+1 t(A) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(54)_disaggregatedVars__t(A)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(55)_disaggregatedVars__t(A)_ ++1 t(D) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(54)_disaggregatedVars__t(D)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(55)_disaggregatedVars__t(D)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(56)_: @@ -384,81 +384,81 @@ c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(56)_: = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(57)_: -+1 t(A) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(56)_disaggregatedVars__t(A)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(57)_disaggregatedVars__t(A)_ ++1 t(D) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(56)_disaggregatedVars__t(D)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(57)_disaggregatedVars__t(D)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(58)_: -+1 t(E) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(E)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(E)_ ++1 t(D) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(D)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(D)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(59)_: -+1 t(A) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(A)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(A)_ ++1 t(G) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(G)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(G)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(60)_: -+1 t(E) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)_disaggregatedVars__t(E)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)_disaggregatedVars__t(E)_ ++1 t(G) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)_disaggregatedVars__t(G)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)_disaggregatedVars__t(G)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(61)_: -+1 t(A) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)_disaggregatedVars__t(A)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)_disaggregatedVars__t(A)_ ++1 t(D) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)_disaggregatedVars__t(D)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)_disaggregatedVars__t(D)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(62)_: -+1 t(D) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)_disaggregatedVars__t(D)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)_disaggregatedVars__t(D)_ ++1 t(F) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)_disaggregatedVars__t(F)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)_disaggregatedVars__t(F)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(63)_: -+1 t(A) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)_disaggregatedVars__t(A)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)_disaggregatedVars__t(A)_ ++1 t(E) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)_disaggregatedVars__t(E)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)_disaggregatedVars__t(E)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(64)_: -+1 t(C) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(C)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(C)_ ++1 t(E) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(E)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(E)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(65)_: -+1 t(A) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(A)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(A)_ ++1 t(G) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(G)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(G)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(66)_: -+1 t(B) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)_disaggregatedVars__t(B)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)_disaggregatedVars__t(B)_ ++1 t(G) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)_disaggregatedVars__t(G)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)_disaggregatedVars__t(G)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(67)_: -+1 t(A) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)_disaggregatedVars__t(A)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)_disaggregatedVars__t(A)_ ++1 t(E) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)_disaggregatedVars__t(E)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)_disaggregatedVars__t(E)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(68)_: -+1 t(B) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)_disaggregatedVars__t(B)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_disaggregatedVars__t(B)_ ++1 t(G) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)_disaggregatedVars__t(G)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_disaggregatedVars__t(G)_ = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(69)_: -+1 t(A) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)_disaggregatedVars__t(A)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_disaggregatedVars__t(A)_ ++1 t(F) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)_disaggregatedVars__t(F)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_disaggregatedVars__t(F)_ = 0 c_e__pyomo_gdp_hull_reformulation_disj_xor(A_B_3)_: @@ -637,546 +637,544 @@ c_e__pyomo_gdp_hull_reformulation_disj_xor(F_G_4)_: = 1 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(G)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(F)_ -+6.0 NoClash(F_G_4_0)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(B)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(A)_ ++4.0 NoClash(A_B_3_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(G)_ --92 NoClash(F_G_4_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(B)_ +-92 NoClash(A_B_3_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)__t(F)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(F)_ --92 NoClash(F_G_4_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(A)_ +-92 NoClash(A_B_3_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(G)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(F)_ -+6.0 NoClash(F_G_4_1)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(B)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(A)_ ++5.0 NoClash(A_B_3_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(G)_ --92 NoClash(F_G_4_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(B)_ +-92 NoClash(A_B_3_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)__t(F)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(F)_ --92 NoClash(F_G_4_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(A)_ +-92 NoClash(A_B_3_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(G)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(E)_ -+7.0 NoClash(E_G_5_0)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(B)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(A)_ ++2.0 NoClash(A_B_5_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(G)_ --92 NoClash(E_G_5_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(B)_ +-92 NoClash(A_B_5_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(E)_ --92 NoClash(E_G_5_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(A)_ +-92 NoClash(A_B_5_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(G)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(E)_ --1 NoClash(E_G_5_1)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(B)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(A)_ ++3.0 NoClash(A_B_5_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(G)_ --92 NoClash(E_G_5_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(B)_ +-92 NoClash(A_B_5_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(E)_ --92 NoClash(E_G_5_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(A)_ +-92 NoClash(A_B_5_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(G)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(E)_ -+8.0 NoClash(E_G_2_0)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(A)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(C)_ ++6.0 NoClash(A_C_1_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(G)_ --92 NoClash(E_G_2_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(A)_ +-92 NoClash(A_C_1_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(E)_ --92 NoClash(E_G_2_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(C)_ +-92 NoClash(A_C_1_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(G)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(E)_ -+4.0 NoClash(E_G_2_1)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(A)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(C)_ ++3.0 NoClash(A_C_1_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(G)_ --92 NoClash(E_G_2_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(A)_ +-92 NoClash(A_C_1_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(E)_ --92 NoClash(E_G_2_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(C)_ +-92 NoClash(A_C_1_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)_disaggregatedVars__t(F)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)_disaggregatedVars__t(E)_ -+3.0 NoClash(E_F_3_0)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)_disaggregatedVars__t(D)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)_disaggregatedVars__t(A)_ ++10.0 NoClash(A_D_3_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)__t(F)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)_disaggregatedVars__t(F)_ --92 NoClash(E_F_3_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)_disaggregatedVars__t(D)_ +-92 NoClash(A_D_3_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)_disaggregatedVars__t(E)_ --92 NoClash(E_F_3_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)_disaggregatedVars__t(A)_ +-92 NoClash(A_D_3_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)_disaggregatedVars__t(F)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)_disaggregatedVars__t(E)_ -+8.0 NoClash(E_F_3_1)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)_disaggregatedVars__t(D)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)_disaggregatedVars__t(A)_ <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)__t(F)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)_disaggregatedVars__t(F)_ --92 NoClash(E_F_3_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)_disaggregatedVars__t(D)_ +-92 NoClash(A_D_3_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)_disaggregatedVars__t(E)_ --92 NoClash(E_F_3_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)_disaggregatedVars__t(A)_ +-92 NoClash(A_D_3_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)_disaggregatedVars__t(G)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)_disaggregatedVars__t(D)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)_disaggregatedVars__t(E)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)_disaggregatedVars__t(A)_ ++7.0 NoClash(A_E_3_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)_disaggregatedVars__t(G)_ --92 NoClash(D_G_4_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)__t(E)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)_disaggregatedVars__t(E)_ +-92 NoClash(A_E_3_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)_disaggregatedVars__t(D)_ --92 NoClash(D_G_4_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)_disaggregatedVars__t(A)_ +-92 NoClash(A_E_3_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)_disaggregatedVars__t(G)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)_disaggregatedVars__t(D)_ -+6.0 NoClash(D_G_4_1)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)_disaggregatedVars__t(E)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)_disaggregatedVars__t(A)_ ++4.0 NoClash(A_E_3_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)_disaggregatedVars__t(G)_ --92 NoClash(D_G_4_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)__t(E)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)_disaggregatedVars__t(E)_ +-92 NoClash(A_E_3_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)_disaggregatedVars__t(D)_ --92 NoClash(D_G_4_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)_disaggregatedVars__t(A)_ +-92 NoClash(A_E_3_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)_disaggregatedVars__t(G)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)_disaggregatedVars__t(D)_ -+8.0 NoClash(D_G_2_0)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)_disaggregatedVars__t(E)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)_disaggregatedVars__t(A)_ ++4.0 NoClash(A_E_5_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)_disaggregatedVars__t(G)_ --92 NoClash(D_G_2_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)__t(E)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)_disaggregatedVars__t(E)_ +-92 NoClash(A_E_5_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)_disaggregatedVars__t(D)_ --92 NoClash(D_G_2_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)_disaggregatedVars__t(A)_ +-92 NoClash(A_E_5_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)_disaggregatedVars__t(G)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)_disaggregatedVars__t(D)_ -+8.0 NoClash(D_G_2_1)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)_disaggregatedVars__t(E)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)_disaggregatedVars__t(A)_ <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)_disaggregatedVars__t(G)_ --92 NoClash(D_G_2_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)__t(E)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)_disaggregatedVars__t(E)_ +-92 NoClash(A_E_5_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)_disaggregatedVars__t(D)_ --92 NoClash(D_G_2_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)_disaggregatedVars__t(A)_ +-92 NoClash(A_E_5_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)_transformedConstraints(c_0_ub)_: +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)_disaggregatedVars__t(A)_ +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)_disaggregatedVars__t(F)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)_disaggregatedVars__t(D)_ -+1 NoClash(D_F_4_0)_binary_indicator_var ++2.0 NoClash(A_F_1_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)__t(F)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)_disaggregatedVars__t(F)_ --92 NoClash(D_F_4_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)_disaggregatedVars__t(A)_ +-92 NoClash(A_F_1_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)_disaggregatedVars__t(D)_ --92 NoClash(D_F_4_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)__t(F)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)_disaggregatedVars__t(F)_ +-92 NoClash(A_F_1_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)_transformedConstraints(c_0_ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)_disaggregatedVars__t(A)_ -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)_disaggregatedVars__t(F)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)_disaggregatedVars__t(D)_ -+7.0 NoClash(D_F_4_1)_binary_indicator_var ++3.0 NoClash(A_F_1_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)__t(F)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)_disaggregatedVars__t(F)_ --92 NoClash(D_F_4_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)_disaggregatedVars__t(A)_ +-92 NoClash(A_F_1_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)_disaggregatedVars__t(D)_ --92 NoClash(D_F_4_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)__t(F)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)_disaggregatedVars__t(F)_ +-92 NoClash(A_F_1_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(14)_transformedConstraints(c_0_ub)_: +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(14)_disaggregatedVars__t(F)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(14)_disaggregatedVars__t(D)_ --1 NoClash(D_F_3_0)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(14)_disaggregatedVars__t(A)_ ++4.0 NoClash(A_F_3_0)_binary_indicator_var <= 0.0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(14)__t(F)_bounds_(ub)_: +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(14)_disaggregatedVars__t(F)_ --92 NoClash(D_F_3_0)_binary_indicator_var +-92 NoClash(A_F_3_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(14)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(14)_disaggregatedVars__t(D)_ --92 NoClash(D_F_3_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(14)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(14)_disaggregatedVars__t(A)_ +-92 NoClash(A_F_3_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(15)_transformedConstraints(c_0_ub)_: -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(15)_disaggregatedVars__t(F)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(15)_disaggregatedVars__t(D)_ -+11.0 NoClash(D_F_3_1)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(15)_disaggregatedVars__t(A)_ ++6.0 NoClash(A_F_3_1)_binary_indicator_var <= 0.0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(15)__t(F)_bounds_(ub)_: +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(15)_disaggregatedVars__t(F)_ --92 NoClash(D_F_3_1)_binary_indicator_var +-92 NoClash(A_F_3_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(15)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(15)_disaggregatedVars__t(D)_ --92 NoClash(D_F_3_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(15)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(15)_disaggregatedVars__t(A)_ +-92 NoClash(A_F_3_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)_disaggregatedVars__t(E)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)_disaggregatedVars__t(D)_ -+2.0 NoClash(D_E_3_0)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)_disaggregatedVars__t(G)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)_disaggregatedVars__t(A)_ ++9.0 NoClash(A_G_5_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)_disaggregatedVars__t(E)_ --92 NoClash(D_E_3_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)__t(G)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)_disaggregatedVars__t(G)_ +-92 NoClash(A_G_5_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)_disaggregatedVars__t(D)_ --92 NoClash(D_E_3_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)_disaggregatedVars__t(A)_ +-92 NoClash(A_G_5_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)_disaggregatedVars__t(E)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)_disaggregatedVars__t(D)_ -+9.0 NoClash(D_E_3_1)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)_disaggregatedVars__t(G)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)_disaggregatedVars__t(A)_ +-3.0 NoClash(A_G_5_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)_disaggregatedVars__t(E)_ --92 NoClash(D_E_3_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)__t(G)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)_disaggregatedVars__t(G)_ +-92 NoClash(A_G_5_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)_disaggregatedVars__t(D)_ --92 NoClash(D_E_3_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)_disaggregatedVars__t(A)_ +-92 NoClash(A_G_5_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(E)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(D)_ -+4.0 NoClash(D_E_2_0)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(B)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(C)_ ++9.0 NoClash(B_C_2_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(E)_ --92 NoClash(D_E_2_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(B)_ +-92 NoClash(B_C_2_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(D)_ --92 NoClash(D_E_2_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(C)_ +-92 NoClash(B_C_2_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(E)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(D)_ -+8.0 NoClash(D_E_2_1)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(B)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(C)_ +-3.0 NoClash(B_C_2_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(E)_ --92 NoClash(D_E_2_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(B)_ +-92 NoClash(B_C_2_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(D)_ --92 NoClash(D_E_2_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(C)_ +-92 NoClash(B_C_2_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(G)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(C)_ -+4.0 NoClash(C_G_4_0)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(B)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(D)_ ++8.0 NoClash(B_D_2_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(G)_ --92 NoClash(C_G_4_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(B)_ +-92 NoClash(B_D_2_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(C)_ --92 NoClash(C_G_4_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(D)_ +-92 NoClash(B_D_2_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(G)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(C)_ -+7.0 NoClash(C_G_4_1)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(B)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(D)_ ++3.0 NoClash(B_D_2_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(G)_ --92 NoClash(C_G_4_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(B)_ +-92 NoClash(B_D_2_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(C)_ --92 NoClash(C_G_4_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(D)_ +-92 NoClash(B_D_2_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)_disaggregatedVars__t(G)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)_disaggregatedVars__t(C)_ -+2.0 NoClash(C_G_2_0)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)_disaggregatedVars__t(D)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)_disaggregatedVars__t(B)_ ++10.0 NoClash(B_D_3_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)_disaggregatedVars__t(G)_ --92 NoClash(C_G_2_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)_disaggregatedVars__t(D)_ +-92 NoClash(B_D_3_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)_disaggregatedVars__t(C)_ --92 NoClash(C_G_2_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)_disaggregatedVars__t(B)_ +-92 NoClash(B_D_3_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)_disaggregatedVars__t(G)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)_disaggregatedVars__t(C)_ -+9.0 NoClash(C_G_2_1)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)_disaggregatedVars__t(D)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)_disaggregatedVars__t(B)_ +-1 NoClash(B_D_3_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)_disaggregatedVars__t(G)_ --92 NoClash(C_G_2_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)_disaggregatedVars__t(D)_ +-92 NoClash(B_D_3_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)_disaggregatedVars__t(C)_ --92 NoClash(C_G_2_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)_disaggregatedVars__t(B)_ +-92 NoClash(B_D_3_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(F)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(C)_ -+5.0 NoClash(C_F_4_0)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(B)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(E)_ ++4.0 NoClash(B_E_2_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)__t(F)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(F)_ --92 NoClash(C_F_4_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(B)_ +-92 NoClash(B_E_2_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(C)_ --92 NoClash(C_F_4_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)__t(E)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(E)_ +-92 NoClash(B_E_2_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(F)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(C)_ -+8.0 NoClash(C_F_4_1)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(B)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(E)_ ++3.0 NoClash(B_E_2_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)__t(F)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(F)_ --92 NoClash(C_F_4_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(B)_ +-92 NoClash(B_E_2_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(C)_ --92 NoClash(C_F_4_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)__t(E)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(E)_ +-92 NoClash(B_E_2_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)_disaggregatedVars__t(F)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)_disaggregatedVars__t(C)_ -+2.0 NoClash(C_F_1_0)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)_disaggregatedVars__t(E)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)_disaggregatedVars__t(B)_ ++7.0 NoClash(B_E_3_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)__t(F)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)_disaggregatedVars__t(F)_ --92 NoClash(C_F_1_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)__t(E)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)_disaggregatedVars__t(E)_ +-92 NoClash(B_E_3_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)_disaggregatedVars__t(C)_ --92 NoClash(C_F_1_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)_disaggregatedVars__t(B)_ +-92 NoClash(B_E_3_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)_disaggregatedVars__t(F)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)_disaggregatedVars__t(C)_ -+6.0 NoClash(C_F_1_1)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)_disaggregatedVars__t(E)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)_disaggregatedVars__t(B)_ ++3.0 NoClash(B_E_3_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)__t(F)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)_disaggregatedVars__t(F)_ --92 NoClash(C_F_1_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)__t(E)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)_disaggregatedVars__t(E)_ +-92 NoClash(B_E_3_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)_disaggregatedVars__t(C)_ --92 NoClash(C_F_1_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)_disaggregatedVars__t(B)_ +-92 NoClash(B_E_3_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(28)_transformedConstraints(c_0_ub)_: +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(28)_disaggregatedVars__t(E)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(28)_disaggregatedVars__t(C)_ --2.0 NoClash(C_E_2_0)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(28)_disaggregatedVars__t(B)_ ++5.0 NoClash(B_E_5_0)_binary_indicator_var <= 0.0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(28)__t(E)_bounds_(ub)_: +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(28)_disaggregatedVars__t(E)_ --92 NoClash(C_E_2_0)_binary_indicator_var +-92 NoClash(B_E_5_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(28)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(28)_disaggregatedVars__t(C)_ --92 NoClash(C_E_2_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(28)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(28)_disaggregatedVars__t(B)_ +-92 NoClash(B_E_5_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(29)_transformedConstraints(c_0_ub)_: -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(29)_disaggregatedVars__t(E)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(29)_disaggregatedVars__t(C)_ -+9.0 NoClash(C_E_2_1)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(29)_disaggregatedVars__t(B)_ <= 0.0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(29)__t(E)_bounds_(ub)_: +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(29)_disaggregatedVars__t(E)_ --92 NoClash(C_E_2_1)_binary_indicator_var +-92 NoClash(B_E_5_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(29)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(29)_disaggregatedVars__t(C)_ --92 NoClash(C_E_2_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(29)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(29)_disaggregatedVars__t(B)_ +-92 NoClash(B_E_5_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)_disaggregatedVars__t(D)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)_disaggregatedVars__t(C)_ -+5.0 NoClash(C_D_4_0)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)_disaggregatedVars__t(F)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)_disaggregatedVars__t(B)_ ++4.0 NoClash(B_F_3_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)_disaggregatedVars__t(D)_ --92 NoClash(C_D_4_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)__t(F)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)_disaggregatedVars__t(F)_ +-92 NoClash(B_F_3_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)_disaggregatedVars__t(C)_ --92 NoClash(C_D_4_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)_disaggregatedVars__t(B)_ +-92 NoClash(B_F_3_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)_disaggregatedVars__t(D)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)_disaggregatedVars__t(C)_ -+2.0 NoClash(C_D_4_1)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)_disaggregatedVars__t(F)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)_disaggregatedVars__t(B)_ ++5.0 NoClash(B_F_3_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)_disaggregatedVars__t(D)_ --92 NoClash(C_D_4_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)__t(F)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)_disaggregatedVars__t(F)_ +-92 NoClash(B_F_3_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)_disaggregatedVars__t(C)_ --92 NoClash(C_D_4_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)_disaggregatedVars__t(B)_ +-92 NoClash(B_F_3_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(D)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(C)_ -+2.0 NoClash(C_D_2_0)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(B)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(G)_ ++8.0 NoClash(B_G_2_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(D)_ --92 NoClash(C_D_2_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(B)_ +-92 NoClash(B_G_2_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(C)_ --92 NoClash(C_D_2_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)__t(G)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(G)_ +-92 NoClash(B_G_2_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(D)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(C)_ -+9.0 NoClash(C_D_2_1)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(B)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(G)_ ++3.0 NoClash(B_G_2_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(D)_ --92 NoClash(C_D_2_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(B)_ +-92 NoClash(B_G_2_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(C)_ --92 NoClash(C_D_2_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)__t(G)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(G)_ +-92 NoClash(B_G_2_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(34)_transformedConstraints(c_0_ub)_: @@ -1212,544 +1210,546 @@ c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(35)__t(B)_bounds_(ub)_: <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)_disaggregatedVars__t(G)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)_disaggregatedVars__t(B)_ -+8.0 NoClash(B_G_2_0)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)_disaggregatedVars__t(D)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)_disaggregatedVars__t(C)_ ++2.0 NoClash(C_D_2_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)_disaggregatedVars__t(G)_ --92 NoClash(B_G_2_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)_disaggregatedVars__t(D)_ +-92 NoClash(C_D_2_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)_disaggregatedVars__t(B)_ --92 NoClash(B_G_2_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)_disaggregatedVars__t(C)_ +-92 NoClash(C_D_2_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)_disaggregatedVars__t(G)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)_disaggregatedVars__t(B)_ -+3.0 NoClash(B_G_2_1)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)_disaggregatedVars__t(D)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)_disaggregatedVars__t(C)_ ++9.0 NoClash(C_D_2_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)_disaggregatedVars__t(G)_ --92 NoClash(B_G_2_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)_disaggregatedVars__t(D)_ +-92 NoClash(C_D_2_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)_disaggregatedVars__t(B)_ --92 NoClash(B_G_2_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)_disaggregatedVars__t(C)_ +-92 NoClash(C_D_2_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)_disaggregatedVars__t(F)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)_disaggregatedVars__t(B)_ -+4.0 NoClash(B_F_3_0)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)_disaggregatedVars__t(D)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)_disaggregatedVars__t(C)_ ++5.0 NoClash(C_D_4_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)__t(F)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)_disaggregatedVars__t(F)_ --92 NoClash(B_F_3_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)_disaggregatedVars__t(D)_ +-92 NoClash(C_D_4_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)_disaggregatedVars__t(B)_ --92 NoClash(B_F_3_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)_disaggregatedVars__t(C)_ +-92 NoClash(C_D_4_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)_disaggregatedVars__t(F)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)_disaggregatedVars__t(B)_ -+5.0 NoClash(B_F_3_1)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)_disaggregatedVars__t(D)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)_disaggregatedVars__t(C)_ ++2.0 NoClash(C_D_4_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)__t(F)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)_disaggregatedVars__t(F)_ --92 NoClash(B_F_3_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)_disaggregatedVars__t(D)_ +-92 NoClash(C_D_4_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)_disaggregatedVars__t(B)_ --92 NoClash(B_F_3_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)_disaggregatedVars__t(C)_ +-92 NoClash(C_D_4_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(40)_transformedConstraints(c_0_ub)_: +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(40)_disaggregatedVars__t(E)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(40)_disaggregatedVars__t(B)_ -+5.0 NoClash(B_E_5_0)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(40)_disaggregatedVars__t(C)_ +-2.0 NoClash(C_E_2_0)_binary_indicator_var <= 0.0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(40)__t(E)_bounds_(ub)_: +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(40)_disaggregatedVars__t(E)_ --92 NoClash(B_E_5_0)_binary_indicator_var +-92 NoClash(C_E_2_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(40)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(40)_disaggregatedVars__t(B)_ --92 NoClash(B_E_5_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(40)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(40)_disaggregatedVars__t(C)_ +-92 NoClash(C_E_2_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(41)_transformedConstraints(c_0_ub)_: -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(41)_disaggregatedVars__t(E)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(41)_disaggregatedVars__t(B)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(41)_disaggregatedVars__t(C)_ ++9.0 NoClash(C_E_2_1)_binary_indicator_var <= 0.0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(41)__t(E)_bounds_(ub)_: +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(41)_disaggregatedVars__t(E)_ --92 NoClash(B_E_5_1)_binary_indicator_var +-92 NoClash(C_E_2_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(41)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(41)_disaggregatedVars__t(B)_ --92 NoClash(B_E_5_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(41)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(41)_disaggregatedVars__t(C)_ +-92 NoClash(C_E_2_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(E)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(B)_ -+7.0 NoClash(B_E_3_0)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(C)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(F)_ ++2.0 NoClash(C_F_1_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(E)_ --92 NoClash(B_E_3_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(C)_ +-92 NoClash(C_F_1_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(B)_ --92 NoClash(B_E_3_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)__t(F)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(F)_ +-92 NoClash(C_F_1_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(E)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(B)_ -+3.0 NoClash(B_E_3_1)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(C)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(F)_ ++6.0 NoClash(C_F_1_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(E)_ --92 NoClash(B_E_3_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(C)_ +-92 NoClash(C_F_1_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(B)_ --92 NoClash(B_E_3_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)__t(F)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(F)_ +-92 NoClash(C_F_1_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)_disaggregatedVars__t(E)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)_disaggregatedVars__t(B)_ -+4.0 NoClash(B_E_2_0)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)_disaggregatedVars__t(F)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)_disaggregatedVars__t(C)_ ++5.0 NoClash(C_F_4_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)_disaggregatedVars__t(E)_ --92 NoClash(B_E_2_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)__t(F)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)_disaggregatedVars__t(F)_ +-92 NoClash(C_F_4_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)_disaggregatedVars__t(B)_ --92 NoClash(B_E_2_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)_disaggregatedVars__t(C)_ +-92 NoClash(C_F_4_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)_disaggregatedVars__t(E)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)_disaggregatedVars__t(B)_ -+3.0 NoClash(B_E_2_1)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)_disaggregatedVars__t(F)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)_disaggregatedVars__t(C)_ ++8.0 NoClash(C_F_4_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)_disaggregatedVars__t(E)_ --92 NoClash(B_E_2_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)__t(F)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)_disaggregatedVars__t(F)_ +-92 NoClash(C_F_4_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)_disaggregatedVars__t(B)_ --92 NoClash(B_E_2_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)_disaggregatedVars__t(C)_ +-92 NoClash(C_F_4_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)_disaggregatedVars__t(D)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)_disaggregatedVars__t(B)_ -+10.0 NoClash(B_D_3_0)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)_disaggregatedVars__t(G)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)_disaggregatedVars__t(C)_ ++2.0 NoClash(C_G_2_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)_disaggregatedVars__t(D)_ --92 NoClash(B_D_3_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)__t(G)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)_disaggregatedVars__t(G)_ +-92 NoClash(C_G_2_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)_disaggregatedVars__t(B)_ --92 NoClash(B_D_3_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)_disaggregatedVars__t(C)_ +-92 NoClash(C_G_2_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)_disaggregatedVars__t(D)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)_disaggregatedVars__t(B)_ --1 NoClash(B_D_3_1)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)_disaggregatedVars__t(G)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)_disaggregatedVars__t(C)_ ++9.0 NoClash(C_G_2_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)_disaggregatedVars__t(D)_ --92 NoClash(B_D_3_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)__t(G)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)_disaggregatedVars__t(G)_ +-92 NoClash(C_G_2_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)_disaggregatedVars__t(B)_ --92 NoClash(B_D_3_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)_disaggregatedVars__t(C)_ +-92 NoClash(C_G_2_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)_disaggregatedVars__t(D)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)_disaggregatedVars__t(B)_ -+8.0 NoClash(B_D_2_0)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)_disaggregatedVars__t(G)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)_disaggregatedVars__t(C)_ ++4.0 NoClash(C_G_4_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)_disaggregatedVars__t(D)_ --92 NoClash(B_D_2_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)__t(G)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)_disaggregatedVars__t(G)_ +-92 NoClash(C_G_4_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)_disaggregatedVars__t(B)_ --92 NoClash(B_D_2_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)_disaggregatedVars__t(C)_ +-92 NoClash(C_G_4_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)_disaggregatedVars__t(D)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)_disaggregatedVars__t(B)_ -+3.0 NoClash(B_D_2_1)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)_disaggregatedVars__t(G)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)_disaggregatedVars__t(C)_ ++7.0 NoClash(C_G_4_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)_disaggregatedVars__t(D)_ --92 NoClash(B_D_2_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)__t(G)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)_disaggregatedVars__t(G)_ +-92 NoClash(C_G_4_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)_disaggregatedVars__t(B)_ --92 NoClash(B_D_2_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)_disaggregatedVars__t(C)_ +-92 NoClash(C_G_4_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(C)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(B)_ -+9.0 NoClash(B_C_2_0)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(D)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(E)_ ++4.0 NoClash(D_E_2_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(C)_ --92 NoClash(B_C_2_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(D)_ +-92 NoClash(D_E_2_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(B)_ --92 NoClash(B_C_2_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)__t(E)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(E)_ +-92 NoClash(D_E_2_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(C)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(B)_ --3.0 NoClash(B_C_2_1)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(D)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(E)_ ++8.0 NoClash(D_E_2_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(C)_ --92 NoClash(B_C_2_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(D)_ +-92 NoClash(D_E_2_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(B)_ --92 NoClash(B_C_2_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)__t(E)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(E)_ +-92 NoClash(D_E_2_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)_disaggregatedVars__t(G)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)_disaggregatedVars__t(A)_ -+9.0 NoClash(A_G_5_0)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)_disaggregatedVars__t(E)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)_disaggregatedVars__t(D)_ ++2.0 NoClash(D_E_3_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)_disaggregatedVars__t(G)_ --92 NoClash(A_G_5_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)__t(E)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)_disaggregatedVars__t(E)_ +-92 NoClash(D_E_3_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)_disaggregatedVars__t(A)_ --92 NoClash(A_G_5_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)_disaggregatedVars__t(D)_ +-92 NoClash(D_E_3_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)_disaggregatedVars__t(G)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)_disaggregatedVars__t(A)_ --3.0 NoClash(A_G_5_1)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)_disaggregatedVars__t(E)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)_disaggregatedVars__t(D)_ ++9.0 NoClash(D_E_3_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)_disaggregatedVars__t(G)_ --92 NoClash(A_G_5_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)__t(E)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)_disaggregatedVars__t(E)_ +-92 NoClash(D_E_3_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)_disaggregatedVars__t(A)_ --92 NoClash(A_G_5_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)_disaggregatedVars__t(D)_ +-92 NoClash(D_E_3_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(54)_transformedConstraints(c_0_ub)_: +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(54)_disaggregatedVars__t(F)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(54)_disaggregatedVars__t(A)_ -+4.0 NoClash(A_F_3_0)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(54)_disaggregatedVars__t(D)_ +-1 NoClash(D_F_3_0)_binary_indicator_var <= 0.0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(54)__t(F)_bounds_(ub)_: +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(54)_disaggregatedVars__t(F)_ --92 NoClash(A_F_3_0)_binary_indicator_var +-92 NoClash(D_F_3_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(54)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(54)_disaggregatedVars__t(A)_ --92 NoClash(A_F_3_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(54)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(54)_disaggregatedVars__t(D)_ +-92 NoClash(D_F_3_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(55)_transformedConstraints(c_0_ub)_: -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(55)_disaggregatedVars__t(F)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(55)_disaggregatedVars__t(A)_ -+6.0 NoClash(A_F_3_1)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(55)_disaggregatedVars__t(D)_ ++11.0 NoClash(D_F_3_1)_binary_indicator_var <= 0.0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(55)__t(F)_bounds_(ub)_: +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(55)_disaggregatedVars__t(F)_ --92 NoClash(A_F_3_1)_binary_indicator_var +-92 NoClash(D_F_3_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(55)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(55)_disaggregatedVars__t(A)_ --92 NoClash(A_F_3_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(55)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(55)_disaggregatedVars__t(D)_ +-92 NoClash(D_F_3_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(56)_transformedConstraints(c_0_ub)_: +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(56)_disaggregatedVars__t(F)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(56)_disaggregatedVars__t(A)_ -+2.0 NoClash(A_F_1_0)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(56)_disaggregatedVars__t(D)_ ++1 NoClash(D_F_4_0)_binary_indicator_var <= 0.0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(56)__t(F)_bounds_(ub)_: +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(56)_disaggregatedVars__t(F)_ --92 NoClash(A_F_1_0)_binary_indicator_var +-92 NoClash(D_F_4_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(56)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(56)_disaggregatedVars__t(A)_ --92 NoClash(A_F_1_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(56)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(56)_disaggregatedVars__t(D)_ +-92 NoClash(D_F_4_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(57)_transformedConstraints(c_0_ub)_: -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(57)_disaggregatedVars__t(F)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(57)_disaggregatedVars__t(A)_ -+3.0 NoClash(A_F_1_1)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(57)_disaggregatedVars__t(D)_ ++7.0 NoClash(D_F_4_1)_binary_indicator_var <= 0.0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(57)__t(F)_bounds_(ub)_: +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(57)_disaggregatedVars__t(F)_ --92 NoClash(A_F_1_1)_binary_indicator_var +-92 NoClash(D_F_4_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(57)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(57)_disaggregatedVars__t(A)_ --92 NoClash(A_F_1_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(57)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(57)_disaggregatedVars__t(D)_ +-92 NoClash(D_F_4_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(E)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(A)_ -+4.0 NoClash(A_E_5_0)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(D)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(G)_ ++8.0 NoClash(D_G_2_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(E)_ --92 NoClash(A_E_5_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(D)_ +-92 NoClash(D_G_2_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(A)_ --92 NoClash(A_E_5_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)__t(G)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(G)_ +-92 NoClash(D_G_2_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(E)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(A)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(D)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(G)_ ++8.0 NoClash(D_G_2_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(E)_ --92 NoClash(A_E_5_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(D)_ +-92 NoClash(D_G_2_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(A)_ --92 NoClash(A_E_5_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)__t(G)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(G)_ +-92 NoClash(D_G_2_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)_disaggregatedVars__t(E)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)_disaggregatedVars__t(A)_ -+7.0 NoClash(A_E_3_0)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)_disaggregatedVars__t(G)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)_disaggregatedVars__t(D)_ <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)_disaggregatedVars__t(E)_ --92 NoClash(A_E_3_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)__t(G)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)_disaggregatedVars__t(G)_ +-92 NoClash(D_G_4_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)_disaggregatedVars__t(A)_ --92 NoClash(A_E_3_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)_disaggregatedVars__t(D)_ +-92 NoClash(D_G_4_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)_disaggregatedVars__t(E)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)_disaggregatedVars__t(A)_ -+4.0 NoClash(A_E_3_1)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)_disaggregatedVars__t(G)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)_disaggregatedVars__t(D)_ ++6.0 NoClash(D_G_4_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)_disaggregatedVars__t(E)_ --92 NoClash(A_E_3_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)__t(G)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)_disaggregatedVars__t(G)_ +-92 NoClash(D_G_4_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)_disaggregatedVars__t(A)_ --92 NoClash(A_E_3_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)__t(D)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)_disaggregatedVars__t(D)_ +-92 NoClash(D_G_4_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)_disaggregatedVars__t(D)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)_disaggregatedVars__t(A)_ -+10.0 NoClash(A_D_3_0)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)_disaggregatedVars__t(F)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)_disaggregatedVars__t(E)_ ++3.0 NoClash(E_F_3_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)_disaggregatedVars__t(D)_ --92 NoClash(A_D_3_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)__t(F)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)_disaggregatedVars__t(F)_ +-92 NoClash(E_F_3_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)_disaggregatedVars__t(A)_ --92 NoClash(A_D_3_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)__t(E)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)_disaggregatedVars__t(E)_ +-92 NoClash(E_F_3_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)_disaggregatedVars__t(D)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)_disaggregatedVars__t(A)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)_disaggregatedVars__t(F)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)_disaggregatedVars__t(E)_ ++8.0 NoClash(E_F_3_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)_disaggregatedVars__t(D)_ --92 NoClash(A_D_3_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)__t(F)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)_disaggregatedVars__t(F)_ +-92 NoClash(E_F_3_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)_disaggregatedVars__t(A)_ --92 NoClash(A_D_3_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)__t(E)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)_disaggregatedVars__t(E)_ +-92 NoClash(E_F_3_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(C)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(A)_ -+6.0 NoClash(A_C_1_0)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(E)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(G)_ ++8.0 NoClash(E_G_2_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(C)_ --92 NoClash(A_C_1_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)__t(E)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(E)_ +-92 NoClash(E_G_2_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(A)_ --92 NoClash(A_C_1_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)__t(G)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(G)_ +-92 NoClash(E_G_2_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(C)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(A)_ -+3.0 NoClash(A_C_1_1)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(E)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(G)_ ++4.0 NoClash(E_G_2_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(C)_ --92 NoClash(A_C_1_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)__t(E)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(E)_ +-92 NoClash(E_G_2_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(A)_ --92 NoClash(A_C_1_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)__t(G)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(G)_ +-92 NoClash(E_G_2_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)_disaggregatedVars__t(B)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)_disaggregatedVars__t(A)_ -+2.0 NoClash(A_B_5_0)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)_disaggregatedVars__t(G)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)_disaggregatedVars__t(E)_ ++7.0 NoClash(E_G_5_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)_disaggregatedVars__t(B)_ --92 NoClash(A_B_5_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)__t(G)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)_disaggregatedVars__t(G)_ +-92 NoClash(E_G_5_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)_disaggregatedVars__t(A)_ --92 NoClash(A_B_5_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)__t(E)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)_disaggregatedVars__t(E)_ +-92 NoClash(E_G_5_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)_disaggregatedVars__t(B)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)_disaggregatedVars__t(A)_ -+3.0 NoClash(A_B_5_1)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)_disaggregatedVars__t(G)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)_disaggregatedVars__t(E)_ +-1 NoClash(E_G_5_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)_disaggregatedVars__t(B)_ --92 NoClash(A_B_5_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)__t(G)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)_disaggregatedVars__t(G)_ +-92 NoClash(E_G_5_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)_disaggregatedVars__t(A)_ --92 NoClash(A_B_5_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)__t(E)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)_disaggregatedVars__t(E)_ +-92 NoClash(E_G_5_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)_disaggregatedVars__t(B)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)_disaggregatedVars__t(A)_ -+4.0 NoClash(A_B_3_0)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)_disaggregatedVars__t(G)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)_disaggregatedVars__t(F)_ ++6.0 NoClash(F_G_4_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)_disaggregatedVars__t(B)_ --92 NoClash(A_B_3_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)__t(G)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)_disaggregatedVars__t(G)_ +-92 NoClash(F_G_4_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)_disaggregatedVars__t(A)_ --92 NoClash(A_B_3_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)__t(F)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)_disaggregatedVars__t(F)_ +-92 NoClash(F_G_4_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_disaggregatedVars__t(B)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_disaggregatedVars__t(A)_ -+5.0 NoClash(A_B_3_1)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_disaggregatedVars__t(G)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_disaggregatedVars__t(F)_ ++6.0 NoClash(F_G_4_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_disaggregatedVars__t(B)_ --92 NoClash(A_B_3_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)__t(G)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_disaggregatedVars__t(G)_ +-92 NoClash(F_G_4_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_disaggregatedVars__t(A)_ --92 NoClash(A_B_3_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)__t(F)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_disaggregatedVars__t(F)_ +-92 NoClash(F_G_4_1)_binary_indicator_var <= 0 bounds @@ -1761,146 +1761,146 @@ bounds 0 <= t(E) <= 92 0 <= t(F) <= 92 0 <= t(G) <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(G)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(G)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(F)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(F)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(G)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(G)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(G)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(G)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)_disaggregatedVars__t(F)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)_disaggregatedVars__t(F)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)_disaggregatedVars__t(G)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)_disaggregatedVars__t(G)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)_disaggregatedVars__t(G)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)_disaggregatedVars__t(G)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(7)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)_disaggregatedVars__t(E)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)_disaggregatedVars__t(E)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(8)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(9)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)_disaggregatedVars__t(E)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)_disaggregatedVars__t(E)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(10)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)_disaggregatedVars__t(A)_ <= 92 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)_disaggregatedVars__t(F)_ <= 92 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)_disaggregatedVars__t(F)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)_disaggregatedVars__t(D)_ <= 92 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(14)_disaggregatedVars__t(F)_ <= 92 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(15)_disaggregatedVars__t(F)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(14)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(15)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(G)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(G)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(C)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(C)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)_disaggregatedVars__t(G)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)_disaggregatedVars__t(G)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)_disaggregatedVars__t(C)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)_disaggregatedVars__t(C)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(F)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(F)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(C)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(C)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)_disaggregatedVars__t(F)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)_disaggregatedVars__t(F)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)_disaggregatedVars__t(C)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(14)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(15)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)_disaggregatedVars__t(G)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)_disaggregatedVars__t(G)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(16)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(E)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(E)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)_disaggregatedVars__t(E)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)_disaggregatedVars__t(E)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(27)_disaggregatedVars__t(B)_ <= 92 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(28)_disaggregatedVars__t(E)_ <= 92 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(29)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(28)_disaggregatedVars__t(C)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(29)_disaggregatedVars__t(C)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)_disaggregatedVars__t(C)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)_disaggregatedVars__t(C)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(C)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(28)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(29)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)_disaggregatedVars__t(F)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)_disaggregatedVars__t(F)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(30)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(G)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(G)_ <= 92 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(34)_disaggregatedVars__t(G)_ <= 92 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(35)_disaggregatedVars__t(G)_ <= 92 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(34)_disaggregatedVars__t(B)_ <= 92 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(35)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)_disaggregatedVars__t(G)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)_disaggregatedVars__t(G)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)_disaggregatedVars__t(F)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)_disaggregatedVars__t(F)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(36)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(37)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(38)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(39)_disaggregatedVars__t(C)_ <= 92 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(40)_disaggregatedVars__t(E)_ <= 92 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(41)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(40)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(41)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(C)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(C)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)_disaggregatedVars__t(G)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)_disaggregatedVars__t(G)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)_disaggregatedVars__t(A)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(40)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(41)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(F)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(F)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)_disaggregatedVars__t(F)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)_disaggregatedVars__t(F)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(45)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)_disaggregatedVars__t(G)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)_disaggregatedVars__t(G)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(46)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(47)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)_disaggregatedVars__t(G)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)_disaggregatedVars__t(G)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(48)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(E)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(E)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)_disaggregatedVars__t(E)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)_disaggregatedVars__t(E)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(53)_disaggregatedVars__t(D)_ <= 92 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(54)_disaggregatedVars__t(F)_ <= 92 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(55)_disaggregatedVars__t(F)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(54)_disaggregatedVars__t(A)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(55)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(54)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(55)_disaggregatedVars__t(D)_ <= 92 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(56)_disaggregatedVars__t(F)_ <= 92 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(57)_disaggregatedVars__t(F)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(56)_disaggregatedVars__t(A)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(57)_disaggregatedVars__t(A)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(A)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(A)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)_disaggregatedVars__t(E)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)_disaggregatedVars__t(A)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)_disaggregatedVars__t(A)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)_disaggregatedVars__t(D)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)_disaggregatedVars__t(A)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)_disaggregatedVars__t(A)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(C)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(C)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(A)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(A)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)_disaggregatedVars__t(A)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)_disaggregatedVars__t(A)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_disaggregatedVars__t(B)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)_disaggregatedVars__t(A)_ <= 92 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(56)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(57)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(G)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(G)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)_disaggregatedVars__t(G)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)_disaggregatedVars__t(G)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(61)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)_disaggregatedVars__t(F)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)_disaggregatedVars__t(F)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(62)_disaggregatedVars__t(E)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)_disaggregatedVars__t(E)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(E)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(E)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(G)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(G)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)_disaggregatedVars__t(G)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)_disaggregatedVars__t(G)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)_disaggregatedVars__t(E)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(67)_disaggregatedVars__t(E)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)_disaggregatedVars__t(G)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_disaggregatedVars__t(G)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(68)_disaggregatedVars__t(F)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(69)_disaggregatedVars__t(F)_ <= 92 0 <= NoClash(A_B_3_0)_binary_indicator_var <= 1 0 <= NoClash(A_B_3_1)_binary_indicator_var <= 1 0 <= NoClash(A_B_5_0)_binary_indicator_var <= 1 diff --git a/pyomo/gdp/tests/jobshop_small_hull.lp b/pyomo/gdp/tests/jobshop_small_hull.lp index c07b9cd048e..ae2d738d29c 100644 --- a/pyomo/gdp/tests/jobshop_small_hull.lp +++ b/pyomo/gdp/tests/jobshop_small_hull.lp @@ -22,29 +22,29 @@ c_u_Feas(C)_: <= -6 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(0)_: -+1 t(C) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(C)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(C)_ -= 0 - -c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(1)_: +1 t(B) -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(B)_ -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(B)_ = 0 -c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(2)_: -+1 t(C) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(C)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(C)_ +c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(1)_: ++1 t(A) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(A)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(A)_ = 0 -c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(3)_: +c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(2)_: +1 t(A) -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(A)_ -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(A)_ = 0 +c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(3)_: ++1 t(C) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(C)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(C)_ += 0 + c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(4)_: +1 t(B) -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(B)_ @@ -52,9 +52,9 @@ c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(4)_: = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(5)_: -+1 t(A) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(A)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(A)_ ++1 t(C) +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(C)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(C)_ = 0 c_e__pyomo_gdp_hull_reformulation_disj_xor(A_B_3)_: @@ -73,98 +73,98 @@ c_e__pyomo_gdp_hull_reformulation_disj_xor(B_C_2)_: = 1 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(C)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(B)_ -+6.0 NoClash(B_C_2_0)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(B)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(A)_ <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(C)_ --19 NoClash(B_C_2_0)_binary_indicator_var -<= 0 - c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)__t(B)_bounds_(ub)_: +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(B)_ --19 NoClash(B_C_2_0)_binary_indicator_var +-19 NoClash(A_B_3_0)_binary_indicator_var +<= 0 + +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(A)_ +-19 NoClash(A_B_3_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(C)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(B)_ -+1 NoClash(B_C_2_1)_binary_indicator_var +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(B)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(A)_ ++5.0 NoClash(A_B_3_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(C)_ --19 NoClash(B_C_2_1)_binary_indicator_var -<= 0 - c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)__t(B)_bounds_(ub)_: +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(B)_ --19 NoClash(B_C_2_1)_binary_indicator_var +-19 NoClash(A_B_3_1)_binary_indicator_var +<= 0 + +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(A)_ +-19 NoClash(A_B_3_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(C)_ -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(A)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(C)_ +2.0 NoClash(A_C_1_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(C)_ +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(A)_ -19 NoClash(A_C_1_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(A)_ +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(C)_ -19 NoClash(A_C_1_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(C)_ +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(A)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(C)_ +5.0 NoClash(A_C_1_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(C)_ +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)__t(A)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(A)_ -19 NoClash(A_C_1_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(A)_ +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(C)_ -19 NoClash(A_C_1_1)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(B)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(A)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(B)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(C)_ ++6.0 NoClash(B_C_2_0)_binary_indicator_var <= 0.0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)__t(B)_bounds_(ub)_: +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(B)_ --19 NoClash(A_B_3_0)_binary_indicator_var +-19 NoClash(B_C_2_0)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(A)_ --19 NoClash(A_B_3_0)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(C)_ +-19 NoClash(B_C_2_0)_binary_indicator_var <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(B)_ -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(A)_ -+5.0 NoClash(A_B_3_1)_binary_indicator_var ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(B)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(C)_ ++1 NoClash(B_C_2_1)_binary_indicator_var <= 0.0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)__t(B)_bounds_(ub)_: +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(B)_ --19 NoClash(A_B_3_1)_binary_indicator_var +-19 NoClash(B_C_2_1)_binary_indicator_var <= 0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(A)_ --19 NoClash(A_B_3_1)_binary_indicator_var +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)__t(C)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(C)_ +-19 NoClash(B_C_2_1)_binary_indicator_var <= 0 bounds @@ -172,18 +172,18 @@ bounds 0 <= t(A) <= 19 0 <= t(B) <= 19 0 <= t(C) <= 19 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(C)_ <= 19 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(C)_ <= 19 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(B)_ <= 19 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(B)_ <= 19 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(C)_ <= 19 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(C)_ <= 19 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(0)_disaggregatedVars__t(A)_ <= 19 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(A)_ <= 19 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(A)_ <= 19 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(A)_ <= 19 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(C)_ <= 19 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(C)_ <= 19 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(B)_ <= 19 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(B)_ <= 19 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(A)_ <= 19 - 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(A)_ <= 19 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(C)_ <= 19 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(C)_ <= 19 0 <= NoClash(A_B_3_0)_binary_indicator_var <= 1 0 <= NoClash(A_B_3_1)_binary_indicator_var <= 1 0 <= NoClash(A_C_1_0)_binary_indicator_var <= 1 From 8ce0ce9657aac63f3013e9c419eec2a5870e3248 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Sat, 17 Feb 2024 20:56:12 -0700 Subject: [PATCH 1026/1204] Changing FME tests that use hull, because I changed the order of transformation --- .../contrib/fme/tests/test_fourier_motzkin_elimination.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/fme/tests/test_fourier_motzkin_elimination.py b/pyomo/contrib/fme/tests/test_fourier_motzkin_elimination.py index 11c008acf82..e997e138724 100644 --- a/pyomo/contrib/fme/tests/test_fourier_motzkin_elimination.py +++ b/pyomo/contrib/fme/tests/test_fourier_motzkin_elimination.py @@ -435,7 +435,7 @@ def check_hull_projected_constraints(self, m, constraints, indices): self.assertIs(body.linear_vars[2], m.startup.binary_indicator_var) self.assertEqual(body.linear_coefs[2], 2) - # 1 <= time1_disjuncts[0].ind_var + time_1.disjuncts[1].ind_var + # 1 <= time1_disjuncts[0].ind_var + time1_disjuncts[1].ind_var cons = constraints[indices[7]] self.assertEqual(cons.lower, 1) self.assertIsNone(cons.upper) @@ -548,12 +548,12 @@ def test_project_disaggregated_vars(self): # we of course get tremendous amounts of garbage, but we make sure that # what should be here is: self.check_hull_projected_constraints( - m, constraints, [23, 19, 8, 10, 54, 67, 35, 3, 4, 1, 2] + m, constraints, [16, 12, 69, 71, 47, 60, 28, 1, 2, 3, 4] ) # and when we filter, it's still there. constraints = filtered._pyomo_contrib_fme_transformation.projected_constraints self.check_hull_projected_constraints( - filtered, constraints, [10, 8, 5, 6, 15, 19, 11, 3, 4, 1, 2] + filtered, constraints, [8, 6, 20, 21, 13, 17, 9, 1, 2, 3, 4] ) @unittest.skipIf(not 'glpk' in solvers, 'glpk not available') @@ -570,7 +570,7 @@ def test_post_processing(self): # They should be the same as the above, but now these are *all* the # constraints self.check_hull_projected_constraints( - m, constraints, [10, 8, 5, 6, 15, 19, 11, 3, 4, 1, 2] + m, constraints, [8, 6, 20, 21, 13, 17, 9, 1, 2, 3, 4] ) # and check that we didn't change the model From 1491523e3eeca485fe3885605397a93437c76b28 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Sun, 18 Feb 2024 14:07:29 -0700 Subject: [PATCH 1027/1204] Changing so that we trust any solver with a name that contains a trusted solver name for now, and adding a TODO about how the future will be better. --- pyomo/gdp/plugins/multiple_bigm.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index 5e23a706361..0dc6d76eb6e 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -65,10 +65,10 @@ 'cbc', 'glpk', 'scip', - 'xpress_direct', - 'mosek_direct', + 'xpress', + 'mosek', 'baron', - 'appsi_highs', + 'highs', } @@ -670,10 +670,17 @@ def _solve_disjunct_for_M( self, other_disjunct, scratch_block, unsuccessful_solve_msg ): solver = self._config.solver - solver_trusted = solver.name in _trusted_solvers results = solver.solve(other_disjunct, load_solutions=False) if results.solver.termination_condition is TerminationCondition.infeasible: - if solver_trusted: + # [2/18/24]: TODO: After the solver rewrite is complete, we will not + # need this check since we can actually determine from the + # termination condition whether or not the solver proved + # infeasibility or just terminated at local infeasiblity. For now, + # while this is not complete, it catches most of the solvers we + # trust, and, unless someone is so pathological as to *rename* an + # untrusted solver using a trusted solver name, it will never do the + # *wrong* thing. + if any(s in solver.name for s in _trusted_solvers): logger.debug( "Disjunct '%s' is infeasible, deactivating." % other_disjunct.name ) From 353939782eb0896527598253b35fd8a23a7d948f Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 18 Feb 2024 16:29:55 -0500 Subject: [PATCH 1028/1204] Make `IsInstance` module qualifiers optional --- pyomo/common/config.py | 29 +++++++++++++++++++++++------ pyomo/common/tests/test_config.py | 25 +++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index 92613266885..4207392389a 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -310,11 +310,16 @@ class IsInstance(object): ---------- *bases : tuple of type Valid types. + document_full_base_names : bool, optional + True to prepend full module qualifier to the name of each + member of `bases` in ``self.domain_name()`` and/or any + error messages generated by this object, False otherwise. """ - def __init__(self, *bases): + def __init__(self, *bases, document_full_base_names=False): assert bases self.baseClasses = bases + self.document_full_base_names = document_full_base_names @staticmethod def _fullname(klass): @@ -325,29 +330,41 @@ def _fullname(klass): module_qual = "" if module_name == "builtins" else f"{module_name}." return f"{module_qual}{klass.__name__}" + def _get_class_name(self, klass): + """ + Get name of class. Module qualifier may be included, + depending on value of `self.document_full_base_names`. + """ + if self.document_full_base_names: + return self._fullname(klass) + else: + return klass.__name__ + def __call__(self, obj): if isinstance(obj, self.baseClasses): return obj if len(self.baseClasses) > 1: class_names = ", ".join( - f"{self._fullname(kls)!r}" for kls in self.baseClasses + f"{self._get_class_name(kls)!r}" for kls in self.baseClasses ) msg = ( "Expected an instance of one of these types: " f"{class_names}, but received value {obj!r} of type " - f"{self._fullname(type(obj))!r}" + f"{self._get_class_name(type(obj))!r}" ) else: msg = ( f"Expected an instance of " - f"{self._fullname(self.baseClasses[0])!r}, " - f"but received value {obj!r} of type {self._fullname(type(obj))!r}" + f"{self._get_class_name(self.baseClasses[0])!r}, " + f"but received value {obj!r} of type " + f"{self._get_class_name(type(obj))!r}" ) raise ValueError(msg) def domain_name(self): + class_names = (self._get_class_name(kls) for kls in self.baseClasses) return ( - f"IsInstance({', '.join(self._fullname(kls) for kls in self.baseClasses)})" + f"IsInstance({', '.join(class_names)})" ) diff --git a/pyomo/common/tests/test_config.py b/pyomo/common/tests/test_config.py index 068017d836f..f3f5cbedad6 100644 --- a/pyomo/common/tests/test_config.py +++ b/pyomo/common/tests/test_config.py @@ -469,13 +469,18 @@ def __repr__(self): c.val2 = testinst self.assertEqual(c.val2, testinst) exc_str = ( - r"Expected an instance of '.*\.TestClass', " + r"Expected an instance of 'TestClass', " "but received value 2.4 of type 'float'" ) with self.assertRaisesRegex(ValueError, exc_str): c.val2 = 2.4 - c.declare("val3", ConfigValue(None, IsInstance(int, TestClass))) + c.declare( + "val3", + ConfigValue( + None, IsInstance(int, TestClass, document_full_base_names=True) + ), + ) self.assertRegex( c.get("val3").domain_name(), r"IsInstance\(int, .*\.TestClass\)" ) @@ -488,6 +493,22 @@ def __repr__(self): with self.assertRaisesRegex(ValueError, exc_str): c.val3 = 2.4 + c.declare( + "val4", + ConfigValue( + None, IsInstance(int, TestClass, document_full_base_names=False) + ), + ) + self.assertEqual(c.get("val4").domain_name(), "IsInstance(int, TestClass)") + c.val4 = 2 + self.assertEqual(c.val4, 2) + exc_str = ( + r"Expected an instance of one of these types: 'int', 'TestClass'" + r", but received value 2.4 of type 'float'" + ) + with self.assertRaisesRegex(ValueError, exc_str): + c.val4 = 2.4 + def test_Path(self): def norm(x): if cwd[1] == ':' and x[0] == '/': From bbba7629703ec3461fd8287a2c4bc6e26e29a558 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 18 Feb 2024 16:35:35 -0500 Subject: [PATCH 1029/1204] Add `IsInstance` to config library reference docs --- doc/OnlineDocs/library_reference/common/config.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/OnlineDocs/library_reference/common/config.rst b/doc/OnlineDocs/library_reference/common/config.rst index 7a400b26ce3..c5dc607977a 100644 --- a/doc/OnlineDocs/library_reference/common/config.rst +++ b/doc/OnlineDocs/library_reference/common/config.rst @@ -36,6 +36,7 @@ Domain validators NonPositiveFloat NonNegativeFloat In + IsInstance InEnum ListOf Module @@ -75,6 +76,7 @@ Domain validators .. autofunction:: NonPositiveFloat .. autofunction:: NonNegativeFloat .. autoclass:: In +.. autoclass:: IsInstance .. autoclass:: InEnum .. autoclass:: ListOf .. autoclass:: Module From 0fc42f1923a68259f362c40227fd8da7c78b5b94 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 18 Feb 2024 16:40:18 -0500 Subject: [PATCH 1030/1204] Implement `Path.domain_name()` --- pyomo/common/config.py | 3 +++ pyomo/common/tests/test_config.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index 4207392389a..8ffb162ac41 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -555,6 +555,9 @@ def __call__(self, path): ) return ans + def domain_name(self): + return type(self).__name__ + class PathList(Path): """Domain validator for a list of path-like objects. diff --git a/pyomo/common/tests/test_config.py b/pyomo/common/tests/test_config.py index f3f5cbedad6..6c657e8d04b 100644 --- a/pyomo/common/tests/test_config.py +++ b/pyomo/common/tests/test_config.py @@ -526,6 +526,8 @@ def __str__(self): path_str = str(self.path) return f"{type(self).__name__}({path_str})" + self.assertEqual(Path().domain_name(), "Path") + cwd = os.getcwd() + os.path.sep c = ConfigDict() From 7df175f4d1fd29ce65d37a3d3bed0dabb563d458 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Sun, 18 Feb 2024 16:09:58 -0700 Subject: [PATCH 1031/1204] Fixing BigM to not assume nested indicator vars are local, editing its tests accordingly --- pyomo/gdp/plugins/bigm.py | 5 +- .../gdp/plugins/gdp_to_mip_transformation.py | 32 ++--- pyomo/gdp/tests/test_bigm.py | 117 ++++++++---------- 3 files changed, 75 insertions(+), 79 deletions(-) diff --git a/pyomo/gdp/plugins/bigm.py b/pyomo/gdp/plugins/bigm.py index bdd353a6136..1f9f561b192 100644 --- a/pyomo/gdp/plugins/bigm.py +++ b/pyomo/gdp/plugins/bigm.py @@ -224,11 +224,10 @@ def _transform_disjunctionData( or_expr += disjunct.binary_indicator_var self._transform_disjunct(disjunct, bigM, transBlock) - rhs = 1 if parent_disjunct is None else parent_disjunct.binary_indicator_var if obj.xor: - xorConstraint[index] = or_expr == rhs + xorConstraint[index] = or_expr == 1 else: - xorConstraint[index] = or_expr >= rhs + xorConstraint[index] = or_expr >= 1 # Mark the DisjunctionData as transformed by mapping it to its XOR # constraint. obj._algebraic_constraint = weakref_ref(xorConstraint[index]) diff --git a/pyomo/gdp/plugins/gdp_to_mip_transformation.py b/pyomo/gdp/plugins/gdp_to_mip_transformation.py index 96d97206c97..5603259a278 100644 --- a/pyomo/gdp/plugins/gdp_to_mip_transformation.py +++ b/pyomo/gdp/plugins/gdp_to_mip_transformation.py @@ -213,21 +213,25 @@ def _setup_transform_disjunctionData(self, obj, root_disjunct): "likely indicative of a modeling error." % obj.name ) - # Create or fetch the transformation block + # We always need to create or fetch a transformation block on the parent block. + trans_block, new_block = self._add_transformation_block( + obj.parent_block()) + # This is where we put exactly_one/or constraint + algebraic_constraint = self._add_xor_constraint(obj.parent_component(), + trans_block) + + # If requested, create or fetch the transformation block above the + # nested hierarchy if root_disjunct is not None: - # We want to put all the transformed things on the root - # Disjunct's parent's block so that they do not get - # re-transformed - transBlock, new_block = self._add_transformation_block( - root_disjunct.parent_block() - ) - else: - # This isn't nested--just put it on the parent block. - transBlock, new_block = self._add_transformation_block(obj.parent_block()) - - xorConstraint = self._add_xor_constraint(obj.parent_component(), transBlock) - - return transBlock, xorConstraint + # We want to put some transformed things on the root Disjunct's + # parent's block so that they do not get re-transformed. (Note this + # is never true for hull, but it calls this method with + # root_disjunct=None. BigM can't put the exactly-one constraint up + # here, but it can put everything else.) + trans_block, new_block = self._add_transformation_block( + root_disjunct.parent_block() ) + + return trans_block, algebraic_constraint def _get_disjunct_transformation_block(self, disjunct, transBlock): if disjunct.transformation_block is not None: diff --git a/pyomo/gdp/tests/test_bigm.py b/pyomo/gdp/tests/test_bigm.py index d518219eabd..f210f3cd660 100644 --- a/pyomo/gdp/tests/test_bigm.py +++ b/pyomo/gdp/tests/test_bigm.py @@ -33,6 +33,7 @@ assertExpressionsStructurallyEqual, ) from pyomo.repn import generate_standard_repn +from pyomo.repn.linear import LinearRepnVisitor from pyomo.common.log import LoggingIntercept import logging @@ -1764,22 +1765,19 @@ def test_transformation_block_structure(self): # we have the XOR constraints for both the outer and inner disjunctions self.assertIsInstance(transBlock.component("disjunction_xor"), Constraint) - def test_transformation_block_on_inner_disjunct_empty(self): - m = models.makeNestedDisjunctions() - TransformationFactory('gdp.bigm').apply_to(m) - self.assertIsNone(m.disjunct[1].component("_pyomo_gdp_bigm_reformulation")) - def test_mappings_between_disjunctions_and_xors(self): m = models.makeNestedDisjunctions() transform = TransformationFactory('gdp.bigm') transform.apply_to(m) transBlock1 = m.component("_pyomo_gdp_bigm_reformulation") + transBlock2 = m.disjunct[1].component("_pyomo_gdp_bigm_reformulation") + transBlock3 = m.simpledisjunct.component("_pyomo_gdp_bigm_reformulation") disjunctionPairs = [ (m.disjunction, transBlock1.disjunction_xor), - (m.disjunct[1].innerdisjunction[0], transBlock1.innerdisjunction_xor_4[0]), - (m.simpledisjunct.innerdisjunction, transBlock1.innerdisjunction_xor), + (m.disjunct[1].innerdisjunction[0], transBlock2.innerdisjunction_xor[0]), + (m.simpledisjunct.innerdisjunction, transBlock3.innerdisjunction_xor), ] # check disjunction mappings @@ -1900,18 +1898,39 @@ def check_bigM_constraint(self, cons, variable, M, indicator_var): ct.check_linear_coef(self, repn, indicator_var, M) def check_inner_xor_constraint( - self, inner_disjunction, outer_disjunct, inner_disjuncts + self, inner_disjunction, outer_disjunct, bigm ): - self.assertIsNotNone(inner_disjunction.algebraic_constraint) - cons = inner_disjunction.algebraic_constraint - self.assertEqual(cons.lower, 0) - self.assertEqual(cons.upper, 0) - repn = generate_standard_repn(cons.body) - self.assertTrue(repn.is_linear()) - self.assertEqual(repn.constant, 0) - for disj in inner_disjuncts: - ct.check_linear_coef(self, repn, disj.binary_indicator_var, 1) - ct.check_linear_coef(self, repn, outer_disjunct.binary_indicator_var, -1) + inner_xor = inner_disjunction.algebraic_constraint + sum_indicators = sum(d.binary_indicator_var for d in + inner_disjunction.disjuncts) + assertExpressionsEqual( + self, + inner_xor.expr, + sum_indicators == 1 + ) + # this guy has been transformed + self.assertFalse(inner_xor.active) + cons = bigm.get_transformed_constraints(inner_xor) + self.assertEqual(len(cons), 2) + lb = cons[0] + ct.check_obj_in_active_tree(self, lb) + lb_expr = self.simplify_cons(lb, leq=False) + assertExpressionsEqual( + self, + lb_expr, + 1.0 <= + sum_indicators + - outer_disjunct.binary_indicator_var + 1.0 + ) + ub = cons[1] + ct.check_obj_in_active_tree(self, ub) + ub_expr = self.simplify_cons(ub, leq=True) + assertExpressionsEqual( + self, + ub_expr, + sum_indicators + + outer_disjunct.binary_indicator_var - 1 <= 1.0 + ) def test_transformed_constraints(self): # We'll check all the transformed constraints to make sure @@ -1993,26 +2012,8 @@ def test_transformed_constraints(self): # Here we check that the xor constraint from # simpledisjunct.innerdisjunction is transformed. - cons5 = m.simpledisjunct.innerdisjunction.algebraic_constraint - self.assertIsNotNone(cons5) - self.check_inner_xor_constraint( - m.simpledisjunct.innerdisjunction, - m.simpledisjunct, - [m.simpledisjunct.innerdisjunct0, m.simpledisjunct.innerdisjunct1], - ) - self.assertIsInstance(cons5, Constraint) - self.assertEqual(cons5.lower, 0) - self.assertEqual(cons5.upper, 0) - repn = generate_standard_repn(cons5.body) - self.assertTrue(repn.is_linear()) - self.assertEqual(repn.constant, 0) - ct.check_linear_coef( - self, repn, m.simpledisjunct.innerdisjunct0.binary_indicator_var, 1 - ) - ct.check_linear_coef( - self, repn, m.simpledisjunct.innerdisjunct1.binary_indicator_var, 1 - ) - ct.check_linear_coef(self, repn, m.simpledisjunct.binary_indicator_var, -1) + self.check_inner_xor_constraint(m.simpledisjunct.innerdisjunction, + m.simpledisjunct, bigm) cons6 = bigm.get_transformed_constraints(m.disjunct[0].c) self.assertEqual(len(cons6), 2) @@ -2029,8 +2030,7 @@ def test_transformed_constraints(self): # is correct. self.check_inner_xor_constraint( m.disjunct[1].innerdisjunction[0], - m.disjunct[1], - [m.disjunct[1].innerdisjunct[0], m.disjunct[1].innerdisjunct[1]], + m.disjunct[1], bigm ) cons8 = bigm.get_transformed_constraints(m.disjunct[1].c) @@ -2136,34 +2136,27 @@ def check_second_disjunct_constraint(self, disj2c, x, ind_var): ct.check_squared_term_coef(self, repn, x[i], 1) ct.check_linear_coef(self, repn, x[i], -6) + def simplify_cons(self, cons, leq): + visitor = LinearRepnVisitor({}, {}, {}, None) + repn = visitor.walk_expression(cons.body) + self.assertIsNone(repn.nonlinear) + if leq: + self.assertIsNone(cons.lower) + ub = cons.upper + return ub >= repn.to_expression(visitor) + else: + self.assertIsNone(cons.upper) + lb = cons.lower + return lb <= repn.to_expression(visitor) + def check_hierarchical_nested_model(self, m, bigm): outer_xor = m.disjunction_block.disjunction.algebraic_constraint ct.check_two_term_disjunction_xor( self, outer_xor, m.disj1, m.disjunct_block.disj2 ) - inner_xor = m.disjunct_block.disj2.disjunction.algebraic_constraint - self.assertEqual(inner_xor.lower, 0) - self.assertEqual(inner_xor.upper, 0) - repn = generate_standard_repn(inner_xor.body) - self.assertTrue(repn.is_linear()) - self.assertEqual(len(repn.linear_vars), 3) - self.assertEqual(repn.constant, 0) - ct.check_linear_coef( - self, - repn, - m.disjunct_block.disj2.disjunction_disjuncts[0].binary_indicator_var, - 1, - ) - ct.check_linear_coef( - self, - repn, - m.disjunct_block.disj2.disjunction_disjuncts[1].binary_indicator_var, - 1, - ) - ct.check_linear_coef( - self, repn, m.disjunct_block.disj2.binary_indicator_var, -1 - ) + self.check_inner_xor_constraint(m.disjunct_block.disj2.disjunction, + m.disjunct_block.disj2, bigm) # outer disjunction constraints disj1c = bigm.get_transformed_constraints(m.disj1.c) From 1e8f359ad5cf8c4c14f2f7b3a49584ca9cbc0a56 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Sun, 18 Feb 2024 16:10:45 -0700 Subject: [PATCH 1032/1204] Black --- .../gdp/plugins/gdp_to_mip_transformation.py | 11 ++++--- pyomo/gdp/tests/test_bigm.py | 33 ++++++++----------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/pyomo/gdp/plugins/gdp_to_mip_transformation.py b/pyomo/gdp/plugins/gdp_to_mip_transformation.py index 5603259a278..59cb221321a 100644 --- a/pyomo/gdp/plugins/gdp_to_mip_transformation.py +++ b/pyomo/gdp/plugins/gdp_to_mip_transformation.py @@ -214,11 +214,11 @@ def _setup_transform_disjunctionData(self, obj, root_disjunct): ) # We always need to create or fetch a transformation block on the parent block. - trans_block, new_block = self._add_transformation_block( - obj.parent_block()) + trans_block, new_block = self._add_transformation_block(obj.parent_block()) # This is where we put exactly_one/or constraint - algebraic_constraint = self._add_xor_constraint(obj.parent_component(), - trans_block) + algebraic_constraint = self._add_xor_constraint( + obj.parent_component(), trans_block + ) # If requested, create or fetch the transformation block above the # nested hierarchy @@ -229,7 +229,8 @@ def _setup_transform_disjunctionData(self, obj, root_disjunct): # root_disjunct=None. BigM can't put the exactly-one constraint up # here, but it can put everything else.) trans_block, new_block = self._add_transformation_block( - root_disjunct.parent_block() ) + root_disjunct.parent_block() + ) return trans_block, algebraic_constraint diff --git a/pyomo/gdp/tests/test_bigm.py b/pyomo/gdp/tests/test_bigm.py index f210f3cd660..daec9a20c93 100644 --- a/pyomo/gdp/tests/test_bigm.py +++ b/pyomo/gdp/tests/test_bigm.py @@ -1897,17 +1897,12 @@ def check_bigM_constraint(self, cons, variable, M, indicator_var): ct.check_linear_coef(self, repn, variable, 1) ct.check_linear_coef(self, repn, indicator_var, M) - def check_inner_xor_constraint( - self, inner_disjunction, outer_disjunct, bigm - ): + def check_inner_xor_constraint(self, inner_disjunction, outer_disjunct, bigm): inner_xor = inner_disjunction.algebraic_constraint - sum_indicators = sum(d.binary_indicator_var for d in - inner_disjunction.disjuncts) - assertExpressionsEqual( - self, - inner_xor.expr, - sum_indicators == 1 + sum_indicators = sum( + d.binary_indicator_var for d in inner_disjunction.disjuncts ) + assertExpressionsEqual(self, inner_xor.expr, sum_indicators == 1) # this guy has been transformed self.assertFalse(inner_xor.active) cons = bigm.get_transformed_constraints(inner_xor) @@ -1918,9 +1913,7 @@ def check_inner_xor_constraint( assertExpressionsEqual( self, lb_expr, - 1.0 <= - sum_indicators - - outer_disjunct.binary_indicator_var + 1.0 + 1.0 <= sum_indicators - outer_disjunct.binary_indicator_var + 1.0, ) ub = cons[1] ct.check_obj_in_active_tree(self, ub) @@ -1928,8 +1921,7 @@ def check_inner_xor_constraint( assertExpressionsEqual( self, ub_expr, - sum_indicators - + outer_disjunct.binary_indicator_var - 1 <= 1.0 + sum_indicators + outer_disjunct.binary_indicator_var - 1 <= 1.0, ) def test_transformed_constraints(self): @@ -2012,8 +2004,9 @@ def test_transformed_constraints(self): # Here we check that the xor constraint from # simpledisjunct.innerdisjunction is transformed. - self.check_inner_xor_constraint(m.simpledisjunct.innerdisjunction, - m.simpledisjunct, bigm) + self.check_inner_xor_constraint( + m.simpledisjunct.innerdisjunction, m.simpledisjunct, bigm + ) cons6 = bigm.get_transformed_constraints(m.disjunct[0].c) self.assertEqual(len(cons6), 2) @@ -2029,8 +2022,7 @@ def test_transformed_constraints(self): # now we check that the xor constraint from disjunct[1].innerdisjunction # is correct. self.check_inner_xor_constraint( - m.disjunct[1].innerdisjunction[0], - m.disjunct[1], bigm + m.disjunct[1].innerdisjunction[0], m.disjunct[1], bigm ) cons8 = bigm.get_transformed_constraints(m.disjunct[1].c) @@ -2155,8 +2147,9 @@ def check_hierarchical_nested_model(self, m, bigm): self, outer_xor, m.disj1, m.disjunct_block.disj2 ) - self.check_inner_xor_constraint(m.disjunct_block.disj2.disjunction, - m.disjunct_block.disj2, bigm) + self.check_inner_xor_constraint( + m.disjunct_block.disj2.disjunction, m.disjunct_block.disj2, bigm + ) # outer disjunction constraints disj1c = bigm.get_transformed_constraints(m.disj1.c) From 263d873c195d9a469b79818cf7d1fcc422c6e492 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Sun, 18 Feb 2024 16:18:29 -0700 Subject: [PATCH 1033/1204] Fixing the algebraic constraint for mbigm to be correct for nested GDPs which is ironic because mbigm doesn't currently support nested GDPs. --- pyomo/gdp/plugins/multiple_bigm.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index a2e7d5beeec..6177de3c037 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -336,8 +336,7 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, root_disjunct) for disjunct in active_disjuncts: or_expr += disjunct.indicator_var.get_associated_binary() self._transform_disjunct(disjunct, transBlock, active_disjuncts, Ms) - rhs = 1 if parent_disjunct is None else parent_disjunct.binary_indicator_var - algebraic_constraint.add(index, (or_expr, rhs)) + algebraic_constraint.add(index, or_expr == 1) # map the DisjunctionData to its XOR constraint to mark it as # transformed obj._algebraic_constraint = weakref_ref(algebraic_constraint[index]) From 664f3026181ac51f01e5d65abff03fc89b424cf0 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Sun, 18 Feb 2024 16:21:24 -0700 Subject: [PATCH 1034/1204] Correcting binary multiplication transformation's handling of nested GDP --- pyomo/gdp/plugins/binary_multiplication.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyomo/gdp/plugins/binary_multiplication.py b/pyomo/gdp/plugins/binary_multiplication.py index ef4239e09dc..d68f7efe76f 100644 --- a/pyomo/gdp/plugins/binary_multiplication.py +++ b/pyomo/gdp/plugins/binary_multiplication.py @@ -92,11 +92,10 @@ def _transform_disjunctionData( or_expr += disjunct.binary_indicator_var self._transform_disjunct(disjunct, transBlock) - rhs = 1 if parent_disjunct is None else parent_disjunct.binary_indicator_var if obj.xor: - xorConstraint[index] = or_expr == rhs + xorConstraint[index] = or_expr == 1 else: - xorConstraint[index] = or_expr >= rhs + xorConstraint[index] = or_expr >= 1 # Mark the DisjunctionData as transformed by mapping it to its XOR # constraint. obj._algebraic_constraint = weakref_ref(xorConstraint[index]) From 019818456cea31153366f8b11f5e69465e226770 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 18 Feb 2024 18:37:14 -0500 Subject: [PATCH 1035/1204] Update documentation of `Path` --- pyomo/common/config.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index 8ffb162ac41..8f5ac513ee4 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -490,9 +490,14 @@ def __call__(self, module_id): class Path(object): - """Domain validator for path-like options. + """ + Domain validator for a + :py:term:`path-like object `. - This will admit any object and convert it to a string. It will then + This will admit a path-like object + and get the object's file system representation + through :py:obj:`os.fsdecode`. + It will then expand any environment variables and leading usernames (e.g., "~myuser" or "~/") appearing in either the value or the base path before concatenating the base path and value, expanding the path to From e4d9d796b45701982c4643b0e5e4ee064893f48a Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Sun, 18 Feb 2024 16:42:38 -0700 Subject: [PATCH 1036/1204] Adding an integration test for correct handling of nested with non-local indicator vars --- pyomo/gdp/tests/common_tests.py | 13 ++++++++ pyomo/gdp/tests/models.py | 32 +++++++++++++++++++ pyomo/gdp/tests/test_bigm.py | 4 +++ pyomo/gdp/tests/test_binary_multiplication.py | 11 +++++++ pyomo/gdp/tests/test_hull.py | 4 +++ 5 files changed, 64 insertions(+) diff --git a/pyomo/gdp/tests/common_tests.py b/pyomo/gdp/tests/common_tests.py index b76de61887f..585aafc967d 100644 --- a/pyomo/gdp/tests/common_tests.py +++ b/pyomo/gdp/tests/common_tests.py @@ -1944,3 +1944,16 @@ def check_nested_disjuncts_in_flat_gdp(self, transformation): for t in m.T: self.assertTrue(value(m.disj1[t].indicator_var)) self.assertTrue(value(m.disj1[t].sub1.indicator_var)) + +def check_do_not_assume_nested_indicators_local(self, transformation): + m = models.why_indicator_vars_are_not_always_local() + TransformationFactory(transformation).apply_to(m) + + results = SolverFactory('gurobi').solve(m) + self.assertEqual(results.solver.termination_condition, TerminationCondition.optimal) + self.assertAlmostEqual(value(m.obj), 9) + self.assertAlmostEqual(value(m.x), 9) + self.assertTrue(value(m.Y2.indicator_var)) + self.assertFalse(value(m.Y1.indicator_var)) + self.assertTrue(value(m.Z1.indicator_var)) + self.assertTrue(value(m.Z1.indicator_var)) diff --git a/pyomo/gdp/tests/models.py b/pyomo/gdp/tests/models.py index 94bf5d0e592..fc5e6327c7e 100644 --- a/pyomo/gdp/tests/models.py +++ b/pyomo/gdp/tests/models.py @@ -563,6 +563,38 @@ def makeNestedDisjunctions_NestedDisjuncts(): return m +def why_indicator_vars_are_not_always_local(): + m = ConcreteModel() + m.x = Var(bounds=(1, 10)) + @m.Disjunct() + def Z1(d): + m = d.model() + d.c = Constraint(expr=m.x >= 1.1) + @m.Disjunct() + def Z2(d): + m = d.model() + d.c = Constraint(expr=m.x >= 1.2) + @m.Disjunct() + def Y1(d): + m = d.model() + d.c = Constraint(expr=(1.15, m.x, 8)) + d.disjunction = Disjunction(expr=[m.Z1, m.Z2]) + @m.Disjunct() + def Y2(d): + m = d.model() + d.c = Constraint(expr=m.x==9) + m.disjunction = Disjunction(expr=[m.Y1, m.Y2]) + + m.logical_cons = LogicalConstraint(expr=m.Y2.indicator_var.implies( + m.Z1.indicator_var.land(m.Z2.indicator_var))) + + # optimal value is 9, but it will be 8 if we wrongly assume that the nested + # indicator_vars are local. + m.obj = Objective(expr=m.x, sense=maximize) + + return m + + def makeTwoSimpleDisjunctions(): """Two SimpleDisjunctions on the same model.""" m = ConcreteModel() diff --git a/pyomo/gdp/tests/test_bigm.py b/pyomo/gdp/tests/test_bigm.py index daec9a20c93..00efcb46485 100644 --- a/pyomo/gdp/tests/test_bigm.py +++ b/pyomo/gdp/tests/test_bigm.py @@ -2200,6 +2200,10 @@ def test_decl_order_opposite_instantiation_order(self): # the same check to make sure everything is transformed correctly. self.check_hierarchical_nested_model(m, bigm) + @unittest.skipUnless(gurobi_available, "Gurobi is not available") + def test_do_not_assume_nested_indicators_local(self): + ct.check_do_not_assume_nested_indicators_local(self, 'gdp.bigm') + class IndexedDisjunction(unittest.TestCase): # this tests that if the targets are a subset of the diff --git a/pyomo/gdp/tests/test_binary_multiplication.py b/pyomo/gdp/tests/test_binary_multiplication.py index 5f4c4f90ab6..fbe6f86fd46 100644 --- a/pyomo/gdp/tests/test_binary_multiplication.py +++ b/pyomo/gdp/tests/test_binary_multiplication.py @@ -18,6 +18,7 @@ ConcreteModel, Var, Any, + SolverFactory, ) from pyomo.gdp import Disjunct, Disjunction from pyomo.core.expr.compare import assertExpressionsEqual @@ -30,6 +31,11 @@ import random +gurobi_available = ( + SolverFactory('gurobi').available(exception_flag=False) + and SolverFactory('gurobi').license_is_valid() +) + class CommonTests: def diff_apply_to_and_create_using(self, model): @@ -297,5 +303,10 @@ def test_local_var(self): self.assertEqual(eq.ub, 0) +class TestNestedGDP(unittest.TestCase): + @unittest.skipUnless(gurobi_available, "Gurobi is not available") + def test_do_not_assume_nested_indicators_local(self): + ct.check_do_not_assume_nested_indicators_local(self, 'gdp.binary_multiplication') + if __name__ == '__main__': unittest.main() diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index 694178ee96f..858764759ee 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -2030,6 +2030,10 @@ def test_nested_with_var_that_skips_a_level(self): cons_expr = self.simplify_cons(cons) assertExpressionsEqual(self, cons_expr, m.y - y_y2 - y_y1 == 0.0) + @unittest.skipUnless(gurobi_available, "Gurobi is not available") + def test_do_not_assume_nested_indicators_local(self): + ct.check_do_not_assume_nested_indicators_local(self, 'gdp.hull') + class TestSpecialCases(unittest.TestCase): def test_local_vars(self): From d97f4ec7d2eeaa441643195aa6a665d1bc53b9da Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Sun, 18 Feb 2024 16:43:08 -0700 Subject: [PATCH 1037/1204] weighing in with black --- pyomo/gdp/tests/common_tests.py | 1 + pyomo/gdp/tests/models.py | 12 +++++++++--- pyomo/gdp/tests/test_binary_multiplication.py | 5 ++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/pyomo/gdp/tests/common_tests.py b/pyomo/gdp/tests/common_tests.py index 585aafc967d..28025816262 100644 --- a/pyomo/gdp/tests/common_tests.py +++ b/pyomo/gdp/tests/common_tests.py @@ -1945,6 +1945,7 @@ def check_nested_disjuncts_in_flat_gdp(self, transformation): self.assertTrue(value(m.disj1[t].indicator_var)) self.assertTrue(value(m.disj1[t].sub1.indicator_var)) + def check_do_not_assume_nested_indicators_local(self, transformation): m = models.why_indicator_vars_are_not_always_local() TransformationFactory(transformation).apply_to(m) diff --git a/pyomo/gdp/tests/models.py b/pyomo/gdp/tests/models.py index fc5e6327c7e..0b84641899c 100644 --- a/pyomo/gdp/tests/models.py +++ b/pyomo/gdp/tests/models.py @@ -566,27 +566,33 @@ def makeNestedDisjunctions_NestedDisjuncts(): def why_indicator_vars_are_not_always_local(): m = ConcreteModel() m.x = Var(bounds=(1, 10)) + @m.Disjunct() def Z1(d): m = d.model() d.c = Constraint(expr=m.x >= 1.1) + @m.Disjunct() def Z2(d): m = d.model() d.c = Constraint(expr=m.x >= 1.2) + @m.Disjunct() def Y1(d): m = d.model() d.c = Constraint(expr=(1.15, m.x, 8)) d.disjunction = Disjunction(expr=[m.Z1, m.Z2]) + @m.Disjunct() def Y2(d): m = d.model() - d.c = Constraint(expr=m.x==9) + d.c = Constraint(expr=m.x == 9) + m.disjunction = Disjunction(expr=[m.Y1, m.Y2]) - m.logical_cons = LogicalConstraint(expr=m.Y2.indicator_var.implies( - m.Z1.indicator_var.land(m.Z2.indicator_var))) + m.logical_cons = LogicalConstraint( + expr=m.Y2.indicator_var.implies(m.Z1.indicator_var.land(m.Z2.indicator_var)) + ) # optimal value is 9, but it will be 8 if we wrongly assume that the nested # indicator_vars are local. diff --git a/pyomo/gdp/tests/test_binary_multiplication.py b/pyomo/gdp/tests/test_binary_multiplication.py index fbe6f86fd46..aa846c4710a 100644 --- a/pyomo/gdp/tests/test_binary_multiplication.py +++ b/pyomo/gdp/tests/test_binary_multiplication.py @@ -306,7 +306,10 @@ def test_local_var(self): class TestNestedGDP(unittest.TestCase): @unittest.skipUnless(gurobi_available, "Gurobi is not available") def test_do_not_assume_nested_indicators_local(self): - ct.check_do_not_assume_nested_indicators_local(self, 'gdp.binary_multiplication') + ct.check_do_not_assume_nested_indicators_local( + self, 'gdp.binary_multiplication' + ) + if __name__ == '__main__': unittest.main() From 4236f63d2afa62bed822b2f49171e13c7bc04650 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 18 Feb 2024 19:17:01 -0500 Subject: [PATCH 1038/1204] Make `PathList` more consistent with `Path` --- pyomo/common/config.py | 27 ++++++++++++++++++--------- pyomo/common/tests/test_config.py | 9 +++++++++ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index 8f5ac513ee4..1da2e603c7b 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -565,12 +565,16 @@ def domain_name(self): class PathList(Path): - """Domain validator for a list of path-like objects. + """ + Domain validator for a list of + :py:term:`path-like objects `. - This will admit any iterable or object convertible to a string. - Iterable objects (other than strings) will have each member - normalized using :py:class:`Path`. Other types will be passed to - :py:class:`Path`, returning a list with the single resulting path. + This admits a path-like object or iterable of such. + If a path-like object is passed, then + a singleton list containing the object normalized through + :py:class:`Path` is returned. + An iterable of path-like objects is cast to a list, each + entry of which is normalized through :py:class:`Path`. Parameters ---------- @@ -587,10 +591,15 @@ class PathList(Path): """ def __call__(self, data): - if hasattr(data, "__iter__") and not isinstance(data, str): - return [super(PathList, self).__call__(i) for i in data] - else: - return [super(PathList, self).__call__(data)] + try: + pathlist = [super(PathList, self).__call__(data)] + except TypeError as err: + is_not_path_like = ("expected str, bytes or os.PathLike" in str(err)) + if is_not_path_like and hasattr(data, "__iter__"): + pathlist = [super(PathList, self).__call__(i) for i in data] + else: + raise + return pathlist class DynamicImplicitDomain(object): diff --git a/pyomo/common/tests/test_config.py b/pyomo/common/tests/test_config.py index 6c657e8d04b..912a9ab1d7c 100644 --- a/pyomo/common/tests/test_config.py +++ b/pyomo/common/tests/test_config.py @@ -748,6 +748,8 @@ def norm(x): cwd = os.getcwd() + os.path.sep c = ConfigDict() + self.assertEqual(PathList().domain_name(), "PathList") + c.declare('a', ConfigValue(None, PathList())) self.assertEqual(c.a, None) c.a = "/a/b/c" @@ -770,6 +772,13 @@ def norm(x): self.assertEqual(len(c.a), 0) self.assertIs(type(c.a), list) + exc_str = r".*expected str, bytes or os.PathLike.*int" + + with self.assertRaisesRegex(ValueError, exc_str): + c.a = 2 + with self.assertRaisesRegex(ValueError, exc_str): + c.a = ["/a/b/c", 2] + def test_ListOf(self): c = ConfigDict() c.declare('a', ConfigValue(domain=ListOf(int), default=None)) From a07d696447d92fa58085c391809cb6762c94a44f Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 18 Feb 2024 19:44:37 -0500 Subject: [PATCH 1039/1204] Apply black --- pyomo/common/config.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index 1da2e603c7b..f156bee79a9 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -363,9 +363,7 @@ def __call__(self, obj): def domain_name(self): class_names = (self._get_class_name(kls) for kls in self.baseClasses) - return ( - f"IsInstance({', '.join(class_names)})" - ) + return f"IsInstance({', '.join(class_names)})" class ListOf(object): @@ -594,7 +592,7 @@ def __call__(self, data): try: pathlist = [super(PathList, self).__call__(data)] except TypeError as err: - is_not_path_like = ("expected str, bytes or os.PathLike" in str(err)) + is_not_path_like = "expected str, bytes or os.PathLike" in str(err) if is_not_path_like and hasattr(data, "__iter__"): pathlist = [super(PathList, self).__call__(i) for i in data] else: From 9c1727bbb7e99a3018a9c8afa56384e1e7d27e80 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 08:07:19 -0700 Subject: [PATCH 1040/1204] Save state: config changes --- pyomo/common/config.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index a796c34340b..d9b495ff1bd 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -211,8 +211,6 @@ def Datetime(val): This domain will return the original object, assuming it is of the right type. """ - if val is None: - return val if not isinstance(val, datetime.datetime): raise ValueError(f"Expected datetime object, but received {type(val)}.") return val From fb6f97e80e507af5418bbacc00c603f48f898920 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 08:53:37 -0700 Subject: [PATCH 1041/1204] Address @jsiirola's comments --- pyomo/common/config.py | 2 +- pyomo/common/formatting.py | 4 +- pyomo/contrib/solver/base.py | 3 - pyomo/contrib/solver/config.py | 6 +- pyomo/contrib/solver/gurobi.py | 7 +- pyomo/contrib/solver/ipopt.py | 40 +++--- pyomo/contrib/solver/results.py | 1 - pyomo/contrib/solver/solution.py | 130 ++++-------------- .../contrib/solver/tests/unit/test_results.py | 99 +++++++++++-- pyomo/opt/plugins/sol.py | 1 - 10 files changed, 141 insertions(+), 152 deletions(-) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index 7ece0e6a48c..3e9b580c7bc 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -40,7 +40,6 @@ deprecation_warning, relocated_module_attribute, ) -from pyomo.common.errors import DeveloperError from pyomo.common.fileutils import import_file from pyomo.common.formatting import wrap_reStructuredText from pyomo.common.modeling import NOTSET @@ -767,6 +766,7 @@ def from_enum_or_string(cls, arg): NegativeFloat NonPositiveFloat NonNegativeFloat + Datetime In InEnum IsInstance diff --git a/pyomo/common/formatting.py b/pyomo/common/formatting.py index 6194f928844..430ec96ca09 100644 --- a/pyomo/common/formatting.py +++ b/pyomo/common/formatting.py @@ -257,8 +257,8 @@ def writelines(self, sequence): r'|(?:\[\s*[A-Za-z0-9\.]+\s*\] +)' # [PASS]|[FAIL]|[ OK ] ) _verbatim_line_start = re.compile( - r'(\| )' - r'|(\+((-{3,})|(={3,}))\+)' # line blocks # grid table + r'(\| )' # line blocks + r'|(\+((-{3,})|(={3,}))\+)' # grid table ) _verbatim_line = re.compile( r'(={3,}[ =]+)' # simple tables, ======== sections diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index a60e770e660..cb13809c438 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -180,9 +180,6 @@ class PersistentSolverBase(SolverBase): CONFIG = PersistentSolverConfig() - def __init__(self, **kwds): - super().__init__(**kwds) - @document_kwargs_from_configdict(CONFIG) @abc.abstractmethod def solve(self, model: _BlockData, **kwargs) -> Results: diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index d13e1caf81d..21f6e233d78 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -94,7 +94,11 @@ def __init__( ), ) self.timer: HierarchicalTimer = self.declare( - 'timer', ConfigValue(default=None, description="A HierarchicalTimer.") + 'timer', + ConfigValue( + default=None, + description="A timer object for recording relevant process timing data.", + ), ) self.threads: Optional[int] = self.declare( 'threads', diff --git a/pyomo/contrib/solver/gurobi.py b/pyomo/contrib/solver/gurobi.py index c1b02c08ef9..2b4986edaf8 100644 --- a/pyomo/contrib/solver/gurobi.py +++ b/pyomo/contrib/solver/gurobi.py @@ -283,11 +283,8 @@ def _check_license(self): if avail: if self._available is None: - res = Gurobi._check_full_license() - self._available = res - return res - else: - return self._available + self._available = Gurobi._check_full_license() + return self._available else: return self.Availability.BadLicense diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index edea4e693b4..0d0d89f837a 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -34,7 +34,7 @@ from pyomo.contrib.solver.factory import SolverFactory from pyomo.contrib.solver.results import Results, TerminationCondition, SolutionStatus from pyomo.contrib.solver.sol_reader import parse_sol_file -from pyomo.contrib.solver.solution import SolSolutionLoader, SolutionLoader +from pyomo.contrib.solver.solution import SolSolutionLoader from pyomo.common.tee import TeeStream from pyomo.common.log import LogStream from pyomo.core.expr.visitor import replace_expressions @@ -103,14 +103,14 @@ def __init__( implicit_domain=implicit_domain, visibility=visibility, ) - self.timing_info.no_function_solve_time: Optional[float] = ( + self.timing_info.ipopt_excluding_nlp_functions: Optional[float] = ( self.timing_info.declare( - 'no_function_solve_time', ConfigValue(domain=NonNegativeFloat) + 'ipopt_excluding_nlp_functions', ConfigValue(domain=NonNegativeFloat) ) ) - self.timing_info.function_solve_time: Optional[float] = ( + self.timing_info.nlp_function_evaluations: Optional[float] = ( self.timing_info.declare( - 'function_solve_time', ConfigValue(domain=NonNegativeFloat) + 'nlp_function_evaluations', ConfigValue(domain=NonNegativeFloat) ) ) @@ -225,10 +225,11 @@ def __init__(self, **kwds): self._writer = NLWriter() self._available_cache = None self._version_cache = None + self.executable = self.config.executable def available(self): if self._available_cache is None: - if self.config.executable.path() is None: + if self.executable.path() is None: self._available_cache = self.Availability.NotFound else: self._available_cache = self.Availability.FullLicense @@ -237,7 +238,7 @@ def available(self): def version(self): if self._version_cache is None: results = subprocess.run( - [str(self.config.executable), '--version'], + [str(self.executable), '--version'], timeout=1, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -266,7 +267,7 @@ def _write_options_file(self, filename: str, options: Mapping): return opt_file_exists def _create_command_line(self, basename: str, config: ipoptConfig, opt_file: bool): - cmd = [str(config.executable), basename + '.nl', '-AMPL'] + cmd = [str(self.executable), basename + '.nl', '-AMPL'] if opt_file: cmd.append('option_file_name=' + basename + '.opt') if 'option_file_name' in config.solver_options: @@ -296,6 +297,7 @@ def solve(self, model, **kwds): ) # Update configuration options, based on keywords passed to solve config: ipoptConfig = self.config(value=kwds, preserve_implicit=True) + self.executable = config.executable if config.threads: logger.log( logging.WARNING, @@ -306,7 +308,6 @@ def solve(self, model, **kwds): else: timer = config.timer StaleFlagManager.mark_all_as_stale() - results = ipoptResults() with TempfileManager.new_context() as tempfile: if config.working_dir is None: dname = tempfile.mkdtemp() @@ -379,16 +380,18 @@ def solve(self, model, **kwds): ) if process.returncode != 0: + results = ipoptResults() + results.extra_info.return_code = process.returncode results.termination_condition = TerminationCondition.error - results.solution_loader = SolutionLoader(None, None, None) + results.solution_loader = SolSolutionLoader(None, None) else: with open(basename + '.sol', 'r') as sol_file: timer.start('parse_sol') - results = self._parse_solution(sol_file, nl_info, results) + results = self._parse_solution(sol_file, nl_info) timer.stop('parse_sol') results.iteration_count = iters - results.timing_info.no_function_solve_time = ipopt_time_nofunc - results.timing_info.function_solve_time = ipopt_time_func + results.timing_info.ipopt_excluding_nlp_functions = ipopt_time_nofunc + results.timing_info.nlp_function_evaluations = ipopt_time_func if ( config.raise_exception_on_nonoptimal_result and results.solution_status != SolutionStatus.optimal @@ -397,7 +400,7 @@ def solve(self, model, **kwds): 'Solver did not find the optimal solution. Set opt.config.raise_exception_on_nonoptimal_result = False to bypass this error.' ) - results.solver_name = 'ipopt' + results.solver_name = self.name results.solver_version = self.version() if ( config.load_solutions @@ -484,15 +487,14 @@ def _parse_ipopt_output(self, stream: io.StringIO): return iters, nofunc_time, func_time - def _parse_solution( - self, instream: io.TextIOBase, nl_info: NLWriterInfo, result: ipoptResults - ): + def _parse_solution(self, instream: io.TextIOBase, nl_info: NLWriterInfo): + results = ipoptResults() res, sol_data = parse_sol_file( - sol_file=instream, nl_info=nl_info, result=result + sol_file=instream, nl_info=nl_info, result=results ) if res.solution_status == SolutionStatus.noSolution: - res.solution_loader = SolutionLoader(None, None, None) + res.solution_loader = SolSolutionLoader(None, None) else: res.solution_loader = ipoptSolutionLoader( sol_data=sol_data, nl_info=nl_info diff --git a/pyomo/contrib/solver/results.py b/pyomo/contrib/solver/results.py index b330773e4f3..f2c9cde64fe 100644 --- a/pyomo/contrib/solver/results.py +++ b/pyomo/contrib/solver/results.py @@ -27,7 +27,6 @@ TerminationCondition as LegacyTerminationCondition, SolverStatus as LegacySolverStatus, ) -from pyomo.common.timing import HierarchicalTimer class TerminationCondition(enum.Enum): diff --git a/pyomo/contrib/solver/solution.py b/pyomo/contrib/solver/solution.py index 31792a76dfe..1812e21a596 100644 --- a/pyomo/contrib/solver/solution.py +++ b/pyomo/contrib/solver/solution.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ import abc -from typing import Sequence, Dict, Optional, Mapping, MutableMapping, NoReturn +from typing import Sequence, Dict, Optional, Mapping, NoReturn from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.var import _GeneralVarData @@ -18,7 +18,6 @@ from pyomo.core.staleflag import StaleFlagManager from pyomo.contrib.solver.sol_reader import SolFileData from pyomo.repn.plugins.nl_writer import NLWriterInfo -from pyomo.core.expr.numvalue import value from pyomo.core.expr.visitor import replace_expressions @@ -106,78 +105,33 @@ def get_reduced_costs( ) -# TODO: This is for development uses only; not to be released to the wild -# May turn into documentation someday -class SolutionLoader(SolutionLoaderBase): - def __init__( - self, - primals: Optional[MutableMapping], - duals: Optional[MutableMapping], - reduced_costs: Optional[MutableMapping], - ): - """ - Parameters - ---------- - primals: dict - maps id(Var) to (var, value) - duals: dict - maps Constraint to dual value - reduced_costs: dict - maps id(Var) to (var, reduced_cost) - """ - self._primals = primals - self._duals = duals - self._reduced_costs = reduced_costs +class PersistentSolutionLoader(SolutionLoaderBase): + def __init__(self, solver): + self._solver = solver + self._valid = True - def get_primals( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: - if self._primals is None: - raise RuntimeError( - 'Solution loader does not currently have a valid solution. Please ' - 'check the termination condition.' - ) - if vars_to_load is None: - return ComponentMap(self._primals.values()) - else: - primals = ComponentMap() - for v in vars_to_load: - primals[v] = self._primals[id(v)][1] - return primals + def _assert_solution_still_valid(self): + if not self._valid: + raise RuntimeError('The results in the solver are no longer valid.') + + def get_primals(self, vars_to_load=None): + self._assert_solution_still_valid() + return self._solver._get_primals(vars_to_load=vars_to_load) def get_duals( self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None ) -> Dict[_GeneralConstraintData, float]: - if self._duals is None: - raise RuntimeError( - 'Solution loader does not currently have valid duals. Please ' - 'check the termination condition and ensure the solver returns duals ' - 'for the given problem type.' - ) - if cons_to_load is None: - duals = dict(self._duals) - else: - duals = {} - for c in cons_to_load: - duals[c] = self._duals[c] - return duals + self._assert_solution_still_valid() + return self._solver._get_duals(cons_to_load=cons_to_load) def get_reduced_costs( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None ) -> Mapping[_GeneralVarData, float]: - if self._reduced_costs is None: - raise RuntimeError( - 'Solution loader does not currently have valid reduced costs. Please ' - 'check the termination condition and ensure the solver returns reduced ' - 'costs for the given problem type.' - ) - if vars_to_load is None: - rc = ComponentMap(self._reduced_costs.values()) - else: - rc = ComponentMap() - for v in vars_to_load: - rc[v] = self._reduced_costs[id(v)][1] - return rc + self._assert_solution_still_valid() + return self._solver._get_reduced_costs(vars_to_load=vars_to_load) + + def invalidate(self): + self._valid = False class SolSolutionLoader(SolutionLoaderBase): @@ -188,17 +142,14 @@ def __init__(self, sol_data: SolFileData, nl_info: NLWriterInfo) -> None: def load_vars( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None ) -> NoReturn: - if self._nl_info.scaling is None: - scale_list = [1] * len(self._nl_info.variables) + if self._nl_info.scaling: + for v, val, scale in zip( + self._nl_info.variables, self._sol_data.primals, self._nl_info.scaling + ): + v.set_value(val / scale, skip_validation=True) else: - scale_list = self._nl_info.scaling.variables - for v, val, scale in zip( - self._nl_info.variables, self._sol_data.primals, scale_list - ): - v.set_value(val / scale, skip_validation=True) - - for v, v_expr in self._nl_info.eliminated_vars: - v.set_value(value(v_expr), skip_validation=True) + for v, val in zip(self._nl_info.variables, self._sol_data.primals): + v.set_value(val, skip_validation=True) StaleFlagManager.mark_all_as_stale(delayed=True) @@ -248,32 +199,3 @@ def get_duals( if c in cons_to_load: res[c] = val * scale return res - - -class PersistentSolutionLoader(SolutionLoaderBase): - def __init__(self, solver): - self._solver = solver - self._valid = True - - def _assert_solution_still_valid(self): - if not self._valid: - raise RuntimeError('The results in the solver are no longer valid.') - - def get_primals(self, vars_to_load=None): - self._assert_solution_still_valid() - return self._solver._get_primals(vars_to_load=vars_to_load) - - def get_duals( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: - self._assert_solution_still_valid() - return self._solver._get_duals(cons_to_load=cons_to_load) - - def get_reduced_costs( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: - self._assert_solution_still_valid() - return self._solver._get_reduced_costs(vars_to_load=vars_to_load) - - def invalidate(self): - self._valid = False diff --git a/pyomo/contrib/solver/tests/unit/test_results.py b/pyomo/contrib/solver/tests/unit/test_results.py index 2d8f6460448..4856b737295 100644 --- a/pyomo/contrib/solver/tests/unit/test_results.py +++ b/pyomo/contrib/solver/tests/unit/test_results.py @@ -10,15 +10,97 @@ # ___________________________________________________________________________ from io import StringIO +from typing import Sequence, Dict, Optional, Mapping, MutableMapping + from pyomo.common import unittest from pyomo.common.config import ConfigDict +from pyomo.core.base.constraint import _GeneralConstraintData +from pyomo.core.base.var import _GeneralVarData +from pyomo.common.collections import ComponentMap from pyomo.contrib.solver import results from pyomo.contrib.solver import solution import pyomo.environ as pyo from pyomo.core.base.var import ScalarVar +class SolutionLoaderExample(solution.SolutionLoaderBase): + """ + This is an example instantiation of a SolutionLoader that is used for + testing generated results. + """ + + def __init__( + self, + primals: Optional[MutableMapping], + duals: Optional[MutableMapping], + reduced_costs: Optional[MutableMapping], + ): + """ + Parameters + ---------- + primals: dict + maps id(Var) to (var, value) + duals: dict + maps Constraint to dual value + reduced_costs: dict + maps id(Var) to (var, reduced_cost) + """ + self._primals = primals + self._duals = duals + self._reduced_costs = reduced_costs + + def get_primals( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> Mapping[_GeneralVarData, float]: + if self._primals is None: + raise RuntimeError( + 'Solution loader does not currently have a valid solution. Please ' + 'check the termination condition.' + ) + if vars_to_load is None: + return ComponentMap(self._primals.values()) + else: + primals = ComponentMap() + for v in vars_to_load: + primals[v] = self._primals[id(v)][1] + return primals + + def get_duals( + self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None + ) -> Dict[_GeneralConstraintData, float]: + if self._duals is None: + raise RuntimeError( + 'Solution loader does not currently have valid duals. Please ' + 'check the termination condition and ensure the solver returns duals ' + 'for the given problem type.' + ) + if cons_to_load is None: + duals = dict(self._duals) + else: + duals = {} + for c in cons_to_load: + duals[c] = self._duals[c] + return duals + + def get_reduced_costs( + self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + ) -> Mapping[_GeneralVarData, float]: + if self._reduced_costs is None: + raise RuntimeError( + 'Solution loader does not currently have valid reduced costs. Please ' + 'check the termination condition and ensure the solver returns reduced ' + 'costs for the given problem type.' + ) + if vars_to_load is None: + rc = ComponentMap(self._reduced_costs.values()) + else: + rc = ComponentMap() + for v in vars_to_load: + rc[v] = self._reduced_costs[id(v)][1] + return rc + + class TestTerminationCondition(unittest.TestCase): def test_member_list(self): member_list = results.TerminationCondition._member_names_ @@ -92,6 +174,7 @@ def test_member_list(self): def test_default_initialization(self): res = results.Results() + self.assertIsNone(res.solution_loader) self.assertIsNone(res.incumbent_objective) self.assertIsNone(res.objective_bound) self.assertEqual( @@ -105,20 +188,6 @@ def test_default_initialization(self): self.assertIsInstance(res.extra_info, ConfigDict) self.assertIsNone(res.timing_info.start_timestamp) self.assertIsNone(res.timing_info.wall_time) - res.solution_loader = solution.SolutionLoader(None, None, None) - - with self.assertRaisesRegex( - RuntimeError, '.*does not currently have a valid solution.*' - ): - res.solution_loader.load_vars() - with self.assertRaisesRegex( - RuntimeError, '.*does not currently have valid duals.*' - ): - res.solution_loader.get_duals() - with self.assertRaisesRegex( - RuntimeError, '.*does not currently have valid reduced costs.*' - ): - res.solution_loader.get_reduced_costs() def test_display(self): res = results.Results() @@ -160,7 +229,7 @@ def test_generated_results(self): rc[id(m.y)] = (m.y, 6) res = results.Results() - res.solution_loader = solution.SolutionLoader( + res.solution_loader = SolutionLoaderExample( primals=primals, duals=duals, reduced_costs=rc ) diff --git a/pyomo/opt/plugins/sol.py b/pyomo/opt/plugins/sol.py index a6088cb25af..10da469f186 100644 --- a/pyomo/opt/plugins/sol.py +++ b/pyomo/opt/plugins/sol.py @@ -189,7 +189,6 @@ def _load(self, fin, res, soln, suffixes): if line == "": continue line = line.split() - # Some sort of garbage we tag onto the solver message, assuming we are past the suffixes if line[0] != 'suffix': # We assume this is the start of a # section like kestrel_option, which From 544aa459ed9cebc890dd92bfa1601ada19aadb4d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 08:54:22 -0700 Subject: [PATCH 1042/1204] Missed one file --- pyomo/contrib/solver/util.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pyomo/contrib/solver/util.py b/pyomo/contrib/solver/util.py index d104022692e..ca499748adf 100644 --- a/pyomo/contrib/solver/util.py +++ b/pyomo/contrib/solver/util.py @@ -153,18 +153,6 @@ def collect_vars_and_named_exprs(expr): ) -class SolverUtils: - pass - - -class SubprocessSolverUtils: - pass - - -class DirectSolverUtils: - pass - - class PersistentSolverUtils(abc.ABC): def __init__(self): self._model = None From 9bf1970ffc2d19c7978a7a5312deb6a68c6f03cc Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 09:09:53 -0700 Subject: [PATCH 1043/1204] Remove because it's not implemented and we have a different idea --- pyomo/contrib/solver/config.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index 21f6e233d78..9b8de245bd6 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -50,14 +50,6 @@ def __init__( description="If True, the solver log prints to stdout.", ), ) - self.log_solver_output: bool = self.declare( - 'log_solver_output', - ConfigValue( - domain=bool, - default=False, - description="If True, the solver output gets logged.", - ), - ) self.working_dir: str = self.declare( 'working_dir', ConfigValue( From 95cd64ecbbbb51363c92a6e2cdac5a0835c2ce90 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 09:14:46 -0700 Subject: [PATCH 1044/1204] Remove from __init__ --- pyomo/contrib/solver/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pyomo/contrib/solver/__init__.py b/pyomo/contrib/solver/__init__.py index 2dc73091ea2..a4a626013c4 100644 --- a/pyomo/contrib/solver/__init__.py +++ b/pyomo/contrib/solver/__init__.py @@ -8,9 +8,3 @@ # rights in this software. # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ - -from . import base -from . import config -from . import results -from . import solution -from . import util From c17d2b9da8639641e5ba910014c4a8a653fcd505 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 09:37:05 -0700 Subject: [PATCH 1045/1204] Reverting: log_solver_output does do something --- pyomo/contrib/solver/config.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index 9b8de245bd6..21f6e233d78 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -50,6 +50,14 @@ def __init__( description="If True, the solver log prints to stdout.", ), ) + self.log_solver_output: bool = self.declare( + 'log_solver_output', + ConfigValue( + domain=bool, + default=False, + description="If True, the solver output gets logged.", + ), + ) self.working_dir: str = self.declare( 'working_dir', ConfigValue( From 10e08f922463f915c5566d973dba8d9440335fe9 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 10:00:45 -0700 Subject: [PATCH 1046/1204] Update domain validator for bools --- pyomo/contrib/solver/config.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index 21f6e233d78..1e9f0b1fbe9 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -17,6 +17,7 @@ NonNegativeFloat, NonNegativeInt, ADVANCED_OPTION, + Bool, ) from pyomo.common.timing import HierarchicalTimer @@ -45,7 +46,7 @@ def __init__( self.tee: bool = self.declare( 'tee', ConfigValue( - domain=bool, + domain=Bool, default=False, description="If True, the solver log prints to stdout.", ), @@ -53,7 +54,7 @@ def __init__( self.log_solver_output: bool = self.declare( 'log_solver_output', ConfigValue( - domain=bool, + domain=Bool, default=False, description="If True, the solver output gets logged.", ), @@ -70,7 +71,7 @@ def __init__( self.load_solutions: bool = self.declare( 'load_solutions', ConfigValue( - domain=bool, + domain=Bool, default=True, description="If True, the values of the primal variables will be loaded into the model.", ), @@ -78,7 +79,7 @@ def __init__( self.raise_exception_on_nonoptimal_result: bool = self.declare( 'raise_exception_on_nonoptimal_result', ConfigValue( - domain=bool, + domain=Bool, default=True, description="If False, the `solve` method will continue processing " "even if the returned result is nonoptimal.", @@ -87,7 +88,7 @@ def __init__( self.symbolic_solver_labels: bool = self.declare( 'symbolic_solver_labels', ConfigValue( - domain=bool, + domain=Bool, default=False, description="If True, the names given to the solver will reflect the names of the Pyomo components. " "Cannot be changed after set_instance is called.", From ca0280c5b50aa09fb0083c753f3b2972ab5c9c50 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 19 Feb 2024 10:03:50 -0700 Subject: [PATCH 1047/1204] update type hints in configs --- pyomo/contrib/solver/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index 1e9f0b1fbe9..ca9557d0002 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -59,7 +59,7 @@ def __init__( description="If True, the solver output gets logged.", ), ) - self.working_dir: str = self.declare( + self.working_dir: Optional[str] = self.declare( 'working_dir', ConfigValue( domain=str, @@ -94,7 +94,7 @@ def __init__( "Cannot be changed after set_instance is called.", ), ) - self.timer: HierarchicalTimer = self.declare( + self.timer: Optional[HierarchicalTimer] = self.declare( 'timer', ConfigValue( default=None, From df339688e125abed7996c68df71cc9fcc19117f6 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 19 Feb 2024 10:30:19 -0700 Subject: [PATCH 1048/1204] properly copy the nl writer config --- pyomo/contrib/solver/ipopt.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 0d0d89f837a..6fb369d4785 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -80,10 +80,7 @@ def __init__( ) self.writer_config: ConfigDict = self.declare( 'writer_config', - ConfigValue( - default=NLWriter.CONFIG(), - description="For the manipulation of NL writer options.", - ), + NLWriter.CONFIG(), ) From 086d53cd197168665cf6f63fcfc57bcd3b97ea14 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 11:07:55 -0700 Subject: [PATCH 1049/1204] Apply black; change to ccapital I --- pyomo/contrib/solver/ipopt.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 6fb369d4785..1aa00c0c8e2 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -53,7 +53,7 @@ class ipoptSolverError(PyomoException): """ -class ipoptConfig(SolverConfig): +class IpoptConfig(SolverConfig): def __init__( self, description=None, @@ -79,12 +79,11 @@ def __init__( ), ) self.writer_config: ConfigDict = self.declare( - 'writer_config', - NLWriter.CONFIG(), + 'writer_config', NLWriter.CONFIG() ) -class ipoptResults(Results): +class IpoptResults(Results): def __init__( self, description=None, @@ -112,7 +111,7 @@ def __init__( ) -class ipoptSolutionLoader(SolSolutionLoader): +class IpoptSolutionLoader(SolSolutionLoader): def get_reduced_costs( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None ) -> Mapping[_GeneralVarData, float]: @@ -214,8 +213,8 @@ def get_reduced_costs( @SolverFactory.register('ipopt_v2', doc='The ipopt NLP solver (new interface)') -class ipopt(SolverBase): - CONFIG = ipoptConfig() +class Ipopt(SolverBase): + CONFIG = IpoptConfig() def __init__(self, **kwds): super().__init__(**kwds) @@ -263,7 +262,7 @@ def _write_options_file(self, filename: str, options: Mapping): opt_file.write(str(k) + ' ' + str(val) + '\n') return opt_file_exists - def _create_command_line(self, basename: str, config: ipoptConfig, opt_file: bool): + def _create_command_line(self, basename: str, config: IpoptConfig, opt_file: bool): cmd = [str(self.executable), basename + '.nl', '-AMPL'] if opt_file: cmd.append('option_file_name=' + basename + '.opt') @@ -293,7 +292,7 @@ def solve(self, model, **kwds): f'Solver {self.__class__} is not available ({avail}).' ) # Update configuration options, based on keywords passed to solve - config: ipoptConfig = self.config(value=kwds, preserve_implicit=True) + config: IpoptConfig = self.config(value=kwds, preserve_implicit=True) self.executable = config.executable if config.threads: logger.log( @@ -377,7 +376,7 @@ def solve(self, model, **kwds): ) if process.returncode != 0: - results = ipoptResults() + results = IpoptResults() results.extra_info.return_code = process.returncode results.termination_condition = TerminationCondition.error results.solution_loader = SolSolutionLoader(None, None) @@ -485,7 +484,7 @@ def _parse_ipopt_output(self, stream: io.StringIO): return iters, nofunc_time, func_time def _parse_solution(self, instream: io.TextIOBase, nl_info: NLWriterInfo): - results = ipoptResults() + results = IpoptResults() res, sol_data = parse_sol_file( sol_file=instream, nl_info=nl_info, result=results ) @@ -493,7 +492,7 @@ def _parse_solution(self, instream: io.TextIOBase, nl_info: NLWriterInfo): if res.solution_status == SolutionStatus.noSolution: res.solution_loader = SolSolutionLoader(None, None) else: - res.solution_loader = ipoptSolutionLoader( + res.solution_loader = IpoptSolutionLoader( sol_data=sol_data, nl_info=nl_info ) From 69fd8d03a5b2349c060a0b6d7a861d045681b4ec Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 11:23:16 -0700 Subject: [PATCH 1050/1204] Move PersistentUtils into own file --- pyomo/contrib/solver/persistent.py | 523 +++++++++++++++++++++++++++++ pyomo/contrib/solver/util.py | 512 +--------------------------- 2 files changed, 524 insertions(+), 511 deletions(-) create mode 100644 pyomo/contrib/solver/persistent.py diff --git a/pyomo/contrib/solver/persistent.py b/pyomo/contrib/solver/persistent.py new file mode 100644 index 00000000000..0994aa53093 --- /dev/null +++ b/pyomo/contrib/solver/persistent.py @@ -0,0 +1,523 @@ +# ___________________________________________________________________________ +# +# 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. +# __________________________________________________________________________ + +import abc +from typing import List + +from pyomo.core.base.constraint import _GeneralConstraintData, Constraint +from pyomo.core.base.sos import _SOSConstraintData, SOSConstraint +from pyomo.core.base.var import _GeneralVarData +from pyomo.core.base.param import _ParamData, Param +from pyomo.core.base.objective import _GeneralObjectiveData +from pyomo.common.collections import ComponentMap +from pyomo.common.timing import HierarchicalTimer +from pyomo.core.expr.numvalue import NumericConstant +from pyomo.contrib.solver.util import collect_vars_and_named_exprs, get_objective + + +class PersistentSolverUtils(abc.ABC): + def __init__(self): + self._model = None + self._active_constraints = {} # maps constraint to (lower, body, upper) + self._vars = {} # maps var id to (var, lb, ub, fixed, domain, value) + self._params = {} # maps param id to param + self._objective = None + self._objective_expr = None + self._objective_sense = None + self._named_expressions = ( + {} + ) # maps constraint to list of tuples (named_expr, named_expr.expr) + self._external_functions = ComponentMap() + self._obj_named_expressions = [] + self._referenced_variables = ( + {} + ) # var_id: [dict[constraints, None], dict[sos constraints, None], None or objective] + self._vars_referenced_by_con = {} + self._vars_referenced_by_obj = [] + self._expr_types = None + + def set_instance(self, model): + saved_config = self.config + self.__init__() + self.config = saved_config + self._model = model + self.add_block(model) + if self._objective is None: + self.set_objective(None) + + @abc.abstractmethod + def _add_variables(self, variables: List[_GeneralVarData]): + pass + + def add_variables(self, variables: List[_GeneralVarData]): + for v in variables: + if id(v) in self._referenced_variables: + raise ValueError( + 'variable {name} has already been added'.format(name=v.name) + ) + self._referenced_variables[id(v)] = [{}, {}, None] + self._vars[id(v)] = ( + v, + v._lb, + v._ub, + v.fixed, + v.domain.get_interval(), + v.value, + ) + self._add_variables(variables) + + @abc.abstractmethod + def _add_params(self, params: List[_ParamData]): + pass + + def add_params(self, params: List[_ParamData]): + for p in params: + self._params[id(p)] = p + self._add_params(params) + + @abc.abstractmethod + def _add_constraints(self, cons: List[_GeneralConstraintData]): + pass + + def _check_for_new_vars(self, variables: List[_GeneralVarData]): + new_vars = {} + for v in variables: + v_id = id(v) + if v_id not in self._referenced_variables: + new_vars[v_id] = v + self.add_variables(list(new_vars.values())) + + def _check_to_remove_vars(self, variables: List[_GeneralVarData]): + vars_to_remove = {} + for v in variables: + v_id = id(v) + ref_cons, ref_sos, ref_obj = self._referenced_variables[v_id] + if len(ref_cons) == 0 and len(ref_sos) == 0 and ref_obj is None: + vars_to_remove[v_id] = v + self.remove_variables(list(vars_to_remove.values())) + + def add_constraints(self, cons: List[_GeneralConstraintData]): + all_fixed_vars = {} + for con in cons: + if con in self._named_expressions: + raise ValueError( + 'constraint {name} has already been added'.format(name=con.name) + ) + self._active_constraints[con] = (con.lower, con.body, con.upper) + tmp = collect_vars_and_named_exprs(con.body) + named_exprs, variables, fixed_vars, external_functions = tmp + self._check_for_new_vars(variables) + self._named_expressions[con] = [(e, e.expr) for e in named_exprs] + if len(external_functions) > 0: + self._external_functions[con] = external_functions + self._vars_referenced_by_con[con] = variables + for v in variables: + self._referenced_variables[id(v)][0][con] = None + if not self.config.auto_updates.treat_fixed_vars_as_params: + for v in fixed_vars: + v.unfix() + all_fixed_vars[id(v)] = v + self._add_constraints(cons) + for v in all_fixed_vars.values(): + v.fix() + + @abc.abstractmethod + def _add_sos_constraints(self, cons: List[_SOSConstraintData]): + pass + + def add_sos_constraints(self, cons: List[_SOSConstraintData]): + for con in cons: + if con in self._vars_referenced_by_con: + raise ValueError( + 'constraint {name} has already been added'.format(name=con.name) + ) + self._active_constraints[con] = tuple() + variables = con.get_variables() + self._check_for_new_vars(variables) + self._named_expressions[con] = [] + self._vars_referenced_by_con[con] = variables + for v in variables: + self._referenced_variables[id(v)][1][con] = None + self._add_sos_constraints(cons) + + @abc.abstractmethod + def _set_objective(self, obj: _GeneralObjectiveData): + pass + + def set_objective(self, obj: _GeneralObjectiveData): + if self._objective is not None: + for v in self._vars_referenced_by_obj: + self._referenced_variables[id(v)][2] = None + self._check_to_remove_vars(self._vars_referenced_by_obj) + self._external_functions.pop(self._objective, None) + if obj is not None: + self._objective = obj + self._objective_expr = obj.expr + self._objective_sense = obj.sense + tmp = collect_vars_and_named_exprs(obj.expr) + named_exprs, variables, fixed_vars, external_functions = tmp + self._check_for_new_vars(variables) + self._obj_named_expressions = [(i, i.expr) for i in named_exprs] + if len(external_functions) > 0: + self._external_functions[obj] = external_functions + self._vars_referenced_by_obj = variables + for v in variables: + self._referenced_variables[id(v)][2] = obj + if not self.config.auto_updates.treat_fixed_vars_as_params: + for v in fixed_vars: + v.unfix() + self._set_objective(obj) + for v in fixed_vars: + v.fix() + else: + self._vars_referenced_by_obj = [] + self._objective = None + self._objective_expr = None + self._objective_sense = None + self._obj_named_expressions = [] + self._set_objective(obj) + + def add_block(self, block): + param_dict = {} + for p in block.component_objects(Param, descend_into=True): + if p.mutable: + for _p in p.values(): + param_dict[id(_p)] = _p + self.add_params(list(param_dict.values())) + self.add_constraints( + list( + block.component_data_objects(Constraint, descend_into=True, active=True) + ) + ) + self.add_sos_constraints( + list( + block.component_data_objects( + SOSConstraint, descend_into=True, active=True + ) + ) + ) + obj = get_objective(block) + if obj is not None: + self.set_objective(obj) + + @abc.abstractmethod + def _remove_constraints(self, cons: List[_GeneralConstraintData]): + pass + + def remove_constraints(self, cons: List[_GeneralConstraintData]): + self._remove_constraints(cons) + for con in cons: + if con not in self._named_expressions: + raise ValueError( + 'cannot remove constraint {name} - it was not added'.format( + name=con.name + ) + ) + for v in self._vars_referenced_by_con[con]: + self._referenced_variables[id(v)][0].pop(con) + self._check_to_remove_vars(self._vars_referenced_by_con[con]) + del self._active_constraints[con] + del self._named_expressions[con] + self._external_functions.pop(con, None) + del self._vars_referenced_by_con[con] + + @abc.abstractmethod + def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): + pass + + def remove_sos_constraints(self, cons: List[_SOSConstraintData]): + self._remove_sos_constraints(cons) + for con in cons: + if con not in self._vars_referenced_by_con: + raise ValueError( + 'cannot remove constraint {name} - it was not added'.format( + name=con.name + ) + ) + for v in self._vars_referenced_by_con[con]: + self._referenced_variables[id(v)][1].pop(con) + self._check_to_remove_vars(self._vars_referenced_by_con[con]) + del self._active_constraints[con] + del self._named_expressions[con] + del self._vars_referenced_by_con[con] + + @abc.abstractmethod + def _remove_variables(self, variables: List[_GeneralVarData]): + pass + + def remove_variables(self, variables: List[_GeneralVarData]): + self._remove_variables(variables) + for v in variables: + v_id = id(v) + if v_id not in self._referenced_variables: + raise ValueError( + 'cannot remove variable {name} - it has not been added'.format( + name=v.name + ) + ) + cons_using, sos_using, obj_using = self._referenced_variables[v_id] + if cons_using or sos_using or (obj_using is not None): + raise ValueError( + 'cannot remove variable {name} - it is still being used by constraints or the objective'.format( + name=v.name + ) + ) + del self._referenced_variables[v_id] + del self._vars[v_id] + + @abc.abstractmethod + def _remove_params(self, params: List[_ParamData]): + pass + + def remove_params(self, params: List[_ParamData]): + self._remove_params(params) + for p in params: + del self._params[id(p)] + + def remove_block(self, block): + self.remove_constraints( + list( + block.component_data_objects( + ctype=Constraint, descend_into=True, active=True + ) + ) + ) + self.remove_sos_constraints( + list( + block.component_data_objects( + ctype=SOSConstraint, descend_into=True, active=True + ) + ) + ) + self.remove_params( + list( + dict( + (id(p), p) + for p in block.component_data_objects( + ctype=Param, descend_into=True + ) + ).values() + ) + ) + + @abc.abstractmethod + def _update_variables(self, variables: List[_GeneralVarData]): + pass + + def update_variables(self, variables: List[_GeneralVarData]): + for v in variables: + self._vars[id(v)] = ( + v, + v._lb, + v._ub, + v.fixed, + v.domain.get_interval(), + v.value, + ) + self._update_variables(variables) + + @abc.abstractmethod + def update_params(self): + pass + + def update(self, timer: HierarchicalTimer = None): + if timer is None: + timer = HierarchicalTimer() + config = self.config.auto_updates + new_vars = [] + old_vars = [] + new_params = [] + old_params = [] + new_cons = [] + old_cons = [] + old_sos = [] + new_sos = [] + current_vars_dict = {} + current_cons_dict = {} + current_sos_dict = {} + timer.start('vars') + if config.update_vars: + start_vars = {v_id: v_tuple[0] for v_id, v_tuple in self._vars.items()} + timer.stop('vars') + timer.start('params') + if config.check_for_new_or_removed_params: + current_params_dict = {} + for p in self._model.component_objects(Param, descend_into=True): + if p.mutable: + for _p in p.values(): + current_params_dict[id(_p)] = _p + for p_id, p in current_params_dict.items(): + if p_id not in self._params: + new_params.append(p) + for p_id, p in self._params.items(): + if p_id not in current_params_dict: + old_params.append(p) + timer.stop('params') + timer.start('cons') + if config.check_for_new_or_removed_constraints or config.update_constraints: + current_cons_dict = { + c: None + for c in self._model.component_data_objects( + Constraint, descend_into=True, active=True + ) + } + current_sos_dict = { + c: None + for c in self._model.component_data_objects( + SOSConstraint, descend_into=True, active=True + ) + } + for c in current_cons_dict.keys(): + if c not in self._vars_referenced_by_con: + new_cons.append(c) + for c in current_sos_dict.keys(): + if c not in self._vars_referenced_by_con: + new_sos.append(c) + for c in self._vars_referenced_by_con.keys(): + if c not in current_cons_dict and c not in current_sos_dict: + if (c.ctype is Constraint) or ( + c.ctype is None and isinstance(c, _GeneralConstraintData) + ): + old_cons.append(c) + else: + assert (c.ctype is SOSConstraint) or ( + c.ctype is None and isinstance(c, _SOSConstraintData) + ) + old_sos.append(c) + self.remove_constraints(old_cons) + self.remove_sos_constraints(old_sos) + timer.stop('cons') + timer.start('params') + self.remove_params(old_params) + + # sticking this between removal and addition + # is important so that we don't do unnecessary work + if config.update_params: + self.update_params() + + self.add_params(new_params) + timer.stop('params') + timer.start('vars') + self.add_variables(new_vars) + timer.stop('vars') + timer.start('cons') + self.add_constraints(new_cons) + self.add_sos_constraints(new_sos) + new_cons_set = set(new_cons) + new_sos_set = set(new_sos) + new_vars_set = set(id(v) for v in new_vars) + cons_to_remove_and_add = {} + need_to_set_objective = False + if config.update_constraints: + cons_to_update = [] + sos_to_update = [] + for c in current_cons_dict.keys(): + if c not in new_cons_set: + cons_to_update.append(c) + for c in current_sos_dict.keys(): + if c not in new_sos_set: + sos_to_update.append(c) + for c in cons_to_update: + lower, body, upper = self._active_constraints[c] + new_lower, new_body, new_upper = c.lower, c.body, c.upper + if new_body is not body: + cons_to_remove_and_add[c] = None + continue + if new_lower is not lower: + if ( + type(new_lower) is NumericConstant + and type(lower) is NumericConstant + and new_lower.value == lower.value + ): + pass + else: + cons_to_remove_and_add[c] = None + continue + if new_upper is not upper: + if ( + type(new_upper) is NumericConstant + and type(upper) is NumericConstant + and new_upper.value == upper.value + ): + pass + else: + cons_to_remove_and_add[c] = None + continue + self.remove_sos_constraints(sos_to_update) + self.add_sos_constraints(sos_to_update) + timer.stop('cons') + timer.start('vars') + if config.update_vars: + end_vars = {v_id: v_tuple[0] for v_id, v_tuple in self._vars.items()} + vars_to_check = [v for v_id, v in end_vars.items() if v_id in start_vars] + if config.update_vars: + vars_to_update = [] + for v in vars_to_check: + _v, lb, ub, fixed, domain_interval, value = self._vars[id(v)] + if (fixed != v.fixed) or (fixed and (value != v.value)): + vars_to_update.append(v) + if self.config.auto_updates.treat_fixed_vars_as_params: + for c in self._referenced_variables[id(v)][0]: + cons_to_remove_and_add[c] = None + if self._referenced_variables[id(v)][2] is not None: + need_to_set_objective = True + elif lb is not v._lb: + vars_to_update.append(v) + elif ub is not v._ub: + vars_to_update.append(v) + elif domain_interval != v.domain.get_interval(): + vars_to_update.append(v) + self.update_variables(vars_to_update) + timer.stop('vars') + timer.start('cons') + cons_to_remove_and_add = list(cons_to_remove_and_add.keys()) + self.remove_constraints(cons_to_remove_and_add) + self.add_constraints(cons_to_remove_and_add) + timer.stop('cons') + timer.start('named expressions') + if config.update_named_expressions: + cons_to_update = [] + for c, expr_list in self._named_expressions.items(): + if c in new_cons_set: + continue + for named_expr, old_expr in expr_list: + if named_expr.expr is not old_expr: + cons_to_update.append(c) + break + self.remove_constraints(cons_to_update) + self.add_constraints(cons_to_update) + for named_expr, old_expr in self._obj_named_expressions: + if named_expr.expr is not old_expr: + need_to_set_objective = True + break + timer.stop('named expressions') + timer.start('objective') + if self.config.auto_updates.check_for_new_objective: + pyomo_obj = get_objective(self._model) + if pyomo_obj is not self._objective: + need_to_set_objective = True + else: + pyomo_obj = self._objective + if self.config.auto_updates.update_objective: + if pyomo_obj is not None and pyomo_obj.expr is not self._objective_expr: + need_to_set_objective = True + elif pyomo_obj is not None and pyomo_obj.sense is not self._objective_sense: + # we can definitely do something faster here than resetting the whole objective + need_to_set_objective = True + if need_to_set_objective: + self.set_objective(pyomo_obj) + timer.stop('objective') + + # this has to be done after the objective and constraints in case the + # old objective/constraints use old variables + timer.start('vars') + self.remove_variables(old_vars) + timer.stop('vars') diff --git a/pyomo/contrib/solver/util.py b/pyomo/contrib/solver/util.py index ca499748adf..c6bbfbd22ad 100644 --- a/pyomo/contrib/solver/util.py +++ b/pyomo/contrib/solver/util.py @@ -9,19 +9,9 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -import abc -from typing import List - from pyomo.core.expr.visitor import ExpressionValueVisitor, nonpyomo_leaf_types import pyomo.core.expr as EXPR -from pyomo.core.base.constraint import _GeneralConstraintData, Constraint -from pyomo.core.base.sos import _SOSConstraintData, SOSConstraint -from pyomo.core.base.var import _GeneralVarData -from pyomo.core.base.param import _ParamData, Param -from pyomo.core.base.objective import Objective, _GeneralObjectiveData -from pyomo.common.collections import ComponentMap -from pyomo.common.timing import HierarchicalTimer -from pyomo.core.expr.numvalue import NumericConstant +from pyomo.core.base.objective import Objective from pyomo.opt.results.solver import ( SolverStatus, TerminationCondition as LegacyTerminationCondition, @@ -151,503 +141,3 @@ def collect_vars_and_named_exprs(expr): list(_visitor.fixed_vars.values()), list(_visitor._external_functions.values()), ) - - -class PersistentSolverUtils(abc.ABC): - def __init__(self): - self._model = None - self._active_constraints = {} # maps constraint to (lower, body, upper) - self._vars = {} # maps var id to (var, lb, ub, fixed, domain, value) - self._params = {} # maps param id to param - self._objective = None - self._objective_expr = None - self._objective_sense = None - self._named_expressions = ( - {} - ) # maps constraint to list of tuples (named_expr, named_expr.expr) - self._external_functions = ComponentMap() - self._obj_named_expressions = [] - self._referenced_variables = ( - {} - ) # var_id: [dict[constraints, None], dict[sos constraints, None], None or objective] - self._vars_referenced_by_con = {} - self._vars_referenced_by_obj = [] - self._expr_types = None - - def set_instance(self, model): - saved_config = self.config - self.__init__() - self.config = saved_config - self._model = model - self.add_block(model) - if self._objective is None: - self.set_objective(None) - - @abc.abstractmethod - def _add_variables(self, variables: List[_GeneralVarData]): - pass - - def add_variables(self, variables: List[_GeneralVarData]): - for v in variables: - if id(v) in self._referenced_variables: - raise ValueError( - 'variable {name} has already been added'.format(name=v.name) - ) - self._referenced_variables[id(v)] = [{}, {}, None] - self._vars[id(v)] = ( - v, - v._lb, - v._ub, - v.fixed, - v.domain.get_interval(), - v.value, - ) - self._add_variables(variables) - - @abc.abstractmethod - def _add_params(self, params: List[_ParamData]): - pass - - def add_params(self, params: List[_ParamData]): - for p in params: - self._params[id(p)] = p - self._add_params(params) - - @abc.abstractmethod - def _add_constraints(self, cons: List[_GeneralConstraintData]): - pass - - def _check_for_new_vars(self, variables: List[_GeneralVarData]): - new_vars = {} - for v in variables: - v_id = id(v) - if v_id not in self._referenced_variables: - new_vars[v_id] = v - self.add_variables(list(new_vars.values())) - - def _check_to_remove_vars(self, variables: List[_GeneralVarData]): - vars_to_remove = {} - for v in variables: - v_id = id(v) - ref_cons, ref_sos, ref_obj = self._referenced_variables[v_id] - if len(ref_cons) == 0 and len(ref_sos) == 0 and ref_obj is None: - vars_to_remove[v_id] = v - self.remove_variables(list(vars_to_remove.values())) - - def add_constraints(self, cons: List[_GeneralConstraintData]): - all_fixed_vars = {} - for con in cons: - if con in self._named_expressions: - raise ValueError( - 'constraint {name} has already been added'.format(name=con.name) - ) - self._active_constraints[con] = (con.lower, con.body, con.upper) - tmp = collect_vars_and_named_exprs(con.body) - named_exprs, variables, fixed_vars, external_functions = tmp - self._check_for_new_vars(variables) - self._named_expressions[con] = [(e, e.expr) for e in named_exprs] - if len(external_functions) > 0: - self._external_functions[con] = external_functions - self._vars_referenced_by_con[con] = variables - for v in variables: - self._referenced_variables[id(v)][0][con] = None - if not self.config.auto_updates.treat_fixed_vars_as_params: - for v in fixed_vars: - v.unfix() - all_fixed_vars[id(v)] = v - self._add_constraints(cons) - for v in all_fixed_vars.values(): - v.fix() - - @abc.abstractmethod - def _add_sos_constraints(self, cons: List[_SOSConstraintData]): - pass - - def add_sos_constraints(self, cons: List[_SOSConstraintData]): - for con in cons: - if con in self._vars_referenced_by_con: - raise ValueError( - 'constraint {name} has already been added'.format(name=con.name) - ) - self._active_constraints[con] = tuple() - variables = con.get_variables() - self._check_for_new_vars(variables) - self._named_expressions[con] = [] - self._vars_referenced_by_con[con] = variables - for v in variables: - self._referenced_variables[id(v)][1][con] = None - self._add_sos_constraints(cons) - - @abc.abstractmethod - def _set_objective(self, obj: _GeneralObjectiveData): - pass - - def set_objective(self, obj: _GeneralObjectiveData): - if self._objective is not None: - for v in self._vars_referenced_by_obj: - self._referenced_variables[id(v)][2] = None - self._check_to_remove_vars(self._vars_referenced_by_obj) - self._external_functions.pop(self._objective, None) - if obj is not None: - self._objective = obj - self._objective_expr = obj.expr - self._objective_sense = obj.sense - tmp = collect_vars_and_named_exprs(obj.expr) - named_exprs, variables, fixed_vars, external_functions = tmp - self._check_for_new_vars(variables) - self._obj_named_expressions = [(i, i.expr) for i in named_exprs] - if len(external_functions) > 0: - self._external_functions[obj] = external_functions - self._vars_referenced_by_obj = variables - for v in variables: - self._referenced_variables[id(v)][2] = obj - if not self.config.auto_updates.treat_fixed_vars_as_params: - for v in fixed_vars: - v.unfix() - self._set_objective(obj) - for v in fixed_vars: - v.fix() - else: - self._vars_referenced_by_obj = [] - self._objective = None - self._objective_expr = None - self._objective_sense = None - self._obj_named_expressions = [] - self._set_objective(obj) - - def add_block(self, block): - param_dict = {} - for p in block.component_objects(Param, descend_into=True): - if p.mutable: - for _p in p.values(): - param_dict[id(_p)] = _p - self.add_params(list(param_dict.values())) - self.add_constraints( - list( - block.component_data_objects(Constraint, descend_into=True, active=True) - ) - ) - self.add_sos_constraints( - list( - block.component_data_objects( - SOSConstraint, descend_into=True, active=True - ) - ) - ) - obj = get_objective(block) - if obj is not None: - self.set_objective(obj) - - @abc.abstractmethod - def _remove_constraints(self, cons: List[_GeneralConstraintData]): - pass - - def remove_constraints(self, cons: List[_GeneralConstraintData]): - self._remove_constraints(cons) - for con in cons: - if con not in self._named_expressions: - raise ValueError( - 'cannot remove constraint {name} - it was not added'.format( - name=con.name - ) - ) - for v in self._vars_referenced_by_con[con]: - self._referenced_variables[id(v)][0].pop(con) - self._check_to_remove_vars(self._vars_referenced_by_con[con]) - del self._active_constraints[con] - del self._named_expressions[con] - self._external_functions.pop(con, None) - del self._vars_referenced_by_con[con] - - @abc.abstractmethod - def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): - pass - - def remove_sos_constraints(self, cons: List[_SOSConstraintData]): - self._remove_sos_constraints(cons) - for con in cons: - if con not in self._vars_referenced_by_con: - raise ValueError( - 'cannot remove constraint {name} - it was not added'.format( - name=con.name - ) - ) - for v in self._vars_referenced_by_con[con]: - self._referenced_variables[id(v)][1].pop(con) - self._check_to_remove_vars(self._vars_referenced_by_con[con]) - del self._active_constraints[con] - del self._named_expressions[con] - del self._vars_referenced_by_con[con] - - @abc.abstractmethod - def _remove_variables(self, variables: List[_GeneralVarData]): - pass - - def remove_variables(self, variables: List[_GeneralVarData]): - self._remove_variables(variables) - for v in variables: - v_id = id(v) - if v_id not in self._referenced_variables: - raise ValueError( - 'cannot remove variable {name} - it has not been added'.format( - name=v.name - ) - ) - cons_using, sos_using, obj_using = self._referenced_variables[v_id] - if cons_using or sos_using or (obj_using is not None): - raise ValueError( - 'cannot remove variable {name} - it is still being used by constraints or the objective'.format( - name=v.name - ) - ) - del self._referenced_variables[v_id] - del self._vars[v_id] - - @abc.abstractmethod - def _remove_params(self, params: List[_ParamData]): - pass - - def remove_params(self, params: List[_ParamData]): - self._remove_params(params) - for p in params: - del self._params[id(p)] - - def remove_block(self, block): - self.remove_constraints( - list( - block.component_data_objects( - ctype=Constraint, descend_into=True, active=True - ) - ) - ) - self.remove_sos_constraints( - list( - block.component_data_objects( - ctype=SOSConstraint, descend_into=True, active=True - ) - ) - ) - self.remove_params( - list( - dict( - (id(p), p) - for p in block.component_data_objects( - ctype=Param, descend_into=True - ) - ).values() - ) - ) - - @abc.abstractmethod - def _update_variables(self, variables: List[_GeneralVarData]): - pass - - def update_variables(self, variables: List[_GeneralVarData]): - for v in variables: - self._vars[id(v)] = ( - v, - v._lb, - v._ub, - v.fixed, - v.domain.get_interval(), - v.value, - ) - self._update_variables(variables) - - @abc.abstractmethod - def update_params(self): - pass - - def update(self, timer: HierarchicalTimer = None): - if timer is None: - timer = HierarchicalTimer() - config = self.config.auto_updates - new_vars = [] - old_vars = [] - new_params = [] - old_params = [] - new_cons = [] - old_cons = [] - old_sos = [] - new_sos = [] - current_vars_dict = {} - current_cons_dict = {} - current_sos_dict = {} - timer.start('vars') - if config.update_vars: - start_vars = {v_id: v_tuple[0] for v_id, v_tuple in self._vars.items()} - timer.stop('vars') - timer.start('params') - if config.check_for_new_or_removed_params: - current_params_dict = {} - for p in self._model.component_objects(Param, descend_into=True): - if p.mutable: - for _p in p.values(): - current_params_dict[id(_p)] = _p - for p_id, p in current_params_dict.items(): - if p_id not in self._params: - new_params.append(p) - for p_id, p in self._params.items(): - if p_id not in current_params_dict: - old_params.append(p) - timer.stop('params') - timer.start('cons') - if config.check_for_new_or_removed_constraints or config.update_constraints: - current_cons_dict = { - c: None - for c in self._model.component_data_objects( - Constraint, descend_into=True, active=True - ) - } - current_sos_dict = { - c: None - for c in self._model.component_data_objects( - SOSConstraint, descend_into=True, active=True - ) - } - for c in current_cons_dict.keys(): - if c not in self._vars_referenced_by_con: - new_cons.append(c) - for c in current_sos_dict.keys(): - if c not in self._vars_referenced_by_con: - new_sos.append(c) - for c in self._vars_referenced_by_con.keys(): - if c not in current_cons_dict and c not in current_sos_dict: - if (c.ctype is Constraint) or ( - c.ctype is None and isinstance(c, _GeneralConstraintData) - ): - old_cons.append(c) - else: - assert (c.ctype is SOSConstraint) or ( - c.ctype is None and isinstance(c, _SOSConstraintData) - ) - old_sos.append(c) - self.remove_constraints(old_cons) - self.remove_sos_constraints(old_sos) - timer.stop('cons') - timer.start('params') - self.remove_params(old_params) - - # sticking this between removal and addition - # is important so that we don't do unnecessary work - if config.update_params: - self.update_params() - - self.add_params(new_params) - timer.stop('params') - timer.start('vars') - self.add_variables(new_vars) - timer.stop('vars') - timer.start('cons') - self.add_constraints(new_cons) - self.add_sos_constraints(new_sos) - new_cons_set = set(new_cons) - new_sos_set = set(new_sos) - new_vars_set = set(id(v) for v in new_vars) - cons_to_remove_and_add = {} - need_to_set_objective = False - if config.update_constraints: - cons_to_update = [] - sos_to_update = [] - for c in current_cons_dict.keys(): - if c not in new_cons_set: - cons_to_update.append(c) - for c in current_sos_dict.keys(): - if c not in new_sos_set: - sos_to_update.append(c) - for c in cons_to_update: - lower, body, upper = self._active_constraints[c] - new_lower, new_body, new_upper = c.lower, c.body, c.upper - if new_body is not body: - cons_to_remove_and_add[c] = None - continue - if new_lower is not lower: - if ( - type(new_lower) is NumericConstant - and type(lower) is NumericConstant - and new_lower.value == lower.value - ): - pass - else: - cons_to_remove_and_add[c] = None - continue - if new_upper is not upper: - if ( - type(new_upper) is NumericConstant - and type(upper) is NumericConstant - and new_upper.value == upper.value - ): - pass - else: - cons_to_remove_and_add[c] = None - continue - self.remove_sos_constraints(sos_to_update) - self.add_sos_constraints(sos_to_update) - timer.stop('cons') - timer.start('vars') - if config.update_vars: - end_vars = {v_id: v_tuple[0] for v_id, v_tuple in self._vars.items()} - vars_to_check = [v for v_id, v in end_vars.items() if v_id in start_vars] - if config.update_vars: - vars_to_update = [] - for v in vars_to_check: - _v, lb, ub, fixed, domain_interval, value = self._vars[id(v)] - if (fixed != v.fixed) or (fixed and (value != v.value)): - vars_to_update.append(v) - if self.config.auto_updates.treat_fixed_vars_as_params: - for c in self._referenced_variables[id(v)][0]: - cons_to_remove_and_add[c] = None - if self._referenced_variables[id(v)][2] is not None: - need_to_set_objective = True - elif lb is not v._lb: - vars_to_update.append(v) - elif ub is not v._ub: - vars_to_update.append(v) - elif domain_interval != v.domain.get_interval(): - vars_to_update.append(v) - self.update_variables(vars_to_update) - timer.stop('vars') - timer.start('cons') - cons_to_remove_and_add = list(cons_to_remove_and_add.keys()) - self.remove_constraints(cons_to_remove_and_add) - self.add_constraints(cons_to_remove_and_add) - timer.stop('cons') - timer.start('named expressions') - if config.update_named_expressions: - cons_to_update = [] - for c, expr_list in self._named_expressions.items(): - if c in new_cons_set: - continue - for named_expr, old_expr in expr_list: - if named_expr.expr is not old_expr: - cons_to_update.append(c) - break - self.remove_constraints(cons_to_update) - self.add_constraints(cons_to_update) - for named_expr, old_expr in self._obj_named_expressions: - if named_expr.expr is not old_expr: - need_to_set_objective = True - break - timer.stop('named expressions') - timer.start('objective') - if self.config.auto_updates.check_for_new_objective: - pyomo_obj = get_objective(self._model) - if pyomo_obj is not self._objective: - need_to_set_objective = True - else: - pyomo_obj = self._objective - if self.config.auto_updates.update_objective: - if pyomo_obj is not None and pyomo_obj.expr is not self._objective_expr: - need_to_set_objective = True - elif pyomo_obj is not None and pyomo_obj.sense is not self._objective_sense: - # we can definitely do something faster here than resetting the whole objective - need_to_set_objective = True - if need_to_set_objective: - self.set_objective(pyomo_obj) - timer.stop('objective') - - # this has to be done after the objective and constraints in case the - # old objective/constraints use old variables - timer.start('vars') - self.remove_variables(old_vars) - timer.stop('vars') From 92c0262090702505ea0b35437713760fb4f78f57 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 11:23:59 -0700 Subject: [PATCH 1051/1204] Change import statement --- pyomo/contrib/solver/gurobi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/solver/gurobi.py b/pyomo/contrib/solver/gurobi.py index 66e03a2a0f4..85131ba73bd 100644 --- a/pyomo/contrib/solver/gurobi.py +++ b/pyomo/contrib/solver/gurobi.py @@ -33,7 +33,7 @@ from pyomo.contrib.solver.base import PersistentSolverBase from pyomo.contrib.solver.results import Results, TerminationCondition, SolutionStatus from pyomo.contrib.solver.config import PersistentBranchAndBoundConfig -from pyomo.contrib.solver.util import PersistentSolverUtils +from pyomo.contrib.solver.persistent import PersistentSolverUtils from pyomo.contrib.solver.solution import PersistentSolutionLoader from pyomo.core.staleflag import StaleFlagManager import sys From 3447f0792f767590e806c5bb9929984c6ecb063f Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 11:54:12 -0700 Subject: [PATCH 1052/1204] Update ipopt imports; change available and version --- .../developer_reference/solvers.rst | 12 ++--- pyomo/contrib/solver/ipopt.py | 45 ++++++++++--------- pyomo/contrib/solver/plugins.py | 4 +- .../solver/tests/solvers/test_ipopt.py | 4 +- .../solver/tests/solvers/test_solvers.py | 16 +++---- 5 files changed, 43 insertions(+), 38 deletions(-) diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index 8c8c9e5b8ee..921e452004d 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -63,7 +63,7 @@ or changed ``SolverFactory`` version. # Direct import import pyomo.environ as pyo from pyomo.contrib.solver.util import assert_optimal_termination - from pyomo.contrib.solver.ipopt import ipopt + from pyomo.contrib.solver.ipopt import Ipopt model = pyo.ConcreteModel() model.x = pyo.Var(initialize=1.5) @@ -74,7 +74,7 @@ or changed ``SolverFactory`` version. model.obj = pyo.Objective(rule=rosenbrock, sense=pyo.minimize) - opt = ipopt() + opt = Ipopt() status = opt.solve(model) assert_optimal_termination(status) # Displays important results information; only available in future capability mode @@ -112,7 +112,7 @@ The new interface will allow for direct manipulation of linear presolve and scal options for certain solvers. Currently, these options are only available for ``ipopt``. -.. autoclass:: pyomo.contrib.solver.ipopt.ipopt +.. autoclass:: pyomo.contrib.solver.ipopt.Ipopt :members: solve The ``writer_config`` configuration option can be used to manipulate presolve @@ -120,8 +120,8 @@ and scaling options: .. code-block:: python - >>> from pyomo.contrib.solver.ipopt import ipopt - >>> opt = ipopt() + >>> from pyomo.contrib.solver.ipopt import Ipopt + >>> opt = Ipopt() >>> opt.config.writer_config.display() show_section_timing: false @@ -213,7 +213,7 @@ Solutions can be loaded back into a model using a ``SolutionLoader``. A specific loader should be written for each unique case. Several have already been implemented. For example, for ``ipopt``: -.. autoclass:: pyomo.contrib.solver.ipopt.ipoptSolutionLoader +.. autoclass:: pyomo.contrib.solver.ipopt.IpoptSolutionLoader :show-inheritance: :members: :inherited-members: diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 1aa00c0c8e2..3b1e95da42c 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -221,20 +221,26 @@ def __init__(self, **kwds): self._writer = NLWriter() self._available_cache = None self._version_cache = None - self.executable = self.config.executable - - def available(self): - if self._available_cache is None: - if self.executable.path() is None: - self._available_cache = self.Availability.NotFound + self._executable = self.config.executable + + def available(self, config=None): + if config is None: + config = self.config + pth = config.executable.path() + if self._available_cache is None or self._available_cache[0] != pth: + if pth is None: + self._available_cache = (None, self.Availability.NotFound) else: - self._available_cache = self.Availability.FullLicense - return self._available_cache - - def version(self): - if self._version_cache is None: + self._available_cache = (pth, self.Availability.FullLicense) + return self._available_cache[1] + + def version(self, config=None): + if config is None: + config = self.config + pth = config.executable.path() + if self._version_cache is None or self._version_cache[0] != pth: results = subprocess.run( - [str(self.executable), '--version'], + [str(pth), '--version'], timeout=1, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -243,8 +249,8 @@ def version(self): version = results.stdout.splitlines()[0] version = version.split(' ')[1].strip() version = tuple(int(i) for i in version.split('.')) - self._version_cache = version - return self._version_cache + self._version_cache = (pth, version) + return self._version_cache[1] def _write_options_file(self, filename: str, options: Mapping): # First we need to determine if we even need to create a file. @@ -263,7 +269,7 @@ def _write_options_file(self, filename: str, options: Mapping): return opt_file_exists def _create_command_line(self, basename: str, config: IpoptConfig, opt_file: bool): - cmd = [str(self.executable), basename + '.nl', '-AMPL'] + cmd = [str(self._executable), basename + '.nl', '-AMPL'] if opt_file: cmd.append('option_file_name=' + basename + '.opt') if 'option_file_name' in config.solver_options: @@ -285,15 +291,14 @@ def _create_command_line(self, basename: str, config: IpoptConfig, opt_file: boo def solve(self, model, **kwds): # Begin time tracking start_timestamp = datetime.datetime.now(datetime.timezone.utc) + # Update configuration options, based on keywords passed to solve + config: IpoptConfig = self.config(value=kwds, preserve_implicit=True) # Check if solver is available - avail = self.available() + avail = self.available(config) if not avail: raise ipoptSolverError( f'Solver {self.__class__} is not available ({avail}).' ) - # Update configuration options, based on keywords passed to solve - config: IpoptConfig = self.config(value=kwds, preserve_implicit=True) - self.executable = config.executable if config.threads: logger.log( logging.WARNING, @@ -397,7 +402,7 @@ def solve(self, model, **kwds): ) results.solver_name = self.name - results.solver_version = self.version() + results.solver_version = self.version(config) if ( config.load_solutions and results.solution_status == SolutionStatus.noSolution diff --git a/pyomo/contrib/solver/plugins.py b/pyomo/contrib/solver/plugins.py index cb089200100..c7da41463a2 100644 --- a/pyomo/contrib/solver/plugins.py +++ b/pyomo/contrib/solver/plugins.py @@ -11,14 +11,14 @@ from .factory import SolverFactory -from .ipopt import ipopt +from .ipopt import Ipopt from .gurobi import Gurobi def load(): SolverFactory.register( name='ipopt', legacy_name='ipopt_v2', doc='The IPOPT NLP solver (new interface)' - )(ipopt) + )(Ipopt) SolverFactory.register( name='gurobi', legacy_name='gurobi_v2', doc='New interface to Gurobi' )(Gurobi) diff --git a/pyomo/contrib/solver/tests/solvers/test_ipopt.py b/pyomo/contrib/solver/tests/solvers/test_ipopt.py index 2886045055c..dc6bcf24855 100644 --- a/pyomo/contrib/solver/tests/solvers/test_ipopt.py +++ b/pyomo/contrib/solver/tests/solvers/test_ipopt.py @@ -13,7 +13,7 @@ import pyomo.environ as pyo from pyomo.common.fileutils import ExecutableData from pyomo.common.config import ConfigDict -from pyomo.contrib.solver.ipopt import ipoptConfig +from pyomo.contrib.solver.ipopt import IpoptConfig from pyomo.contrib.solver.factory import SolverFactory from pyomo.common import unittest @@ -42,7 +42,7 @@ def rosenbrock(m): def test_ipopt_config(self): # Test default initialization - config = ipoptConfig() + config = IpoptConfig() self.assertTrue(config.load_solutions) self.assertIsInstance(config.solver_options, ConfigDict) self.assertIsInstance(config.executable, ExecutableData) diff --git a/pyomo/contrib/solver/tests/solvers/test_solvers.py b/pyomo/contrib/solver/tests/solvers/test_solvers.py index e5af2ada170..2b9e783ad16 100644 --- a/pyomo/contrib/solver/tests/solvers/test_solvers.py +++ b/pyomo/contrib/solver/tests/solvers/test_solvers.py @@ -17,7 +17,7 @@ parameterized = parameterized.parameterized from pyomo.contrib.solver.results import TerminationCondition, SolutionStatus, Results from pyomo.contrib.solver.base import SolverBase -from pyomo.contrib.solver.ipopt import ipopt +from pyomo.contrib.solver.ipopt import Ipopt from pyomo.contrib.solver.gurobi import Gurobi from typing import Type from pyomo.core.expr.numeric_expr import LinearExpression @@ -31,10 +31,10 @@ if not param_available: raise unittest.SkipTest('Parameterized is not available.') -all_solvers = [('gurobi', Gurobi), ('ipopt', ipopt)] +all_solvers = [('gurobi', Gurobi), ('ipopt', Ipopt)] mip_solvers = [('gurobi', Gurobi)] -nlp_solvers = [('ipopt', ipopt)] -qcp_solvers = [('gurobi', Gurobi), ('ipopt', ipopt)] +nlp_solvers = [('ipopt', Ipopt)] +qcp_solvers = [('gurobi', Gurobi), ('ipopt', Ipopt)] miqcqp_solvers = [('gurobi', Gurobi)] @@ -256,7 +256,7 @@ def test_equality(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') - if isinstance(opt, ipopt): + if isinstance(opt, Ipopt): opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() @@ -429,7 +429,7 @@ def test_results_infeasible(self, name: str, opt_class: Type[SolverBase]): opt.config.raise_exception_on_nonoptimal_result = False res = opt.solve(m) self.assertNotEqual(res.solution_status, SolutionStatus.optimal) - if isinstance(opt, ipopt): + if isinstance(opt, Ipopt): acceptable_termination_conditions = { TerminationCondition.locallyInfeasible, TerminationCondition.unbounded, @@ -444,7 +444,7 @@ def test_results_infeasible(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(m.y.value, None) self.assertTrue(res.incumbent_objective is None) - if not isinstance(opt, ipopt): + if not isinstance(opt, Ipopt): # ipopt can return the values of the variables/duals at the last iterate # even if it did not converge; raise_exception_on_nonoptimal_result # is set to False, so we are free to load infeasible solutions @@ -970,7 +970,7 @@ def test_time_limit(self, name: str, opt_class: Type[SolverBase]): constant=0, ) m.c2[t] = expr == 1 - if isinstance(opt, ipopt): + if isinstance(opt, Ipopt): opt.config.time_limit = 1e-6 else: opt.config.time_limit = 0 From 74a9c7b32a44c9242d8f1abdb56045e8ae99bcb8 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 19 Feb 2024 11:57:03 -0700 Subject: [PATCH 1053/1204] add mpc readme --- pyomo/contrib/mpc/README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 pyomo/contrib/mpc/README.md diff --git a/pyomo/contrib/mpc/README.md b/pyomo/contrib/mpc/README.md new file mode 100644 index 00000000000..21fe39c5f50 --- /dev/null +++ b/pyomo/contrib/mpc/README.md @@ -0,0 +1,34 @@ +# Pyomo MPC + +Pyomo MPC is an extension for developing model predictive control simulations +using Pyomo models. Please see the +[documentation](https://pyomo.readthedocs.io/en/stable/contributed_packages/mpc/index.html) +for more detailed information. + +Pyomo MPC helps with, among other things, the following use cases: +- Transfering values between different points in time in a dynamic model +(e.g. to initialize a dynamic model to its initial conditions) +- Extracting or loading disturbances and inputs from or to models, and storing +these in model-agnostic, easily JSON-serializable data structures +- Constructing common modeling components, such as weighted-least-squares +tracking objective functions, piecewise-constant input constraints, or +terminal region constraints. + +## Citation + +If you use Pyomo MPC in your research, please cite the following paper, which +discusses the motivation for the Pyomo MPC data structures and the underlying +Pyomo features that make them possible. +```bibtex +@article{parker2023mpc, +title = {Model predictive control simulations with block-hierarchical differential-algebraic process models}, +journal = {Journal of Process Control}, +volume = {132}, +pages = {103113}, +year = {2023}, +issn = {0959-1524}, +doi = {https://doi.org/10.1016/j.jprocont.2023.103113}, +url = {https://www.sciencedirect.com/science/article/pii/S0959152423002007}, +author = {Robert B. Parker and Bethany L. Nicholson and John D. Siirola and Lorenz T. Biegler}, +} +``` From 321a18f8085d5b0c77ed6295606bd0b4d221e0ae Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 19 Feb 2024 12:04:13 -0700 Subject: [PATCH 1054/1204] add citation to mpc/index --- .../contributed_packages/mpc/index.rst | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/doc/OnlineDocs/contributed_packages/mpc/index.rst b/doc/OnlineDocs/contributed_packages/mpc/index.rst index b93abf223e2..c9ac929a71a 100644 --- a/doc/OnlineDocs/contributed_packages/mpc/index.rst +++ b/doc/OnlineDocs/contributed_packages/mpc/index.rst @@ -1,7 +1,7 @@ MPC === -This package contains data structures and utilities for dynamic optimization +Pyomo MPC contains data structures and utilities for dynamic optimization and rolling horizon applications, e.g. model predictive control. .. toctree:: @@ -10,3 +10,22 @@ and rolling horizon applications, e.g. model predictive control. overview.rst examples.rst faq.rst + +Citation +-------- + +If you use Pyomo MPC in your research, please cite the following paper: + +.. code-block:: bibtex + + @article{parker2023mpc, + title = {Model predictive control simulations with block-hierarchical differential-algebraic process models}, + journal = {Journal of Process Control}, + volume = {132}, + pages = {103113}, + year = {2023}, + issn = {0959-1524}, + doi = {https://doi.org/10.1016/j.jprocont.2023.103113}, + url = {https://www.sciencedirect.com/science/article/pii/S0959152423002007}, + author = {Robert B. Parker and Bethany L. Nicholson and John D. Siirola and Lorenz T. Biegler}, + } From b902a30ec8dd0f259aaef32516b3d94009ddb448 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 12:16:42 -0700 Subject: [PATCH 1055/1204] If sol file exists, parse it --- pyomo/contrib/solver/ipopt.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 3b1e95da42c..580f350dff3 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -380,16 +380,18 @@ def solve(self, model, **kwds): ostreams[0] ) - if process.returncode != 0: + if os.path.isfile(basename + '.sol'): + with open(basename + '.sol', 'r') as sol_file: + timer.start('parse_sol') + results = self._parse_solution(sol_file, nl_info) + timer.stop('parse_sol') + else: results = IpoptResults() + if process.returncode != 0: results.extra_info.return_code = process.returncode results.termination_condition = TerminationCondition.error results.solution_loader = SolSolutionLoader(None, None) else: - with open(basename + '.sol', 'r') as sol_file: - timer.start('parse_sol') - results = self._parse_solution(sol_file, nl_info) - timer.stop('parse_sol') results.iteration_count = iters results.timing_info.ipopt_excluding_nlp_functions = ipopt_time_nofunc results.timing_info.nlp_function_evaluations = ipopt_time_func From 15eddd3a21560cd46d306cee15fba3a61863dedc Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 12:37:13 -0700 Subject: [PATCH 1056/1204] Update _parse_ipopt_output to address newer versions of IPOPT --- pyomo/contrib/solver/ipopt.py | 46 ++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 580f350dff3..fc009c77522 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -101,14 +101,33 @@ def __init__( ) self.timing_info.ipopt_excluding_nlp_functions: Optional[float] = ( self.timing_info.declare( - 'ipopt_excluding_nlp_functions', ConfigValue(domain=NonNegativeFloat) + 'ipopt_excluding_nlp_functions', + ConfigValue( + domain=NonNegativeFloat, + default=None, + description="Total CPU seconds in IPOPT without function evaluations.", + ), ) ) self.timing_info.nlp_function_evaluations: Optional[float] = ( self.timing_info.declare( - 'nlp_function_evaluations', ConfigValue(domain=NonNegativeFloat) + 'nlp_function_evaluations', + ConfigValue( + domain=NonNegativeFloat, + default=None, + description="Total CPU seconds in NLP function evaluations.", + ), ) ) + self.timing_info.total_seconds: Optional[float] = self.timing_info.declare( + 'total_seconds', + ConfigValue( + domain=NonNegativeFloat, + default=None, + description="Total seconds in IPOPT. NOTE: Newer versions of IPOPT (3.14+) " + "no longer separate timing information.", + ), + ) class IpoptSolutionLoader(SolSolutionLoader): @@ -376,8 +395,8 @@ def solve(self, model, **kwds): timer.stop('subprocess') # This is the stuff we need to parse to get the iterations # and time - iters, ipopt_time_nofunc, ipopt_time_func = self._parse_ipopt_output( - ostreams[0] + iters, ipopt_time_nofunc, ipopt_time_func, ipopt_total_time = ( + self._parse_ipopt_output(ostreams[0]) ) if os.path.isfile(basename + '.sol'): @@ -395,12 +414,14 @@ def solve(self, model, **kwds): results.iteration_count = iters results.timing_info.ipopt_excluding_nlp_functions = ipopt_time_nofunc results.timing_info.nlp_function_evaluations = ipopt_time_func + results.timing_info.total_seconds = ipopt_total_time if ( config.raise_exception_on_nonoptimal_result and results.solution_status != SolutionStatus.optimal ): raise RuntimeError( - 'Solver did not find the optimal solution. Set opt.config.raise_exception_on_nonoptimal_result = False to bypass this error.' + 'Solver did not find the optimal solution. Set ' + 'opt.config.raise_exception_on_nonoptimal_result = False to bypass this error.' ) results.solver_name = self.name @@ -411,7 +432,7 @@ def solve(self, model, **kwds): ): raise RuntimeError( 'A feasible solution was not found, so no solution can be loaded.' - 'Please set config.load_solutions=False to bypass this error.' + 'Please set opt.config.load_solutions=False to bypass this error.' ) if config.load_solutions: @@ -472,23 +493,30 @@ def _parse_ipopt_output(self, stream: io.StringIO): iters = None nofunc_time = None func_time = None + total_time = None # parse the output stream to get the iteration count and solver time for line in stream.getvalue().splitlines(): if line.startswith("Number of Iterations....:"): tokens = line.split() iters = int(tokens[3]) + elif line.startswith( + "Total seconds in IPOPT =" + ): + # Newer versions of IPOPT no longer separate the + tokens = line.split() + total_time = float(tokens[-1]) elif line.startswith( "Total CPU secs in IPOPT (w/o function evaluations) =" ): tokens = line.split() - nofunc_time = float(tokens[9]) + nofunc_time = float(tokens[-1]) elif line.startswith( "Total CPU secs in NLP function evaluations =" ): tokens = line.split() - func_time = float(tokens[8]) + func_time = float(tokens[-1]) - return iters, nofunc_time, func_time + return iters, nofunc_time, func_time, total_time def _parse_solution(self, instream: io.TextIOBase, nl_info: NLWriterInfo): results = IpoptResults() From d8536b6cd1751c594d81c7c88ca0bcaa0d380589 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 19 Feb 2024 12:37:26 -0700 Subject: [PATCH 1057/1204] add api docs to mpi --- doc/OnlineDocs/contributed_packages/mpc/api.rst | 10 ++++++++++ .../contributed_packages/mpc/conversion.rst | 5 +++++ .../contributed_packages/mpc/data.rst | 17 +++++++++++++++++ .../contributed_packages/mpc/index.rst | 1 + .../contributed_packages/mpc/interface.rst | 8 ++++++++ .../contributed_packages/mpc/modeling.rst | 11 +++++++++++ 6 files changed, 52 insertions(+) create mode 100644 doc/OnlineDocs/contributed_packages/mpc/api.rst create mode 100644 doc/OnlineDocs/contributed_packages/mpc/conversion.rst create mode 100644 doc/OnlineDocs/contributed_packages/mpc/data.rst create mode 100644 doc/OnlineDocs/contributed_packages/mpc/interface.rst create mode 100644 doc/OnlineDocs/contributed_packages/mpc/modeling.rst diff --git a/doc/OnlineDocs/contributed_packages/mpc/api.rst b/doc/OnlineDocs/contributed_packages/mpc/api.rst new file mode 100644 index 00000000000..2752fea8af6 --- /dev/null +++ b/doc/OnlineDocs/contributed_packages/mpc/api.rst @@ -0,0 +1,10 @@ +.. _mpc_api: + +API Reference +============= + +.. toctree:: + data.rst + conversion.rst + interface.rst + modeling.rst diff --git a/doc/OnlineDocs/contributed_packages/mpc/conversion.rst b/doc/OnlineDocs/contributed_packages/mpc/conversion.rst new file mode 100644 index 00000000000..9d9406edb75 --- /dev/null +++ b/doc/OnlineDocs/contributed_packages/mpc/conversion.rst @@ -0,0 +1,5 @@ +Data Conversion +=============== + +.. automodule:: pyomo.contrib.mpc.data.convert + :members: diff --git a/doc/OnlineDocs/contributed_packages/mpc/data.rst b/doc/OnlineDocs/contributed_packages/mpc/data.rst new file mode 100644 index 00000000000..73cb6543b1e --- /dev/null +++ b/doc/OnlineDocs/contributed_packages/mpc/data.rst @@ -0,0 +1,17 @@ +Data Structures +=============== + +.. automodule:: pyomo.contrib.mpc.data.get_cuid + :members: + +.. automodule:: pyomo.contrib.mpc.data.dynamic_data_base + :members: + +.. automodule:: pyomo.contrib.mpc.data.scalar_data + :members: + +.. automodule:: pyomo.contrib.mpc.data.series_data + :members: + +.. automodule:: pyomo.contrib.mpc.data.interval_data + :members: diff --git a/doc/OnlineDocs/contributed_packages/mpc/index.rst b/doc/OnlineDocs/contributed_packages/mpc/index.rst index c9ac929a71a..e512d1a6ef5 100644 --- a/doc/OnlineDocs/contributed_packages/mpc/index.rst +++ b/doc/OnlineDocs/contributed_packages/mpc/index.rst @@ -10,6 +10,7 @@ and rolling horizon applications, e.g. model predictive control. overview.rst examples.rst faq.rst + api.rst Citation -------- diff --git a/doc/OnlineDocs/contributed_packages/mpc/interface.rst b/doc/OnlineDocs/contributed_packages/mpc/interface.rst new file mode 100644 index 00000000000..eb5bac548fd --- /dev/null +++ b/doc/OnlineDocs/contributed_packages/mpc/interface.rst @@ -0,0 +1,8 @@ +Interfaces +========== + +.. automodule:: pyomo.contrib.mpc.interfaces.model_interface + :members: + +.. automodule:: pyomo.contrib.mpc.interfaces.var_linker + :members: diff --git a/doc/OnlineDocs/contributed_packages/mpc/modeling.rst b/doc/OnlineDocs/contributed_packages/mpc/modeling.rst new file mode 100644 index 00000000000..cbae03161b1 --- /dev/null +++ b/doc/OnlineDocs/contributed_packages/mpc/modeling.rst @@ -0,0 +1,11 @@ +Modeling Components +=================== + +.. automodule:: pyomo.contrib.mpc.modeling.constraints + :members: + +.. automodule:: pyomo.contrib.mpc.modeling.cost_expressions + :members: + +.. automodule:: pyomo.contrib.mpc.modeling.terminal + :members: From 7ae3ed8d737f82857ab8e5654bb1c7f6973933c9 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 19 Feb 2024 12:53:13 -0700 Subject: [PATCH 1058/1204] clarify definition of "flatten" and add citation --- .../advanced_topics/flattener/index.rst | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/doc/OnlineDocs/advanced_topics/flattener/index.rst b/doc/OnlineDocs/advanced_topics/flattener/index.rst index 377de5233ec..982a8931d36 100644 --- a/doc/OnlineDocs/advanced_topics/flattener/index.rst +++ b/doc/OnlineDocs/advanced_topics/flattener/index.rst @@ -30,8 +30,9 @@ The ``pyomo.dae.flatten`` module aims to address this use case by providing utilities to generate all components indexed, explicitly or implicitly, by user-provided sets. -**When we say "flatten a model," we mean "generate all components in the model, -preserving all user-specified indexing sets."** +**When we say "flatten a model," we mean "recursively generate all components in +the model, where a component can be indexed only by user-specified indexing +sets (or is not indexed at all)**. Data structures --------------- @@ -42,3 +43,23 @@ Slices are necessary as they can encode "implicit indexing" -- where a component is contained in an indexed block. It is natural to return references to these slices, so they may be accessed and manipulated like any other component. + +Citation +-------- +If you use the ``pyomo.dae.flatten`` module in your research, we would appreciate +you citing the following paper, which gives more detail about the motivation for +and examples of using this functinoality. + +.. code-block:: bibtex + + @article{parker2023mpc, + title = {Model predictive control simulations with block-hierarchical differential-algebraic process models}, + journal = {Journal of Process Control}, + volume = {132}, + pages = {103113}, + year = {2023}, + issn = {0959-1524}, + doi = {https://doi.org/10.1016/j.jprocont.2023.103113}, + url = {https://www.sciencedirect.com/science/article/pii/S0959152423002007}, + author = {Robert B. Parker and Bethany L. Nicholson and John D. Siirola and Lorenz T. Biegler}, + } From 90d03c15b3e732b2f00f5f49ab6f63d6e1f3f744 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 19 Feb 2024 13:03:36 -0700 Subject: [PATCH 1059/1204] improve docstring and fix typo --- pyomo/contrib/mpc/data/get_cuid.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/mpc/data/get_cuid.py b/pyomo/contrib/mpc/data/get_cuid.py index 03659d6153f..ef0df7ea679 100644 --- a/pyomo/contrib/mpc/data/get_cuid.py +++ b/pyomo/contrib/mpc/data/get_cuid.py @@ -16,14 +16,13 @@ def get_indexed_cuid(var, sets=None, dereference=None, context=None): - """ - Attempts to convert the provided "var" object into a CUID with - with wildcards. + """Attempt to convert the provided "var" object into a CUID with wildcards Arguments --------- var: - Object to process + Object to process. May be a VarData, IndexedVar (reference or otherwise), + ComponentUID, slice, or string. sets: Tuple of sets Sets to use if slicing a vardata object dereference: None or int @@ -32,6 +31,11 @@ def get_indexed_cuid(var, sets=None, dereference=None, context=None): context: Block Block with respect to which slices and CUIDs will be generated + Returns + ------- + ``ComponentUID`` + ComponentUID corresponding to the provided ``var`` and sets + """ # TODO: Does this function have a good name? # Should this function be generalized beyond a single indexing set? From bc4c71bf3469ac1fa68f888290bfc7f42458f7a7 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 19 Feb 2024 13:07:08 -0700 Subject: [PATCH 1060/1204] add end quote --- doc/OnlineDocs/advanced_topics/flattener/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/OnlineDocs/advanced_topics/flattener/index.rst b/doc/OnlineDocs/advanced_topics/flattener/index.rst index 982a8931d36..f9dd8ea6abb 100644 --- a/doc/OnlineDocs/advanced_topics/flattener/index.rst +++ b/doc/OnlineDocs/advanced_topics/flattener/index.rst @@ -31,7 +31,7 @@ utilities to generate all components indexed, explicitly or implicitly, by user-provided sets. **When we say "flatten a model," we mean "recursively generate all components in -the model, where a component can be indexed only by user-specified indexing +the model," where a component can be indexed only by user-specified indexing sets (or is not indexed at all)**. Data structures From 76c970f35f9cedfa6890b64fddd30570f53961a4 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 19 Feb 2024 13:08:03 -0700 Subject: [PATCH 1061/1204] fix typo --- pyomo/contrib/mpc/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/mpc/README.md b/pyomo/contrib/mpc/README.md index 21fe39c5f50..7e03163f703 100644 --- a/pyomo/contrib/mpc/README.md +++ b/pyomo/contrib/mpc/README.md @@ -6,7 +6,7 @@ using Pyomo models. Please see the for more detailed information. Pyomo MPC helps with, among other things, the following use cases: -- Transfering values between different points in time in a dynamic model +- Transferring values between different points in time in a dynamic model (e.g. to initialize a dynamic model to its initial conditions) - Extracting or loading disturbances and inputs from or to models, and storing these in model-agnostic, easily JSON-serializable data structures From afcedb15449f76d308d73a96fed18dbb0eda5638 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 13:27:08 -0700 Subject: [PATCH 1062/1204] Incomplete comment; stronger parsing --- pyomo/contrib/solver/ipopt.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index fc009c77522..074e5b19c5c 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -498,11 +498,13 @@ def _parse_ipopt_output(self, stream: io.StringIO): for line in stream.getvalue().splitlines(): if line.startswith("Number of Iterations....:"): tokens = line.split() - iters = int(tokens[3]) + iters = int(tokens[-1]) elif line.startswith( "Total seconds in IPOPT =" ): - # Newer versions of IPOPT no longer separate the + # Newer versions of IPOPT no longer separate timing into + # two different values. This is so we have compatibility with + # both new and old versions tokens = line.split() total_time = float(tokens[-1]) elif line.startswith( From dfea3ecca321cef955e31d9a5d48412014f2fad6 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 19 Feb 2024 13:29:51 -0700 Subject: [PATCH 1063/1204] Update Component{Map,Set} to support tuple keys --- pyomo/common/collections/component_map.py | 56 +++++++++++++----- pyomo/common/collections/component_set.py | 70 +++++++++++------------ 2 files changed, 77 insertions(+), 49 deletions(-) diff --git a/pyomo/common/collections/component_map.py b/pyomo/common/collections/component_map.py index 80ba5fe0d1c..0851ffad301 100644 --- a/pyomo/common/collections/component_map.py +++ b/pyomo/common/collections/component_map.py @@ -9,21 +9,49 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from collections.abc import MutableMapping as collections_MutableMapping +import collections from collections.abc import Mapping as collections_Mapping from pyomo.common.autoslots import AutoSlots -def _rebuild_ids(encode, val): +def _rehash_keys(encode, val): if encode: - return val + return list(val.values()) else: # object id() may have changed after unpickling, # so we rebuild the dictionary keys - return {id(obj): (obj, v) for obj, v in val.values()} + return {_hasher[obj.__class__](obj): (obj, v) for obj, v in val} + + +class _Hasher(collections.defaultdict): + def __init__(self, *args, **kwargs): + super().__init__(lambda: self._missing_impl, *args, **kwargs) + self[tuple] = self._tuple + + def _missing_impl(self, val): + try: + hash(val) + self[val.__class__] = self._hashable + except: + self[val.__class__] = self._unhashable + return self[val.__class__](val) + + @staticmethod + def _hashable(val): + return val + + @staticmethod + def _unhashable(val): + return id(val) + + def _tuple(self, val): + return tuple(self[i.__class__](i) for i in val) + + +_hasher = _Hasher() -class ComponentMap(AutoSlots.Mixin, collections_MutableMapping): +class ComponentMap(AutoSlots.Mixin, collections.abc.MutableMapping): """ This class is a replacement for dict that allows Pyomo modeling components to be used as entry keys. The @@ -49,7 +77,7 @@ class ComponentMap(AutoSlots.Mixin, collections_MutableMapping): """ __slots__ = ("_dict",) - __autoslot_mappers__ = {'_dict': _rebuild_ids} + __autoslot_mappers__ = {'_dict': _rehash_keys} def __init__(self, *args, **kwds): # maps id(obj) -> (obj,val) @@ -68,18 +96,20 @@ def __str__(self): def __getitem__(self, obj): try: - return self._dict[id(obj)][1] + return self._dict[_hasher[obj.__class__](obj)][1] except KeyError: - raise KeyError("Component with id '%s': %s" % (id(obj), str(obj))) + _id = _hasher[obj.__class__](obj) + raise KeyError("Component with id '%s': %s" % (_id, obj)) def __setitem__(self, obj, val): - self._dict[id(obj)] = (obj, val) + self._dict[_hasher[obj.__class__](obj)] = (obj, val) def __delitem__(self, obj): try: - del self._dict[id(obj)] + del self._dict[_hasher[obj.__class__](obj)] except KeyError: - raise KeyError("Component with id '%s': %s" % (id(obj), str(obj))) + _id = _hasher[obj.__class__](obj) + raise KeyError("Component with id '%s': %s" % (_id, obj)) def __iter__(self): return (obj for obj, val in self._dict.values()) @@ -107,7 +137,7 @@ def __eq__(self, other): return False # Note we have already verified the dicts are the same size for key, val in other.items(): - other_id = id(key) + other_id = _hasher[key.__class__](key) if other_id not in self._dict: return False self_val = self._dict[other_id][1] @@ -130,7 +160,7 @@ def __ne__(self, other): # def __contains__(self, obj): - return id(obj) in self._dict + return _hasher[obj.__class__](obj) in self._dict def clear(self): 'D.clear() -> None. Remove all items from D.' diff --git a/pyomo/common/collections/component_set.py b/pyomo/common/collections/component_set.py index dfeac5cbfa5..f1fe7bc8cd6 100644 --- a/pyomo/common/collections/component_set.py +++ b/pyomo/common/collections/component_set.py @@ -12,8 +12,20 @@ from collections.abc import MutableSet as collections_MutableSet from collections.abc import Set as collections_Set +from pyomo.common.autoslots import AutoSlots +from pyomo.common.collections.component_map import _hasher -class ComponentSet(collections_MutableSet): + +def _rehash_keys(encode, val): + if encode: + return list(val.values()) + else: + # object id() may have changed after unpickling, + # so we rebuild the dictionary keys + return {_hasher[obj.__class__](obj): obj for obj in val} + + +class ComponentSet(AutoSlots.Mixin, collections_MutableSet): """ This class is a replacement for set that allows Pyomo modeling components to be used as entries. The @@ -38,16 +50,12 @@ class ComponentSet(collections_MutableSet): """ __slots__ = ("_data",) + __autoslot_mappers__ = {'_data': _rehash_keys} - def __init__(self, *args): - self._data = dict() - if len(args) > 0: - if len(args) > 1: - raise TypeError( - "%s expected at most 1 arguments, " - "got %s" % (self.__class__.__name__, len(args)) - ) - self.update(args[0]) + def __init__(self, iterable=None): + self._data = {} + if iterable is not None: + self.update(iterable) def __str__(self): """String representation of the mapping.""" @@ -56,29 +64,19 @@ def __str__(self): tmp.append(str(obj) + " (id=" + str(objid) + ")") return "ComponentSet(" + str(tmp) + ")" - def update(self, args): + def update(self, iterable): """Update a set with the union of itself and others.""" - self._data.update((id(obj), obj) for obj in args) - - # - # This method must be defined for deepcopy/pickling - # because this class relies on Python ids. - # - def __setstate__(self, state): - # object id() may have changed after unpickling, - # so we rebuild the dictionary keys - assert len(state) == 1 - self._data = {id(obj): obj for obj in state['_data']} - - def __getstate__(self): - return {'_data': tuple(self._data.values())} + if isinstance(iterable, ComponentSet): + self._data.update(iterable._data) + else: + self._data.update((_hasher[val.__class__](val), val) for val in iterable) # # Implement MutableSet abstract methods # def __contains__(self, val): - return self._data.__contains__(id(val)) + return _hasher[val.__class__](val) in self._data def __iter__(self): return iter(self._data.values()) @@ -88,27 +86,26 @@ def __len__(self): def add(self, val): """Add an element.""" - self._data[id(val)] = val + self._data[_hasher[val.__class__](val)] = val def discard(self, val): """Remove an element. Do not raise an exception if absent.""" - if id(val) in self._data: - del self._data[id(val)] + _id = _hasher[val.__class__](val) + if _id in self._data: + del self._data[_id] # # Overload MutableSet default implementations # - # We want to avoid generating Pyomo expressions due to - # comparison of values, so we convert both objects to a - # plain dictionary mapping key->(type(val), id(val)) and - # compare that instead. def __eq__(self, other): if self is other: return True if not isinstance(other, collections_Set): return False - return len(self) == len(other) and all(id(key) in self._data for key in other) + return len(self) == len(other) and all( + _hasher[val.__class__](val) in self._data for val in other + ) def __ne__(self, other): return not (self == other) @@ -125,6 +122,7 @@ def clear(self): def remove(self, val): """Remove an element. If not a member, raise a KeyError.""" try: - del self._data[id(val)] + del self._data[_hasher[val.__class__](val)] except KeyError: - raise KeyError("Component with id '%s': %s" % (id(val), str(val))) + _id = _hasher[val.__class__](val) + raise KeyError("Component with id '%s': %s" % (_id, val)) From b817cfcb1362e59cef492329aa835a5e4cdad2d7 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 19 Feb 2024 13:30:18 -0700 Subject: [PATCH 1064/1204] Add test if using tuples in ComponentMap --- pyomo/common/tests/test_component_map.py | 50 ++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 pyomo/common/tests/test_component_map.py diff --git a/pyomo/common/tests/test_component_map.py b/pyomo/common/tests/test_component_map.py new file mode 100644 index 00000000000..cc746642f28 --- /dev/null +++ b/pyomo/common/tests/test_component_map.py @@ -0,0 +1,50 @@ +# ___________________________________________________________________________ +# +# 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.common.unittest as unittest + +from pyomo.common.collections import ComponentMap +from pyomo.environ import ConcreteModel, Var, Constraint + + +class TestComponentMap(unittest.TestCase): + def test_tuple(self): + m = ConcreteModel() + m.v = Var() + m.c = Constraint(expr=m.v >= 0) + m.cm = cm = ComponentMap() + + cm[(1,2)] = 5 + self.assertEqual(len(cm), 1) + self.assertIn((1,2), cm) + self.assertEqual(cm[1,2], 5) + + cm[(1,2)] = 50 + self.assertEqual(len(cm), 1) + self.assertIn((1,2), cm) + self.assertEqual(cm[1,2], 50) + + cm[(1, (2, m.v))] = 10 + self.assertEqual(len(cm), 2) + self.assertIn((1,(2, m.v)), cm) + self.assertEqual(cm[1, (2, m.v)], 10) + + cm[(1, (2, m.v))] = 100 + self.assertEqual(len(cm), 2) + self.assertIn((1,(2, m.v)), cm) + self.assertEqual(cm[1, (2, m.v)], 100) + + i = m.clone() + self.assertIn((1, 2), i.cm) + self.assertIn((1, (2, i.v)), i.cm) + self.assertNotIn((1, (2, i.v)), m.cm) + self.assertIn((1, (2, m.v)), m.cm) + self.assertNotIn((1, (2, m.v)), i.cm) From eb672d5e0190ea9c27d40e66e252b07f37b06353 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 19 Feb 2024 13:32:42 -0700 Subject: [PATCH 1065/1204] Clean up / update OrderedSet (for post-Python 3.7) --- pyomo/common/collections/orderedset.py | 30 ++++++++------------------ 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/pyomo/common/collections/orderedset.py b/pyomo/common/collections/orderedset.py index f29245b75fe..6bcf0c2fafb 100644 --- a/pyomo/common/collections/orderedset.py +++ b/pyomo/common/collections/orderedset.py @@ -9,42 +9,30 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from collections.abc import MutableSet from collections import OrderedDict +from collections.abc import MutableSet +from pyomo.common.autoslots import AutoSlots -class OrderedSet(MutableSet): +class OrderedSet(AutoSlots.Mixin, MutableSet): __slots__ = ('_dict',) def __init__(self, iterable=None): # TODO: Starting in Python 3.7, dict is ordered (and is faster # than OrderedDict). dict began supporting reversed() in 3.8. - # We should consider changing the underlying data type here from - # OrderedDict to dict. - self._dict = OrderedDict() + self._dict = {} if iterable is not None: - if iterable.__class__ is OrderedSet: - self._dict.update(iterable._dict) - else: - self.update(iterable) + self.update(iterable) def __str__(self): """String representation of the mapping.""" return "OrderedSet(%s)" % (', '.join(repr(x) for x in self)) def update(self, iterable): - for val in iterable: - self.add(val) - - # - # This method must be defined for deepcopy/pickling - # because this class is slotized. - # - def __setstate__(self, state): - self._dict = state - - def __getstate__(self): - return self._dict + if isinstance(iterable, OrderedSet): + self._dict.update(iterable._dict) + else: + self._dict.update((val, None) for val in iterable) # # Implement MutableSet abstract methods From d91ced077a8c3a7beb2f8cf7785e0e8e0f532a22 Mon Sep 17 00:00:00 2001 From: jasherma Date: Mon, 19 Feb 2024 15:38:40 -0500 Subject: [PATCH 1066/1204] Simplify `PathList.__call__` --- pyomo/common/config.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index f156bee79a9..09a1706ee5a 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -589,15 +589,11 @@ class PathList(Path): """ def __call__(self, data): - try: - pathlist = [super(PathList, self).__call__(data)] - except TypeError as err: - is_not_path_like = "expected str, bytes or os.PathLike" in str(err) - if is_not_path_like and hasattr(data, "__iter__"): - pathlist = [super(PathList, self).__call__(i) for i in data] - else: - raise - return pathlist + is_path_like = isinstance(data, (str, bytes)) or hasattr(data, "__fspath__") + if hasattr(data, "__iter__") and not is_path_like: + return [super(PathList, self).__call__(i) for i in data] + else: + return [super(PathList, self).__call__(data)] class DynamicImplicitDomain(object): From 4c0effdbf74263f9cb9cf6cba6708d537194775f Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 14:00:23 -0700 Subject: [PATCH 1067/1204] Add overwrite flag --- .github/workflows/release_wheel_creation.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index ef44806d6d4..f978415e99e 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -45,6 +45,7 @@ jobs: with: name: native_wheels path: dist/*.whl + overwrite: true alternative_wheels: name: Build wheels (${{ matrix.wheel-version }}) on ${{ matrix.os }} for aarch64 @@ -76,6 +77,7 @@ jobs: with: name: alt_wheels path: dist/*.whl + overwrite: true generictarball: name: ${{ matrix.TARGET }} @@ -106,4 +108,5 @@ jobs: with: name: generictarball path: dist + overwrite: true From 153e24920cfa77ffe418dfba3a77178c900dc8b3 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 14:07:05 -0700 Subject: [PATCH 1068/1204] Update action version --- .github/workflows/release_wheel_creation.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index f978415e99e..19c2a6c50a9 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -29,7 +29,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build wheels - uses: pypa/cibuildwheel@v2.16.2 + uses: pypa/cibuildwheel@v2 with: output-dir: dist env: @@ -63,7 +63,7 @@ jobs: with: platforms: all - name: Build wheels - uses: pypa/cibuildwheel@v2.16.2 + uses: pypa/cibuildwheel@v2 with: output-dir: dist env: From fcb7cef0f64fe3457f3b2e955bb41ba5c8831189 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 14:11:56 -0700 Subject: [PATCH 1069/1204] Update action version --- .github/workflows/release_wheel_creation.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index 19c2a6c50a9..2dd44652489 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -29,7 +29,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build wheels - uses: pypa/cibuildwheel@v2 + uses: pypa/cibuildwheel@v2.16.5 with: output-dir: dist env: @@ -63,7 +63,7 @@ jobs: with: platforms: all - name: Build wheels - uses: pypa/cibuildwheel@v2 + uses: pypa/cibuildwheel@v2.16.5 with: output-dir: dist env: From 217adeaec63873ec6ce945a240cd70d087587d98 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 19 Feb 2024 14:15:35 -0700 Subject: [PATCH 1070/1204] NFC: apply black --- pyomo/common/tests/test_component_map.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyomo/common/tests/test_component_map.py b/pyomo/common/tests/test_component_map.py index cc746642f28..9e771175d42 100644 --- a/pyomo/common/tests/test_component_map.py +++ b/pyomo/common/tests/test_component_map.py @@ -22,24 +22,24 @@ def test_tuple(self): m.c = Constraint(expr=m.v >= 0) m.cm = cm = ComponentMap() - cm[(1,2)] = 5 + cm[(1, 2)] = 5 self.assertEqual(len(cm), 1) - self.assertIn((1,2), cm) - self.assertEqual(cm[1,2], 5) + self.assertIn((1, 2), cm) + self.assertEqual(cm[1, 2], 5) - cm[(1,2)] = 50 + cm[(1, 2)] = 50 self.assertEqual(len(cm), 1) - self.assertIn((1,2), cm) - self.assertEqual(cm[1,2], 50) + self.assertIn((1, 2), cm) + self.assertEqual(cm[1, 2], 50) cm[(1, (2, m.v))] = 10 self.assertEqual(len(cm), 2) - self.assertIn((1,(2, m.v)), cm) + self.assertIn((1, (2, m.v)), cm) self.assertEqual(cm[1, (2, m.v)], 10) cm[(1, (2, m.v))] = 100 self.assertEqual(len(cm), 2) - self.assertIn((1,(2, m.v)), cm) + self.assertIn((1, (2, m.v)), cm) self.assertEqual(cm[1, (2, m.v)], 100) i = m.clone() From a80c20605e61f764ec2371683676ace2739f0a96 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 14:18:19 -0700 Subject: [PATCH 1071/1204] Change from overwrite to merge --- .github/workflows/release_wheel_creation.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index 2dd44652489..203b4f391c7 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -45,7 +45,7 @@ jobs: with: name: native_wheels path: dist/*.whl - overwrite: true + merge-multiple: true alternative_wheels: name: Build wheels (${{ matrix.wheel-version }}) on ${{ matrix.os }} for aarch64 @@ -77,7 +77,7 @@ jobs: with: name: alt_wheels path: dist/*.whl - overwrite: true + merge-multiple: true generictarball: name: ${{ matrix.TARGET }} @@ -108,5 +108,5 @@ jobs: with: name: generictarball path: dist - overwrite: true + merge-multiple: true From 88aab73d33d8cfaecef8337a3121f986285f6a2a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 14:24:49 -0700 Subject: [PATCH 1072/1204] Have to give unique names now. Yay. --- .github/workflows/release_wheel_creation.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index 203b4f391c7..d847b0f2cff 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -43,9 +43,9 @@ jobs: CIBW_CONFIG_SETTINGS: '--global-option="--with-cython --with-distributable-extensions"' - uses: actions/upload-artifact@v4 with: - name: native_wheels + name: alt_wheels-${{ matrix.os }}-${{ matrix.wheel-version }} path: dist/*.whl - merge-multiple: true + overwrite: true alternative_wheels: name: Build wheels (${{ matrix.wheel-version }}) on ${{ matrix.os }} for aarch64 @@ -75,9 +75,9 @@ jobs: CIBW_CONFIG_SETTINGS: '--global-option="--with-cython --with-distributable-extensions"' - uses: actions/upload-artifact@v4 with: - name: alt_wheels + name: alt_wheels-${{ matrix.os }}-${{ matrix.wheel-version }} path: dist/*.whl - merge-multiple: true + overwrite: true generictarball: name: ${{ matrix.TARGET }} @@ -108,5 +108,5 @@ jobs: with: name: generictarball path: dist - merge-multiple: true + overwrite: true From dd9cf09b290302e854eb032644cf9e0ed8ee9509 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 14:35:52 -0700 Subject: [PATCH 1073/1204] Add fail fast; target names --- .github/workflows/release_wheel_creation.yml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index d847b0f2cff..6b65938706c 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -22,10 +22,23 @@ jobs: name: Build wheels (${{ matrix.wheel-version }}) on ${{ matrix.os }} for native and cross-compiled architecture runs-on: ${{ matrix.os }} strategy: + fail-fast: true matrix: os: [ubuntu-22.04, windows-latest, macos-latest] arch: [all] wheel-version: ['cp38*', 'cp39*', 'cp310*', 'cp311*', 'cp312*'] + + include: + - wheel-version: 'cp38*' + TARGET: 'py38' + - wheel-version: 'cp39*' + TARGET: 'py39' + - wheel-version: 'cp310*' + TARGET: 'py310' + - wheel-version: 'cp311*' + TARGET: 'py311' + - wheel-version: 'cp312*' + TARGET: 'py312' steps: - uses: actions/checkout@v4 - name: Build wheels @@ -43,7 +56,7 @@ jobs: CIBW_CONFIG_SETTINGS: '--global-option="--with-cython --with-distributable-extensions"' - uses: actions/upload-artifact@v4 with: - name: alt_wheels-${{ matrix.os }}-${{ matrix.wheel-version }} + name: alt_wheels-${{ matrix.os }}-${{ matrix.TARGET }} path: dist/*.whl overwrite: true @@ -75,7 +88,7 @@ jobs: CIBW_CONFIG_SETTINGS: '--global-option="--with-cython --with-distributable-extensions"' - uses: actions/upload-artifact@v4 with: - name: alt_wheels-${{ matrix.os }}-${{ matrix.wheel-version }} + name: alt_wheels-${{ matrix.os }}-${{ matrix.TARGET }} path: dist/*.whl overwrite: true From 8c643ca08867b646a7dea8964ff20061737ee1fb Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 14:57:34 -0700 Subject: [PATCH 1074/1204] Copy-pasta failure --- .github/workflows/release_wheel_creation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index 6b65938706c..17152dc3d1e 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -56,7 +56,7 @@ jobs: CIBW_CONFIG_SETTINGS: '--global-option="--with-cython --with-distributable-extensions"' - uses: actions/upload-artifact@v4 with: - name: alt_wheels-${{ matrix.os }}-${{ matrix.TARGET }} + name: native_wheels-${{ matrix.os }}-${{ matrix.TARGET }} path: dist/*.whl overwrite: true From 5ad721cc20d8e2db73df7beb19d029cf1d11e46b Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 19 Feb 2024 15:27:24 -0700 Subject: [PATCH 1075/1204] updating results processing and tests --- pyomo/contrib/solver/ipopt.py | 147 +++++--- pyomo/contrib/solver/results.py | 4 + pyomo/contrib/solver/solution.py | 64 +++- .../solver/tests/solvers/test_solvers.py | 350 +++++++++++++++--- 4 files changed, 439 insertions(+), 126 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index fc009c77522..82d145d2e93 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -134,10 +134,20 @@ class IpoptSolutionLoader(SolSolutionLoader): def get_reduced_costs( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None ) -> Mapping[_GeneralVarData, float]: + if self._nl_info is None: + raise RuntimeError( + 'Solution loader does not currently have a valid solution. Please ' + 'check the termination condition.' + ) + if len(self._nl_info.eliminated_vars) > 0: + raise NotImplementedError('For now, turn presolve off (opt.config.writer_config.linear_presolve=False) to get reduced costs.') + assert self._sol_data is not None if self._nl_info.scaling is None: scale_list = [1] * len(self._nl_info.variables) + obj_scale = 1 else: scale_list = self._nl_info.scaling.variables + obj_scale = self._nl_info.scaling.objectives[0] sol_data = self._sol_data nl_info = self._nl_info zl_map = sol_data.var_suffixes['ipopt_zL_out'] @@ -148,11 +158,11 @@ def get_reduced_costs( v_id = id(v) rc[v_id] = (v, 0) if ndx in zl_map: - zl = zl_map[ndx] * scale + zl = zl_map[ndx] * scale / obj_scale if abs(zl) > abs(rc[v_id][1]): rc[v_id] = (v, zl) if ndx in zu_map: - zu = zu_map[ndx] * scale + zu = zu_map[ndx] * scale / obj_scale if abs(zu) > abs(rc[v_id][1]): rc[v_id] = (v, zu) @@ -353,68 +363,82 @@ def solve(self, model, **kwds): symbolic_solver_labels=config.symbolic_solver_labels, ) timer.stop('write_nl_file') - # Get a copy of the environment to pass to the subprocess - env = os.environ.copy() - if nl_info.external_function_libraries: - if env.get('AMPLFUNC'): - nl_info.external_function_libraries.append(env.get('AMPLFUNC')) - env['AMPLFUNC'] = "\n".join(nl_info.external_function_libraries) - # Write the opt_file, if there should be one; return a bool to say - # whether or not we have one (so we can correctly build the command line) - opt_file = self._write_options_file( - filename=basename, options=config.solver_options - ) - # Call ipopt - passing the files via the subprocess - cmd = self._create_command_line( - basename=basename, config=config, opt_file=opt_file - ) - # this seems silly, but we have to give the subprocess slightly longer to finish than - # ipopt - if config.time_limit is not None: - timeout = config.time_limit + min( - max(1.0, 0.01 * config.time_limit), 100 + if len(nl_info.variables) > 0: + # Get a copy of the environment to pass to the subprocess + env = os.environ.copy() + if nl_info.external_function_libraries: + if env.get('AMPLFUNC'): + nl_info.external_function_libraries.append(env.get('AMPLFUNC')) + env['AMPLFUNC'] = "\n".join(nl_info.external_function_libraries) + # Write the opt_file, if there should be one; return a bool to say + # whether or not we have one (so we can correctly build the command line) + opt_file = self._write_options_file( + filename=basename, options=config.solver_options ) - else: - timeout = None - - ostreams = [io.StringIO()] - if config.tee: - ostreams.append(sys.stdout) - if config.log_solver_output: - ostreams.append(LogStream(level=logging.INFO, logger=logger)) - with TeeStream(*ostreams) as t: - timer.start('subprocess') - process = subprocess.run( - cmd, - timeout=timeout, - env=env, - universal_newlines=True, - stdout=t.STDOUT, - stderr=t.STDERR, - ) - timer.stop('subprocess') - # This is the stuff we need to parse to get the iterations - # and time - iters, ipopt_time_nofunc, ipopt_time_func, ipopt_total_time = ( - self._parse_ipopt_output(ostreams[0]) + # Call ipopt - passing the files via the subprocess + cmd = self._create_command_line( + basename=basename, config=config, opt_file=opt_file ) + # this seems silly, but we have to give the subprocess slightly longer to finish than + # ipopt + if config.time_limit is not None: + timeout = config.time_limit + min( + max(1.0, 0.01 * config.time_limit), 100 + ) + else: + timeout = None + + ostreams = [io.StringIO()] + if config.tee: + ostreams.append(sys.stdout) + if config.log_solver_output: + ostreams.append(LogStream(level=logging.INFO, logger=logger)) + with TeeStream(*ostreams) as t: + timer.start('subprocess') + process = subprocess.run( + cmd, + timeout=timeout, + env=env, + universal_newlines=True, + stdout=t.STDOUT, + stderr=t.STDERR, + ) + timer.stop('subprocess') + # This is the stuff we need to parse to get the iterations + # and time + iters, ipopt_time_nofunc, ipopt_time_func, ipopt_total_time = ( + self._parse_ipopt_output(ostreams[0]) + ) - if os.path.isfile(basename + '.sol'): - with open(basename + '.sol', 'r') as sol_file: - timer.start('parse_sol') - results = self._parse_solution(sol_file, nl_info) - timer.stop('parse_sol') - else: - results = IpoptResults() - if process.returncode != 0: - results.extra_info.return_code = process.returncode - results.termination_condition = TerminationCondition.error - results.solution_loader = SolSolutionLoader(None, None) + if len(nl_info.variables) == 0: + if len(nl_info.eliminated_vars) == 0: + results = IpoptResults() + results.termination_condition = TerminationCondition.emptyModel + results.solution_loader = SolSolutionLoader(None, None) + else: + results = IpoptResults() + results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied + results.solution_status = SolutionStatus.optimal + results.solution_loader = SolSolutionLoader(None, nl_info=nl_info) + results.iteration_count = 0 + results.timing_info.total_seconds = 0 else: - results.iteration_count = iters - results.timing_info.ipopt_excluding_nlp_functions = ipopt_time_nofunc - results.timing_info.nlp_function_evaluations = ipopt_time_func - results.timing_info.total_seconds = ipopt_total_time + if os.path.isfile(basename + '.sol'): + with open(basename + '.sol', 'r') as sol_file: + timer.start('parse_sol') + results = self._parse_solution(sol_file, nl_info) + timer.stop('parse_sol') + else: + results = IpoptResults() + if process.returncode != 0: + results.extra_info.return_code = process.returncode + results.termination_condition = TerminationCondition.error + results.solution_loader = SolSolutionLoader(None, None) + else: + results.iteration_count = iters + results.timing_info.ipopt_excluding_nlp_functions = ipopt_time_nofunc + results.timing_info.nlp_function_evaluations = ipopt_time_func + results.timing_info.total_seconds = ipopt_total_time if ( config.raise_exception_on_nonoptimal_result and results.solution_status != SolutionStatus.optimal @@ -470,7 +494,8 @@ def solve(self, model, **kwds): ) results.solver_configuration = config - results.solver_log = ostreams[0].getvalue() + if len(nl_info.variables) > 0: + results.solver_log = ostreams[0].getvalue() # Capture/record end-time / wall-time end_timestamp = datetime.datetime.now(datetime.timezone.utc) diff --git a/pyomo/contrib/solver/results.py b/pyomo/contrib/solver/results.py index f2c9cde64fe..88de0624629 100644 --- a/pyomo/contrib/solver/results.py +++ b/pyomo/contrib/solver/results.py @@ -73,6 +73,8 @@ class TerminationCondition(enum.Enum): license was found, the license is of the wrong type for the problem (e.g., problem is too big for type of license), or there was an issue contacting a licensing server. + emptyModel: 12 + The model being solved did not have any variables unknown: 42 All other unrecognized exit statuses fall in this category. """ @@ -101,6 +103,8 @@ class TerminationCondition(enum.Enum): licensingProblems = 11 + emptyModel = 12 + unknown = 42 diff --git a/pyomo/contrib/solver/solution.py b/pyomo/contrib/solver/solution.py index 1812e21a596..5c971597789 100644 --- a/pyomo/contrib/solver/solution.py +++ b/pyomo/contrib/solver/solution.py @@ -14,6 +14,7 @@ from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.var import _GeneralVarData +from pyomo.core.expr import value from pyomo.common.collections import ComponentMap from pyomo.core.staleflag import StaleFlagManager from pyomo.contrib.solver.sol_reader import SolFileData @@ -142,29 +143,48 @@ def __init__(self, sol_data: SolFileData, nl_info: NLWriterInfo) -> None: def load_vars( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None ) -> NoReturn: - if self._nl_info.scaling: - for v, val, scale in zip( - self._nl_info.variables, self._sol_data.primals, self._nl_info.scaling - ): - v.set_value(val / scale, skip_validation=True) + if self._nl_info is None: + raise RuntimeError( + 'Solution loader does not currently have a valid solution. Please ' + 'check the termination condition.' + ) + if self._sol_data is None: + assert len(self._nl_info.variables) == 0 else: - for v, val in zip(self._nl_info.variables, self._sol_data.primals): - v.set_value(val, skip_validation=True) + if self._nl_info.scaling: + for v, val, scale in zip( + self._nl_info.variables, self._sol_data.primals, self._nl_info.scaling.variables + ): + v.set_value(val / scale, skip_validation=True) + else: + for v, val in zip(self._nl_info.variables, self._sol_data.primals): + v.set_value(val, skip_validation=True) + + for v, v_expr in self._nl_info.eliminated_vars: + v.value = value(v_expr) StaleFlagManager.mark_all_as_stale(delayed=True) def get_primals( self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None ) -> Mapping[_GeneralVarData, float]: - if self._nl_info.scaling is None: - scale_list = [1] * len(self._nl_info.variables) - else: - scale_list = self._nl_info.scaling.variables + if self._nl_info is None: + raise RuntimeError( + 'Solution loader does not currently have a valid solution. Please ' + 'check the termination condition.' + ) val_map = dict() - for v, val, scale in zip( - self._nl_info.variables, self._sol_data.primals, scale_list - ): - val_map[id(v)] = val / scale + if self._sol_data is None: + assert len(self._nl_info.variables) == 0 + else: + if self._nl_info.scaling is None: + scale_list = [1] * len(self._nl_info.variables) + else: + scale_list = self._nl_info.scaling.variables + for v, val, scale in zip( + self._nl_info.variables, self._sol_data.primals, scale_list + ): + val_map[id(v)] = val / scale for v, v_expr in self._nl_info.eliminated_vars: val = replace_expressions(v_expr, substitution_map=val_map) @@ -184,18 +204,28 @@ def get_primals( def get_duals( self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None ) -> Dict[_GeneralConstraintData, float]: + if self._nl_info is None: + raise RuntimeError( + 'Solution loader does not currently have a valid solution. Please ' + 'check the termination condition.' + ) + if len(self._nl_info.eliminated_vars) > 0: + raise NotImplementedError('For now, turn presolve off (opt.config.writer_config.linear_presolve=False) to get dual variable values.') + assert self._sol_data is not None + res = dict() if self._nl_info.scaling is None: scale_list = [1] * len(self._nl_info.constraints) + obj_scale = 1 else: scale_list = self._nl_info.scaling.constraints + obj_scale = self._nl_info.scaling.objectives[0] if cons_to_load is None: cons_to_load = set(self._nl_info.constraints) else: cons_to_load = set(cons_to_load) - res = dict() for c, val, scale in zip( self._nl_info.constraints, self._sol_data.duals, scale_list ): if c in cons_to_load: - res[c] = val * scale + res[c] = val * scale / obj_scale return res diff --git a/pyomo/contrib/solver/tests/solvers/test_solvers.py b/pyomo/contrib/solver/tests/solvers/test_solvers.py index 2b9e783ad16..7f916c21dd7 100644 --- a/pyomo/contrib/solver/tests/solvers/test_solvers.py +++ b/pyomo/contrib/solver/tests/solvers/test_solvers.py @@ -36,26 +36,43 @@ nlp_solvers = [('ipopt', Ipopt)] qcp_solvers = [('gurobi', Gurobi), ('ipopt', Ipopt)] miqcqp_solvers = [('gurobi', Gurobi)] +nl_solvers = [('ipopt', Ipopt)] +nl_solvers_set = {i[0] for i in nl_solvers} def _load_tests(solver_list): res = list() for solver_name, solver in solver_list: - test_name = f"{solver_name}" - res.append((test_name, solver)) + if solver_name in nl_solvers_set: + test_name = f"{solver_name}_presolve" + res.append((test_name, solver, True)) + test_name = f"{solver_name}" + res.append((test_name, solver, False)) + else: + test_name = f"{solver_name}" + res.append((test_name, solver, None)) return res @unittest.skipUnless(numpy_available, 'numpy is not available') class TestSolvers(unittest.TestCase): + @parameterized.expand(input=all_solvers) + def test_config_overwrite(self, name: str, opt_class: Type[SolverBase]): + self.assertIsNot(SolverBase.CONFIG, opt_class.CONFIG) + @parameterized.expand(input=_load_tests(all_solvers)) def test_remove_variable_and_objective( - self, name: str, opt_class: Type[SolverBase] + self, name: str, opt_class: Type[SolverBase], use_presolve ): # this test is for issue #2888 opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var(bounds=(2, None)) m.obj = pe.Objective(expr=m.x) @@ -72,10 +89,15 @@ def test_remove_variable_and_objective( self.assertAlmostEqual(m.x.value, 2) @parameterized.expand(input=_load_tests(all_solvers)) - def test_stale_vars(self, name: str, opt_class: Type[SolverBase]): + def test_stale_vars(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -113,10 +135,15 @@ def test_stale_vars(self, name: str, opt_class: Type[SolverBase]): self.assertFalse(m.y.stale) @parameterized.expand(input=_load_tests(all_solvers)) - def test_range_constraint(self, name: str, opt_class: Type[SolverBase]): + def test_range_constraint(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() m.obj = pe.Objective(expr=m.x) @@ -134,10 +161,15 @@ def test_range_constraint(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(duals[m.c], 1) @parameterized.expand(input=_load_tests(all_solvers)) - def test_reduced_costs(self, name: str, opt_class: Type[SolverBase]): + def test_reduced_costs(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var(bounds=(-1, 1)) m.y = pe.Var(bounds=(-2, 2)) @@ -156,10 +188,15 @@ def test_reduced_costs(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(rc[m.y], -4) @parameterized.expand(input=_load_tests(all_solvers)) - def test_reduced_costs2(self, name: str, opt_class: Type[SolverBase]): + def test_reduced_costs2(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var(bounds=(-1, 1)) m.obj = pe.Objective(expr=m.x) @@ -176,10 +213,15 @@ def test_reduced_costs2(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(rc[m.x], 1) @parameterized.expand(input=_load_tests(all_solvers)) - def test_param_changes(self, name: str, opt_class: Type[SolverBase]): + def test_param_changes(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -212,7 +254,7 @@ def test_param_changes(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) @parameterized.expand(input=_load_tests(all_solvers)) - def test_immutable_param(self, name: str, opt_class: Type[SolverBase]): + def test_immutable_param(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): """ This test is important because component_data_objects returns immutable params as floats. We want to make sure we process these correctly. @@ -220,6 +262,11 @@ def test_immutable_param(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -252,12 +299,17 @@ def test_immutable_param(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) @parameterized.expand(input=_load_tests(all_solvers)) - def test_equality(self, name: str, opt_class: Type[SolverBase]): + def test_equality(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') - if isinstance(opt, Ipopt): - opt.config.writer_config.linear_presolve = False + check_duals = True + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + check_duals = False + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -285,15 +337,21 @@ def test_equality(self, name: str, opt_class: Type[SolverBase]): else: bound = res.objective_bound self.assertTrue(bound <= m.y.value) - duals = res.solution_loader.get_duals() - self.assertAlmostEqual(duals[m.c1], (1 + a1 / (a2 - a1))) - self.assertAlmostEqual(duals[m.c2], -a1 / (a2 - a1)) + if check_duals: + duals = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.c1], (1 + a1 / (a2 - a1))) + self.assertAlmostEqual(duals[m.c2], -a1 / (a2 - a1)) @parameterized.expand(input=_load_tests(all_solvers)) - def test_linear_expression(self, name: str, opt_class: Type[SolverBase]): + def test_linear_expression(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -328,10 +386,17 @@ def test_linear_expression(self, name: str, opt_class: Type[SolverBase]): self.assertTrue(bound <= m.y.value) @parameterized.expand(input=_load_tests(all_solvers)) - def test_no_objective(self, name: str, opt_class: Type[SolverBase]): + def test_no_objective(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + check_duals = True + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + check_duals = False + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -354,15 +419,21 @@ def test_no_objective(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) self.assertEqual(res.incumbent_objective, None) self.assertEqual(res.objective_bound, None) - duals = res.solution_loader.get_duals() - self.assertAlmostEqual(duals[m.c1], 0) - self.assertAlmostEqual(duals[m.c2], 0) + if check_duals: + duals = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.c1], 0) + self.assertAlmostEqual(duals[m.c2], 0) @parameterized.expand(input=_load_tests(all_solvers)) - def test_add_remove_cons(self, name: str, opt_class: Type[SolverBase]): + def test_add_remove_cons(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -413,10 +484,15 @@ def test_add_remove_cons(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) @parameterized.expand(input=_load_tests(all_solvers)) - def test_results_infeasible(self, name: str, opt_class: Type[SolverBase]): + def test_results_infeasible(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -462,10 +538,15 @@ def test_results_infeasible(self, name: str, opt_class: Type[SolverBase]): res.solution_loader.get_reduced_costs() @parameterized.expand(input=_load_tests(all_solvers)) - def test_duals(self, name: str, opt_class: Type[SolverBase]): + def test_duals(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -486,11 +567,16 @@ def test_duals(self, name: str, opt_class: Type[SolverBase]): @parameterized.expand(input=_load_tests(qcp_solvers)) def test_mutable_quadratic_coefficient( - self, name: str, opt_class: Type[SolverBase] + self, name: str, opt_class: Type[SolverBase], use_presolve: bool ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -509,10 +595,15 @@ def test_mutable_quadratic_coefficient( self.assertAlmostEqual(m.y.value, 0.0869525991355825, 4) @parameterized.expand(input=_load_tests(qcp_solvers)) - def test_mutable_quadratic_objective(self, name: str, opt_class: Type[SolverBase]): + def test_mutable_quadratic_objective(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -534,7 +625,7 @@ def test_mutable_quadratic_objective(self, name: str, opt_class: Type[SolverBase self.assertAlmostEqual(m.y.value, 0.09227926676152151, 4) @parameterized.expand(input=_load_tests(all_solvers)) - def test_fixed_vars(self, name: str, opt_class: Type[SolverBase]): + def test_fixed_vars(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): for treat_fixed_vars_as_params in [True, False]: opt: SolverBase = opt_class() if opt.is_persistent(): @@ -543,6 +634,11 @@ def test_fixed_vars(self, name: str, opt_class: Type[SolverBase]): ) if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() m.x.fix(0) @@ -575,12 +671,17 @@ def test_fixed_vars(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(m.y.value, 2) @parameterized.expand(input=_load_tests(all_solvers)) - def test_fixed_vars_2(self, name: str, opt_class: Type[SolverBase]): + def test_fixed_vars_2(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if opt.is_persistent(): opt.config.auto_updates.treat_fixed_vars_as_params = True if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() m.x.fix(0) @@ -613,12 +714,17 @@ def test_fixed_vars_2(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(m.y.value, 2) @parameterized.expand(input=_load_tests(all_solvers)) - def test_fixed_vars_3(self, name: str, opt_class: Type[SolverBase]): + def test_fixed_vars_3(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if opt.is_persistent(): opt.config.auto_updates.treat_fixed_vars_as_params = True if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -626,15 +732,21 @@ def test_fixed_vars_3(self, name: str, opt_class: Type[SolverBase]): m.c1 = pe.Constraint(expr=m.x == 2 / m.y) m.y.fix(1) res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 3) self.assertAlmostEqual(m.x.value, 2) @parameterized.expand(input=_load_tests(nlp_solvers)) - def test_fixed_vars_4(self, name: str, opt_class: Type[SolverBase]): + def test_fixed_vars_4(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if opt.is_persistent(): opt.config.auto_updates.treat_fixed_vars_as_params = True if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -649,10 +761,15 @@ def test_fixed_vars_4(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(m.y.value, 2**0.5) @parameterized.expand(input=_load_tests(all_solvers)) - def test_mutable_param_with_range(self, name: str, opt_class: Type[SolverBase]): + def test_mutable_param_with_range(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False try: import numpy as np except: @@ -743,10 +860,15 @@ def test_mutable_param_with_range(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(duals[m.con2], -a1 / (a2 - a1), 6) @parameterized.expand(input=_load_tests(all_solvers)) - def test_add_and_remove_vars(self, name: str, opt_class: Type[SolverBase]): + def test_add_and_remove_vars(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.y = pe.Var(bounds=(-1, None)) m.obj = pe.Objective(expr=m.y) @@ -789,10 +911,15 @@ def test_add_and_remove_vars(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(m.y.value, -1) @parameterized.expand(input=_load_tests(nlp_solvers)) - def test_exp(self, name: str, opt_class: Type[SolverBase]): + def test_exp(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -803,10 +930,15 @@ def test_exp(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(m.y.value, 0.6529186341994245) @parameterized.expand(input=_load_tests(nlp_solvers)) - def test_log(self, name: str, opt_class: Type[SolverBase]): + def test_log(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var(initialize=1) m.y = pe.Var() @@ -817,10 +949,15 @@ def test_log(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(m.y.value, -0.42630274815985264) @parameterized.expand(input=_load_tests(all_solvers)) - def test_with_numpy(self, name: str, opt_class: Type[SolverBase]): + def test_with_numpy(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -845,10 +982,15 @@ def test_with_numpy(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) @parameterized.expand(input=_load_tests(all_solvers)) - def test_bounds_with_params(self, name: str, opt_class: Type[SolverBase]): + def test_bounds_with_params(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.y = pe.Var() m.p = pe.Param(mutable=True) @@ -877,10 +1019,15 @@ def test_bounds_with_params(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(m.y.value, 3) @parameterized.expand(input=_load_tests(all_solvers)) - def test_solution_loader(self, name: str, opt_class: Type[SolverBase]): + def test_solution_loader(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var(bounds=(1, None)) m.y = pe.Var() @@ -927,10 +1074,15 @@ def test_solution_loader(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(duals[m.c1], 1) @parameterized.expand(input=_load_tests(all_solvers)) - def test_time_limit(self, name: str, opt_class: Type[SolverBase]): + def test_time_limit(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False from sys import platform if platform == 'win32': @@ -983,10 +1135,15 @@ def test_time_limit(self, name: str, opt_class: Type[SolverBase]): ) @parameterized.expand(input=_load_tests(all_solvers)) - def test_objective_changes(self, name: str, opt_class: Type[SolverBase]): + def test_objective_changes(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -1047,10 +1204,15 @@ def test_objective_changes(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(res.incumbent_objective, 4) @parameterized.expand(input=_load_tests(all_solvers)) - def test_domain(self, name: str, opt_class: Type[SolverBase]): + def test_domain(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var(bounds=(1, None), domain=pe.NonNegativeReals) m.obj = pe.Objective(expr=m.x) @@ -1071,10 +1233,15 @@ def test_domain(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(res.incumbent_objective, 0) @parameterized.expand(input=_load_tests(mip_solvers)) - def test_domain_with_integers(self, name: str, opt_class: Type[SolverBase]): + def test_domain_with_integers(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var(bounds=(-1, None), domain=pe.NonNegativeIntegers) m.obj = pe.Objective(expr=m.x) @@ -1095,10 +1262,15 @@ def test_domain_with_integers(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(res.incumbent_objective, 1) @parameterized.expand(input=_load_tests(all_solvers)) - def test_fixed_binaries(self, name: str, opt_class: Type[SolverBase]): + def test_fixed_binaries(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var(domain=pe.Binary) m.y = pe.Var() @@ -1122,10 +1294,15 @@ def test_fixed_binaries(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(res.incumbent_objective, 1) @parameterized.expand(input=_load_tests(mip_solvers)) - def test_with_gdp(self, name: str, opt_class: Type[SolverBase]): + def test_with_gdp(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var(bounds=(-10, 10)) @@ -1152,11 +1329,16 @@ def test_with_gdp(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(m.x.value, 0) self.assertAlmostEqual(m.y.value, 1) - @parameterized.expand(input=all_solvers) - def test_variables_elsewhere(self, name: str, opt_class: Type[SolverBase]): + @parameterized.expand(input=_load_tests(all_solvers)) + def test_variables_elsewhere(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() @@ -1179,11 +1361,16 @@ def test_variables_elsewhere(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(m.x.value, 0) self.assertAlmostEqual(m.y.value, 2) - @parameterized.expand(input=all_solvers) - def test_variables_elsewhere2(self, name: str, opt_class: Type[SolverBase]): + @parameterized.expand(input=_load_tests(all_solvers)) + def test_variables_elsewhere2(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var() @@ -1215,10 +1402,15 @@ def test_variables_elsewhere2(self, name: str, opt_class: Type[SolverBase]): self.assertNotIn(m.z, sol) @parameterized.expand(input=_load_tests(all_solvers)) - def test_bug_1(self, name: str, opt_class: Type[SolverBase]): + def test_bug_1(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False m = pe.ConcreteModel() m.x = pe.Var(bounds=(3, 7)) @@ -1238,7 +1430,7 @@ def test_bug_1(self, name: str, opt_class: Type[SolverBase]): self.assertAlmostEqual(res.incumbent_objective, 3) @parameterized.expand(input=_load_tests(all_solvers)) - def test_bug_2(self, name: str, opt_class: Type[SolverBase]): + def test_bug_2(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): """ This test is for a bug where an objective containing a fixed variable does not get updated properly when the variable is unfixed. @@ -1247,6 +1439,11 @@ def test_bug_2(self, name: str, opt_class: Type[SolverBase]): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False if opt.is_persistent(): opt.config.auto_updates.treat_fixed_vars_as_params = fixed_var_option @@ -1266,6 +1463,63 @@ def test_bug_2(self, name: str, opt_class: Type[SolverBase]): res = opt.solve(m) self.assertAlmostEqual(res.incumbent_objective, -18, 5) + @parameterized.expand(input=_load_tests(all_solvers)) + def test_scaling(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest(f'Solver {opt.name} not available.') + check_duals = True + if any(name.startswith(i) for i in nl_solvers_set): + if use_presolve: + check_duals = False + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False + + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.obj = pe.Objective(expr=m.y) + m.c1 = pe.Constraint(expr=m.y >= (m.x - 1) + 1) + m.c2 = pe.Constraint(expr=m.y >= -(m.x - 1) + 1) + m.scaling_factor = pe.Suffix(direction=pe.Suffix.EXPORT) + m.scaling_factor[m.x] = 0.5 + m.scaling_factor[m.y] = 2 + m.scaling_factor[m.c1] = 0.5 + m.scaling_factor[m.c2] = 2 + m.scaling_factor[m.obj] = 2 + + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 1) + self.assertAlmostEqual(m.x.value, 1) + self.assertAlmostEqual(m.y.value, 1) + primals = res.solution_loader.get_primals() + self.assertAlmostEqual(primals[m.x], 1) + self.assertAlmostEqual(primals[m.y], 1) + if check_duals: + duals = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.c1], -0.5) + self.assertAlmostEqual(duals[m.c2], -0.5) + rc = res.solution_loader.get_reduced_costs() + self.assertAlmostEqual(rc[m.x], 0) + self.assertAlmostEqual(rc[m.y], 0) + + m.x.setlb(2) + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 2) + self.assertAlmostEqual(m.x.value, 2) + self.assertAlmostEqual(m.y.value, 2) + primals = res.solution_loader.get_primals() + self.assertAlmostEqual(primals[m.x], 2) + self.assertAlmostEqual(primals[m.y], 2) + if check_duals: + duals = res.solution_loader.get_duals() + self.assertAlmostEqual(duals[m.c1], -1) + self.assertAlmostEqual(duals[m.c2], 0) + rc = res.solution_loader.get_reduced_costs() + self.assertAlmostEqual(rc[m.x], 1) + self.assertAlmostEqual(rc[m.y], 0) + class TestLegacySolverInterface(unittest.TestCase): @parameterized.expand(input=all_solvers) From 2368ab94fc12d38eff15277ce4acd97c471cbd81 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 19 Feb 2024 15:37:41 -0700 Subject: [PATCH 1076/1204] Work around strange deepcopy bug --- pyomo/common/collections/component_map.py | 2 +- pyomo/common/collections/component_set.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pyomo/common/collections/component_map.py b/pyomo/common/collections/component_map.py index 0851ffad301..90d985990d1 100644 --- a/pyomo/common/collections/component_map.py +++ b/pyomo/common/collections/component_map.py @@ -16,7 +16,7 @@ def _rehash_keys(encode, val): if encode: - return list(val.values()) + return tuple(val.values()) else: # object id() may have changed after unpickling, # so we rebuild the dictionary keys diff --git a/pyomo/common/collections/component_set.py b/pyomo/common/collections/component_set.py index f1fe7bc8cd6..bad40e90195 100644 --- a/pyomo/common/collections/component_set.py +++ b/pyomo/common/collections/component_set.py @@ -18,7 +18,17 @@ def _rehash_keys(encode, val): if encode: - return list(val.values()) + # TBD [JDS 2/2024]: if we + # + # return list(val.values()) + # + # here, then we get a strange failure when deepcopying + # ComponentSets containing an _ImplicitAny domain. We could + # track it down to teh implementation of + # autoslots.fast_deepcopy, but couldn't find an obvious bug. + # There is no error if we just return the original dict, or if + # we return a tuple(val.values) + return tuple(val.values()) else: # object id() may have changed after unpickling, # so we rebuild the dictionary keys From 0b6fd076de3a4b7bb48d924d3bce23f6913d689a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 19 Feb 2024 15:41:04 -0700 Subject: [PATCH 1077/1204] NFC: fix spelling --- pyomo/common/autoslots.py | 2 +- pyomo/common/collections/component_set.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/common/autoslots.py b/pyomo/common/autoslots.py index cb79d4a0338..89fefaf4f21 100644 --- a/pyomo/common/autoslots.py +++ b/pyomo/common/autoslots.py @@ -29,7 +29,7 @@ def _deepcopy_tuple(obj, memo, _id): unchanged = False if unchanged: # Python does not duplicate "unchanged" tuples (i.e. allows the - # original objecct to be returned from deepcopy()). We will + # original object to be returned from deepcopy()). We will # preserve that behavior here. # # It also appears to be faster *not* to cache the fact that this diff --git a/pyomo/common/collections/component_set.py b/pyomo/common/collections/component_set.py index bad40e90195..d99dd694b64 100644 --- a/pyomo/common/collections/component_set.py +++ b/pyomo/common/collections/component_set.py @@ -24,7 +24,7 @@ def _rehash_keys(encode, val): # # here, then we get a strange failure when deepcopying # ComponentSets containing an _ImplicitAny domain. We could - # track it down to teh implementation of + # track it down to the implementation of # autoslots.fast_deepcopy, but couldn't find an obvious bug. # There is no error if we just return the original dict, or if # we return a tuple(val.values) From b9a6e8341da751c3b1ff6f834cfb110d8c5049d8 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 19 Feb 2024 15:46:45 -0700 Subject: [PATCH 1078/1204] error message --- pyomo/contrib/solver/solution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/solver/solution.py b/pyomo/contrib/solver/solution.py index 5c971597789..e4734bd8a46 100644 --- a/pyomo/contrib/solver/solution.py +++ b/pyomo/contrib/solver/solution.py @@ -211,7 +211,7 @@ def get_duals( ) if len(self._nl_info.eliminated_vars) > 0: raise NotImplementedError('For now, turn presolve off (opt.config.writer_config.linear_presolve=False) to get dual variable values.') - assert self._sol_data is not None + assert self._sol_data is not None, "report this to the Pyomo developers" res = dict() if self._nl_info.scaling is None: scale_list = [1] * len(self._nl_info.constraints) From 52391fc198bc4adc80b13ac8676f2cda9b1755d9 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 15:52:23 -0700 Subject: [PATCH 1079/1204] Missed capitalization --- pyomo/contrib/solver/ipopt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 074e5b19c5c..3ec69879675 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -47,7 +47,7 @@ logger = logging.getLogger(__name__) -class ipoptSolverError(PyomoException): +class IpoptSolverError(PyomoException): """ General exception to catch solver system errors """ @@ -315,7 +315,7 @@ def solve(self, model, **kwds): # Check if solver is available avail = self.available(config) if not avail: - raise ipoptSolverError( + raise IpoptSolverError( f'Solver {self.__class__} is not available ({avail}).' ) if config.threads: From bc1b3e9cec2266b7383627601511ac541212ffd4 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 15:54:37 -0700 Subject: [PATCH 1080/1204] Apply black to updates --- pyomo/contrib/solver/ipopt.py | 12 +- pyomo/contrib/solver/solution.py | 8 +- .../solver/tests/solvers/test_solvers.py | 104 +++++++++++++----- 3 files changed, 93 insertions(+), 31 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 876ca749921..537e6f85968 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -140,7 +140,9 @@ def get_reduced_costs( 'check the termination condition.' ) if len(self._nl_info.eliminated_vars) > 0: - raise NotImplementedError('For now, turn presolve off (opt.config.writer_config.linear_presolve=False) to get reduced costs.') + raise NotImplementedError( + 'For now, turn presolve off (opt.config.writer_config.linear_presolve=False) to get reduced costs.' + ) assert self._sol_data is not None if self._nl_info.scaling is None: scale_list = [1] * len(self._nl_info.variables) @@ -417,7 +419,9 @@ def solve(self, model, **kwds): results.solution_loader = SolSolutionLoader(None, None) else: results = IpoptResults() - results.termination_condition = TerminationCondition.convergenceCriteriaSatisfied + results.termination_condition = ( + TerminationCondition.convergenceCriteriaSatisfied + ) results.solution_status = SolutionStatus.optimal results.solution_loader = SolSolutionLoader(None, nl_info=nl_info) results.iteration_count = 0 @@ -436,7 +440,9 @@ def solve(self, model, **kwds): results.solution_loader = SolSolutionLoader(None, None) else: results.iteration_count = iters - results.timing_info.ipopt_excluding_nlp_functions = ipopt_time_nofunc + results.timing_info.ipopt_excluding_nlp_functions = ( + ipopt_time_nofunc + ) results.timing_info.nlp_function_evaluations = ipopt_time_func results.timing_info.total_seconds = ipopt_total_time if ( diff --git a/pyomo/contrib/solver/solution.py b/pyomo/contrib/solver/solution.py index e4734bd8a46..7cef86a4e8f 100644 --- a/pyomo/contrib/solver/solution.py +++ b/pyomo/contrib/solver/solution.py @@ -153,7 +153,9 @@ def load_vars( else: if self._nl_info.scaling: for v, val, scale in zip( - self._nl_info.variables, self._sol_data.primals, self._nl_info.scaling.variables + self._nl_info.variables, + self._sol_data.primals, + self._nl_info.scaling.variables, ): v.set_value(val / scale, skip_validation=True) else: @@ -210,7 +212,9 @@ def get_duals( 'check the termination condition.' ) if len(self._nl_info.eliminated_vars) > 0: - raise NotImplementedError('For now, turn presolve off (opt.config.writer_config.linear_presolve=False) to get dual variable values.') + raise NotImplementedError( + 'For now, turn presolve off (opt.config.writer_config.linear_presolve=False) to get dual variable values.' + ) assert self._sol_data is not None, "report this to the Pyomo developers" res = dict() if self._nl_info.scaling is None: diff --git a/pyomo/contrib/solver/tests/solvers/test_solvers.py b/pyomo/contrib/solver/tests/solvers/test_solvers.py index 7f916c21dd7..c6c73ea2dc7 100644 --- a/pyomo/contrib/solver/tests/solvers/test_solvers.py +++ b/pyomo/contrib/solver/tests/solvers/test_solvers.py @@ -89,7 +89,9 @@ def test_remove_variable_and_objective( self.assertAlmostEqual(m.x.value, 2) @parameterized.expand(input=_load_tests(all_solvers)) - def test_stale_vars(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_stale_vars( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -135,7 +137,9 @@ def test_stale_vars(self, name: str, opt_class: Type[SolverBase], use_presolve: self.assertFalse(m.y.stale) @parameterized.expand(input=_load_tests(all_solvers)) - def test_range_constraint(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_range_constraint( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -161,7 +165,9 @@ def test_range_constraint(self, name: str, opt_class: Type[SolverBase], use_pres self.assertAlmostEqual(duals[m.c], 1) @parameterized.expand(input=_load_tests(all_solvers)) - def test_reduced_costs(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_reduced_costs( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -188,7 +194,9 @@ def test_reduced_costs(self, name: str, opt_class: Type[SolverBase], use_presolv self.assertAlmostEqual(rc[m.y], -4) @parameterized.expand(input=_load_tests(all_solvers)) - def test_reduced_costs2(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_reduced_costs2( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -213,7 +221,9 @@ def test_reduced_costs2(self, name: str, opt_class: Type[SolverBase], use_presol self.assertAlmostEqual(rc[m.x], 1) @parameterized.expand(input=_load_tests(all_solvers)) - def test_param_changes(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_param_changes( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -254,7 +264,9 @@ def test_param_changes(self, name: str, opt_class: Type[SolverBase], use_presolv self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) @parameterized.expand(input=_load_tests(all_solvers)) - def test_immutable_param(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_immutable_param( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): """ This test is important because component_data_objects returns immutable params as floats. We want to make sure we process these correctly. @@ -343,7 +355,9 @@ def test_equality(self, name: str, opt_class: Type[SolverBase], use_presolve: bo self.assertAlmostEqual(duals[m.c2], -a1 / (a2 - a1)) @parameterized.expand(input=_load_tests(all_solvers)) - def test_linear_expression(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_linear_expression( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -386,7 +400,9 @@ def test_linear_expression(self, name: str, opt_class: Type[SolverBase], use_pre self.assertTrue(bound <= m.y.value) @parameterized.expand(input=_load_tests(all_solvers)) - def test_no_objective(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_no_objective( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -425,7 +441,9 @@ def test_no_objective(self, name: str, opt_class: Type[SolverBase], use_presolve self.assertAlmostEqual(duals[m.c2], 0) @parameterized.expand(input=_load_tests(all_solvers)) - def test_add_remove_cons(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_add_remove_cons( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -484,7 +502,9 @@ def test_add_remove_cons(self, name: str, opt_class: Type[SolverBase], use_preso self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) @parameterized.expand(input=_load_tests(all_solvers)) - def test_results_infeasible(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_results_infeasible( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -595,7 +615,9 @@ def test_mutable_quadratic_coefficient( self.assertAlmostEqual(m.y.value, 0.0869525991355825, 4) @parameterized.expand(input=_load_tests(qcp_solvers)) - def test_mutable_quadratic_objective(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_mutable_quadratic_objective( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -625,7 +647,9 @@ def test_mutable_quadratic_objective(self, name: str, opt_class: Type[SolverBase self.assertAlmostEqual(m.y.value, 0.09227926676152151, 4) @parameterized.expand(input=_load_tests(all_solvers)) - def test_fixed_vars(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_fixed_vars( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): for treat_fixed_vars_as_params in [True, False]: opt: SolverBase = opt_class() if opt.is_persistent(): @@ -671,7 +695,9 @@ def test_fixed_vars(self, name: str, opt_class: Type[SolverBase], use_presolve: self.assertAlmostEqual(m.y.value, 2) @parameterized.expand(input=_load_tests(all_solvers)) - def test_fixed_vars_2(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_fixed_vars_2( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if opt.is_persistent(): opt.config.auto_updates.treat_fixed_vars_as_params = True @@ -714,7 +740,9 @@ def test_fixed_vars_2(self, name: str, opt_class: Type[SolverBase], use_presolve self.assertAlmostEqual(m.y.value, 2) @parameterized.expand(input=_load_tests(all_solvers)) - def test_fixed_vars_3(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_fixed_vars_3( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if opt.is_persistent(): opt.config.auto_updates.treat_fixed_vars_as_params = True @@ -736,7 +764,9 @@ def test_fixed_vars_3(self, name: str, opt_class: Type[SolverBase], use_presolve self.assertAlmostEqual(m.x.value, 2) @parameterized.expand(input=_load_tests(nlp_solvers)) - def test_fixed_vars_4(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_fixed_vars_4( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if opt.is_persistent(): opt.config.auto_updates.treat_fixed_vars_as_params = True @@ -761,7 +791,9 @@ def test_fixed_vars_4(self, name: str, opt_class: Type[SolverBase], use_presolve self.assertAlmostEqual(m.y.value, 2**0.5) @parameterized.expand(input=_load_tests(all_solvers)) - def test_mutable_param_with_range(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_mutable_param_with_range( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -860,7 +892,9 @@ def test_mutable_param_with_range(self, name: str, opt_class: Type[SolverBase], self.assertAlmostEqual(duals[m.con2], -a1 / (a2 - a1), 6) @parameterized.expand(input=_load_tests(all_solvers)) - def test_add_and_remove_vars(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_add_and_remove_vars( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -949,7 +983,9 @@ def test_log(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): self.assertAlmostEqual(m.y.value, -0.42630274815985264) @parameterized.expand(input=_load_tests(all_solvers)) - def test_with_numpy(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_with_numpy( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -982,7 +1018,9 @@ def test_with_numpy(self, name: str, opt_class: Type[SolverBase], use_presolve: self.assertAlmostEqual(m.y.value, a1 * (b2 - b1) / (a1 - a2) + b1) @parameterized.expand(input=_load_tests(all_solvers)) - def test_bounds_with_params(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_bounds_with_params( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -1019,7 +1057,9 @@ def test_bounds_with_params(self, name: str, opt_class: Type[SolverBase], use_pr self.assertAlmostEqual(m.y.value, 3) @parameterized.expand(input=_load_tests(all_solvers)) - def test_solution_loader(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_solution_loader( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -1074,7 +1114,9 @@ def test_solution_loader(self, name: str, opt_class: Type[SolverBase], use_preso self.assertAlmostEqual(duals[m.c1], 1) @parameterized.expand(input=_load_tests(all_solvers)) - def test_time_limit(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_time_limit( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -1135,7 +1177,9 @@ def test_time_limit(self, name: str, opt_class: Type[SolverBase], use_presolve: ) @parameterized.expand(input=_load_tests(all_solvers)) - def test_objective_changes(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_objective_changes( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -1233,7 +1277,9 @@ def test_domain(self, name: str, opt_class: Type[SolverBase], use_presolve: bool self.assertAlmostEqual(res.incumbent_objective, 0) @parameterized.expand(input=_load_tests(mip_solvers)) - def test_domain_with_integers(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_domain_with_integers( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -1262,7 +1308,9 @@ def test_domain_with_integers(self, name: str, opt_class: Type[SolverBase], use_ self.assertAlmostEqual(res.incumbent_objective, 1) @parameterized.expand(input=_load_tests(all_solvers)) - def test_fixed_binaries(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_fixed_binaries( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -1330,7 +1378,9 @@ def test_with_gdp(self, name: str, opt_class: Type[SolverBase], use_presolve: bo self.assertAlmostEqual(m.y.value, 1) @parameterized.expand(input=_load_tests(all_solvers)) - def test_variables_elsewhere(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_variables_elsewhere( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') @@ -1362,7 +1412,9 @@ def test_variables_elsewhere(self, name: str, opt_class: Type[SolverBase], use_p self.assertAlmostEqual(m.y.value, 2) @parameterized.expand(input=_load_tests(all_solvers)) - def test_variables_elsewhere2(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): + def test_variables_elsewhere2( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): opt: SolverBase = opt_class() if not opt.available(): raise unittest.SkipTest(f'Solver {opt.name} not available.') From ac7ce8b2bfbd06a1631f3f25a96ac0a48a4e7571 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 16:01:18 -0700 Subject: [PATCH 1081/1204] Update error messages --- pyomo/contrib/solver/solution.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/solver/solution.py b/pyomo/contrib/solver/solution.py index 7cef86a4e8f..32e84d2abca 100644 --- a/pyomo/contrib/solver/solution.py +++ b/pyomo/contrib/solver/solution.py @@ -16,6 +16,7 @@ from pyomo.core.base.var import _GeneralVarData from pyomo.core.expr import value from pyomo.common.collections import ComponentMap +from pyomo.common.errors import DeveloperError from pyomo.core.staleflag import StaleFlagManager from pyomo.contrib.solver.sol_reader import SolFileData from pyomo.repn.plugins.nl_writer import NLWriterInfo @@ -146,7 +147,7 @@ def load_vars( if self._nl_info is None: raise RuntimeError( 'Solution loader does not currently have a valid solution. Please ' - 'check the termination condition.' + 'check results.TerminationCondition and/or results.SolutionStatus.' ) if self._sol_data is None: assert len(self._nl_info.variables) == 0 @@ -173,7 +174,7 @@ def get_primals( if self._nl_info is None: raise RuntimeError( 'Solution loader does not currently have a valid solution. Please ' - 'check the termination condition.' + 'check results.TerminationCondition and/or results.SolutionStatus.' ) val_map = dict() if self._sol_data is None: @@ -209,13 +210,18 @@ def get_duals( if self._nl_info is None: raise RuntimeError( 'Solution loader does not currently have a valid solution. Please ' - 'check the termination condition.' + 'check results.TerminationCondition and/or results.SolutionStatus.' ) if len(self._nl_info.eliminated_vars) > 0: raise NotImplementedError( - 'For now, turn presolve off (opt.config.writer_config.linear_presolve=False) to get dual variable values.' + 'For now, turn presolve off (opt.config.writer_config.linear_presolve=False) ' + 'to get dual variable values.' + ) + if self._sol_data is None: + raise DeveloperError( + "Solution data is empty. This should not " + "have happened. Report this error to the Pyomo Developers." ) - assert self._sol_data is not None, "report this to the Pyomo developers" res = dict() if self._nl_info.scaling is None: scale_list = [1] * len(self._nl_info.constraints) From 48533dbc566525deb1c6f20acb93bdd394d8e162 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 19 Feb 2024 16:18:10 -0700 Subject: [PATCH 1082/1204] NFC: update copyright --- pyomo/common/tests/test_component_map.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/common/tests/test_component_map.py b/pyomo/common/tests/test_component_map.py index 9e771175d42..b9e2a953047 100644 --- a/pyomo/common/tests/test_component_map.py +++ b/pyomo/common/tests/test_component_map.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# 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 From 3828841bf5e327f69c5369acd9ce089e28a81751 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 16:33:21 -0700 Subject: [PATCH 1083/1204] Forgot targets for alt_wheels --- .github/workflows/release_wheel_creation.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml index 17152dc3d1e..932b0d8eea6 100644 --- a/.github/workflows/release_wheel_creation.yml +++ b/.github/workflows/release_wheel_creation.yml @@ -68,6 +68,18 @@ jobs: os: [ubuntu-22.04] arch: [all] wheel-version: ['cp38*', 'cp39*', 'cp310*', 'cp311*', 'cp312*'] + + include: + - wheel-version: 'cp38*' + TARGET: 'py38' + - wheel-version: 'cp39*' + TARGET: 'py39' + - wheel-version: 'cp310*' + TARGET: 'py310' + - wheel-version: 'cp311*' + TARGET: 'py311' + - wheel-version: 'cp312*' + TARGET: 'py312' steps: - uses: actions/checkout@v4 - name: Set up QEMU From e9a99499d1b5b4184a2ada6accd160f67b4c7cd5 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 19 Feb 2024 16:40:52 -0700 Subject: [PATCH 1084/1204] Add DefaultComponentMap --- pyomo/common/collections/__init__.py | 2 +- pyomo/common/collections/component_map.py | 29 +++++++++++++++ pyomo/common/tests/test_component_map.py | 44 +++++++++++++++++++++-- 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/pyomo/common/collections/__init__.py b/pyomo/common/collections/__init__.py index 93785124e3c..717caf87b2c 100644 --- a/pyomo/common/collections/__init__.py +++ b/pyomo/common/collections/__init__.py @@ -14,6 +14,6 @@ from collections import UserDict from .orderedset import OrderedDict, OrderedSet -from .component_map import ComponentMap +from .component_map import ComponentMap, DefaultComponentMap from .component_set import ComponentSet from .bunch import Bunch diff --git a/pyomo/common/collections/component_map.py b/pyomo/common/collections/component_map.py index 90d985990d1..be44bd6ff68 100644 --- a/pyomo/common/collections/component_map.py +++ b/pyomo/common/collections/component_map.py @@ -179,3 +179,32 @@ def setdefault(self, key, default=None): else: self[key] = default return default + + +class DefaultComponentMap(ComponentMap): + """A :py:class:`defaultdict` admitting Pyomo Components as keys + + This class is a replacement for defaultdict that allows Pyomo + modeling components to be used as entry keys. The base + implementation builds on :py:class:`ComponentMap`. + + """ + + __slots__ = ('default_factory',) + + def __init__(self, default_factory=None, *args, **kwargs): + super().__init__(*args, **kwargs) + self.default_factory = default_factory + + def __missing__(self, key): + if self.default_factory is None: + raise KeyError(key) + self[key] = ans = self.default_factory() + return ans + + def __getitem__(self, obj): + _key = _hasher[obj.__class__](obj) + if _key in self._dict: + return self._dict[_key][1] + else: + return self.__missing__(obj) diff --git a/pyomo/common/tests/test_component_map.py b/pyomo/common/tests/test_component_map.py index b9e2a953047..7cd4ec2c458 100644 --- a/pyomo/common/tests/test_component_map.py +++ b/pyomo/common/tests/test_component_map.py @@ -11,8 +11,8 @@ import pyomo.common.unittest as unittest -from pyomo.common.collections import ComponentMap -from pyomo.environ import ConcreteModel, Var, Constraint +from pyomo.common.collections import ComponentMap, ComponentSet, DefaultComponentMap +from pyomo.environ import ConcreteModel, Block, Var, Constraint class TestComponentMap(unittest.TestCase): @@ -48,3 +48,43 @@ def test_tuple(self): self.assertNotIn((1, (2, i.v)), m.cm) self.assertIn((1, (2, m.v)), m.cm) self.assertNotIn((1, (2, m.v)), i.cm) + + +class TestDefaultComponentMap(unittest.TestCase): + def test_default_component_map(self): + dcm = DefaultComponentMap(ComponentSet) + + m = ConcreteModel() + m.x = Var() + m.b = Block() + m.b.y = Var() + + self.assertEqual(len(dcm), 0) + + dcm[m.x].add(m) + self.assertEqual(len(dcm), 1) + self.assertIn(m.x, dcm) + self.assertIn(m, dcm[m.x]) + + dcm[m.b.y].add(m.b) + self.assertEqual(len(dcm), 2) + self.assertIn(m.b.y, dcm) + self.assertNotIn(m, dcm[m.b.y]) + self.assertIn(m.b, dcm[m.b.y]) + + dcm[m.b.y].add(m) + self.assertEqual(len(dcm), 2) + self.assertIn(m.b.y, dcm) + self.assertIn(m, dcm[m.b.y]) + self.assertIn(m.b, dcm[m.b.y]) + + def test_no_default_factory(self): + dcm = DefaultComponentMap() + + dcm['found'] = 5 + self.assertEqual(len(dcm), 1) + self.assertIn('found', dcm) + self.assertEqual(dcm['found'], 5) + + with self.assertRaisesRegex(KeyError, "'missing'"): + dcm["missing"] From cf6364cc870aa2050a49a9baba7687b1ceb45742 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 19 Feb 2024 17:42:55 -0700 Subject: [PATCH 1085/1204] Additional attempt to resolve ComponentMap deepcopy --- pyomo/common/collections/component_map.py | 4 ++-- pyomo/common/collections/component_set.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/common/collections/component_map.py b/pyomo/common/collections/component_map.py index be44bd6ff68..c110e9f390b 100644 --- a/pyomo/common/collections/component_map.py +++ b/pyomo/common/collections/component_map.py @@ -16,11 +16,11 @@ def _rehash_keys(encode, val): if encode: - return tuple(val.values()) + return val else: # object id() may have changed after unpickling, # so we rebuild the dictionary keys - return {_hasher[obj.__class__](obj): (obj, v) for obj, v in val} + return {_hasher[obj.__class__](obj): (obj, v) for obj, v in val.values()} class _Hasher(collections.defaultdict): diff --git a/pyomo/common/collections/component_set.py b/pyomo/common/collections/component_set.py index d99dd694b64..19d2ef2f7f9 100644 --- a/pyomo/common/collections/component_set.py +++ b/pyomo/common/collections/component_set.py @@ -28,11 +28,11 @@ def _rehash_keys(encode, val): # autoslots.fast_deepcopy, but couldn't find an obvious bug. # There is no error if we just return the original dict, or if # we return a tuple(val.values) - return tuple(val.values()) + return val else: # object id() may have changed after unpickling, # so we rebuild the dictionary keys - return {_hasher[obj.__class__](obj): obj for obj in val} + return {_hasher[obj.__class__](obj): obj for obj in val.values()} class ComponentSet(AutoSlots.Mixin, collections_MutableSet): From 54c3ab197a4aee657f5ea161991fd8277b04b033 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 20 Feb 2024 08:49:07 -0700 Subject: [PATCH 1086/1204] Catch an edge case assigning new numeric types to Var/Param with units --- pyomo/core/base/param.py | 25 ++++++++++++++++++++----- pyomo/core/base/var.py | 15 ++++++++++----- pyomo/core/tests/unit/test_numvalue.py | 8 +++++++- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/pyomo/core/base/param.py b/pyomo/core/base/param.py index 03d700140e8..fc77c7b6f8f 100644 --- a/pyomo/core/base/param.py +++ b/pyomo/core/base/param.py @@ -162,16 +162,31 @@ def set_value(self, value, idx=NOTSET): # required to be mutable. # _comp = self.parent_component() - if type(value) in native_types: + if value.__class__ in native_types: # TODO: warn/error: check if this Param has units: assigning # a dimensionless value to a united param should be an error pass elif _comp._units is not None: _src_magnitude = expr_value(value) - _src_units = units.get_units(value) - value = units.convert_value( - num_value=_src_magnitude, from_units=_src_units, to_units=_comp._units - ) + # Note: expr_value() could have just registered a new numeric type + if value.__class__ in native_types: + value = _src_magnitude + else: + _src_units = units.get_units(value) + value = units.convert_value( + num_value=_src_magnitude, + from_units=_src_units, + to_units=_comp._units, + ) + # FIXME: we should call value() here [to ensure types get + # registered], but doing so breks non-numeric Params (which we + # allow). The real fix will be to follow the precedent from + # GetItemExpressiona and have separate types based on which + # expression "system" the Param should participate in (numeric, + # logical, or structural). + # + # else: + # value = expr_value(value) old_value, self._value = self._value, value try: diff --git a/pyomo/core/base/var.py b/pyomo/core/base/var.py index d03fd0b677f..f426c9c4f55 100644 --- a/pyomo/core/base/var.py +++ b/pyomo/core/base/var.py @@ -384,17 +384,22 @@ def set_value(self, val, skip_validation=False): # # Check if this Var has units: assigning dimensionless # values to a variable with units should be an error - if type(val) not in native_numeric_types: - if self.parent_component()._units is not None: - _src_magnitude = value(val) + if val.__class__ in native_numeric_types: + pass + elif self.parent_component()._units is not None: + _src_magnitude = value(val) + # Note: value() could have just registered a new numeric type + if val.__class__ in native_numeric_types: + val = _src_magnitude + else: _src_units = units.get_units(val) val = units.convert_value( num_value=_src_magnitude, from_units=_src_units, to_units=self.parent_component()._units, ) - else: - val = value(val) + else: + val = value(val) if not skip_validation: if val not in self.domain: diff --git a/pyomo/core/tests/unit/test_numvalue.py b/pyomo/core/tests/unit/test_numvalue.py index eceab3a42d9..bd784d655e8 100644 --- a/pyomo/core/tests/unit/test_numvalue.py +++ b/pyomo/core/tests/unit/test_numvalue.py @@ -562,7 +562,8 @@ def test_numpy_basic_bool_registration(self): @unittest.skipUnless(numpy_available, "This test requires NumPy") def test_automatic_numpy_registration(self): cmd = ( - 'import pyomo; from pyomo.core.base import Var, Param; import numpy as np; ' + 'import pyomo; from pyomo.core.base import Var, Param; ' + 'from pyomo.core.base.units_container import units; import numpy as np; ' 'print(np.float64 in pyomo.common.numeric_types.native_numeric_types); ' '%s; print(np.float64 in pyomo.common.numeric_types.native_numeric_types)' ) @@ -582,6 +583,11 @@ def _tester(expr): _tester('Var() + np.float64(5)') _tester('v = Var(); v.construct(); v.value = np.float64(5)') _tester('p = Param(mutable=True); p.construct(); p.value = np.float64(5)') + _tester('v = Var(units=units.m); v.construct(); v.value = np.float64(5)') + _tester( + 'p = Param(mutable=True, units=units.m); p.construct(); ' + 'p.value = np.float64(5)' + ) if __name__ == "__main__": From 0ad34438220112d6ac9213bd789fe679d50770f0 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 20 Feb 2024 10:12:47 -0700 Subject: [PATCH 1087/1204] Catch numpy.bool_ in the simple_constraint_rule decorator --- pyomo/core/base/constraint.py | 33 ++++++++++++++-------------- pyomo/core/base/indexed_component.py | 11 ++++++---- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index c67236656be..f3f0681d0fe 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -27,6 +27,7 @@ as_numeric, is_fixed, native_numeric_types, + native_logical_types, native_types, ) from pyomo.core.expr import ( @@ -84,14 +85,14 @@ def C_rule(model, i, j): model.c = Constraint(rule=simple_constraint_rule(...)) """ - return rule_wrapper( - rule, - { - None: Constraint.Skip, - True: Constraint.Feasible, - False: Constraint.Infeasible, - }, - ) + result_map = {None: Constraint.Skip} + for l_type in native_logical_types: + result_map[l_type(True)] = Constraint.Feasible + result_map[l_type(False)] = Constraint.Infeasible + # Note: some logical types has the same as bool (e.g., np.bool_), so + # we will pass the set of all logical types in addition to the + # result_map + return rule_wrapper(rule, result_map, map_types=native_logical_types) def simple_constraintlist_rule(rule): @@ -109,14 +110,14 @@ def C_rule(model, i, j): model.c = ConstraintList(expr=simple_constraintlist_rule(...)) """ - return rule_wrapper( - rule, - { - None: ConstraintList.End, - True: Constraint.Feasible, - False: Constraint.Infeasible, - }, - ) + result_map = {None: ConstraintList.End} + for l_type in native_logical_types: + result_map[l_type(True)] = Constraint.Feasible + result_map[l_type(False)] = Constraint.Infeasible + # Note: some logical types has the same as bool (e.g., np.bool_), so + # we will pass the set of all logical types in addition to the + # result_map + return rule_wrapper(rule, result_map, map_types=native_logical_types) # diff --git a/pyomo/core/base/indexed_component.py b/pyomo/core/base/indexed_component.py index abb29580960..0d498da091d 100644 --- a/pyomo/core/base/indexed_component.py +++ b/pyomo/core/base/indexed_component.py @@ -160,9 +160,12 @@ def _get_indexed_component_data_name(component, index): """ -def rule_result_substituter(result_map): +def rule_result_substituter(result_map, map_types): _map = result_map - _map_types = set(type(key) for key in result_map) + if map_types is None: + _map_types = set(type(key) for key in result_map) + else: + _map_types = map_types def rule_result_substituter_impl(rule, *args, **kwargs): if rule.__class__ in _map_types: @@ -203,7 +206,7 @@ def rule_result_substituter_impl(rule, *args, **kwargs): """ -def rule_wrapper(rule, wrapping_fcn, positional_arg_map=None): +def rule_wrapper(rule, wrapping_fcn, positional_arg_map=None, map_types=None): """Wrap a rule with another function This utility method provides a way to wrap a function (rule) with @@ -230,7 +233,7 @@ def rule_wrapper(rule, wrapping_fcn, positional_arg_map=None): """ if isinstance(wrapping_fcn, dict): - wrapping_fcn = rule_result_substituter(wrapping_fcn) + wrapping_fcn = rule_result_substituter(wrapping_fcn, map_types) if not inspect.isfunction(rule): return wrapping_fcn(rule) # Because some of our processing of initializer functions relies on From 044f8476abfad0c4c3e1f3bc9d12235b22d80e62 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 20 Feb 2024 10:21:51 -0700 Subject: [PATCH 1088/1204] Apply @blnicho 's csuggestions --- .../developer_reference/solvers.rst | 44 +++++++++++++++---- pyomo/__future__.py | 2 +- pyomo/contrib/solver/base.py | 2 +- pyomo/contrib/solver/config.py | 2 +- pyomo/contrib/solver/gurobi.py | 2 +- pyomo/contrib/solver/persistent.py | 10 ++--- pyomo/contrib/solver/sol_reader.py | 4 +- pyomo/contrib/solver/tests/unit/test_base.py | 8 ++-- 8 files changed, 51 insertions(+), 23 deletions(-) diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index 921e452004d..237bc7e523b 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -34,7 +34,7 @@ available are: Backwards Compatible Mode ^^^^^^^^^^^^^^^^^^^^^^^^^ -.. code-block:: python +.. testcode:: import pyomo.environ as pyo from pyomo.contrib.solver.util import assert_optimal_termination @@ -52,13 +52,20 @@ Backwards Compatible Mode assert_optimal_termination(status) model.pprint() +.. testoutput:: + :hide: + + 2 Var Declarations + ... + 3 Declarations: x y obj + Future Capability Mode ^^^^^^^^^^^^^^^^^^^^^^ -There are multiple ways to utilize the future compatibility mode: direct import +There are multiple ways to utilize the future capability mode: direct import or changed ``SolverFactory`` version. -.. code-block:: python +.. testcode:: # Direct import import pyomo.environ as pyo @@ -81,9 +88,16 @@ or changed ``SolverFactory`` version. status.display() model.pprint() +.. testoutput:: + :hide: + + solution_loader: ... + ... + 3 Declarations: x y obj + Changing the ``SolverFactory`` version: -.. code-block:: python +.. testcode:: # Change SolverFactory version import pyomo.environ as pyo @@ -105,6 +119,18 @@ Changing the ``SolverFactory`` version: status.display() model.pprint() +.. testoutput:: + :hide: + + solution_loader: ... + ... + 3 Declarations: x y obj + +.. testcode:: + :hide: + + from pyomo.__future__ import solver_factory_v1 + Linear Presolve and Scaling ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -118,11 +144,13 @@ options for certain solvers. Currently, these options are only available for The ``writer_config`` configuration option can be used to manipulate presolve and scaling options: -.. code-block:: python +.. testcode:: + + from pyomo.contrib.solver.ipopt import Ipopt + opt = Ipopt() + opt.config.writer_config.display() - >>> from pyomo.contrib.solver.ipopt import Ipopt - >>> opt = Ipopt() - >>> opt.config.writer_config.display() +.. testoutput:: show_section_timing: false skip_trivial_constraints: true diff --git a/pyomo/__future__.py b/pyomo/__future__.py index 87b1d4e77b3..d298e12cab6 100644 --- a/pyomo/__future__.py +++ b/pyomo/__future__.py @@ -43,7 +43,7 @@ def solver_factory(version=None): This allows users to query / set the current implementation of the SolverFactory that should be used throughout Pyomo. Valid options are: - - ``1``: the original Pyomo SolverFactor + - ``1``: the original Pyomo SolverFactory - ``2``: the SolverFactory from APPSI - ``3``: the SolverFactory from pyomo.contrib.solver diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index cb13809c438..3bfa83050ad 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -288,7 +288,7 @@ def add_variables(self, variables: List[_GeneralVarData]): """ @abc.abstractmethod - def add_params(self, params: List[_ParamData]): + def add_parameters(self, params: List[_ParamData]): """ Add parameters to the model """ diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index ca9557d0002..0c86f7646d3 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -234,7 +234,7 @@ def __init__( default=True, description=""" If False, new/old parameters will not be automatically detected on subsequent - solves. Use False only when manually updating the solver with opt.add_params() and + solves. Use False only when manually updating the solver with opt.add_parameters() and opt.remove_params() or when you are certain parameters are not being added to / removed from the model.""", ), diff --git a/pyomo/contrib/solver/gurobi.py b/pyomo/contrib/solver/gurobi.py index 85131ba73bd..ad476b9261e 100644 --- a/pyomo/contrib/solver/gurobi.py +++ b/pyomo/contrib/solver/gurobi.py @@ -475,7 +475,7 @@ def _add_variables(self, variables: List[_GeneralVarData]): self._vars_added_since_update.update(variables) self._needs_updated = True - def _add_params(self, params: List[_ParamData]): + def _add_parameters(self, params: List[_ParamData]): pass def _reinit(self): diff --git a/pyomo/contrib/solver/persistent.py b/pyomo/contrib/solver/persistent.py index 0994aa53093..e389e5d4019 100644 --- a/pyomo/contrib/solver/persistent.py +++ b/pyomo/contrib/solver/persistent.py @@ -75,13 +75,13 @@ def add_variables(self, variables: List[_GeneralVarData]): self._add_variables(variables) @abc.abstractmethod - def _add_params(self, params: List[_ParamData]): + def _add_parameters(self, params: List[_ParamData]): pass - def add_params(self, params: List[_ParamData]): + def add_parameters(self, params: List[_ParamData]): for p in params: self._params[id(p)] = p - self._add_params(params) + self._add_parameters(params) @abc.abstractmethod def _add_constraints(self, cons: List[_GeneralConstraintData]): @@ -191,7 +191,7 @@ def add_block(self, block): if p.mutable: for _p in p.values(): param_dict[id(_p)] = _p - self.add_params(list(param_dict.values())) + self.add_parameters(list(param_dict.values())) self.add_constraints( list( block.component_data_objects(Constraint, descend_into=True, active=True) @@ -403,7 +403,7 @@ def update(self, timer: HierarchicalTimer = None): if config.update_params: self.update_params() - self.add_params(new_params) + self.add_parameters(new_params) timer.stop('params') timer.start('vars') self.add_variables(new_vars) diff --git a/pyomo/contrib/solver/sol_reader.py b/pyomo/contrib/solver/sol_reader.py index 2817dab4516..41d840f8d07 100644 --- a/pyomo/contrib/solver/sol_reader.py +++ b/pyomo/contrib/solver/sol_reader.py @@ -36,7 +36,7 @@ def parse_sol_file( # # Some solvers (minto) do not write a message. We will assume - # all non-blank lines up the 'Options' line is the message. + # all non-blank lines up to the 'Options' line is the message. # For backwards compatibility and general safety, we will parse all # lines until "Options" appears. Anything before "Options" we will # consider to be the solver message. @@ -168,7 +168,7 @@ def parse_sol_file( # The fourth entry is table "length", e.g., memory size. number_of_string_lines = int(line[5]) suffix_name = sol_file.readline().strip() - # Add any of arbitrary string lines to the "other" list + # Add any arbitrary string lines to the "other" list for line in range(number_of_string_lines): sol_data.other.append(sol_file.readline()) if data_type == 0: # Var diff --git a/pyomo/contrib/solver/tests/unit/test_base.py b/pyomo/contrib/solver/tests/unit/test_base.py index 5fecd012cda..a9b3e4f4711 100644 --- a/pyomo/contrib/solver/tests/unit/test_base.py +++ b/pyomo/contrib/solver/tests/unit/test_base.py @@ -92,7 +92,7 @@ def test_abstract_member_list(self): 'remove_block', 'add_block', 'available', - 'add_params', + 'add_parameters', 'remove_constraints', 'add_variables', 'solve', @@ -110,7 +110,7 @@ def test_class_method_list(self): '_load_vars', 'add_block', 'add_constraints', - 'add_params', + 'add_parameters', 'add_variables', 'available', 'is_persistent', @@ -138,7 +138,7 @@ def test_init(self): self.assertTrue(self.instance.is_persistent()) self.assertEqual(self.instance.set_instance(None), None) self.assertEqual(self.instance.add_variables(None), None) - self.assertEqual(self.instance.add_params(None), None) + self.assertEqual(self.instance.add_parameters(None), None) self.assertEqual(self.instance.add_constraints(None), None) self.assertEqual(self.instance.add_block(None), None) self.assertEqual(self.instance.remove_variables(None), None) @@ -164,7 +164,7 @@ def test_context_manager(self): self.assertTrue(self.instance.is_persistent()) self.assertEqual(self.instance.set_instance(None), None) self.assertEqual(self.instance.add_variables(None), None) - self.assertEqual(self.instance.add_params(None), None) + self.assertEqual(self.instance.add_parameters(None), None) self.assertEqual(self.instance.add_constraints(None), None) self.assertEqual(self.instance.add_block(None), None) self.assertEqual(self.instance.remove_variables(None), None) From 6cb1180ed79aefbbf3dffb5623621cda05934785 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 20 Feb 2024 10:42:25 -0700 Subject: [PATCH 1089/1204] NFC: updating comments --- pyomo/common/collections/component_map.py | 2 +- pyomo/common/collections/component_set.py | 1 + pyomo/common/collections/orderedset.py | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pyomo/common/collections/component_map.py b/pyomo/common/collections/component_map.py index c110e9f390b..caeabb1e650 100644 --- a/pyomo/common/collections/component_map.py +++ b/pyomo/common/collections/component_map.py @@ -80,7 +80,7 @@ class ComponentMap(AutoSlots.Mixin, collections.abc.MutableMapping): __autoslot_mappers__ = {'_dict': _rehash_keys} def __init__(self, *args, **kwds): - # maps id(obj) -> (obj,val) + # maps id_hash(obj) -> (obj,val) self._dict = {} # handle the dict-style initialization scenarios self.update(*args, **kwds) diff --git a/pyomo/common/collections/component_set.py b/pyomo/common/collections/component_set.py index 19d2ef2f7f9..5e9d794ff8e 100644 --- a/pyomo/common/collections/component_set.py +++ b/pyomo/common/collections/component_set.py @@ -63,6 +63,7 @@ class ComponentSet(AutoSlots.Mixin, collections_MutableSet): __autoslot_mappers__ = {'_data': _rehash_keys} def __init__(self, iterable=None): + # maps id_hash(obj) -> obj self._data = {} if iterable is not None: self.update(iterable) diff --git a/pyomo/common/collections/orderedset.py b/pyomo/common/collections/orderedset.py index 6bcf0c2fafb..834101e3896 100644 --- a/pyomo/common/collections/orderedset.py +++ b/pyomo/common/collections/orderedset.py @@ -18,8 +18,8 @@ class OrderedSet(AutoSlots.Mixin, MutableSet): __slots__ = ('_dict',) def __init__(self, iterable=None): - # TODO: Starting in Python 3.7, dict is ordered (and is faster - # than OrderedDict). dict began supporting reversed() in 3.8. + # Starting in Python 3.7, dict is ordered (and is faster than + # OrderedDict). dict began supporting reversed() in 3.8. self._dict = {} if iterable is not None: self.update(iterable) From f3ded2787e18757a2ec5ff0330c64acd338b45fd Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 20 Feb 2024 10:42:49 -0700 Subject: [PATCH 1090/1204] Clean up exception messages / string representation --- pyomo/common/collections/component_map.py | 8 ++++---- pyomo/common/collections/component_set.py | 8 +++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/pyomo/common/collections/component_map.py b/pyomo/common/collections/component_map.py index caeabb1e650..8dcfdb6c837 100644 --- a/pyomo/common/collections/component_map.py +++ b/pyomo/common/collections/component_map.py @@ -87,8 +87,8 @@ def __init__(self, *args, **kwds): def __str__(self): """String representation of the mapping.""" - tmp = {str(c) + " (id=" + str(id(c)) + ")": v for c, v in self.items()} - return "ComponentMap(" + str(tmp) + ")" + tmp = {f"{v[0]} (key={k})": v[1] for k, v in self._dict.items()} + return f"ComponentMap({tmp})" # # Implement MutableMapping abstract methods @@ -99,7 +99,7 @@ def __getitem__(self, obj): return self._dict[_hasher[obj.__class__](obj)][1] except KeyError: _id = _hasher[obj.__class__](obj) - raise KeyError("Component with id '%s': %s" % (_id, obj)) + raise KeyError(f"{obj} (key={_id})") from None def __setitem__(self, obj, val): self._dict[_hasher[obj.__class__](obj)] = (obj, val) @@ -109,7 +109,7 @@ def __delitem__(self, obj): del self._dict[_hasher[obj.__class__](obj)] except KeyError: _id = _hasher[obj.__class__](obj) - raise KeyError("Component with id '%s': %s" % (_id, obj)) + raise KeyError(f"{obj} (key={_id})") from None def __iter__(self): return (obj for obj, val in self._dict.values()) diff --git a/pyomo/common/collections/component_set.py b/pyomo/common/collections/component_set.py index 5e9d794ff8e..6e12bad7277 100644 --- a/pyomo/common/collections/component_set.py +++ b/pyomo/common/collections/component_set.py @@ -70,10 +70,8 @@ def __init__(self, iterable=None): def __str__(self): """String representation of the mapping.""" - tmp = [] - for objid, obj in self._data.items(): - tmp.append(str(obj) + " (id=" + str(objid) + ")") - return "ComponentSet(" + str(tmp) + ")" + tmp = [f"{v} (key={k})" for k, v in self._data.items()] + return f"ComponentSet({tmp})" def update(self, iterable): """Update a set with the union of itself and others.""" @@ -136,4 +134,4 @@ def remove(self, val): del self._data[_hasher[val.__class__](val)] except KeyError: _id = _hasher[val.__class__](val) - raise KeyError("Component with id '%s': %s" % (_id, val)) + raise KeyError(f"{val} (key={_id})") from None From c9a9f0d5132da90cecb43278dd2697d0389c2189 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 20 Feb 2024 10:55:27 -0700 Subject: [PATCH 1091/1204] Ensure NoneType is in the map_types --- pyomo/core/base/constraint.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index f3f0681d0fe..108ff7383c3 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -85,6 +85,7 @@ def C_rule(model, i, j): model.c = Constraint(rule=simple_constraint_rule(...)) """ + map_types = set([type(None)]) | native_logical_types result_map = {None: Constraint.Skip} for l_type in native_logical_types: result_map[l_type(True)] = Constraint.Feasible @@ -92,7 +93,7 @@ def C_rule(model, i, j): # Note: some logical types has the same as bool (e.g., np.bool_), so # we will pass the set of all logical types in addition to the # result_map - return rule_wrapper(rule, result_map, map_types=native_logical_types) + return rule_wrapper(rule, result_map, map_types=map_types) def simple_constraintlist_rule(rule): @@ -110,6 +111,7 @@ def C_rule(model, i, j): model.c = ConstraintList(expr=simple_constraintlist_rule(...)) """ + map_types = set([type(None)]) | native_logical_types result_map = {None: ConstraintList.End} for l_type in native_logical_types: result_map[l_type(True)] = Constraint.Feasible @@ -117,7 +119,7 @@ def C_rule(model, i, j): # Note: some logical types has the same as bool (e.g., np.bool_), so # we will pass the set of all logical types in addition to the # result_map - return rule_wrapper(rule, result_map, map_types=native_logical_types) + return rule_wrapper(rule, result_map, map_types=map_types) # From 7ecdcc930c9f4d89777412da5915eadf78d70330 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 20 Feb 2024 10:55:50 -0700 Subject: [PATCH 1092/1204] NFC: fix comment --- pyomo/core/base/constraint.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index 108ff7383c3..8cf3c48ad0a 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -90,7 +90,7 @@ def C_rule(model, i, j): for l_type in native_logical_types: result_map[l_type(True)] = Constraint.Feasible result_map[l_type(False)] = Constraint.Infeasible - # Note: some logical types has the same as bool (e.g., np.bool_), so + # Note: some logical types hash the same as bool (e.g., np.bool_), so # we will pass the set of all logical types in addition to the # result_map return rule_wrapper(rule, result_map, map_types=map_types) @@ -116,7 +116,7 @@ def C_rule(model, i, j): for l_type in native_logical_types: result_map[l_type(True)] = Constraint.Feasible result_map[l_type(False)] = Constraint.Infeasible - # Note: some logical types has the same as bool (e.g., np.bool_), so + # Note: some logical types hash the same as bool (e.g., np.bool_), so # we will pass the set of all logical types in addition to the # result_map return rule_wrapper(rule, result_map, map_types=map_types) From 423b1412fb4e2696b1a976a06793957505320dee Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 20 Feb 2024 11:34:35 -0700 Subject: [PATCH 1093/1204] Add skip statement to doctests; start ipopt unit tests --- .../developer_reference/solvers.rst | 7 ++ pyomo/contrib/solver/tests/unit/test_ipopt.py | 73 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 pyomo/contrib/solver/tests/unit/test_ipopt.py diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index 237bc7e523b..45945c18b12 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -35,6 +35,7 @@ Backwards Compatible Mode ^^^^^^^^^^^^^^^^^^^^^^^^^ .. testcode:: + :skipif: not ipopt_available import pyomo.environ as pyo from pyomo.contrib.solver.util import assert_optimal_termination @@ -53,6 +54,7 @@ Backwards Compatible Mode model.pprint() .. testoutput:: + :skipif: not ipopt_available :hide: 2 Var Declarations @@ -66,6 +68,7 @@ There are multiple ways to utilize the future capability mode: direct import or changed ``SolverFactory`` version. .. testcode:: + :skipif: not ipopt_available # Direct import import pyomo.environ as pyo @@ -89,6 +92,7 @@ or changed ``SolverFactory`` version. model.pprint() .. testoutput:: + :skipif: not ipopt_available :hide: solution_loader: ... @@ -98,6 +102,7 @@ or changed ``SolverFactory`` version. Changing the ``SolverFactory`` version: .. testcode:: + :skipif: not ipopt_available # Change SolverFactory version import pyomo.environ as pyo @@ -120,6 +125,7 @@ Changing the ``SolverFactory`` version: model.pprint() .. testoutput:: + :skipif: not ipopt_available :hide: solution_loader: ... @@ -127,6 +133,7 @@ Changing the ``SolverFactory`` version: 3 Declarations: x y obj .. testcode:: + :skipif: not ipopt_available :hide: from pyomo.__future__ import solver_factory_v1 diff --git a/pyomo/contrib/solver/tests/unit/test_ipopt.py b/pyomo/contrib/solver/tests/unit/test_ipopt.py new file mode 100644 index 00000000000..2ddcce2e456 --- /dev/null +++ b/pyomo/contrib/solver/tests/unit/test_ipopt.py @@ -0,0 +1,73 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + +from pyomo.common import unittest, Executable +from pyomo.repn.plugins.nl_writer import NLWriter +from pyomo.contrib.solver import ipopt + + +ipopt_available = ipopt.Ipopt().available() + + +@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") +class TestIpoptSolverConfig(unittest.TestCase): + def test_default_instantiation(self): + config = ipopt.IpoptConfig() + # Should be inherited + self.assertIsNone(config._description) + self.assertEqual(config._visibility, 0) + self.assertFalse(config.tee) + self.assertTrue(config.load_solutions) + self.assertTrue(config.raise_exception_on_nonoptimal_result) + self.assertFalse(config.symbolic_solver_labels) + self.assertIsNone(config.timer) + self.assertIsNone(config.threads) + self.assertIsNone(config.time_limit) + # Unique to this object + self.assertIsInstance(config.executable, type(Executable('path'))) + self.assertIsInstance(config.writer_config, type(NLWriter.CONFIG())) + + def test_custom_instantiation(self): + config = ipopt.IpoptConfig(description="A description") + config.tee = True + self.assertTrue(config.tee) + self.assertEqual(config._description, "A description") + self.assertFalse(config.time_limit) + # Default should be `ipopt` + self.assertIsNotNone(str(config.executable)) + self.assertIn('ipopt', str(config.executable)) + # Set to a totally bogus path + config.executable = Executable('/bogus/path') + self.assertIsNone(config.executable.executable) + self.assertFalse(config.executable.available()) + + +class TestIpoptResults(unittest.TestCase): + def test_default_instantiation(self): + res = ipopt.IpoptResults() + # Inherited methods/attributes + self.assertIsNone(res.solution_loader) + self.assertIsNone(res.incumbent_objective) + self.assertIsNone(res.objective_bound) + self.assertIsNone(res.solver_name) + self.assertIsNone(res.solver_version) + self.assertIsNone(res.iteration_count) + self.assertIsNone(res.timing_info.start_timestamp) + self.assertIsNone(res.timing_info.wall_time) + # Unique to this object + self.assertIsNone(res.timing_info.ipopt_excluding_nlp_functions) + self.assertIsNone(res.timing_info.nlp_function_evaluations) + self.assertIsNone(res.timing_info.total_seconds) + + +@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") +class TestIpoptInterface(unittest.TestCase): + pass From 924c38aebb82d2a72ba340df39f2948116fdc861 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Tue, 20 Feb 2024 11:43:00 -0700 Subject: [PATCH 1094/1204] NFC: Fixing typos in comments in param.py --- pyomo/core/base/param.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/core/base/param.py b/pyomo/core/base/param.py index fc77c7b6f8f..3ef33b9ee45 100644 --- a/pyomo/core/base/param.py +++ b/pyomo/core/base/param.py @@ -179,9 +179,9 @@ def set_value(self, value, idx=NOTSET): to_units=_comp._units, ) # FIXME: we should call value() here [to ensure types get - # registered], but doing so breks non-numeric Params (which we + # registered], but doing so breaks non-numeric Params (which we # allow). The real fix will be to follow the precedent from - # GetItemExpressiona and have separate types based on which + # GetItemExpression and have separate types based on which # expression "system" the Param should participate in (numeric, # logical, or structural). # From d0cdeaab066b35f164a9a05de01c580284559405 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 20 Feb 2024 12:11:34 -0700 Subject: [PATCH 1095/1204] Consolidate log_solevr_output into tee --- pyomo/contrib/solver/config.py | 43 +++++++++++++++------ pyomo/contrib/solver/gurobi.py | 68 ++++++++++++++++------------------ pyomo/contrib/solver/ipopt.py | 7 +--- 3 files changed, 64 insertions(+), 54 deletions(-) diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index 0c86f7646d3..7ca9ac104ae 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -9,6 +9,11 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +import io +import logging +import sys + +from collections.abc import Sequence from typing import Optional from pyomo.common.config import ( @@ -19,9 +24,29 @@ ADVANCED_OPTION, Bool, ) +from pyomo.common.log import LogStream +from pyomo.common.numeric_types import native_logical_types from pyomo.common.timing import HierarchicalTimer +def TextIO_or_Logger(val): + ans = [] + if not isinstance(val, Sequence): + val = [val] + for v in val: + if v.__class__ in native_logical_types: + if v: + ans.append(sys.stdout) + elif isinstance(v, io.TextIOBase): + ans.append(v) + elif isinstance(v, logging.Logger): + ans.append(LogStream(level=logging.INFO, logger=v)) + else: + raise ValueError( + "Expected bool, TextIOBase, or Logger, but received {v.__class__}" + ) + return ans + class SolverConfig(ConfigDict): """ Base config for all direct solver interfaces @@ -43,20 +68,16 @@ def __init__( visibility=visibility, ) - self.tee: bool = self.declare( + self.tee: List[TextIO] = self.declare( 'tee', ConfigValue( - domain=Bool, - default=False, - description="If True, the solver log prints to stdout.", - ), - ) - self.log_solver_output: bool = self.declare( - 'log_solver_output', - ConfigValue( - domain=Bool, + domain=TextIO_or_Logger, default=False, - description="If True, the solver output gets logged.", + description="""`tee` accepts :py:class:`bool`, + :py:class:`io.TextIOBase`, or :py:class:`logging.Logger` + (or a list of these types). ``True`` is mapped to + ``sys.stdout``. The solver log will be printed to each of + these streams / destinations. """, ), ) self.working_dir: Optional[str] = self.declare( diff --git a/pyomo/contrib/solver/gurobi.py b/pyomo/contrib/solver/gurobi.py index ad476b9261e..63387730c45 100644 --- a/pyomo/contrib/solver/gurobi.py +++ b/pyomo/contrib/solver/gurobi.py @@ -14,7 +14,6 @@ import math from typing import List, Optional from pyomo.common.collections import ComponentSet, ComponentMap, OrderedSet -from pyomo.common.log import LogStream from pyomo.common.dependencies import attempt_import from pyomo.common.errors import PyomoException from pyomo.common.tee import capture_output, TeeStream @@ -326,42 +325,37 @@ def symbol_map(self): def _solve(self): config = self._config timer = config.timer - ostreams = [io.StringIO()] - if config.tee: - ostreams.append(sys.stdout) - if config.log_solver_output: - ostreams.append(LogStream(level=logging.INFO, logger=logger)) - - with TeeStream(*ostreams) as t: - with capture_output(output=t.STDOUT, capture_fd=False): - options = config.solver_options - - self._solver_model.setParam('LogToConsole', 1) - - if config.threads is not None: - self._solver_model.setParam('Threads', config.threads) - if config.time_limit is not None: - self._solver_model.setParam('TimeLimit', config.time_limit) - if config.rel_gap is not None: - self._solver_model.setParam('MIPGap', config.rel_gap) - if config.abs_gap is not None: - self._solver_model.setParam('MIPGapAbs', config.abs_gap) - - if config.use_mipstart: - for ( - pyomo_var_id, - gurobi_var, - ) in self._pyomo_var_to_solver_var_map.items(): - pyomo_var = self._vars[pyomo_var_id][0] - if pyomo_var.is_integer() and pyomo_var.value is not None: - self.set_var_attr(pyomo_var, 'Start', pyomo_var.value) - - for key, option in options.items(): - self._solver_model.setParam(key, option) - - timer.start('optimize') - self._solver_model.optimize(self._callback) - timer.stop('optimize') + ostreams = [io.StringIO()] + config.tee + + with TeeStream(*ostreams) as t, capture_output(t.STDOUT, capture_fd=False): + options = config.solver_options + + self._solver_model.setParam('LogToConsole', 1) + + if config.threads is not None: + self._solver_model.setParam('Threads', config.threads) + if config.time_limit is not None: + self._solver_model.setParam('TimeLimit', config.time_limit) + if config.rel_gap is not None: + self._solver_model.setParam('MIPGap', config.rel_gap) + if config.abs_gap is not None: + self._solver_model.setParam('MIPGapAbs', config.abs_gap) + + if config.use_mipstart: + for ( + pyomo_var_id, + gurobi_var, + ) in self._pyomo_var_to_solver_var_map.items(): + pyomo_var = self._vars[pyomo_var_id][0] + if pyomo_var.is_integer() and pyomo_var.value is not None: + self.set_var_attr(pyomo_var, 'Start', pyomo_var.value) + + for key, option in options.items(): + self._solver_model.setParam(key, option) + + timer.start('optimize') + self._solver_model.optimize(self._callback) + timer.stop('optimize') self._needs_updated = False res = self._postsolve(timer) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 537e6f85968..38272d58fa1 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -36,7 +36,6 @@ from pyomo.contrib.solver.sol_reader import parse_sol_file from pyomo.contrib.solver.solution import SolSolutionLoader from pyomo.common.tee import TeeStream -from pyomo.common.log import LogStream from pyomo.core.expr.visitor import replace_expressions from pyomo.core.expr.numvalue import value from pyomo.core.base.suffix import Suffix @@ -390,11 +389,7 @@ def solve(self, model, **kwds): else: timeout = None - ostreams = [io.StringIO()] - if config.tee: - ostreams.append(sys.stdout) - if config.log_solver_output: - ostreams.append(LogStream(level=logging.INFO, logger=logger)) + ostreams = [io.StringIO()] + config.tee with TeeStream(*ostreams) as t: timer.start('subprocess') process = subprocess.run( From ff92c62b89cc376ef87849adb35e5b0c741eed3a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 20 Feb 2024 12:55:00 -0700 Subject: [PATCH 1096/1204] More unit tests for ipopt; fix some bugs as well --- pyomo/contrib/solver/ipopt.py | 49 ++--- pyomo/contrib/solver/tests/unit/test_ipopt.py | 172 +++++++++++++++++- 2 files changed, 199 insertions(+), 22 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 537e6f85968..42ac24ec352 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -23,7 +23,7 @@ document_kwargs_from_configdict, ConfigDict, ) -from pyomo.common.errors import PyomoException +from pyomo.common.errors import PyomoException, DeveloperError from pyomo.common.tempfiles import TempfileManager from pyomo.common.timing import HierarchicalTimer from pyomo.core.base.var import _GeneralVarData @@ -137,13 +137,18 @@ def get_reduced_costs( if self._nl_info is None: raise RuntimeError( 'Solution loader does not currently have a valid solution. Please ' - 'check the termination condition.' + 'check results.TerminationCondition and/or results.SolutionStatus.' ) if len(self._nl_info.eliminated_vars) > 0: raise NotImplementedError( - 'For now, turn presolve off (opt.config.writer_config.linear_presolve=False) to get reduced costs.' + 'For now, turn presolve off (opt.config.writer_config.linear_presolve=False) ' + 'to get dual variable values.' + ) + if self._sol_data is None: + raise DeveloperError( + "Solution data is empty. This should not " + "have happened. Report this error to the Pyomo Developers." ) - assert self._sol_data is not None if self._nl_info.scaling is None: scale_list = [1] * len(self._nl_info.variables) obj_scale = 1 @@ -252,7 +257,6 @@ def __init__(self, **kwds): self._writer = NLWriter() self._available_cache = None self._version_cache = None - self._executable = self.config.executable def available(self, config=None): if config is None: @@ -270,17 +274,20 @@ def version(self, config=None): config = self.config pth = config.executable.path() if self._version_cache is None or self._version_cache[0] != pth: - results = subprocess.run( - [str(pth), '--version'], - timeout=1, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True, - ) - version = results.stdout.splitlines()[0] - version = version.split(' ')[1].strip() - version = tuple(int(i) for i in version.split('.')) - self._version_cache = (pth, version) + if pth is None: + self._version_cache = (None, None) + else: + results = subprocess.run( + [str(pth), '--version'], + timeout=1, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + ) + version = results.stdout.splitlines()[0] + version = version.split(' ')[1].strip() + version = tuple(int(i) for i in version.split('.')) + self._version_cache = (pth, version) return self._version_cache[1] def _write_options_file(self, filename: str, options: Mapping): @@ -292,15 +299,15 @@ def _write_options_file(self, filename: str, options: Mapping): # If it has options in it, parse them and write them to a file. # If they are command line options, ignore them; they will be # parsed during _create_command_line - with open(filename + '.opt', 'w') as opt_file: - for k, val in options.items(): - if k not in ipopt_command_line_options: - opt_file_exists = True + for k, val in options.items(): + if k not in ipopt_command_line_options: + opt_file_exists = True + with open(filename + '.opt', 'a+') as opt_file: opt_file.write(str(k) + ' ' + str(val) + '\n') return opt_file_exists def _create_command_line(self, basename: str, config: IpoptConfig, opt_file: bool): - cmd = [str(self._executable), basename + '.nl', '-AMPL'] + cmd = [str(config.executable), basename + '.nl', '-AMPL'] if opt_file: cmd.append('option_file_name=' + basename + '.opt') if 'option_file_name' in config.solver_options: diff --git a/pyomo/contrib/solver/tests/unit/test_ipopt.py b/pyomo/contrib/solver/tests/unit/test_ipopt.py index 2ddcce2e456..ae07bd37f86 100644 --- a/pyomo/contrib/solver/tests/unit/test_ipopt.py +++ b/pyomo/contrib/solver/tests/unit/test_ipopt.py @@ -9,7 +9,11 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +import os + from pyomo.common import unittest, Executable +from pyomo.common.errors import DeveloperError +from pyomo.common.tempfiles import TempfileManager from pyomo.repn.plugins.nl_writer import NLWriter from pyomo.contrib.solver import ipopt @@ -68,6 +72,172 @@ def test_default_instantiation(self): self.assertIsNone(res.timing_info.total_seconds) +class TestIpoptSolutionLoader(unittest.TestCase): + def test_get_reduced_costs_error(self): + loader = ipopt.IpoptSolutionLoader(None, None) + with self.assertRaises(RuntimeError): + loader.get_reduced_costs() + + # Set _nl_info to something completely bogus but is not None + class NLInfo: + pass + + loader._nl_info = NLInfo() + loader._nl_info.eliminated_vars = [1, 2, 3] + with self.assertRaises(NotImplementedError): + loader.get_reduced_costs() + # Reset _nl_info so we can ensure we get an error + # when _sol_data is None + loader._nl_info.eliminated_vars = [] + with self.assertRaises(DeveloperError): + loader.get_reduced_costs() + + @unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") class TestIpoptInterface(unittest.TestCase): - pass + def test_class_member_list(self): + opt = ipopt.Ipopt() + expected_list = [ + 'Availability', + 'CONFIG', + 'config', + 'available', + 'is_persistent', + 'solve', + 'version', + 'name', + ] + method_list = [method for method in dir(opt) if method.startswith('_') is False] + self.assertEqual(sorted(expected_list), sorted(method_list)) + + def test_default_instantiation(self): + opt = ipopt.Ipopt() + self.assertFalse(opt.is_persistent()) + self.assertIsNotNone(opt.version()) + self.assertEqual(opt.name, 'ipopt') + self.assertEqual(opt.CONFIG, opt.config) + self.assertTrue(opt.available()) + + def test_context_manager(self): + with ipopt.Ipopt() as opt: + self.assertFalse(opt.is_persistent()) + self.assertIsNotNone(opt.version()) + self.assertEqual(opt.name, 'ipopt') + self.assertEqual(opt.CONFIG, opt.config) + self.assertTrue(opt.available()) + + def test_available_cache(self): + opt = ipopt.Ipopt() + opt.available() + self.assertTrue(opt._available_cache[1]) + self.assertIsNotNone(opt._available_cache[0]) + # Now we will try with a custom config that has a fake path + config = ipopt.IpoptConfig() + config.executable = Executable('/a/bogus/path') + opt.available(config=config) + self.assertFalse(opt._available_cache[1]) + self.assertIsNone(opt._available_cache[0]) + + def test_version_cache(self): + opt = ipopt.Ipopt() + opt.version() + self.assertIsNotNone(opt._version_cache[0]) + self.assertIsNotNone(opt._version_cache[1]) + # Now we will try with a custom config that has a fake path + config = ipopt.IpoptConfig() + config.executable = Executable('/a/bogus/path') + opt.version(config=config) + self.assertIsNone(opt._version_cache[0]) + self.assertIsNone(opt._version_cache[1]) + + def test_write_options_file(self): + # If we have no options, we should get false back + opt = ipopt.Ipopt() + result = opt._write_options_file('fakename', None) + self.assertFalse(result) + # Pass it some options that ARE on the command line + opt = ipopt.Ipopt(solver_options={'max_iter': 4}) + result = opt._write_options_file('myfile', opt.config.solver_options) + self.assertFalse(result) + self.assertFalse(os.path.isfile('myfile.opt')) + # Now we are going to actually pass it some options that are NOT on + # the command line + opt = ipopt.Ipopt(solver_options={'custom_option': 4}) + with TempfileManager.new_context() as temp: + dname = temp.mkdtemp() + if not os.path.exists(dname): + os.mkdir(dname) + filename = os.path.join(dname, 'myfile') + result = opt._write_options_file(filename, opt.config.solver_options) + self.assertTrue(result) + self.assertTrue(os.path.isfile(filename + '.opt')) + # Make sure all options are writing to the file + opt = ipopt.Ipopt(solver_options={'custom_option_1': 4, 'custom_option_2': 3}) + with TempfileManager.new_context() as temp: + dname = temp.mkdtemp() + if not os.path.exists(dname): + os.mkdir(dname) + filename = os.path.join(dname, 'myfile') + result = opt._write_options_file(filename, opt.config.solver_options) + self.assertTrue(result) + self.assertTrue(os.path.isfile(filename + '.opt')) + with open(filename + '.opt', 'r') as f: + data = f.readlines() + self.assertEqual(len(data), len(list(opt.config.solver_options.keys()))) + + def test_create_command_line(self): + opt = ipopt.Ipopt() + # No custom options, no file created. Plain and simple. + result = opt._create_command_line('myfile', opt.config, False) + self.assertEqual(result, [str(opt.config.executable), 'myfile.nl', '-AMPL']) + # Custom command line options + opt = ipopt.Ipopt(solver_options={'max_iter': 4}) + result = opt._create_command_line('myfile', opt.config, False) + self.assertEqual( + result, [str(opt.config.executable), 'myfile.nl', '-AMPL', 'max_iter=4'] + ) + # Let's see if we correctly parse config.time_limit + opt = ipopt.Ipopt(solver_options={'max_iter': 4}, time_limit=10) + result = opt._create_command_line('myfile', opt.config, False) + self.assertEqual( + result, + [ + str(opt.config.executable), + 'myfile.nl', + '-AMPL', + 'max_iter=4', + 'max_cpu_time=10.0', + ], + ) + # Now let's do multiple command line options + opt = ipopt.Ipopt(solver_options={'max_iter': 4, 'max_cpu_time': 10}) + result = opt._create_command_line('myfile', opt.config, False) + self.assertEqual( + result, + [ + str(opt.config.executable), + 'myfile.nl', + '-AMPL', + 'max_cpu_time=10', + 'max_iter=4', + ], + ) + # Let's now include if we "have" an options file + result = opt._create_command_line('myfile', opt.config, True) + self.assertEqual( + result, + [ + '/Users/mmundt/Documents/idaes/venv-pyomo/bin/ipopt', + 'myfile.nl', + '-AMPL', + 'option_file_name=myfile.opt', + 'max_cpu_time=10', + 'max_iter=4', + ], + ) + # Finally, let's make sure it errors if someone tries to pass option_file_name + opt = ipopt.Ipopt( + solver_options={'max_iter': 4, 'option_file_name': 'myfile.opt'} + ) + with self.assertRaises(ValueError): + result = opt._create_command_line('myfile', opt.config, False) From c2472b3cb09cf367fb711845fb9780908c028f89 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 20 Feb 2024 13:10:27 -0700 Subject: [PATCH 1097/1204] Remove Datetime validator; replace with IsInstance --- pyomo/common/config.py | 12 ------------ pyomo/common/tests/test_config.py | 17 ----------------- pyomo/contrib/solver/config.py | 1 + pyomo/contrib/solver/results.py | 5 +++-- 4 files changed, 4 insertions(+), 31 deletions(-) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index 46a05494094..238bdd78e9d 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -18,7 +18,6 @@ import argparse import builtins -import datetime import enum import importlib import inspect @@ -205,16 +204,6 @@ def NonNegativeFloat(val): return ans -def Datetime(val): - """Domain validation function to check for datetime.datetime type. - - This domain will return the original object, assuming it is of the right type. - """ - if not isinstance(val, datetime.datetime): - raise ValueError(f"Expected datetime object, but received {type(val)}.") - return val - - class In(object): """In(domain, cast=None) Domain validation class admitting a Container of possible values @@ -794,7 +783,6 @@ def from_enum_or_string(cls, arg): NegativeFloat NonPositiveFloat NonNegativeFloat - Datetime In InEnum IsInstance diff --git a/pyomo/common/tests/test_config.py b/pyomo/common/tests/test_config.py index e2f64a3a9d5..02f4fc88251 100644 --- a/pyomo/common/tests/test_config.py +++ b/pyomo/common/tests/test_config.py @@ -48,7 +48,6 @@ def yaml_load(arg): ConfigDict, ConfigValue, ConfigList, - Datetime, MarkImmutable, ImmutableConfigValue, Bool, @@ -937,22 +936,6 @@ def _rule(key, val): } ) - def test_Datetime(self): - c = ConfigDict() - c.declare('a', ConfigValue(domain=Datetime, default=None)) - self.assertEqual(c.get('a').domain_name(), 'Datetime') - - self.assertEqual(c.a, None) - c.a = datetime.datetime(2022, 1, 1) - self.assertEqual(c.a, datetime.datetime(2022, 1, 1)) - - with self.assertRaises(ValueError): - c.a = 5 - with self.assertRaises(ValueError): - c.a = 'Hello' - with self.assertRaises(ValueError): - c.a = False - class TestImmutableConfigValue(unittest.TestCase): def test_immutable_config_value(self): diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index 7ca9ac104ae..8f715ac7250 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -47,6 +47,7 @@ def TextIO_or_Logger(val): ) return ans + class SolverConfig(ConfigDict): """ Base config for all direct solver interfaces diff --git a/pyomo/contrib/solver/results.py b/pyomo/contrib/solver/results.py index 88de0624629..699137d2fc9 100644 --- a/pyomo/contrib/solver/results.py +++ b/pyomo/contrib/solver/results.py @@ -16,7 +16,7 @@ from pyomo.common.config import ( ConfigDict, ConfigValue, - Datetime, + IsInstance, NonNegativeInt, In, NonNegativeFloat, @@ -262,7 +262,8 @@ def __init__( self.timing_info.start_timestamp: datetime = self.timing_info.declare( 'start_timestamp', ConfigValue( - domain=Datetime, description="UTC timestamp of when run was initiated." + domain=IsInstance(datetime), + description="UTC timestamp of when run was initiated.", ), ) self.timing_info.wall_time: Optional[float] = self.timing_info.declare( From d1549d69c21c754b9ffe2c0a2f60d5aafc2e70a6 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 20 Feb 2024 13:21:50 -0700 Subject: [PATCH 1098/1204] Not checking isinstance when we check ctype in util --- pyomo/gdp/util.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/gdp/util.py b/pyomo/gdp/util.py index 0e4e5f5e9ff..57eef29eded 100644 --- a/pyomo/gdp/util.py +++ b/pyomo/gdp/util.py @@ -169,7 +169,7 @@ def parent_disjunct(self, u): Arg: u : A node in the forest """ - if isinstance(u, _DisjunctData) or u.ctype is Disjunct: + if u.ctype is Disjunct: return self.parent(self.parent(u)) else: return self.parent(u) @@ -186,7 +186,7 @@ def root_disjunct(self, u): while True: if parent is None: return rootmost_disjunct - if isinstance(parent, _DisjunctData) or parent.ctype is Disjunct: + if parent.ctype is Disjunct: rootmost_disjunct = parent parent = self.parent(parent) @@ -246,7 +246,7 @@ def leaves(self): @property def disjunct_nodes(self): for v in self._vertices: - if isinstance(v, _DisjunctData) or v.ctype is Disjunct: + if v.ctype is Disjunct: yield v From 06621a75a2151076d90ee7b9a91b9794fdbac29b Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 20 Feb 2024 13:22:45 -0700 Subject: [PATCH 1099/1204] Not checking isinstance when we check ctype in hull --- pyomo/gdp/plugins/hull.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index a70aa2760eb..b2e1ffd76fd 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -212,7 +212,7 @@ def _get_user_defined_local_vars(self, targets): # we cache what Blocks/Disjuncts we've already looked on so that we # don't duplicate effort. for t in targets: - if t.ctype is Disjunct or isinstance(t, _DisjunctData): + if t.ctype is Disjunct: # first look beneath where we are (there could be Blocks on this # disjunct) for b in t.component_data_objects( From 3435aa1a82fcfec68227faba0918f0629680e699 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 20 Feb 2024 13:24:16 -0700 Subject: [PATCH 1100/1204] Prettier defaultdicts --- pyomo/gdp/plugins/hull.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index b2e1ffd76fd..5a3349a8b34 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -206,7 +206,7 @@ def _collect_local_vars_from_block(self, block, local_var_dict): local_var_dict[disj].update(var_list) def _get_user_defined_local_vars(self, targets): - user_defined_local_vars = defaultdict(lambda: ComponentSet()) + user_defined_local_vars = defaultdict(ComponentSet) seen_blocks = set() # we go through the targets looking both up and down the hierarchy, but # we cache what Blocks/Disjuncts we've already looked on so that we @@ -369,7 +369,7 @@ def _transform_disjunctionData( # actually appear in any Constraints on that Disjunct, but in order to # do this, we will explicitly collect the set of local_vars in this # loop. - local_vars = defaultdict(lambda: ComponentSet()) + local_vars = defaultdict(ComponentSet) for var in var_order: disjuncts = disjuncts_var_appears_in[var] # clearly not local if used in more than one disjunct From b673bf74c0664e33679a8998ffc2a2ce72cb3d3e Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 20 Feb 2024 13:32:03 -0700 Subject: [PATCH 1101/1204] stopping the Suffix search going up once we hit a seen block --- pyomo/gdp/plugins/hull.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 5a3349a8b34..911233a0b2b 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -227,11 +227,12 @@ def _get_user_defined_local_vars(self, targets): # now look up in the tree blk = t while blk is not None: - if blk not in seen_blocks: - self._collect_local_vars_from_block( - blk, user_defined_local_vars - ) - seen_blocks.add(blk) + if blk in seen_blocks: + break + self._collect_local_vars_from_block( + blk, user_defined_local_vars + ) + seen_blocks.add(blk) blk = blk.parent_block() return user_defined_local_vars From 044316c796f11a8e2bf77b8ca797d95a7cb4fcf4 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 20 Feb 2024 13:59:13 -0700 Subject: [PATCH 1102/1204] Black --- pyomo/gdp/plugins/hull.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 911233a0b2b..6ee329cbff7 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -229,9 +229,7 @@ def _get_user_defined_local_vars(self, targets): while blk is not None: if blk in seen_blocks: break - self._collect_local_vars_from_block( - blk, user_defined_local_vars - ) + self._collect_local_vars_from_block(blk, user_defined_local_vars) seen_blocks.add(blk) blk = blk.parent_block() return user_defined_local_vars From 074ea7807b226c8854c74d15a6d433955dd32da5 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 20 Feb 2024 14:01:10 -0700 Subject: [PATCH 1103/1204] Generalize the tests --- pyomo/contrib/solver/tests/unit/test_ipopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/solver/tests/unit/test_ipopt.py b/pyomo/contrib/solver/tests/unit/test_ipopt.py index ae07bd37f86..eff8787592e 100644 --- a/pyomo/contrib/solver/tests/unit/test_ipopt.py +++ b/pyomo/contrib/solver/tests/unit/test_ipopt.py @@ -227,7 +227,7 @@ def test_create_command_line(self): self.assertEqual( result, [ - '/Users/mmundt/Documents/idaes/venv-pyomo/bin/ipopt', + str(opt.config.executable), 'myfile.nl', '-AMPL', 'option_file_name=myfile.opt', From 66285d9d9ff544a03ad3c176f6018b0858d9abad Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 20 Feb 2024 14:35:25 -0700 Subject: [PATCH 1104/1204] Switching the bigm constraint map to a DefaultComponentMap :) --- pyomo/gdp/plugins/hull.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 6ee329cbff7..f1ed574907c 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -15,7 +15,7 @@ import pyomo.common.config as cfg from pyomo.common import deprecated -from pyomo.common.collections import ComponentMap, ComponentSet +from pyomo.common.collections import ComponentMap, ComponentSet, DefaultComponentMap from pyomo.common.modeling import unique_component_name from pyomo.core.expr.numvalue import ZeroConstant import pyomo.core.expr as EXPR @@ -457,11 +457,9 @@ def _transform_disjunctionData( dis_var_info['original_var_map'] = ComponentMap() original_var_map = dis_var_info['original_var_map'] if 'bigm_constraint_map' not in dis_var_info: - dis_var_info['bigm_constraint_map'] = ComponentMap() + dis_var_info['bigm_constraint_map'] = DefaultComponentMap(dict) bigm_constraint_map = dis_var_info['bigm_constraint_map'] - if disaggregated_var not in bigm_constraint_map: - bigm_constraint_map[disaggregated_var] = {} bigm_constraint_map[disaggregated_var][obj] = Reference( disaggregated_var_bounds[idx, :] ) @@ -565,9 +563,7 @@ def _transform_disjunct( # update the bigm constraint mappings data_dict = disaggregatedVar.parent_block().private_data() if 'bigm_constraint_map' not in data_dict: - data_dict['bigm_constraint_map'] = ComponentMap() - if disaggregatedVar not in data_dict['bigm_constraint_map']: - data_dict['bigm_constraint_map'][disaggregatedVar] = {} + data_dict['bigm_constraint_map'] = DefaultComponentMap(dict) data_dict['bigm_constraint_map'][disaggregatedVar][obj] = bigmConstraint disjunct_disaggregated_var_map[obj][var] = disaggregatedVar @@ -598,9 +594,7 @@ def _transform_disjunct( # update the bigm constraint mappings data_dict = var.parent_block().private_data() if 'bigm_constraint_map' not in data_dict: - data_dict['bigm_constraint_map'] = ComponentMap() - if var not in data_dict['bigm_constraint_map']: - data_dict['bigm_constraint_map'][var] = {} + data_dict['bigm_constraint_map'] = DefaultComponentMap(dict) data_dict['bigm_constraint_map'][var][obj] = bigmConstraint disjunct_disaggregated_var_map[obj][var] = var From b96bd2a583f894d38a963d5f133404e551aea1e7 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 20 Feb 2024 15:05:17 -0700 Subject: [PATCH 1105/1204] Add Block.register_private_data_initializer() --- pyomo/core/base/block.py | 27 ++++++++++++-- pyomo/core/tests/unit/test_block.py | 58 +++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 48353078fca..a0948c693d7 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -14,13 +14,13 @@ import sys import weakref import textwrap -from contextlib import contextmanager +from collections import defaultdict +from contextlib import contextmanager from inspect import isclass, currentframe +from io import StringIO from itertools import filterfalse, chain from operator import itemgetter, attrgetter -from io import StringIO -from pyomo.common.pyomo_typing import overload from pyomo.common.autoslots import AutoSlots from pyomo.common.collections import Mapping @@ -28,6 +28,7 @@ from pyomo.common.formatting import StreamIndenter from pyomo.common.gc_manager import PauseGC from pyomo.common.log import is_debug_set +from pyomo.common.pyomo_typing import overload from pyomo.common.timing import ConstructionTimer from pyomo.core.base.component import ( Component, @@ -1986,7 +1987,7 @@ def private_data(self, scope=None): if self._private_data is None: self._private_data = {} if scope not in self._private_data: - self._private_data[scope] = {} + self._private_data[scope] = Block._private_data_initializers[scope]() return self._private_data[scope] @@ -2004,6 +2005,7 @@ class Block(ActiveIndexedComponent): """ _ComponentDataClass = _BlockData + _private_data_initializers = defaultdict(lambda: dict) def __new__(cls, *args, **kwds): if cls != Block: @@ -2207,6 +2209,23 @@ def display(self, filename=None, ostream=None, prefix=""): for key in sorted(self): _BlockData.display(self[key], filename, ostream, prefix) + @staticmethod + def register_private_data_initializer(initializer, scope=None): + mod = currentframe().f_back.f_globals['__name__'] + if scope is None: + scope = mod + elif not mod.startswith(scope): + raise ValueError( + "'private_data' scope must be substrings of the caller's module name. " + f"Received '{scope}' when calling register_private_data_initializer()." + ) + if scope in Block._private_data_initializers: + raise RuntimeError( + "Duplicate initializer registration for 'private_data' dictionary " + f"(scope={scope})" + ) + Block._private_data_initializers[scope] = initializer + class ScalarBlock(_BlockData, Block): def __init__(self, *args, **kwds): diff --git a/pyomo/core/tests/unit/test_block.py b/pyomo/core/tests/unit/test_block.py index c9c68a820f7..88646643703 100644 --- a/pyomo/core/tests/unit/test_block.py +++ b/pyomo/core/tests/unit/test_block.py @@ -3437,6 +3437,64 @@ def test_private_data(self): mfe4 = m.b.b[1].private_data('pyomo.core.tests') self.assertIs(mfe4, mfe3) + def test_register_private_data(self): + _save = Block._private_data_initializers + + Block._private_data_initializers = pdi = _save.copy() + pdi.clear() + try: + self.assertEqual(len(pdi), 0) + b = Block(concrete=True) + ps = b.private_data() + self.assertEqual(ps, {}) + self.assertEqual(len(pdi), 1) + finally: + Block._private_data_initializers = _save + + def init(): + return {'a': None, 'b': 1} + + Block._private_data_initializers = pdi = _save.copy() + pdi.clear() + try: + self.assertEqual(len(pdi), 0) + Block.register_private_data_initializer(init) + self.assertEqual(len(pdi), 1) + + b = Block(concrete=True) + ps = b.private_data() + self.assertEqual(ps, {'a': None, 'b': 1}) + self.assertEqual(len(pdi), 1) + finally: + Block._private_data_initializers = _save + + Block._private_data_initializers = pdi = _save.copy() + pdi.clear() + try: + Block.register_private_data_initializer(init) + self.assertEqual(len(pdi), 1) + Block.register_private_data_initializer(init, 'pyomo') + self.assertEqual(len(pdi), 2) + + with self.assertRaisesRegex( + RuntimeError, + r"Duplicate initializer registration for 'private_data' " + r"dictionary \(scope=pyomo.core.tests.unit.test_block\)", + ): + Block.register_private_data_initializer(init) + + with self.assertRaisesRegex( + ValueError, + r"'private_data' scope must be substrings of the caller's " + r"module name. Received 'invalid' when calling " + r"register_private_data_initializer\(\).", + ): + Block.register_private_data_initializer(init, 'invalid') + + self.assertEqual(len(pdi), 2) + finally: + Block._private_data_initializers = _save + if __name__ == "__main__": unittest.main() From 05e0b470731611dc50725c1128613c1db3d84f58 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 20 Feb 2024 16:08:45 -0700 Subject: [PATCH 1106/1204] Bug fix: missing imports --- pyomo/contrib/solver/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index 8f715ac7250..c91eb603b32 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -14,7 +14,7 @@ import sys from collections.abc import Sequence -from typing import Optional +from typing import Optional, List, TextIO from pyomo.common.config import ( ConfigDict, From a462f362a0dd4214ee28a7f9a4d829a6e90d8419 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 20 Feb 2024 16:34:09 -0700 Subject: [PATCH 1107/1204] Registering a data class with the private data for the hull scope--this is very pretty --- pyomo/gdp/plugins/hull.py | 103 ++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 60 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index f1ed574907c..78d4e917fca 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -13,6 +13,7 @@ from collections import defaultdict +from pyomo.common.autoslots import AutoSlots import pyomo.common.config as cfg from pyomo.common import deprecated from pyomo.common.collections import ComponentMap, ComponentSet, DefaultComponentMap @@ -55,6 +56,17 @@ logger = logging.getLogger('pyomo.gdp.hull') +class _HullTransformationData(AutoSlots.Mixin): + __slots__ = ('disaggregated_var_map', + 'original_var_map', + 'bigm_constraint_map') + + def __init__(self): + self.disaggregated_var_map = DefaultComponentMap(ComponentMap) + self.original_var_map = ComponentMap() + self.bigm_constraint_map = DefaultComponentMap(ComponentMap) + +Block.register_private_data_initializer(_HullTransformationData) @TransformationFactory.register( 'gdp.hull', doc="Relax disjunctive model by forming the hull reformulation." @@ -449,21 +461,13 @@ def _transform_disjunctionData( ) # Update mappings: var_info = var.parent_block().private_data() - if 'disaggregated_var_map' not in var_info: - var_info['disaggregated_var_map'] = ComponentMap() - disaggregated_var_map = var_info['disaggregated_var_map'] + disaggregated_var_map = var_info.disaggregated_var_map dis_var_info = disaggregated_var.parent_block().private_data() - if 'original_var_map' not in dis_var_info: - dis_var_info['original_var_map'] = ComponentMap() - original_var_map = dis_var_info['original_var_map'] - if 'bigm_constraint_map' not in dis_var_info: - dis_var_info['bigm_constraint_map'] = DefaultComponentMap(dict) - bigm_constraint_map = dis_var_info['bigm_constraint_map'] - - bigm_constraint_map[disaggregated_var][obj] = Reference( + + dis_var_info.bigm_constraint_map[disaggregated_var][obj] = Reference( disaggregated_var_bounds[idx, :] ) - original_var_map[disaggregated_var] = var + dis_var_info.original_var_map[disaggregated_var] = var # For every Disjunct the Var does not appear in, we want to map # that this new variable is its disaggreggated variable. @@ -475,8 +479,6 @@ def _transform_disjunctionData( disj._transformation_block is not None and disj not in disjuncts_var_appears_in[var] ): - if not disj in disaggregated_var_map: - disaggregated_var_map[disj] = ComponentMap() disaggregated_var_map[disj][var] = disaggregated_var # start the expression for the reaggregation constraint with @@ -562,9 +564,7 @@ def _transform_disjunct( ) # update the bigm constraint mappings data_dict = disaggregatedVar.parent_block().private_data() - if 'bigm_constraint_map' not in data_dict: - data_dict['bigm_constraint_map'] = DefaultComponentMap(dict) - data_dict['bigm_constraint_map'][disaggregatedVar][obj] = bigmConstraint + data_dict.bigm_constraint_map[disaggregatedVar][obj] = bigmConstraint disjunct_disaggregated_var_map[obj][var] = disaggregatedVar for var in local_vars: @@ -593,9 +593,7 @@ def _transform_disjunct( ) # update the bigm constraint mappings data_dict = var.parent_block().private_data() - if 'bigm_constraint_map' not in data_dict: - data_dict['bigm_constraint_map'] = DefaultComponentMap(dict) - data_dict['bigm_constraint_map'][var][obj] = bigmConstraint + data_dict.bigm_constraint_map[var][obj] = bigmConstraint disjunct_disaggregated_var_map[obj][var] = var var_substitute_map = dict( @@ -645,21 +643,13 @@ def _declare_disaggregated_var_bounds( bigmConstraint.add(ub_idx, disaggregatedVar <= ub * var_free_indicator) original_var_info = original_var.parent_block().private_data() - if 'disaggregated_var_map' not in original_var_info: - original_var_info['disaggregated_var_map'] = ComponentMap() - disaggregated_var_map = original_var_info['disaggregated_var_map'] - + disaggregated_var_map = original_var_info.disaggregated_var_map disaggregated_var_info = disaggregatedVar.parent_block().private_data() - if 'original_var_map' not in disaggregated_var_info: - disaggregated_var_info['original_var_map'] = ComponentMap() - original_var_map = disaggregated_var_info['original_var_map'] # store the mappings from variables to their disaggregated selves on # the transformation block - if disjunct not in disaggregated_var_map: - disaggregated_var_map[disjunct] = ComponentMap() disaggregated_var_map[disjunct][original_var] = disaggregatedVar - original_var_map[disaggregatedVar] = original_var + disaggregated_var_info.original_var_map[disaggregatedVar] = original_var def _get_local_var_list(self, parent_disjunct): # Add or retrieve Suffix from parent_disjunct so that, if this is @@ -885,17 +875,12 @@ def get_disaggregated_var(self, v, disjunct, raise_exception=True): "It does not appear '%s' is a " "variable that appears in disjunct '%s'" % (v.name, disjunct.name) ) - var_map = v.parent_block().private_data() - if 'disaggregated_var_map' in var_map: - try: - return var_map['disaggregated_var_map'][disjunct][v] - except: - if raise_exception: - logger.error(msg) - raise - elif raise_exception: - raise GDP_Error(msg) - return None + disaggregated_var_map = v.parent_block().private_data().disaggregated_var_map + if v in disaggregated_var_map[disjunct]: + return disaggregated_var_map[disjunct][v] + else: + if raise_exception: + raise GDP_Error(msg) def get_src_var(self, disaggregated_var): """ @@ -910,9 +895,8 @@ def get_src_var(self, disaggregated_var): of some Disjunct) """ var_map = disaggregated_var.parent_block().private_data() - if 'original_var_map' in var_map: - if disaggregated_var in var_map['original_var_map']: - return var_map['original_var_map'][disaggregated_var] + if disaggregated_var in var_map.original_var_map: + return var_map.original_var_map[disaggregated_var] raise GDP_Error( "'%s' does not appear to be a " "disaggregated variable" % disaggregated_var.name @@ -979,22 +963,21 @@ def get_var_bounds_constraint(self, v, disjunct=None): Optional since for non-nested models this can be inferred. """ info = v.parent_block().private_data() - if 'bigm_constraint_map' in info: - if v in info['bigm_constraint_map']: - if len(info['bigm_constraint_map'][v]) == 1: - # Not nested, or it's at the top layer, so we're fine. - return list(info['bigm_constraint_map'][v].values())[0] - elif disjunct is not None: - # This is nested, so we need to walk up to find the active ones - return info['bigm_constraint_map'][v][disjunct] - else: - raise ValueError( - "It appears that the variable '%s' appears " - "within a nested GDP hierarchy, and no " - "'disjunct' argument was specified. Please " - "specify for which Disjunct the bounds " - "constraint for '%s' should be returned." % (v, v) - ) + if v in info.bigm_constraint_map: + if len(info.bigm_constraint_map[v]) == 1: + # Not nested, or it's at the top layer, so we're fine. + return list(info.bigm_constraint_map[v].values())[0] + elif disjunct is not None: + # This is nested, so we need to walk up to find the active ones + return info.bigm_constraint_map[v][disjunct] + else: + raise ValueError( + "It appears that the variable '%s' appears " + "within a nested GDP hierarchy, and no " + "'disjunct' argument was specified. Please " + "specify for which Disjunct the bounds " + "constraint for '%s' should be returned." % (v, v) + ) raise GDP_Error( "Either '%s' is not a disaggregated variable, or " "the disjunction that disaggregates it has not " From 5a71219cb49d236719b8a9c725ac37ac6a7c020f Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 20 Feb 2024 16:35:22 -0700 Subject: [PATCH 1108/1204] Black is relatively tame --- pyomo/gdp/plugins/hull.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 78d4e917fca..d1c38bde039 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -56,18 +56,19 @@ logger = logging.getLogger('pyomo.gdp.hull') + class _HullTransformationData(AutoSlots.Mixin): - __slots__ = ('disaggregated_var_map', - 'original_var_map', - 'bigm_constraint_map') + __slots__ = ('disaggregated_var_map', 'original_var_map', 'bigm_constraint_map') def __init__(self): self.disaggregated_var_map = DefaultComponentMap(ComponentMap) self.original_var_map = ComponentMap() self.bigm_constraint_map = DefaultComponentMap(ComponentMap) + Block.register_private_data_initializer(_HullTransformationData) + @TransformationFactory.register( 'gdp.hull', doc="Relax disjunctive model by forming the hull reformulation." ) From 33a05453c2251d0a51821ea1b8733fb11f57a7c1 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 20 Feb 2024 23:42:21 -0700 Subject: [PATCH 1109/1204] Update intersphinx links, remove documentation of nonfunctional code --- doc/OnlineDocs/conf.py | 4 ++-- .../library_reference/expressions/context_managers.rst | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/doc/OnlineDocs/conf.py b/doc/OnlineDocs/conf.py index 89c346f5abc..7196606b7d6 100644 --- a/doc/OnlineDocs/conf.py +++ b/doc/OnlineDocs/conf.py @@ -57,8 +57,8 @@ 'numpy': ('https://numpy.org/doc/stable/', None), 'pandas': ('https://pandas.pydata.org/docs/', None), 'scikit-learn': ('https://scikit-learn.org/stable/', None), - 'scipy': ('https://docs.scipy.org/doc/scipy/reference/', None), - 'Sphinx': ('https://www.sphinx-doc.org/en/stable/', None), + 'scipy': ('https://docs.scipy.org/doc/scipy/', None), + 'Sphinx': ('https://www.sphinx-doc.org/en/master/', None), } # -- General configuration ------------------------------------------------ diff --git a/doc/OnlineDocs/library_reference/expressions/context_managers.rst b/doc/OnlineDocs/library_reference/expressions/context_managers.rst index 0e92f583c73..ae6884d684f 100644 --- a/doc/OnlineDocs/library_reference/expressions/context_managers.rst +++ b/doc/OnlineDocs/library_reference/expressions/context_managers.rst @@ -8,6 +8,3 @@ Context Managers .. autoclass:: pyomo.core.expr.linear_expression :members: -.. autoclass:: pyomo.core.expr.current.clone_counter - :members: - From 90f6901c51ca5272d5ccfd1ce6787bfdce1ad499 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 21 Feb 2024 01:30:42 -0700 Subject: [PATCH 1110/1204] Updating CHANGELOG in preparation for the release --- CHANGELOG.md | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 553a4f1c3bd..747025a8bdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,81 @@ Pyomo CHANGELOG =============== +------------------------------------------------------------------------------- +Pyomo 6.7.1 (21 Feb 2024) +------------------------------------------------------------------------------- + +- General + - Add support for tuples in `ComponentMap`; add `DefaultComponentMap` (#3150) + - Update `Path`, `PathList`, and `IsInstance` Domain Validators (#3144) + - Remove usage of `__all__` (#3142) + - Extend Path and Type Checking Validators of `common.config` (#3140) + - Update Copyright Statements (#3139) + - Update `ExitNodeDispatcher` to better support extensibility (#3125) + - Create contributors data gathering script (#3117) + - Prevent duplicate entries in ConfigDict declaration order (#3116) + - Remove unnecessary `__future__` imports (#3109) + - Import pandas through pyomo.common.dependencies (#3102) + - Update links to workshop slides (#3079) + - Remove incorrect use of identity (is) comparisons (#3061) +- Core + - Add `Block.register_private_data_initializer()` (#3153) + - Generalize the simple_constraint_rule decorator (#3152) + - Fix edge case assigning new numeric types to Var/Param with units (#3151) + - Add private_data to `_BlockData` (#3138) + - Convert implicit sets created by `IndexedComponent`s to "anonymous" sets (#3075) + - Add `all_different` and `count_if` to the logical expression system (#3058) + - Fix RangeSet.__len__ when defined by floats (#3119) + - Overhaul the `Suffix` component (#3072) + - Enforce expression immutability in `expr.args` (#3099) + - Improve NumPy registration when assigning numpy to Param (#3093) + - Track changes in PyPy behavior introduced in 7.3.14 (#3087) + - Remove automatic numpy import (#3077) + - Fix `range_difference` for Sets with nonzero anchor points (#3063) + - Clarify errors raised by accessing Sets by positional index (#3062) +- Documentation + - Update MPC documentation and citation (#3148) + - Fix an error in the documentation for LinearExpression (#3090) + - Fix bugs in the documentation of Pyomo.DoE (#3070) + - Fix a latex_printer vestige in the documentation (#3066) +- Solver Interfaces + - Make error msg more explicit wrt different interfaces (#3141) + - NLv2: only raise exception for empty models in the legacy API (#3135) + - Add `to_expr()` to AMPLRepn, fix NLWriterInfo return type (#3095) +- Testing + - Update Release Wheel Builder Action (#3149) + - Actions Version Update: Address node.js deprecations (#3118) + - New Black Major Release (24.1.0) (#3108) + - Use scip for PyROS tests (#3104) + - Add missing solver dependency flags for OnlineDocs tests (#3094) + - Re-enable `contrib.viewer.tests.test_qt.py` (#3085) + - Add automated testing of OnlineDocs examples (#3080) + - Silence deprecation warnings emitted by Pyomo tests (#3076) + - Fix Python 3.12 tests (manage `pyutilib`, `distutils` dependencies) (#3065) +- DAE + - Replace deprecated `numpy.math` alias with standard `math` module (#3074) +- GDP + - Handle nested GDPs correctly in all the transformations (#3145) + - Fix bugs in nested models in gdp.hull transformation (#3143) + - Various bug fixes in gdp.mbigm transformation (#3073) + - Add GDP => MINLP Transformation (#3082) +- Contributed Packages + - GDPopt: Fix lbb solve_data bug (#3133) + - GDPopt: Adding missing import for gdpopt.enumerate (#3105) + - FBBT: Extend `fbbt.ExpressionBoundsVisitor` to handle relational + expressions and Expr_if (#3129) + - incidence_analysis: Method to add an edge in IncidenceGraphInterface (#3120) + - incidence_analysis: Add subgraph method to IncidencegraphInterface (#3122) + - incidence_analysis: Add `ampl_repn` option (#3069) + - incidence_analysis: Fix config documentation of `linear_only` argument in + `get_incident_variables` (#3067) + - interior_point: Workaround for improvement in Mumps memory prediction + algorithm (#3114) + - MindtPy: Various bug fixes (#3034) + - PyROS: Update Solver Argument Resolution and Validation Routines (#3126) + - PyROS: Update Subproblem Initialization Routines (#3071) + - PyROS: Fix DR polishing under nominal objective focus (#3060) + ------------------------------------------------------------------------------- Pyomo 6.7.0 (29 Nov 2023) ------------------------------------------------------------------------------- From 1bccb1699bd504af1e851eb8ffdb61e152af6578 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 21 Feb 2024 08:03:57 -0700 Subject: [PATCH 1111/1204] Address comments from @jsiirola, @blnicho --- pyomo/common/tests/test_config.py | 1 - pyomo/contrib/appsi/base.py | 1 + pyomo/contrib/solver/base.py | 12 ++- pyomo/contrib/solver/config.py | 22 +++--- pyomo/contrib/solver/gurobi.py | 4 +- pyomo/contrib/solver/ipopt.py | 79 ++++--------------- pyomo/contrib/solver/persistent.py | 16 ++-- .../tests/solvers/test_gurobi_persistent.py | 2 +- .../solver/tests/solvers/test_solvers.py | 31 +++----- pyomo/contrib/solver/tests/unit/test_base.py | 16 ++-- pyomo/contrib/solver/tests/unit/test_ipopt.py | 20 +---- .../contrib/solver/tests/unit/test_results.py | 6 +- 12 files changed, 68 insertions(+), 142 deletions(-) diff --git a/pyomo/common/tests/test_config.py b/pyomo/common/tests/test_config.py index 02f4fc88251..0bbed43423d 100644 --- a/pyomo/common/tests/test_config.py +++ b/pyomo/common/tests/test_config.py @@ -25,7 +25,6 @@ # ___________________________________________________________________________ import argparse -import datetime import enum import os import os.path diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index 201e5975ac9..d028bdc4fde 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -597,6 +597,7 @@ def __init__( class Solver(abc.ABC): class Availability(enum.IntEnum): + """Docstring""" NotFound = 0 BadVersion = -1 BadLicense = -2 diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 3bfa83050ad..f3d60bef03d 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -125,7 +125,7 @@ def solve(self, model: _BlockData, **kwargs) -> Results: """ @abc.abstractmethod - def available(self): + def available(self) -> bool: """Test if the solver is available on this system. Nominally, this will return True if the solver interface is @@ -159,7 +159,7 @@ def version(self) -> Tuple: A tuple representing the version """ - def is_persistent(self): + def is_persistent(self) -> bool: """ Returns ------- @@ -178,9 +178,7 @@ class PersistentSolverBase(SolverBase): Example usage can be seen in the Gurobi interface. """ - CONFIG = PersistentSolverConfig() - - @document_kwargs_from_configdict(CONFIG) + @document_kwargs_from_configdict(PersistentSolverConfig()) @abc.abstractmethod def solve(self, model: _BlockData, **kwargs) -> Results: super().solve(model, kwargs) @@ -312,7 +310,7 @@ def remove_variables(self, variables: List[_GeneralVarData]): """ @abc.abstractmethod - def remove_params(self, params: List[_ParamData]): + def remove_parameters(self, params: List[_ParamData]): """ Remove parameters from the model """ @@ -336,7 +334,7 @@ def update_variables(self, variables: List[_GeneralVarData]): """ @abc.abstractmethod - def update_params(self): + def update_parameters(self): """ Update parameters on the model """ diff --git a/pyomo/contrib/solver/config.py b/pyomo/contrib/solver/config.py index c91eb603b32..e60219a74b5 100644 --- a/pyomo/contrib/solver/config.py +++ b/pyomo/contrib/solver/config.py @@ -23,6 +23,7 @@ NonNegativeInt, ADVANCED_OPTION, Bool, + Path, ) from pyomo.common.log import LogStream from pyomo.common.numeric_types import native_logical_types @@ -74,17 +75,17 @@ def __init__( ConfigValue( domain=TextIO_or_Logger, default=False, - description="""`tee` accepts :py:class:`bool`, + description="""``tee`` accepts :py:class:`bool`, :py:class:`io.TextIOBase`, or :py:class:`logging.Logger` (or a list of these types). ``True`` is mapped to ``sys.stdout``. The solver log will be printed to each of - these streams / destinations. """, + these streams / destinations.""", ), ) - self.working_dir: Optional[str] = self.declare( + self.working_dir: Optional[Path] = self.declare( 'working_dir', ConfigValue( - domain=str, + domain=Path(), default=None, description="The directory in which generated files should be saved. " "This replaces the `keepfiles` option.", @@ -134,7 +135,8 @@ def __init__( self.time_limit: Optional[float] = self.declare( 'time_limit', ConfigValue( - domain=NonNegativeFloat, description="Time limit applied to the solver." + domain=NonNegativeFloat, + description="Time limit applied to the solver (in seconds).", ), ) self.solver_options: ConfigDict = self.declare( @@ -201,7 +203,7 @@ class AutoUpdateConfig(ConfigDict): check_for_new_objective: bool update_constraints: bool update_vars: bool - update_params: bool + update_parameters: bool update_named_expressions: bool update_objective: bool treat_fixed_vars_as_params: bool @@ -257,7 +259,7 @@ def __init__( description=""" If False, new/old parameters will not be automatically detected on subsequent solves. Use False only when manually updating the solver with opt.add_parameters() and - opt.remove_params() or when you are certain parameters are not being added to / + opt.remove_parameters() or when you are certain parameters are not being added to / removed from the model.""", ), ) @@ -297,15 +299,15 @@ def __init__( opt.update_variables() or when you are certain variables are not being modified.""", ), ) - self.update_params: bool = self.declare( - 'update_params', + self.update_parameters: bool = self.declare( + 'update_parameters', ConfigValue( domain=bool, default=True, description=""" If False, changes to parameter values will not be automatically detected on subsequent solves. Use False only when manually updating the solver with - opt.update_params() or when you are certain parameters are not being modified.""", + opt.update_parameters() or when you are certain parameters are not being modified.""", ), ) self.update_named_expressions: bool = self.declare( diff --git a/pyomo/contrib/solver/gurobi.py b/pyomo/contrib/solver/gurobi.py index 63387730c45..d0ac0d80f45 100644 --- a/pyomo/contrib/solver/gurobi.py +++ b/pyomo/contrib/solver/gurobi.py @@ -747,7 +747,7 @@ def _remove_variables(self, variables: List[_GeneralVarData]): self._mutable_bounds.pop(v_id, None) self._needs_updated = True - def _remove_params(self, params: List[_ParamData]): + def _remove_parameters(self, params: List[_ParamData]): pass def _update_variables(self, variables: List[_GeneralVarData]): @@ -770,7 +770,7 @@ def _update_variables(self, variables: List[_GeneralVarData]): gurobipy_var.setAttr('vtype', vtype) self._needs_updated = True - def update_params(self): + def update_parameters(self): for con, helpers in self._mutable_helpers.items(): for helper in helpers: helper.update() diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 3e911aea036..ad12e26ee92 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -13,16 +13,10 @@ import subprocess import datetime import io -import sys from typing import Mapping, Optional, Sequence from pyomo.common import Executable -from pyomo.common.config import ( - ConfigValue, - NonNegativeFloat, - document_kwargs_from_configdict, - ConfigDict, -) +from pyomo.common.config import ConfigValue, document_kwargs_from_configdict, ConfigDict from pyomo.common.errors import PyomoException, DeveloperError from pyomo.common.tempfiles import TempfileManager from pyomo.common.timing import HierarchicalTimer @@ -78,54 +72,7 @@ def __init__( ), ) self.writer_config: ConfigDict = self.declare( - 'writer_config', NLWriter.CONFIG() - ) - - -class IpoptResults(Results): - def __init__( - self, - description=None, - doc=None, - implicit=False, - implicit_domain=None, - visibility=0, - ): - super().__init__( - description=description, - doc=doc, - implicit=implicit, - implicit_domain=implicit_domain, - visibility=visibility, - ) - self.timing_info.ipopt_excluding_nlp_functions: Optional[float] = ( - self.timing_info.declare( - 'ipopt_excluding_nlp_functions', - ConfigValue( - domain=NonNegativeFloat, - default=None, - description="Total CPU seconds in IPOPT without function evaluations.", - ), - ) - ) - self.timing_info.nlp_function_evaluations: Optional[float] = ( - self.timing_info.declare( - 'nlp_function_evaluations', - ConfigValue( - domain=NonNegativeFloat, - default=None, - description="Total CPU seconds in NLP function evaluations.", - ), - ) - ) - self.timing_info.total_seconds: Optional[float] = self.timing_info.declare( - 'total_seconds', - ConfigValue( - domain=NonNegativeFloat, - default=None, - description="Total seconds in IPOPT. NOTE: Newer versions of IPOPT (3.14+) " - "no longer separate timing information.", - ), + 'writer_config', ConfigValue(default=NLWriter.CONFIG(), description="Configuration that controls options in the NL writer.") ) @@ -416,11 +363,11 @@ def solve(self, model, **kwds): if len(nl_info.variables) == 0: if len(nl_info.eliminated_vars) == 0: - results = IpoptResults() + results = Results() results.termination_condition = TerminationCondition.emptyModel results.solution_loader = SolSolutionLoader(None, None) else: - results = IpoptResults() + results = Results() results.termination_condition = ( TerminationCondition.convergenceCriteriaSatisfied ) @@ -435,18 +382,22 @@ def solve(self, model, **kwds): results = self._parse_solution(sol_file, nl_info) timer.stop('parse_sol') else: - results = IpoptResults() + results = Results() if process.returncode != 0: results.extra_info.return_code = process.returncode results.termination_condition = TerminationCondition.error results.solution_loader = SolSolutionLoader(None, None) else: results.iteration_count = iters - results.timing_info.ipopt_excluding_nlp_functions = ( - ipopt_time_nofunc - ) - results.timing_info.nlp_function_evaluations = ipopt_time_func - results.timing_info.total_seconds = ipopt_total_time + if ipopt_time_nofunc is not None: + results.timing_info.ipopt_excluding_nlp_functions = ( + ipopt_time_nofunc + ) + + if ipopt_time_func is not None: + results.timing_info.nlp_function_evaluations = ipopt_time_func + if ipopt_total_time is not None: + results.timing_info.total_seconds = ipopt_total_time if ( config.raise_exception_on_nonoptimal_result and results.solution_status != SolutionStatus.optimal @@ -554,7 +505,7 @@ def _parse_ipopt_output(self, stream: io.StringIO): return iters, nofunc_time, func_time, total_time def _parse_solution(self, instream: io.TextIOBase, nl_info: NLWriterInfo): - results = IpoptResults() + results = Results() res, sol_data = parse_sol_file( sol_file=instream, nl_info=nl_info, result=results ) diff --git a/pyomo/contrib/solver/persistent.py b/pyomo/contrib/solver/persistent.py index e389e5d4019..4b1a7c58dcd 100644 --- a/pyomo/contrib/solver/persistent.py +++ b/pyomo/contrib/solver/persistent.py @@ -274,11 +274,11 @@ def remove_variables(self, variables: List[_GeneralVarData]): del self._vars[v_id] @abc.abstractmethod - def _remove_params(self, params: List[_ParamData]): + def _remove_parameters(self, params: List[_ParamData]): pass - def remove_params(self, params: List[_ParamData]): - self._remove_params(params) + def remove_parameters(self, params: List[_ParamData]): + self._remove_parameters(params) for p in params: del self._params[id(p)] @@ -297,7 +297,7 @@ def remove_block(self, block): ) ) ) - self.remove_params( + self.remove_parameters( list( dict( (id(p), p) @@ -325,7 +325,7 @@ def update_variables(self, variables: List[_GeneralVarData]): self._update_variables(variables) @abc.abstractmethod - def update_params(self): + def update_parameters(self): pass def update(self, timer: HierarchicalTimer = None): @@ -396,12 +396,12 @@ def update(self, timer: HierarchicalTimer = None): self.remove_sos_constraints(old_sos) timer.stop('cons') timer.start('params') - self.remove_params(old_params) + self.remove_parameters(old_params) # sticking this between removal and addition # is important so that we don't do unnecessary work - if config.update_params: - self.update_params() + if config.update_parameters: + self.update_parameters() self.add_parameters(new_params) timer.stop('params') diff --git a/pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py b/pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py index f2dd79619b4..2f281e2abf0 100644 --- a/pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py +++ b/pyomo/contrib/solver/tests/solvers/test_gurobi_persistent.py @@ -487,7 +487,7 @@ def setUp(self): opt.config.auto_updates.check_for_new_or_removed_params = False opt.config.auto_updates.check_for_new_or_removed_vars = False opt.config.auto_updates.check_for_new_or_removed_constraints = False - opt.config.auto_updates.update_params = False + opt.config.auto_updates.update_parameters = False opt.config.auto_updates.update_vars = False opt.config.auto_updates.update_constraints = False opt.config.auto_updates.update_named_expressions = False diff --git a/pyomo/contrib/solver/tests/solvers/test_solvers.py b/pyomo/contrib/solver/tests/solvers/test_solvers.py index c6c73ea2dc7..cf5f6cf5c57 100644 --- a/pyomo/contrib/solver/tests/solvers/test_solvers.py +++ b/pyomo/contrib/solver/tests/solvers/test_solvers.py @@ -9,23 +9,24 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +import random +import math +from typing import Type + import pyomo.environ as pe +from pyomo import gdp from pyomo.common.dependencies import attempt_import import pyomo.common.unittest as unittest - -parameterized, param_available = attempt_import('parameterized') -parameterized = parameterized.parameterized from pyomo.contrib.solver.results import TerminationCondition, SolutionStatus, Results from pyomo.contrib.solver.base import SolverBase from pyomo.contrib.solver.ipopt import Ipopt from pyomo.contrib.solver.gurobi import Gurobi -from typing import Type from pyomo.core.expr.numeric_expr import LinearExpression -import math -numpy, numpy_available = attempt_import('numpy') -import random -from pyomo import gdp + +np, numpy_available = attempt_import('numpy') +parameterized, param_available = attempt_import('parameterized') +parameterized = parameterized.parameterized if not param_available: @@ -802,10 +803,6 @@ def test_mutable_param_with_range( opt.config.writer_config.linear_presolve = True else: opt.config.writer_config.linear_presolve = False - try: - import numpy as np - except: - raise unittest.SkipTest('numpy is not available') m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() @@ -907,7 +904,7 @@ def test_add_and_remove_vars( m.y = pe.Var(bounds=(-1, None)) m.obj = pe.Objective(expr=m.y) if opt.is_persistent(): - opt.config.auto_updates.update_params = False + opt.config.auto_updates.update_parameters = False opt.config.auto_updates.update_vars = False opt.config.auto_updates.update_constraints = False opt.config.auto_updates.update_named_expressions = False @@ -1003,14 +1000,10 @@ def test_with_numpy( a2 = -2 b2 = 1 m.c1 = pe.Constraint( - expr=(numpy.float64(0), m.y - numpy.int64(1) * m.x - numpy.float32(3), None) + expr=(np.float64(0), m.y - np.int64(1) * m.x - np.float32(3), None) ) m.c2 = pe.Constraint( - expr=( - None, - -m.y + numpy.int32(-2) * m.x + numpy.float64(1), - numpy.float16(0), - ) + expr=(None, -m.y + np.int32(-2) * m.x + np.float64(1), np.float16(0)) ) res = opt.solve(m) self.assertEqual(res.solution_status, SolutionStatus.optimal) diff --git a/pyomo/contrib/solver/tests/unit/test_base.py b/pyomo/contrib/solver/tests/unit/test_base.py index a9b3e4f4711..74c495b86cc 100644 --- a/pyomo/contrib/solver/tests/unit/test_base.py +++ b/pyomo/contrib/solver/tests/unit/test_base.py @@ -80,7 +80,7 @@ def test_custom_solver_name(self): class TestPersistentSolverBase(unittest.TestCase): def test_abstract_member_list(self): expected_list = [ - 'remove_params', + 'remove_parameters', 'version', 'update_variables', 'remove_variables', @@ -88,7 +88,7 @@ def test_abstract_member_list(self): '_get_primals', 'set_instance', 'set_objective', - 'update_params', + 'update_parameters', 'remove_block', 'add_block', 'available', @@ -116,12 +116,12 @@ def test_class_method_list(self): 'is_persistent', 'remove_block', 'remove_constraints', - 'remove_params', + 'remove_parameters', 'remove_variables', 'set_instance', 'set_objective', 'solve', - 'update_params', + 'update_parameters', 'update_variables', 'version', ] @@ -142,12 +142,12 @@ def test_init(self): self.assertEqual(self.instance.add_constraints(None), None) self.assertEqual(self.instance.add_block(None), None) self.assertEqual(self.instance.remove_variables(None), None) - self.assertEqual(self.instance.remove_params(None), None) + self.assertEqual(self.instance.remove_parameters(None), None) self.assertEqual(self.instance.remove_constraints(None), None) self.assertEqual(self.instance.remove_block(None), None) self.assertEqual(self.instance.set_objective(None), None) self.assertEqual(self.instance.update_variables(None), None) - self.assertEqual(self.instance.update_params(), None) + self.assertEqual(self.instance.update_parameters(), None) with self.assertRaises(NotImplementedError): self.instance._get_primals() @@ -168,12 +168,12 @@ def test_context_manager(self): self.assertEqual(self.instance.add_constraints(None), None) self.assertEqual(self.instance.add_block(None), None) self.assertEqual(self.instance.remove_variables(None), None) - self.assertEqual(self.instance.remove_params(None), None) + self.assertEqual(self.instance.remove_parameters(None), None) self.assertEqual(self.instance.remove_constraints(None), None) self.assertEqual(self.instance.remove_block(None), None) self.assertEqual(self.instance.set_objective(None), None) self.assertEqual(self.instance.update_variables(None), None) - self.assertEqual(self.instance.update_params(), None) + self.assertEqual(self.instance.update_parameters(), None) class TestLegacySolverWrapper(unittest.TestCase): diff --git a/pyomo/contrib/solver/tests/unit/test_ipopt.py b/pyomo/contrib/solver/tests/unit/test_ipopt.py index eff8787592e..cc459245506 100644 --- a/pyomo/contrib/solver/tests/unit/test_ipopt.py +++ b/pyomo/contrib/solver/tests/unit/test_ipopt.py @@ -44,7 +44,7 @@ def test_custom_instantiation(self): config.tee = True self.assertTrue(config.tee) self.assertEqual(config._description, "A description") - self.assertFalse(config.time_limit) + self.assertIsNone(config.time_limit) # Default should be `ipopt` self.assertIsNotNone(str(config.executable)) self.assertIn('ipopt', str(config.executable)) @@ -54,24 +54,6 @@ def test_custom_instantiation(self): self.assertFalse(config.executable.available()) -class TestIpoptResults(unittest.TestCase): - def test_default_instantiation(self): - res = ipopt.IpoptResults() - # Inherited methods/attributes - self.assertIsNone(res.solution_loader) - self.assertIsNone(res.incumbent_objective) - self.assertIsNone(res.objective_bound) - self.assertIsNone(res.solver_name) - self.assertIsNone(res.solver_version) - self.assertIsNone(res.iteration_count) - self.assertIsNone(res.timing_info.start_timestamp) - self.assertIsNone(res.timing_info.wall_time) - # Unique to this object - self.assertIsNone(res.timing_info.ipopt_excluding_nlp_functions) - self.assertIsNone(res.timing_info.nlp_function_evaluations) - self.assertIsNone(res.timing_info.total_seconds) - - class TestIpoptSolutionLoader(unittest.TestCase): def test_get_reduced_costs_error(self): loader = ipopt.IpoptSolutionLoader(None, None) diff --git a/pyomo/contrib/solver/tests/unit/test_results.py b/pyomo/contrib/solver/tests/unit/test_results.py index 4856b737295..74404aaba4c 100644 --- a/pyomo/contrib/solver/tests/unit/test_results.py +++ b/pyomo/contrib/solver/tests/unit/test_results.py @@ -21,7 +21,7 @@ from pyomo.contrib.solver import results from pyomo.contrib.solver import solution import pyomo.environ as pyo -from pyomo.core.base.var import ScalarVar +from pyomo.core.base.var import Var class SolutionLoaderExample(solution.SolutionLoaderBase): @@ -213,8 +213,8 @@ def test_display(self): def test_generated_results(self): m = pyo.ConcreteModel() - m.x = ScalarVar() - m.y = ScalarVar() + m.x = Var() + m.y = Var() m.c1 = pyo.Constraint(expr=m.x == 1) m.c2 = pyo.Constraint(expr=m.y == 2) From 9b21273f92d4b62d3666f9ee1dbcb61741376b65 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 21 Feb 2024 08:08:41 -0700 Subject: [PATCH 1112/1204] Apply black --- pyomo/contrib/appsi/base.py | 1 - pyomo/contrib/solver/ipopt.py | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index d028bdc4fde..201e5975ac9 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -597,7 +597,6 @@ def __init__( class Solver(abc.ABC): class Availability(enum.IntEnum): - """Docstring""" NotFound = 0 BadVersion = -1 BadLicense = -2 diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index ad12e26ee92..3ac1a5ac4a2 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -72,7 +72,11 @@ def __init__( ), ) self.writer_config: ConfigDict = self.declare( - 'writer_config', ConfigValue(default=NLWriter.CONFIG(), description="Configuration that controls options in the NL writer.") + 'writer_config', + ConfigValue( + default=NLWriter.CONFIG(), + description="Configuration that controls options in the NL writer.", + ), ) From 434c1dadeeed7531a6b372661891dbf17b7d647b Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 21 Feb 2024 09:11:46 -0700 Subject: [PATCH 1113/1204] More updates to the CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 747025a8bdf..c548a8c830c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Pyomo 6.7.1 (21 Feb 2024) - Fix `range_difference` for Sets with nonzero anchor points (#3063) - Clarify errors raised by accessing Sets by positional index (#3062) - Documentation + - Update intersphinx links, remove docs for nonfunctional code (#3155) - Update MPC documentation and citation (#3148) - Fix an error in the documentation for LinearExpression (#3090) - Fix bugs in the documentation of Pyomo.DoE (#3070) From 3e5cca27025a04d09ecf938433a9afb3534d501b Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 21 Feb 2024 09:30:29 -0700 Subject: [PATCH 1114/1204] More updates to the CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c548a8c830c..daba7cac96c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ Pyomo 6.7.1 (21 Feb 2024) - PyROS: Update Solver Argument Resolution and Validation Routines (#3126) - PyROS: Update Subproblem Initialization Routines (#3071) - PyROS: Fix DR polishing under nominal objective focus (#3060) + - solver: Solver Refactor Part 1: Introducing the new solver interface (#3137) ------------------------------------------------------------------------------- Pyomo 6.7.0 (29 Nov 2023) From d027b190e1331db30b40bf25c2ff4b4a0fccd621 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 21 Feb 2024 09:35:49 -0700 Subject: [PATCH 1115/1204] Updating deprecation version --- pyomo/contrib/solver/base.py | 2 +- pyomo/core/base/suffix.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index f3d60bef03d..13bd5ddb212 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -396,7 +396,7 @@ def _map_config( "`keepfiles` has been deprecated in the new solver interface. " "Use `working_dir` instead to designate a directory in which " f"files should be generated and saved. Setting `working_dir` to `{cwd}`.", - version='6.7.1.dev0', + version='6.7.1', ) self.config.working_dir = cwd # I believe this currently does nothing; however, it is unclear what diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index 0c27eee060f..be2f732650d 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -341,7 +341,7 @@ def clear_all_values(self): @deprecated( 'Suffix.set_datatype is replaced with the Suffix.datatype property', - version='6.7.1.dev0', + version='6.7.1', ) def set_datatype(self, datatype): """ @@ -351,7 +351,7 @@ def set_datatype(self, datatype): @deprecated( 'Suffix.get_datatype is replaced with the Suffix.datatype property', - version='6.7.1.dev0', + version='6.7.1', ) def get_datatype(self): """ @@ -361,7 +361,7 @@ def get_datatype(self): @deprecated( 'Suffix.set_direction is replaced with the Suffix.direction property', - version='6.7.1.dev0', + version='6.7.1', ) def set_direction(self, direction): """ @@ -371,7 +371,7 @@ def set_direction(self, direction): @deprecated( 'Suffix.get_direction is replaced with the Suffix.direction property', - version='6.7.1.dev0', + version='6.7.1', ) def get_direction(self): """ From 156bf168ce817ad3a9c942b94535bf2876baae64 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 21 Feb 2024 09:36:26 -0700 Subject: [PATCH 1116/1204] Updating RELEASE.md --- RELEASE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE.md b/RELEASE.md index 03baa803ac9..8313c969f25 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -11,6 +11,7 @@ The following are highlights of the 6.7 release series: - New writer for converting linear models to matrix form - New packages: - latex_printer (print Pyomo models to a LaTeX compatible format) + - contrib.solve: Part 1 of refactoring Pyomo's solver interfaces - ...and of course numerous minor bug fixes and performance enhancements A full list of updates and changes is available in the From a4d109425a4e7f1f271aeddf0fd536c909f6c9fc Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 21 Feb 2024 09:36:46 -0700 Subject: [PATCH 1117/1204] Clarify some solver documentation --- .../developer_reference/solvers.rst | 113 +++++++++++++----- 1 file changed, 83 insertions(+), 30 deletions(-) diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index 45945c18b12..9f18119e373 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -1,10 +1,11 @@ Future Solver Interface Changes =============================== -Pyomo offers interfaces into multiple solvers, both commercial and open source. -To support better capabilities for solver interfaces, the Pyomo team is actively -redesigning the existing interfaces to make them more maintainable and intuitive -for use. Redesigned interfaces can be found in ``pyomo.contrib.solver``. +Pyomo offers interfaces into multiple solvers, both commercial and open +source. To support better capabilities for solver interfaces, the Pyomo +team is actively redesigning the existing interfaces to make them more +maintainable and intuitive for use. A preview of the redesigned +interfaces can be found in ``pyomo.contrib.solver``. .. currentmodule:: pyomo.contrib.solver @@ -12,27 +13,39 @@ for use. Redesigned interfaces can be found in ``pyomo.contrib.solver``. New Interface Usage ------------------- -The new interfaces have two modes: backwards compatible and future capability. -The future capability mode can be accessed directly or by switching the default -``SolverFactory`` version (see :doc:`future`). Currently, the new versions -available are: +The new interfaces are not completely backwards compatible with the +existing Pyomo solver interfaces. However, to aid in testing and +evaluation, we are distributing versions of the new solver interfaces +that are compatible with the existing ("legacy") solver interface. +These "legacy" interfaces are registered with the current +``SolverFactory`` using slightly different names (to avoid conflicts +with existing interfaces). -.. list-table:: Available Redesigned Solvers - :widths: 25 25 25 +.. |br| raw:: html + +
+ +.. list-table:: Available Redesigned Solvers and Names Registered + in the SolverFactories :header-rows: 1 * - Solver - - ``SolverFactory`` (v1) Name - - ``SolverFactory`` (v3) Name - * - ipopt - - ``ipopt_v2`` + - Name registered in the |br| ``pyomo.contrib.solver.factory.SolverFactory`` + - Name registered in the |br| ``pyomo.opt.base.solvers.LegacySolverFactory`` + * - Ipopt - ``ipopt`` + - ``ipopt_v2`` * - Gurobi - - ``gurobi_v2`` - ``gurobi`` + - ``gurobi_v2`` -Backwards Compatible Mode -^^^^^^^^^^^^^^^^^^^^^^^^^ +Using the new interfaces through the legacy interface +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Here we use the new interface as exposed through the existing (legacy) +solver factory and solver interface wrapper. This provides an API that +is compatible with the existing (legacy) Pyomo solver interface and can +be used with other Pyomo tools / capabilities. .. testcode:: :skipif: not ipopt_available @@ -61,11 +74,10 @@ Backwards Compatible Mode ... 3 Declarations: x y obj -Future Capability Mode -^^^^^^^^^^^^^^^^^^^^^^ +Using the new interfaces directly +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -There are multiple ways to utilize the future capability mode: direct import -or changed ``SolverFactory`` version. +Here we use the new interface by importing it directly: .. testcode:: :skipif: not ipopt_available @@ -87,7 +99,7 @@ or changed ``SolverFactory`` version. opt = Ipopt() status = opt.solve(model) assert_optimal_termination(status) - # Displays important results information; only available in future capability mode + # Displays important results information; only available through the new interfaces status.display() model.pprint() @@ -99,7 +111,49 @@ or changed ``SolverFactory`` version. ... 3 Declarations: x y obj -Changing the ``SolverFactory`` version: +Using the new interfaces through the "new" SolverFactory +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Here we use the new interface by retrieving it from the new ``SolverFactory``: + +.. testcode:: + :skipif: not ipopt_available + + # Direct import + import pyomo.environ as pyo + from pyomo.contrib.solver.util import assert_optimal_termination + from pyomo.contrib.solver.factory import SolverFactory + + model = pyo.ConcreteModel() + model.x = pyo.Var(initialize=1.5) + model.y = pyo.Var(initialize=1.5) + + def rosenbrock(model): + return (1.0 - model.x) ** 2 + 100.0 * (model.y - model.x**2) ** 2 + + model.obj = pyo.Objective(rule=rosenbrock, sense=pyo.minimize) + + opt = SolverFactory('ipopt') + status = opt.solve(model) + assert_optimal_termination(status) + # Displays important results information; only available through the new interfaces + status.display() + model.pprint() + +.. testoutput:: + :skipif: not ipopt_available + :hide: + + solution_loader: ... + ... + 3 Declarations: x y obj + +Switching all of Pyomo to use the new interfaces +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We also provide a mechansim to get a "preview" of the future where we +replace the existing (legacy) SolverFactory and utilities with the new +(development) version: .. testcode:: :skipif: not ipopt_available @@ -120,7 +174,7 @@ Changing the ``SolverFactory`` version: status = pyo.SolverFactory('ipopt').solve(model) assert_optimal_termination(status) - # Displays important results information; only available in future capability mode + # Displays important results information; only available through the new interfaces status.display() model.pprint() @@ -141,16 +195,15 @@ Changing the ``SolverFactory`` version: Linear Presolve and Scaling ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The new interface will allow for direct manipulation of linear presolve and scaling -options for certain solvers. Currently, these options are only available for -``ipopt``. +The new interface allows access to new capabilities in the various +problem writers, including the linear presolve and scaling options +recently incorporated into the redesigned NL writer. For example, you +can control the NL writer in the new ``ipopt`` interface through the +solver's ``writer_config`` configuration option: .. autoclass:: pyomo.contrib.solver.ipopt.Ipopt :members: solve -The ``writer_config`` configuration option can be used to manipulate presolve -and scaling options: - .. testcode:: from pyomo.contrib.solver.ipopt import Ipopt From 286e99de1e076c19f74df6705d1b8493cfb82992 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 21 Feb 2024 09:42:55 -0700 Subject: [PATCH 1118/1204] Fix typos --- doc/OnlineDocs/developer_reference/solvers.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index 9f18119e373..cdf36b74397 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -82,7 +82,7 @@ Here we use the new interface by importing it directly: .. testcode:: :skipif: not ipopt_available - # Direct import + # Direct import import pyomo.environ as pyo from pyomo.contrib.solver.util import assert_optimal_termination from pyomo.contrib.solver.ipopt import Ipopt @@ -119,7 +119,7 @@ Here we use the new interface by retrieving it from the new ``SolverFactory``: .. testcode:: :skipif: not ipopt_available - # Direct import + # Import through new SolverFactory import pyomo.environ as pyo from pyomo.contrib.solver.util import assert_optimal_termination from pyomo.contrib.solver.factory import SolverFactory @@ -151,14 +151,14 @@ Here we use the new interface by retrieving it from the new ``SolverFactory``: Switching all of Pyomo to use the new interfaces ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -We also provide a mechansim to get a "preview" of the future where we +We also provide a mechanism to get a "preview" of the future where we replace the existing (legacy) SolverFactory and utilities with the new (development) version: .. testcode:: :skipif: not ipopt_available - # Change SolverFactory version + # Change default SolverFactory version import pyomo.environ as pyo from pyomo.contrib.solver.util import assert_optimal_termination from pyomo.__future__ import solver_factory_v3 From 93fff175609a32262728a33a16e27866398b5f62 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 21 Feb 2024 09:45:34 -0700 Subject: [PATCH 1119/1204] Restore link to future docs --- doc/OnlineDocs/developer_reference/solvers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index cdf36b74397..7b17c4b40f0 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -153,7 +153,7 @@ Switching all of Pyomo to use the new interfaces We also provide a mechanism to get a "preview" of the future where we replace the existing (legacy) SolverFactory and utilities with the new -(development) version: +(development) version (see :doc:`future`): .. testcode:: :skipif: not ipopt_available From a906f9fb760d64bf96d60aa0093ddafacefd14d7 Mon Sep 17 00:00:00 2001 From: Miranda Mundt <55767766+mrmundt@users.noreply.github.com> Date: Wed, 21 Feb 2024 09:52:29 -0700 Subject: [PATCH 1120/1204] Fix incorrect docstring --- pyomo/contrib/solver/results.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/solver/results.py b/pyomo/contrib/solver/results.py index 699137d2fc9..cbc04681235 100644 --- a/pyomo/contrib/solver/results.py +++ b/pyomo/contrib/solver/results.py @@ -164,10 +164,12 @@ class Results(ConfigDict): iteration_count: int The total number of iterations. timing_info: ConfigDict - A ConfigDict containing two pieces of information: - start_timestamp: UTC timestamp of when run was initiated - wall_time: elapsed wall clock time for entire process - timer: a HierarchicalTimer object containing timing data about the solve + A ConfigDict containing three pieces of information: + - ``start_timestamp``: UTC timestamp of when run was initiated + - ``wall_time``: elapsed wall clock time for entire process + - ``timer``: a HierarchicalTimer object containing timing data about the solve + + Specific solvers may add other relevant timing information, as appropriate. extra_info: ConfigDict A ConfigDict to store extra information such as solver messages. solver_configuration: ConfigDict From cf5dc9c954700d106152ff6afc47a766a4062001 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 21 Feb 2024 09:55:39 -0700 Subject: [PATCH 1121/1204] Add warning / link to #1030 --- doc/OnlineDocs/developer_reference/solvers.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index 7b17c4b40f0..6168da3480e 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -1,6 +1,16 @@ Future Solver Interface Changes =============================== +.. note:: + + The new solver interfaces are still under active development. They + are included in the releases as development previews. Please be + aware that APIs and functionality may change with no notice. + + We welcome any feedback and ideas as we develop this capability. + Please post feedback on + `Issue 1030 `_. + Pyomo offers interfaces into multiple solvers, both commercial and open source. To support better capabilities for solver interfaces, the Pyomo team is actively redesigning the existing interfaces to make them more From 63cc14d28a4b15552bb2e8d82eae5dcc75bbac2b Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 21 Feb 2024 10:02:09 -0700 Subject: [PATCH 1122/1204] More edits to the CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index daba7cac96c..faa2fa094f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,7 +77,7 @@ Pyomo 6.7.1 (21 Feb 2024) - PyROS: Update Solver Argument Resolution and Validation Routines (#3126) - PyROS: Update Subproblem Initialization Routines (#3071) - PyROS: Fix DR polishing under nominal objective focus (#3060) - - solver: Solver Refactor Part 1: Introducing the new solver interface (#3137) + - solver: Solver Refactor Part 1: Introducing the new solver interface (#3137, #3156) ------------------------------------------------------------------------------- Pyomo 6.7.0 (29 Nov 2023) From 83040cdfd08a26aab94966a50b1e5e4d16cc62fa Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 21 Feb 2024 10:10:41 -0700 Subject: [PATCH 1123/1204] More updates to the CHANGELOG --- CHANGELOG.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index faa2fa094f8..c06e0f71378 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ Pyomo 6.7.1 (21 Feb 2024) - Generalize the simple_constraint_rule decorator (#3152) - Fix edge case assigning new numeric types to Var/Param with units (#3151) - Add private_data to `_BlockData` (#3138) - - Convert implicit sets created by `IndexedComponent`s to "anonymous" sets (#3075) + - IndexComponent create implicit sets as "anonymous" sets (#3075) - Add `all_different` and `count_if` to the logical expression system (#3058) - Fix RangeSet.__len__ when defined by floats (#3119) - Overhaul the `Suffix` component (#3072) @@ -38,9 +38,11 @@ Pyomo 6.7.1 (21 Feb 2024) - Update intersphinx links, remove docs for nonfunctional code (#3155) - Update MPC documentation and citation (#3148) - Fix an error in the documentation for LinearExpression (#3090) - - Fix bugs in the documentation of Pyomo.DoE (#3070) - - Fix a latex_printer vestige in the documentation (#3066) + - Fix Pyomo.DoE documentation (#3070) + - Fix latex_printer documentation (#3066) - Solver Interfaces + - Preview release of new solver interfaces as pyomo.contrib.solver + (#3137, #3156) - Make error msg more explicit wrt different interfaces (#3141) - NLv2: only raise exception for empty models in the legacy API (#3135) - Add `to_expr()` to AMPLRepn, fix NLWriterInfo return type (#3095) @@ -69,15 +71,12 @@ Pyomo 6.7.1 (21 Feb 2024) - incidence_analysis: Method to add an edge in IncidenceGraphInterface (#3120) - incidence_analysis: Add subgraph method to IncidencegraphInterface (#3122) - incidence_analysis: Add `ampl_repn` option (#3069) - - incidence_analysis: Fix config documentation of `linear_only` argument in - `get_incident_variables` (#3067) - - interior_point: Workaround for improvement in Mumps memory prediction - algorithm (#3114) + - incidence_analysis: Update documentation (#3067) + - interior_point: Resolve test failure due to Mumps update (#3114) - MindtPy: Various bug fixes (#3034) - PyROS: Update Solver Argument Resolution and Validation Routines (#3126) - PyROS: Update Subproblem Initialization Routines (#3071) - PyROS: Fix DR polishing under nominal objective focus (#3060) - - solver: Solver Refactor Part 1: Introducing the new solver interface (#3137, #3156) ------------------------------------------------------------------------------- Pyomo 6.7.0 (29 Nov 2023) From 7b7f3881103a0333453417b268f87a259d1eeeec Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 21 Feb 2024 10:21:32 -0700 Subject: [PATCH 1124/1204] Update for 6.7.1 release --- .coin-or/projDesc.xml | 4 ++-- README.md | 2 +- RELEASE.md | 5 +++-- pyomo/version/info.py | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.coin-or/projDesc.xml b/.coin-or/projDesc.xml index 1ee247e100f..da977677d1f 100644 --- a/.coin-or/projDesc.xml +++ b/.coin-or/projDesc.xml @@ -227,8 +227,8 @@ Carl D. Laird, Chair, Pyomo Management Committee, claird at andrew dot cmu dot e Use explicit overrides to disable use of automated version reporting. --> - 6.7.0 - 6.7.0 + 6.7.1 + 6.7.1 diff --git a/README.md b/README.md index 2f8a25403c2..95558e52a42 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ To get help from the Pyomo community ask a question on one of the following: ### Developers -Pyomo development moved to this repository in June, 2016 from +Pyomo development moved to this repository in June 2016 from Sandia National Laboratories. Developer discussions are hosted by [Google Groups](https://groups.google.com/forum/#!forum/pyomo-developers). diff --git a/RELEASE.md b/RELEASE.md index 8313c969f25..9b101e0999a 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,4 +1,4 @@ -We are pleased to announce the release of Pyomo 6.7.0. +We are pleased to announce the release of Pyomo 6.7.1. Pyomo is a collection of Python software packages that supports a diverse set of optimization capabilities for formulating and analyzing @@ -9,9 +9,10 @@ The following are highlights of the 6.7 release series: - Added support for Python 3.12 - Removed support for Python 3.7 - New writer for converting linear models to matrix form + - Improved handling of nested GDPs - New packages: - latex_printer (print Pyomo models to a LaTeX compatible format) - - contrib.solve: Part 1 of refactoring Pyomo's solver interfaces + - contrib.solver: preview of redesigned solver interfaces - ...and of course numerous minor bug fixes and performance enhancements A full list of updates and changes is available in the diff --git a/pyomo/version/info.py b/pyomo/version/info.py index 0db00ac240f..dae1b6b6c7f 100644 --- a/pyomo/version/info.py +++ b/pyomo/version/info.py @@ -27,8 +27,8 @@ major = 6 minor = 7 micro = 1 -releaselevel = 'invalid' -# releaselevel = 'final' +# releaselevel = 'invalid' +releaselevel = 'final' serial = 0 if releaselevel == 'final': From e7ec104640433f9e507dc7690664974075b4b9d9 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 21 Feb 2024 10:39:18 -0700 Subject: [PATCH 1125/1204] Resetting main for development (6.7.2.dev0) --- pyomo/version/info.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/version/info.py b/pyomo/version/info.py index dae1b6b6c7f..de2efe83fb6 100644 --- a/pyomo/version/info.py +++ b/pyomo/version/info.py @@ -26,9 +26,9 @@ # main and needs a hard reference to "suitably new" development. major = 6 minor = 7 -micro = 1 -# releaselevel = 'invalid' -releaselevel = 'final' +micro = 2 +releaselevel = 'invalid' +# releaselevel = 'final' serial = 0 if releaselevel == 'final': From 41d8197bbc2627aa6742d0358e16ade0a93a4747 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 21 Feb 2024 16:23:41 -0700 Subject: [PATCH 1126/1204] Support config domains with either method or attribute domain_name --- pyomo/common/config.py | 6 +++++- pyomo/common/tests/test_config.py | 35 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/pyomo/common/config.py b/pyomo/common/config.py index 238bdd78e9d..f9c3a725bb8 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -1134,7 +1134,11 @@ def _domain_name(domain): if domain is None: return "" elif hasattr(domain, 'domain_name'): - return domain.domain_name() + dn = domain.domain_name + if hasattr(dn, '__call__'): + return dn() + else: + return dn elif domain.__class__ is type: return domain.__name__ elif inspect.isfunction(domain): diff --git a/pyomo/common/tests/test_config.py b/pyomo/common/tests/test_config.py index 0bbed43423d..12657481764 100644 --- a/pyomo/common/tests/test_config.py +++ b/pyomo/common/tests/test_config.py @@ -3265,6 +3265,41 @@ def __init__( OUT.getvalue().replace('null', 'None'), ) + def test_domain_name(self): + cfg = ConfigDict() + + cfg.declare('none', ConfigValue()) + self.assertEqual(cfg.get('none').domain_name(), '') + + def fcn(val): + return val + + cfg.declare('fcn', ConfigValue(domain=fcn)) + self.assertEqual(cfg.get('fcn').domain_name(), 'fcn') + + fcn.domain_name = 'custom fcn' + self.assertEqual(cfg.get('fcn').domain_name(), 'custom fcn') + + class functor: + def __call__(self, val): + return val + + cfg.declare('functor', ConfigValue(domain=functor())) + self.assertEqual(cfg.get('functor').domain_name(), 'functor') + + class cfunctor: + def __call__(self, val): + return val + + def domain_name(self): + return 'custom functor' + + cfg.declare('cfunctor', ConfigValue(domain=cfunctor())) + self.assertEqual(cfg.get('cfunctor').domain_name(), 'custom functor') + + cfg.declare('type', ConfigValue(domain=int)) + self.assertEqual(cfg.get('type').domain_name(), 'int') + if __name__ == "__main__": unittest.main() From fcddaf5a0a10503a73ecd1c30009eaefca6cee86 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 23 Feb 2024 08:02:57 -0700 Subject: [PATCH 1127/1204] Creating a GDP-wide private data class and converting hull to use it for constraint mappings --- .../gdp/plugins/gdp_to_mip_transformation.py | 11 ++++ pyomo/gdp/plugins/hull.py | 53 ++++++++----------- pyomo/gdp/util.py | 8 +-- 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/pyomo/gdp/plugins/gdp_to_mip_transformation.py b/pyomo/gdp/plugins/gdp_to_mip_transformation.py index 59cb221321a..7cc55b80f78 100644 --- a/pyomo/gdp/plugins/gdp_to_mip_transformation.py +++ b/pyomo/gdp/plugins/gdp_to_mip_transformation.py @@ -11,6 +11,7 @@ from functools import wraps +from pyomo.common.autoslots import AutoSlots from pyomo.common.collections import ComponentMap from pyomo.common.log import is_debug_set from pyomo.common.modeling import unique_component_name @@ -48,6 +49,16 @@ from weakref import ref as weakref_ref +class _GDPTransformationData(AutoSlots.Mixin): + __slots__ = ('src_constraint', 'transformed_constraint') + def __init__(self): + self.src_constraint = ComponentMap() + self.transformed_constraint = ComponentMap() + + +Block.register_private_data_initializer(_GDPTransformationData, scope='pyomo.gdp') + + class GDP_to_MIP_Transformation(Transformation): """ Base class for transformations from GDP to MIP diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index d1c38bde039..b4e9fccc089 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -95,20 +95,11 @@ class Hull_Reformulation(GDP_to_MIP_Transformation): The transformation will create a new Block with a unique name beginning "_pyomo_gdp_hull_reformulation". It will contain an indexed Block named "relaxedDisjuncts" that will hold the relaxed - disjuncts. This block is indexed by an integer indicating the order - in which the disjuncts were relaxed. Each block has a dictionary - "_constraintMap": - - 'srcConstraints': ComponentMap(: - ), - 'transformedConstraints': - ComponentMap( : - , - : []) - - All transformed Disjuncts will have a pointer to the block their transformed - constraints are on, and all transformed Disjunctions will have a - pointer to the corresponding OR or XOR constraint. + disjuncts. This block is indexed by an integer indicating the order + in which the disjuncts were relaxed. All transformed Disjuncts will + have a pointer to the block their transformed constraints are on, + and all transformed Disjunctions will have a pointer to the + corresponding OR or XOR constraint. The _pyomo_gdp_hull_reformulation block will have a ComponentMap "_disaggregationConstraintMap": @@ -675,7 +666,7 @@ def _transform_constraint( ): # we will put a new transformed constraint on the relaxation block. relaxationBlock = disjunct._transformation_block() - constraintMap = relaxationBlock._constraintMap + constraint_map = relaxationBlock.private_data('pyomo.gdp') # We will make indexes from ({obj.local_name} x obj.index_set() x ['lb', # 'ub']), but don't bother construct that set here, as taking Cartesian @@ -757,19 +748,19 @@ def _transform_constraint( # this variable, so I'm going to return # it. Alternatively we could return an empty list, but I # think I like this better. - constraintMap['transformedConstraints'][c] = [v[0]] + constraint_map.transformed_constraint[c] = [v[0]] # Reverse map also (this is strange) - constraintMap['srcConstraints'][v[0]] = c + constraint_map.src_constraint[v[0]] = c continue newConsExpr = expr - (1 - y) * h_0 == c.lower * y if obj.is_indexed(): newConstraint.add((name, i, 'eq'), newConsExpr) # map the _ConstraintDatas (we mapped the container above) - constraintMap['transformedConstraints'][c] = [ + constraint_map.transformed_constraint[c] = [ newConstraint[name, i, 'eq'] ] - constraintMap['srcConstraints'][newConstraint[name, i, 'eq']] = c + constraint_map.src_constraint[newConstraint[name, i, 'eq']] = c else: newConstraint.add((name, 'eq'), newConsExpr) # map to the _ConstraintData (And yes, for @@ -779,10 +770,10 @@ def _transform_constraint( # IndexedConstraints, we can map the container to the # container, but more importantly, we are mapping the # _ConstraintDatas to each other above) - constraintMap['transformedConstraints'][c] = [ + constraint_map.transformed_constraint[c] = [ newConstraint[name, 'eq'] ] - constraintMap['srcConstraints'][newConstraint[name, 'eq']] = c + constraint_map.src_constraint[newConstraint[name, 'eq']] = c continue @@ -797,16 +788,16 @@ def _transform_constraint( if obj.is_indexed(): newConstraint.add((name, i, 'lb'), newConsExpr) - constraintMap['transformedConstraints'][c] = [ + constraint_map.transformed_constraint[c] = [ newConstraint[name, i, 'lb'] ] - constraintMap['srcConstraints'][newConstraint[name, i, 'lb']] = c + constraint_map.src_constraint[newConstraint[name, i, 'lb']] = c else: newConstraint.add((name, 'lb'), newConsExpr) - constraintMap['transformedConstraints'][c] = [ + constraint_map.transformed_constraint[c] = [ newConstraint[name, 'lb'] ] - constraintMap['srcConstraints'][newConstraint[name, 'lb']] = c + constraint_map.src_constraint[newConstraint[name, 'lb']] = c if c.upper is not None: if self._generate_debug_messages: @@ -821,24 +812,24 @@ def _transform_constraint( newConstraint.add((name, i, 'ub'), newConsExpr) # map (have to account for fact we might have created list # above - transformed = constraintMap['transformedConstraints'].get(c) + transformed = constraint_map.transformed_constraint.get(c) if transformed is not None: transformed.append(newConstraint[name, i, 'ub']) else: - constraintMap['transformedConstraints'][c] = [ + constraint_map.transformed_constraint[c] = [ newConstraint[name, i, 'ub'] ] - constraintMap['srcConstraints'][newConstraint[name, i, 'ub']] = c + constraint_map.src_constraint[newConstraint[name, i, 'ub']] = c else: newConstraint.add((name, 'ub'), newConsExpr) - transformed = constraintMap['transformedConstraints'].get(c) + transformed = constraint_map.transformed_constraint.get(c) if transformed is not None: transformed.append(newConstraint[name, 'ub']) else: - constraintMap['transformedConstraints'][c] = [ + constraint_map.transformed_constraint[c] = [ newConstraint[name, 'ub'] ] - constraintMap['srcConstraints'][newConstraint[name, 'ub']] = c + constraint_map.src_constraint[newConstraint[name, 'ub']] = c # deactivate now that we have transformed obj.deactivate() diff --git a/pyomo/gdp/util.py b/pyomo/gdp/util.py index 57eef29eded..9f929fbc621 100644 --- a/pyomo/gdp/util.py +++ b/pyomo/gdp/util.py @@ -477,16 +477,17 @@ def get_src_constraint(transformedConstraint): a transformation block """ transBlock = transformedConstraint.parent_block() + src_constraints = transBlock.private_data('pyomo.gdp').src_constraint # This should be our block, so if it's not, the user messed up and gave # us the wrong thing. If they happen to also have a _constraintMap then # the world is really against us. - if not hasattr(transBlock, "_constraintMap"): + if transformedConstraint not in src_constraints: raise GDP_Error( "Constraint '%s' is not a transformed constraint" % transformedConstraint.name ) # if something goes wrong here, it's a bug in the mappings. - return transBlock._constraintMap['srcConstraints'][transformedConstraint] + return src_constraints[transformedConstraint] def _find_parent_disjunct(constraint): @@ -538,7 +539,8 @@ def get_transformed_constraints(srcConstraint): ) transBlock = _get_constraint_transBlock(srcConstraint) try: - return transBlock._constraintMap['transformedConstraints'][srcConstraint] + return transBlock.private_data('pyomo.gdp').transformed_constraint[ + srcConstraint] except: logger.error("Constraint '%s' has not been transformed." % srcConstraint.name) raise From 735056b9a8e90b5d0efd4b981e84dcdaa022267e Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 23 Feb 2024 08:18:43 -0700 Subject: [PATCH 1128/1204] Moving bigm transformations onto private data for mapping constraints --- pyomo/gdp/plugins/bigm.py | 15 ++----- pyomo/gdp/plugins/bigm_mixin.py | 14 +++---- .../gdp/plugins/gdp_to_mip_transformation.py | 7 ---- pyomo/gdp/plugins/multiple_bigm.py | 40 +++++++++---------- 4 files changed, 30 insertions(+), 46 deletions(-) diff --git a/pyomo/gdp/plugins/bigm.py b/pyomo/gdp/plugins/bigm.py index 1f9f561b192..ce98180e9d0 100644 --- a/pyomo/gdp/plugins/bigm.py +++ b/pyomo/gdp/plugins/bigm.py @@ -94,15 +94,8 @@ class BigM_Transformation(GDP_to_MIP_Transformation, _BigM_MixIn): name beginning "_pyomo_gdp_bigm_reformulation". That Block will contain an indexed Block named "relaxedDisjuncts", which will hold the relaxed disjuncts. This block is indexed by an integer - indicating the order in which the disjuncts were relaxed. - Each block has a dictionary "_constraintMap": - - 'srcConstraints': ComponentMap(: - ) - 'transformedConstraints': ComponentMap(: - ) - - All transformed Disjuncts will have a pointer to the block their transformed + indicating the order in which the disjuncts were relaxed. All + transformed Disjuncts will have a pointer to the block their transformed constraints are on, and all transformed Disjunctions will have a pointer to the corresponding 'Or' or 'ExactlyOne' constraint. @@ -279,7 +272,7 @@ def _transform_constraint( # add constraint to the transformation block, we'll transform it there. transBlock = disjunct._transformation_block() bigm_src = transBlock.bigm_src - constraintMap = transBlock._constraintMap + constraint_map = transBlock.private_data('pyomo.gdp') disjunctionRelaxationBlock = transBlock.parent_block() @@ -346,7 +339,7 @@ def _transform_constraint( bigm_src[c] = (lower, upper) self._add_constraint_expressions( - c, i, M, disjunct.binary_indicator_var, newConstraint, constraintMap + c, i, M, disjunct.binary_indicator_var, newConstraint, constraint_map ) # deactivate because we relaxed diff --git a/pyomo/gdp/plugins/bigm_mixin.py b/pyomo/gdp/plugins/bigm_mixin.py index ad6e6dcad86..59d79331a34 100644 --- a/pyomo/gdp/plugins/bigm_mixin.py +++ b/pyomo/gdp/plugins/bigm_mixin.py @@ -232,7 +232,7 @@ def _estimate_M(self, expr, constraint): return tuple(M) def _add_constraint_expressions( - self, c, i, M, indicator_var, newConstraint, constraintMap + self, c, i, M, indicator_var, newConstraint, constraint_map ): # Since we are both combining components from multiple blocks and using # local names, we need to make sure that the first index for @@ -253,8 +253,8 @@ def _add_constraint_expressions( ) M_expr = M[0] * (1 - indicator_var) newConstraint.add((name, i, 'lb'), c.lower <= c.body - M_expr) - constraintMap['transformedConstraints'][c] = [newConstraint[name, i, 'lb']] - constraintMap['srcConstraints'][newConstraint[name, i, 'lb']] = c + constraint_map.transformed_constraint[c] = [newConstraint[name, i, 'lb']] + constraint_map.src_constraint[newConstraint[name, i, 'lb']] = c if c.upper is not None: if M[1] is None: raise GDP_Error( @@ -263,13 +263,13 @@ def _add_constraint_expressions( ) M_expr = M[1] * (1 - indicator_var) newConstraint.add((name, i, 'ub'), c.body - M_expr <= c.upper) - transformed = constraintMap['transformedConstraints'].get(c) + transformed = constraint_map.transformed_constraint.get(c) if transformed is not None: - constraintMap['transformedConstraints'][c].append( + constraint_map.transformed_constraint[c].append( newConstraint[name, i, 'ub'] ) else: - constraintMap['transformedConstraints'][c] = [ + constraint_map.transformed_constraint[c] = [ newConstraint[name, i, 'ub'] ] - constraintMap['srcConstraints'][newConstraint[name, i, 'ub']] = c + constraint_map.src_constraint[newConstraint[name, i, 'ub']] = c diff --git a/pyomo/gdp/plugins/gdp_to_mip_transformation.py b/pyomo/gdp/plugins/gdp_to_mip_transformation.py index 7cc55b80f78..9547d80bab3 100644 --- a/pyomo/gdp/plugins/gdp_to_mip_transformation.py +++ b/pyomo/gdp/plugins/gdp_to_mip_transformation.py @@ -254,14 +254,7 @@ def _get_disjunct_transformation_block(self, disjunct, transBlock): relaxationBlock = relaxedDisjuncts[len(relaxedDisjuncts)] relaxationBlock.transformedConstraints = Constraint(Any) - relaxationBlock.localVarReferences = Block() - # add the map that will link back and forth between transformed - # constraints and their originals. - relaxationBlock._constraintMap = { - 'srcConstraints': ComponentMap(), - 'transformedConstraints': ComponentMap(), - } # add mappings to source disjunct (so we'll know we've relaxed) disjunct._transformation_block = weakref_ref(relaxationBlock) diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index 6177de3c037..3d867798161 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -359,7 +359,7 @@ def _transform_disjunct(self, obj, transBlock, active_disjuncts, Ms): def _transform_constraint(self, obj, disjunct, active_disjuncts, Ms): # we will put a new transformed constraint on the relaxation block. relaxationBlock = disjunct._transformation_block() - constraintMap = relaxationBlock._constraintMap + constraint_map = relaxationBlock.private_data('pyomo.gdp') transBlock = relaxationBlock.parent_block() # Though rare, it is possible to get naming conflicts here @@ -397,8 +397,8 @@ def _transform_constraint(self, obj, disjunct, active_disjuncts, Ms): newConstraint.add((i, 'ub'), c.body - c.upper <= rhs) transformed.append(newConstraint[i, 'ub']) for c_new in transformed: - constraintMap['srcConstraints'][c_new] = [c] - constraintMap['transformedConstraints'][c] = transformed + constraint_map.src_constraint[c_new] = [c] + constraint_map.transformed_constraint[c] = transformed else: lower = (None, None, None) upper = (None, None, None) @@ -427,7 +427,7 @@ def _transform_constraint(self, obj, disjunct, active_disjuncts, Ms): M, disjunct.indicator_var.get_associated_binary(), newConstraint, - constraintMap, + constraint_map, ) # deactivate now that we have transformed @@ -496,6 +496,7 @@ def _transform_bound_constraints(self, active_disjuncts, transBlock, Ms): relaxationBlock = self._get_disjunct_transformation_block( disj, transBlock ) + constraint_map = relaxationBlock.private_data('pyomo.gdp') if len(lower_dict) > 0: M = lower_dict.get(disj, None) if M is None: @@ -527,39 +528,36 @@ def _transform_bound_constraints(self, active_disjuncts, transBlock, Ms): idx = i + offset if len(lower_dict) > 0: transformed.add((idx, 'lb'), v >= lower_rhs) - relaxationBlock._constraintMap['srcConstraints'][ + constraint_map.src_constraint[ transformed[idx, 'lb'] ] = [] for c, disj in lower_bound_constraints_by_var[v]: - relaxationBlock._constraintMap['srcConstraints'][ + constraint_map.src_constraint[ transformed[idx, 'lb'] ].append(c) - disj.transformation_block._constraintMap['transformedConstraints'][ + disj.transformation_block.private_data( + 'pyomo.gdp').transformed_constraint[ c ] = [transformed[idx, 'lb']] if len(upper_dict) > 0: transformed.add((idx, 'ub'), v <= upper_rhs) - relaxationBlock._constraintMap['srcConstraints'][ + constraint_map.src_constraint[ transformed[idx, 'ub'] ] = [] for c, disj in upper_bound_constraints_by_var[v]: - relaxationBlock._constraintMap['srcConstraints'][ + constraint_map.src_constraint[ transformed[idx, 'ub'] ].append(c) # might already be here if it had an upper bound - if ( - c - in disj.transformation_block._constraintMap[ - 'transformedConstraints' - ] - ): - disj.transformation_block._constraintMap[ - 'transformedConstraints' - ][c].append(transformed[idx, 'ub']) + disj_constraint_map = disj.transformation_block.private_data( + 'pyomo.gdp') + if c in disj_constraint_map.transformed_constraint: + disj_constraint_map.transformed_constraint[c].append( + transformed[idx, 'ub']) else: - disj.transformation_block._constraintMap[ - 'transformedConstraints' - ][c] = [transformed[idx, 'ub']] + disj_constraint_map.transformed_constraint[c] = [ + transformed[idx, 'ub'] + ] return transformed_constraints From 763586e44b6081f800094d954bdf5fe6a0f4105c Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 23 Feb 2024 08:21:44 -0700 Subject: [PATCH 1129/1204] Moving binary multiplication onto private data mappings --- pyomo/gdp/plugins/binary_multiplication.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pyomo/gdp/plugins/binary_multiplication.py b/pyomo/gdp/plugins/binary_multiplication.py index d68f7efe76f..6d0955c95a7 100644 --- a/pyomo/gdp/plugins/binary_multiplication.py +++ b/pyomo/gdp/plugins/binary_multiplication.py @@ -121,7 +121,7 @@ def _transform_disjunct(self, obj, transBlock): def _transform_constraint(self, obj, disjunct): # add constraint to the transformation block, we'll transform it there. transBlock = disjunct._transformation_block() - constraintMap = transBlock._constraintMap + constraint_map = transBlock.private_data('pyomo.gdp') disjunctionRelaxationBlock = transBlock.parent_block() @@ -137,14 +137,14 @@ def _transform_constraint(self, obj, disjunct): continue self._add_constraint_expressions( - c, i, disjunct.binary_indicator_var, newConstraint, constraintMap + c, i, disjunct.binary_indicator_var, newConstraint, constraint_map ) # deactivate because we relaxed c.deactivate() def _add_constraint_expressions( - self, c, i, indicator_var, newConstraint, constraintMap + self, c, i, indicator_var, newConstraint, constraint_map ): # Since we are both combining components from multiple blocks and using # local names, we need to make sure that the first index for @@ -156,21 +156,21 @@ def _add_constraint_expressions( # over the constraint indices, but I don't think it matters a lot.) unique = len(newConstraint) name = c.local_name + "_%s" % unique - transformed = constraintMap['transformedConstraints'][c] = [] + transformed = constraint_map.transformed_constraint[c] = [] lb, ub = c.lower, c.upper if (c.equality or lb is ub) and lb is not None: # equality newConstraint.add((name, i, 'eq'), (c.body - lb) * indicator_var == 0) transformed.append(newConstraint[name, i, 'eq']) - constraintMap['srcConstraints'][newConstraint[name, i, 'eq']] = c + constraint_map.src_constraint[newConstraint[name, i, 'eq']] = c else: # inequality if lb is not None: newConstraint.add((name, i, 'lb'), 0 <= (c.body - lb) * indicator_var) transformed.append(newConstraint[name, i, 'lb']) - constraintMap['srcConstraints'][newConstraint[name, i, 'lb']] = c + constraint_map.src_constraint[newConstraint[name, i, 'lb']] = c if ub is not None: newConstraint.add((name, i, 'ub'), (c.body - ub) * indicator_var <= 0) transformed.append(newConstraint[name, i, 'ub']) - constraintMap['srcConstraints'][newConstraint[name, i, 'ub']] = c + constraint_map.src_constraint[newConstraint[name, i, 'ub']] = c From b0da69ce9a6ae69aae3cb07ef38fcd890ecefcae Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 23 Feb 2024 08:22:59 -0700 Subject: [PATCH 1130/1204] Even black thinks this is prettier --- .../gdp/plugins/gdp_to_mip_transformation.py | 1 + pyomo/gdp/plugins/multiple_bigm.py | 27 +++++++------------ pyomo/gdp/util.py | 3 ++- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/pyomo/gdp/plugins/gdp_to_mip_transformation.py b/pyomo/gdp/plugins/gdp_to_mip_transformation.py index 9547d80bab3..94dde433a15 100644 --- a/pyomo/gdp/plugins/gdp_to_mip_transformation.py +++ b/pyomo/gdp/plugins/gdp_to_mip_transformation.py @@ -51,6 +51,7 @@ class _GDPTransformationData(AutoSlots.Mixin): __slots__ = ('src_constraint', 'transformed_constraint') + def __init__(self): self.src_constraint = ComponentMap() self.transformed_constraint = ComponentMap() diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index 3d867798161..fccb0514dfb 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -528,32 +528,25 @@ def _transform_bound_constraints(self, active_disjuncts, transBlock, Ms): idx = i + offset if len(lower_dict) > 0: transformed.add((idx, 'lb'), v >= lower_rhs) - constraint_map.src_constraint[ - transformed[idx, 'lb'] - ] = [] + constraint_map.src_constraint[transformed[idx, 'lb']] = [] for c, disj in lower_bound_constraints_by_var[v]: - constraint_map.src_constraint[ - transformed[idx, 'lb'] - ].append(c) + constraint_map.src_constraint[transformed[idx, 'lb']].append(c) disj.transformation_block.private_data( - 'pyomo.gdp').transformed_constraint[ - c - ] = [transformed[idx, 'lb']] + 'pyomo.gdp' + ).transformed_constraint[c] = [transformed[idx, 'lb']] if len(upper_dict) > 0: transformed.add((idx, 'ub'), v <= upper_rhs) - constraint_map.src_constraint[ - transformed[idx, 'ub'] - ] = [] + constraint_map.src_constraint[transformed[idx, 'ub']] = [] for c, disj in upper_bound_constraints_by_var[v]: - constraint_map.src_constraint[ - transformed[idx, 'ub'] - ].append(c) + constraint_map.src_constraint[transformed[idx, 'ub']].append(c) # might already be here if it had an upper bound disj_constraint_map = disj.transformation_block.private_data( - 'pyomo.gdp') + 'pyomo.gdp' + ) if c in disj_constraint_map.transformed_constraint: disj_constraint_map.transformed_constraint[c].append( - transformed[idx, 'ub']) + transformed[idx, 'ub'] + ) else: disj_constraint_map.transformed_constraint[c] = [ transformed[idx, 'ub'] diff --git a/pyomo/gdp/util.py b/pyomo/gdp/util.py index 9f929fbc621..b3e7de8a7cd 100644 --- a/pyomo/gdp/util.py +++ b/pyomo/gdp/util.py @@ -540,7 +540,8 @@ def get_transformed_constraints(srcConstraint): transBlock = _get_constraint_transBlock(srcConstraint) try: return transBlock.private_data('pyomo.gdp').transformed_constraint[ - srcConstraint] + srcConstraint + ] except: logger.error("Constraint '%s' has not been transformed." % srcConstraint.name) raise From e7acc12dbefa4a2d53305cd2b517bfeaac5222a0 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 23 Feb 2024 08:30:46 -0700 Subject: [PATCH 1131/1204] fix division by zero error in linear presolve --- pyomo/contrib/solver/ipopt.py | 44 ++++++++----- .../solver/tests/solvers/test_solvers.py | 65 +++++++++++++++++++ 2 files changed, 92 insertions(+), 17 deletions(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 3ac1a5ac4a2..dc632adb184 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -17,7 +17,11 @@ from pyomo.common import Executable from pyomo.common.config import ConfigValue, document_kwargs_from_configdict, ConfigDict -from pyomo.common.errors import PyomoException, DeveloperError +from pyomo.common.errors import ( + PyomoException, + DeveloperError, + InfeasibleConstraintException, +) from pyomo.common.tempfiles import TempfileManager from pyomo.common.timing import HierarchicalTimer from pyomo.core.base.var import _GeneralVarData @@ -72,11 +76,7 @@ def __init__( ), ) self.writer_config: ConfigDict = self.declare( - 'writer_config', - ConfigValue( - default=NLWriter.CONFIG(), - description="Configuration that controls options in the NL writer.", - ), + 'writer_config', NLWriter.CONFIG() ) @@ -314,15 +314,19 @@ def solve(self, model, **kwds): ) as row_file, open(basename + '.col', 'w') as col_file: timer.start('write_nl_file') self._writer.config.set_value(config.writer_config) - nl_info = self._writer.write( - model, - nl_file, - row_file, - col_file, - symbolic_solver_labels=config.symbolic_solver_labels, - ) + try: + nl_info = self._writer.write( + model, + nl_file, + row_file, + col_file, + symbolic_solver_labels=config.symbolic_solver_labels, + ) + proven_infeasible = False + except InfeasibleConstraintException: + proven_infeasible = True timer.stop('write_nl_file') - if len(nl_info.variables) > 0: + if not proven_infeasible and len(nl_info.variables) > 0: # Get a copy of the environment to pass to the subprocess env = os.environ.copy() if nl_info.external_function_libraries: @@ -361,11 +365,17 @@ def solve(self, model, **kwds): timer.stop('subprocess') # This is the stuff we need to parse to get the iterations # and time - iters, ipopt_time_nofunc, ipopt_time_func, ipopt_total_time = ( + (iters, ipopt_time_nofunc, ipopt_time_func, ipopt_total_time) = ( self._parse_ipopt_output(ostreams[0]) ) - if len(nl_info.variables) == 0: + if proven_infeasible: + results = Results() + results.termination_condition = TerminationCondition.provenInfeasible + results.solution_loader = SolSolutionLoader(None, None) + results.iteration_count = 0 + results.timing_info.total_seconds = 0 + elif len(nl_info.variables) == 0: if len(nl_info.eliminated_vars) == 0: results = Results() results.termination_condition = TerminationCondition.emptyModel @@ -457,7 +467,7 @@ def solve(self, model, **kwds): ) results.solver_configuration = config - if len(nl_info.variables) > 0: + if not proven_infeasible and len(nl_info.variables) > 0: results.solver_log = ostreams[0].getvalue() # Capture/record end-time / wall-time diff --git a/pyomo/contrib/solver/tests/solvers/test_solvers.py b/pyomo/contrib/solver/tests/solvers/test_solvers.py index cf5f6cf5c57..a4f4a3bc389 100644 --- a/pyomo/contrib/solver/tests/solvers/test_solvers.py +++ b/pyomo/contrib/solver/tests/solvers/test_solvers.py @@ -1508,6 +1508,71 @@ def test_bug_2(self, name: str, opt_class: Type[SolverBase], use_presolve: bool) res = opt.solve(m) self.assertAlmostEqual(res.incumbent_objective, -18, 5) + @parameterized.expand(input=_load_tests(nl_solvers)) + def test_presolve_with_zero_coef( + self, name: str, opt_class: Type[SolverBase], use_presolve: bool + ): + opt: SolverBase = opt_class() + if not opt.available(): + raise unittest.SkipTest(f'Solver {opt.name} not available.') + if use_presolve: + opt.config.writer_config.linear_presolve = True + else: + opt.config.writer_config.linear_presolve = False + + """ + when c2 gets presolved out, c1 becomes + x - y + y = 0 which becomes + x - 0*y == 0 which is the zero we are testing for + """ + m = pe.ConcreteModel() + m.x = pe.Var() + m.y = pe.Var() + m.z = pe.Var() + m.obj = pe.Objective(expr=m.x**2 + m.y**2 + m.z**2) + m.c1 = pe.Constraint(expr=m.x == m.y + m.z + 1.5) + m.c2 = pe.Constraint(expr=m.z == -m.y) + + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 2.25) + self.assertAlmostEqual(m.x.value, 1.5) + self.assertAlmostEqual(m.y.value, 0) + self.assertAlmostEqual(m.z.value, 0) + + m.x.setlb(2) + res = opt.solve( + m, load_solutions=False, raise_exception_on_nonoptimal_result=False + ) + if use_presolve: + exp = TerminationCondition.provenInfeasible + else: + exp = TerminationCondition.locallyInfeasible + self.assertEqual(res.termination_condition, exp) + + m = pe.ConcreteModel() + m.w = pe.Var() + m.x = pe.Var() + m.y = pe.Var() + m.z = pe.Var() + m.obj = pe.Objective(expr=m.x**2 + m.y**2 + m.z**2 + m.w**2) + m.c1 = pe.Constraint(expr=m.x + m.w == m.y + m.z) + m.c2 = pe.Constraint(expr=m.z == -m.y) + m.c3 = pe.Constraint(expr=m.x == -m.w) + + res = opt.solve(m) + self.assertAlmostEqual(res.incumbent_objective, 0) + self.assertAlmostEqual(m.w.value, 0) + self.assertAlmostEqual(m.x.value, 0) + self.assertAlmostEqual(m.y.value, 0) + self.assertAlmostEqual(m.z.value, 0) + + del m.c1 + m.c1 = pe.Constraint(expr=m.x + m.w == m.y + m.z + 1.5) + res = opt.solve( + m, load_solutions=False, raise_exception_on_nonoptimal_result=False + ) + self.assertEqual(res.termination_condition, exp) + @parameterized.expand(input=_load_tests(all_solvers)) def test_scaling(self, name: str, opt_class: Type[SolverBase], use_presolve: bool): opt: SolverBase = opt_class() From 0dabe3fe9d0636e9122544dbdcec16d61907bd84 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 23 Feb 2024 08:35:17 -0700 Subject: [PATCH 1132/1204] fix division by zero error in linear presolve --- pyomo/repn/plugins/nl_writer.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index f3ff94ea8c9..66c695dafa3 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1820,10 +1820,24 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): # appropriately (that expr_info is persisting in the # eliminated_vars dict - and we will use that to # update other linear expressions later.) + old_nnz = len(expr_info.linear) c = expr_info.linear.pop(_id, 0) + nnz = old_nnz - 1 expr_info.const += c * b if x in expr_info.linear: expr_info.linear[x] += c * a + if expr_info.linear[x] == 0: + nnz -= 1 + coef = expr_info.linear.pop(x) + if not nnz: + if abs(expr_info.const) > TOL: + # constraint is trivially infeasible + raise InfeasibleConstraintException( + "model contains a trivially infeasible constrint " + f"{expr_info.const} == {coef}*{var_map[x]}" + ) + # constraint is trivially feasible + eliminated_cons.add(con_id) elif a: expr_info.linear[x] = c * a # replacing _id with x... NNZ is not changing, @@ -1831,9 +1845,7 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): # this constraint comp_by_linear_var[x].append((con_id, expr_info)) continue - # NNZ has been reduced by 1 - nnz = len(expr_info.linear) - _old = lcon_by_linear_nnz[nnz + 1] + _old = lcon_by_linear_nnz[old_nnz] if con_id in _old: lcon_by_linear_nnz[nnz][con_id] = _old.pop(con_id) # If variables were replaced by the variable that From b59978e8b8fba623e4affa00ba94713b006af32f Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 23 Feb 2024 08:35:25 -0700 Subject: [PATCH 1133/1204] Moving hull disaggregation constraint mappings to private_data --- pyomo/gdp/plugins/hull.py | 27 +++++++-------------------- pyomo/gdp/tests/test_hull.py | 3 +-- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index b4e9fccc089..4a7445283a9 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -58,12 +58,14 @@ class _HullTransformationData(AutoSlots.Mixin): - __slots__ = ('disaggregated_var_map', 'original_var_map', 'bigm_constraint_map') + __slots__ = ('disaggregated_var_map', 'original_var_map', 'bigm_constraint_map', + 'disaggregation_constraint_map') def __init__(self): self.disaggregated_var_map = DefaultComponentMap(ComponentMap) self.original_var_map = ComponentMap() self.bigm_constraint_map = DefaultComponentMap(ComponentMap) + self.disaggregation_constraint_map = DefaultComponentMap(ComponentMap) Block.register_private_data_initializer(_HullTransformationData) @@ -100,10 +102,6 @@ class Hull_Reformulation(GDP_to_MIP_Transformation): have a pointer to the block their transformed constraints are on, and all transformed Disjunctions will have a pointer to the corresponding OR or XOR constraint. - - The _pyomo_gdp_hull_reformulation block will have a ComponentMap - "_disaggregationConstraintMap": - :ComponentMap(: ) """ CONFIG = cfg.ConfigDict('gdp.hull') @@ -285,10 +283,6 @@ def _add_transformation_block(self, to_block): # Disjunctions we transform onto this block here. transBlock.disaggregationConstraints = Constraint(NonNegativeIntegers) - # This will map from srcVar to a map of srcDisjunction to the - # disaggregation constraint corresponding to srcDisjunction - transBlock._disaggregationConstraintMap = ComponentMap() - # we are going to store some of the disaggregated vars directly here # when we have vars that don't appear in every disjunct transBlock._disaggregatedVars = Var(NonNegativeIntegers, dense=False) @@ -321,7 +315,7 @@ def _transform_disjunctionData( ) disaggregationConstraint = transBlock.disaggregationConstraints - disaggregationConstraintMap = transBlock._disaggregationConstraintMap + disaggregationConstraintMap = transBlock.private_data().disaggregation_constraint_map disaggregatedVars = transBlock._disaggregatedVars disaggregated_var_bounds = transBlock._boundsConstraints @@ -490,13 +484,7 @@ def _transform_disjunctionData( # and update the map so that we can find this later. We index by # variable and the particular disjunction because there is a # different one for each disjunction - if var in disaggregationConstraintMap: - disaggregationConstraintMap[var][obj] = disaggregationConstraint[ - cons_idx - ] - else: - thismap = disaggregationConstraintMap[var] = ComponentMap() - thismap[obj] = disaggregationConstraint[cons_idx] + disaggregationConstraintMap[var][obj] = disaggregationConstraint[cons_idx] # deactivate for the writers obj.deactivate() @@ -922,9 +910,8 @@ def get_disaggregation_constraint( ) try: - cons = transBlock.parent_block()._disaggregationConstraintMap[original_var][ - disjunction - ] + cons = transBlock.parent_block().private_data().disaggregation_constraint_map[ + original_var][disjunction] except: if raise_exception: logger.error( diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index 858764759ee..98322d4888d 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -2314,8 +2314,7 @@ def test_mapping_method_errors(self): with LoggingIntercept(log, 'pyomo.gdp.hull', logging.ERROR): self.assertRaisesRegex( KeyError, - r".*_pyomo_gdp_hull_reformulation.relaxedDisjuncts\[1\]." - r"disaggregatedVars.w", + r".*disjunction", hull.get_disaggregation_constraint, m.d[1].transformation_block.disaggregatedVars.w, m.disjunction, From 9f3d6980eaff3b996bf434da2f862d07ce4fd53e Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 23 Feb 2024 08:36:01 -0700 Subject: [PATCH 1134/1204] black adding newlines --- pyomo/gdp/plugins/hull.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 4a7445283a9..a2b050298e2 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -58,8 +58,12 @@ class _HullTransformationData(AutoSlots.Mixin): - __slots__ = ('disaggregated_var_map', 'original_var_map', 'bigm_constraint_map', - 'disaggregation_constraint_map') + __slots__ = ( + 'disaggregated_var_map', + 'original_var_map', + 'bigm_constraint_map', + 'disaggregation_constraint_map', + ) def __init__(self): self.disaggregated_var_map = DefaultComponentMap(ComponentMap) @@ -315,7 +319,9 @@ def _transform_disjunctionData( ) disaggregationConstraint = transBlock.disaggregationConstraints - disaggregationConstraintMap = transBlock.private_data().disaggregation_constraint_map + disaggregationConstraintMap = ( + transBlock.private_data().disaggregation_constraint_map + ) disaggregatedVars = transBlock._disaggregatedVars disaggregated_var_bounds = transBlock._boundsConstraints @@ -910,8 +916,11 @@ def get_disaggregation_constraint( ) try: - cons = transBlock.parent_block().private_data().disaggregation_constraint_map[ - original_var][disjunction] + cons = ( + transBlock.parent_block() + .private_data() + .disaggregation_constraint_map[original_var][disjunction] + ) except: if raise_exception: logger.error( From 1acf6188789f63d558d521e3d8f1c8e7a99d55d8 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 23 Feb 2024 08:37:03 -0700 Subject: [PATCH 1135/1204] fix typo --- pyomo/repn/plugins/nl_writer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 66c695dafa3..bd7ade34923 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1760,7 +1760,7 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): id2_isdiscrete = var_map[id2].domain.isdiscrete() if var_map[_id].domain.isdiscrete() ^ id2_isdiscrete: # if only one variable is discrete, then we need to - # substiitute out the other + # substitute out the other if id2_isdiscrete: _id, id2 = id2, _id coef, coef2 = coef2, coef From 4cceaa99653c0bdcdec759b75e249f73a2f9723c Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 23 Feb 2024 08:42:41 -0700 Subject: [PATCH 1136/1204] fix typo --- pyomo/repn/plugins/nl_writer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index bd7ade34923..3fd97ac06d0 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1833,7 +1833,7 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): if abs(expr_info.const) > TOL: # constraint is trivially infeasible raise InfeasibleConstraintException( - "model contains a trivially infeasible constrint " + "model contains a trivially infeasible constraint " f"{expr_info.const} == {coef}*{var_map[x]}" ) # constraint is trivially feasible From 6811943281374a5732d35660e1cda0c1c9ed1ca5 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 23 Feb 2024 08:45:33 -0700 Subject: [PATCH 1137/1204] Moving bigM src mapping to private data --- pyomo/gdp/plugins/bigm.py | 43 ++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/pyomo/gdp/plugins/bigm.py b/pyomo/gdp/plugins/bigm.py index ce98180e9d0..bcf9606877b 100644 --- a/pyomo/gdp/plugins/bigm.py +++ b/pyomo/gdp/plugins/bigm.py @@ -13,6 +13,7 @@ import logging +from pyomo.common.autoslots import AutoSlots from pyomo.common.collections import ComponentMap from pyomo.common.config import ConfigDict, ConfigValue from pyomo.common.gc_manager import PauseGC @@ -58,6 +59,23 @@ logger = logging.getLogger('pyomo.gdp.bigm') +class _BigMData(AutoSlots.Mixin): + __slots__ = ('bigm_src',) + def __init__(self): + # we will keep a map of constraints (hashable, ha!) to a tuple to + # indicate what their M value is and where it came from, of the form: + # ((lower_value, lower_source, lower_key), (upper_value, upper_source, + # upper_key)), where the first tuple is the information for the lower M, + # the second tuple is the info for the upper M, source is the Suffix or + # argument dictionary and None if the value was calculated, and key is + # the key in the Suffix or argument dictionary, and None if it was + # calculated. (Note that it is possible the lower or upper is + # user-specified and the other is not, hence the need to store + # information for both.) + self.bigm_src = {} + +Block.register_private_data_initializer(_BigMData) + @TransformationFactory.register( 'gdp.bigm', doc="Relax disjunctive model using big-M terms." ) @@ -240,18 +258,6 @@ def _transform_disjunct(self, obj, bigM, transBlock): relaxationBlock = self._get_disjunct_transformation_block(obj, transBlock) - # we will keep a map of constraints (hashable, ha!) to a tuple to - # indicate what their M value is and where it came from, of the form: - # ((lower_value, lower_source, lower_key), (upper_value, upper_source, - # upper_key)), where the first tuple is the information for the lower M, - # the second tuple is the info for the upper M, source is the Suffix or - # argument dictionary and None if the value was calculated, and key is - # the key in the Suffix or argument dictionary, and None if it was - # calculated. (Note that it is possible the lower or upper is - # user-specified and the other is not, hence the need to store - # information for both.) - relaxationBlock.bigm_src = {} - # This is crazy, but if the disjunction has been previously # relaxed, the disjunct *could* be deactivated. This is a big # deal for Hull, as it uses the component_objects / @@ -271,7 +277,7 @@ def _transform_constraint( ): # add constraint to the transformation block, we'll transform it there. transBlock = disjunct._transformation_block() - bigm_src = transBlock.bigm_src + bigm_src = transBlock.private_data().bigm_src constraint_map = transBlock.private_data('pyomo.gdp') disjunctionRelaxationBlock = transBlock.parent_block() @@ -402,7 +408,7 @@ def _update_M_from_suffixes(self, constraint, suffix_list, lower, upper): def get_m_value_src(self, constraint): transBlock = _get_constraint_transBlock(constraint) ((lower_val, lower_source, lower_key), (upper_val, upper_source, upper_key)) = ( - transBlock.bigm_src[constraint] + transBlock.private_data().bigm_src[constraint] ) if ( @@ -457,7 +463,7 @@ def get_M_value_src(self, constraint): transBlock = _get_constraint_transBlock(constraint) # This is a KeyError if it fails, but it is also my fault if it # fails... (That is, it's a bug in the mapping.) - return transBlock.bigm_src[constraint] + return transBlock.private_data().bigm_src[constraint] def get_M_value(self, constraint): """Returns the M values used to transform constraint. Return is a tuple: @@ -472,7 +478,7 @@ def get_M_value(self, constraint): transBlock = _get_constraint_transBlock(constraint) # This is a KeyError if it fails, but it is also my fault if it # fails... (That is, it's a bug in the mapping.) - lower, upper = transBlock.bigm_src[constraint] + lower, upper = transBlock.private_data().bigm_src[constraint] return (lower[0], upper[0]) def get_all_M_values_by_constraint(self, model): @@ -492,9 +498,8 @@ def get_all_M_values_by_constraint(self, model): # First check if it was transformed at all. if transBlock is not None: # If it was transformed with BigM, we get the M values. - if hasattr(transBlock, 'bigm_src'): - for cons in transBlock.bigm_src: - m_values[cons] = self.get_M_value(cons) + for cons in transBlock.private_data().bigm_src: + m_values[cons] = self.get_M_value(cons) return m_values def get_largest_M_value(self, model): From c9232e9439507919a714cfd3d7dd31b724ada14c Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 23 Feb 2024 08:45:57 -0700 Subject: [PATCH 1138/1204] black --- pyomo/gdp/plugins/bigm.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyomo/gdp/plugins/bigm.py b/pyomo/gdp/plugins/bigm.py index bcf9606877b..3f450dbbd4f 100644 --- a/pyomo/gdp/plugins/bigm.py +++ b/pyomo/gdp/plugins/bigm.py @@ -61,6 +61,7 @@ class _BigMData(AutoSlots.Mixin): __slots__ = ('bigm_src',) + def __init__(self): # we will keep a map of constraints (hashable, ha!) to a tuple to # indicate what their M value is and where it came from, of the form: @@ -74,8 +75,10 @@ def __init__(self): # information for both.) self.bigm_src = {} + Block.register_private_data_initializer(_BigMData) + @TransformationFactory.register( 'gdp.bigm', doc="Relax disjunctive model using big-M terms." ) From 1221996f3a940ddc0e0de7240158f4c49551d386 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 23 Feb 2024 13:20:11 -0700 Subject: [PATCH 1139/1204] Making transformed_constraints a DefaultComponentMap --- pyomo/gdp/plugins/bigm_mixin.py | 15 +++---- pyomo/gdp/plugins/binary_multiplication.py | 2 +- .../gdp/plugins/gdp_to_mip_transformation.py | 6 +-- pyomo/gdp/plugins/hull.py | 39 +++++++------------ pyomo/gdp/plugins/multiple_bigm.py | 16 +++----- pyomo/gdp/tests/test_bigm.py | 36 ++++++----------- pyomo/gdp/tests/test_hull.py | 17 +++----- pyomo/gdp/util.py | 14 +++---- 8 files changed, 53 insertions(+), 92 deletions(-) diff --git a/pyomo/gdp/plugins/bigm_mixin.py b/pyomo/gdp/plugins/bigm_mixin.py index 59d79331a34..b76c8d43279 100644 --- a/pyomo/gdp/plugins/bigm_mixin.py +++ b/pyomo/gdp/plugins/bigm_mixin.py @@ -253,7 +253,8 @@ def _add_constraint_expressions( ) M_expr = M[0] * (1 - indicator_var) newConstraint.add((name, i, 'lb'), c.lower <= c.body - M_expr) - constraint_map.transformed_constraint[c] = [newConstraint[name, i, 'lb']] + constraint_map.transformed_constraints[c].append( + newConstraint[name, i, 'lb']) constraint_map.src_constraint[newConstraint[name, i, 'lb']] = c if c.upper is not None: if M[1] is None: @@ -263,13 +264,7 @@ def _add_constraint_expressions( ) M_expr = M[1] * (1 - indicator_var) newConstraint.add((name, i, 'ub'), c.body - M_expr <= c.upper) - transformed = constraint_map.transformed_constraint.get(c) - if transformed is not None: - constraint_map.transformed_constraint[c].append( - newConstraint[name, i, 'ub'] - ) - else: - constraint_map.transformed_constraint[c] = [ - newConstraint[name, i, 'ub'] - ] + constraint_map.transformed_constraints[c].append( + newConstraint[name, i, 'ub'] + ) constraint_map.src_constraint[newConstraint[name, i, 'ub']] = c diff --git a/pyomo/gdp/plugins/binary_multiplication.py b/pyomo/gdp/plugins/binary_multiplication.py index 6d0955c95a7..bea33580ed6 100644 --- a/pyomo/gdp/plugins/binary_multiplication.py +++ b/pyomo/gdp/plugins/binary_multiplication.py @@ -156,7 +156,7 @@ def _add_constraint_expressions( # over the constraint indices, but I don't think it matters a lot.) unique = len(newConstraint) name = c.local_name + "_%s" % unique - transformed = constraint_map.transformed_constraint[c] = [] + transformed = constraint_map.transformed_constraints[c] lb, ub = c.lower, c.upper if (c.equality or lb is ub) and lb is not None: diff --git a/pyomo/gdp/plugins/gdp_to_mip_transformation.py b/pyomo/gdp/plugins/gdp_to_mip_transformation.py index 94dde433a15..8dcd22b292a 100644 --- a/pyomo/gdp/plugins/gdp_to_mip_transformation.py +++ b/pyomo/gdp/plugins/gdp_to_mip_transformation.py @@ -12,7 +12,7 @@ from functools import wraps from pyomo.common.autoslots import AutoSlots -from pyomo.common.collections import ComponentMap +from pyomo.common.collections import ComponentMap, DefaultComponentMap from pyomo.common.log import is_debug_set from pyomo.common.modeling import unique_component_name @@ -50,11 +50,11 @@ class _GDPTransformationData(AutoSlots.Mixin): - __slots__ = ('src_constraint', 'transformed_constraint') + __slots__ = ('src_constraint', 'transformed_constraints') def __init__(self): self.src_constraint = ComponentMap() - self.transformed_constraint = ComponentMap() + self.transformed_constraints = DefaultComponentMap(list) Block.register_private_data_initializer(_GDPTransformationData, scope='pyomo.gdp') diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index a2b050298e2..1dc6b76e6a6 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -742,7 +742,7 @@ def _transform_constraint( # this variable, so I'm going to return # it. Alternatively we could return an empty list, but I # think I like this better. - constraint_map.transformed_constraint[c] = [v[0]] + constraint_map.transformed_constraints[c].append(v[0]) # Reverse map also (this is strange) constraint_map.src_constraint[v[0]] = c continue @@ -751,9 +751,8 @@ def _transform_constraint( if obj.is_indexed(): newConstraint.add((name, i, 'eq'), newConsExpr) # map the _ConstraintDatas (we mapped the container above) - constraint_map.transformed_constraint[c] = [ - newConstraint[name, i, 'eq'] - ] + constraint_map.transformed_constraints[c].append( + newConstraint[name, i, 'eq']) constraint_map.src_constraint[newConstraint[name, i, 'eq']] = c else: newConstraint.add((name, 'eq'), newConsExpr) @@ -764,9 +763,9 @@ def _transform_constraint( # IndexedConstraints, we can map the container to the # container, but more importantly, we are mapping the # _ConstraintDatas to each other above) - constraint_map.transformed_constraint[c] = [ + constraint_map.transformed_constraints[c].append( newConstraint[name, 'eq'] - ] + ) constraint_map.src_constraint[newConstraint[name, 'eq']] = c continue @@ -782,15 +781,15 @@ def _transform_constraint( if obj.is_indexed(): newConstraint.add((name, i, 'lb'), newConsExpr) - constraint_map.transformed_constraint[c] = [ + constraint_map.transformed_constraints[c].append( newConstraint[name, i, 'lb'] - ] + ) constraint_map.src_constraint[newConstraint[name, i, 'lb']] = c else: newConstraint.add((name, 'lb'), newConsExpr) - constraint_map.transformed_constraint[c] = [ + constraint_map.transformed_constraints[c].append( newConstraint[name, 'lb'] - ] + ) constraint_map.src_constraint[newConstraint[name, 'lb']] = c if c.upper is not None: @@ -806,23 +805,15 @@ def _transform_constraint( newConstraint.add((name, i, 'ub'), newConsExpr) # map (have to account for fact we might have created list # above - transformed = constraint_map.transformed_constraint.get(c) - if transformed is not None: - transformed.append(newConstraint[name, i, 'ub']) - else: - constraint_map.transformed_constraint[c] = [ - newConstraint[name, i, 'ub'] - ] + constraint_map.transformed_constraints[c].append( + newConstraint[name, i, 'ub'] + ) constraint_map.src_constraint[newConstraint[name, i, 'ub']] = c else: newConstraint.add((name, 'ub'), newConsExpr) - transformed = constraint_map.transformed_constraint.get(c) - if transformed is not None: - transformed.append(newConstraint[name, 'ub']) - else: - constraint_map.transformed_constraint[c] = [ - newConstraint[name, 'ub'] - ] + constraint_map.transformed_constraints[c].append( + newConstraint[name, 'ub'] + ) constraint_map.src_constraint[newConstraint[name, 'ub']] = c # deactivate now that we have transformed diff --git a/pyomo/gdp/plugins/multiple_bigm.py b/pyomo/gdp/plugins/multiple_bigm.py index fccb0514dfb..4dffd4e9f9a 100644 --- a/pyomo/gdp/plugins/multiple_bigm.py +++ b/pyomo/gdp/plugins/multiple_bigm.py @@ -378,7 +378,7 @@ def _transform_constraint(self, obj, disjunct, active_disjuncts, Ms): continue if not self._config.only_mbigm_bound_constraints: - transformed = [] + transformed = constraint_map.transformed_constraints[c] if c.lower is not None: rhs = sum( Ms[c, disj][0] * disj.indicator_var.get_associated_binary() @@ -398,7 +398,6 @@ def _transform_constraint(self, obj, disjunct, active_disjuncts, Ms): transformed.append(newConstraint[i, 'ub']) for c_new in transformed: constraint_map.src_constraint[c_new] = [c] - constraint_map.transformed_constraint[c] = transformed else: lower = (None, None, None) upper = (None, None, None) @@ -533,7 +532,7 @@ def _transform_bound_constraints(self, active_disjuncts, transBlock, Ms): constraint_map.src_constraint[transformed[idx, 'lb']].append(c) disj.transformation_block.private_data( 'pyomo.gdp' - ).transformed_constraint[c] = [transformed[idx, 'lb']] + ).transformed_constraints[c].append(transformed[idx, 'lb']) if len(upper_dict) > 0: transformed.add((idx, 'ub'), v <= upper_rhs) constraint_map.src_constraint[transformed[idx, 'ub']] = [] @@ -543,14 +542,9 @@ def _transform_bound_constraints(self, active_disjuncts, transBlock, Ms): disj_constraint_map = disj.transformation_block.private_data( 'pyomo.gdp' ) - if c in disj_constraint_map.transformed_constraint: - disj_constraint_map.transformed_constraint[c].append( - transformed[idx, 'ub'] - ) - else: - disj_constraint_map.transformed_constraint[c] = [ - transformed[idx, 'ub'] - ] + disj_constraint_map.transformed_constraints[c].append( + transformed[idx, 'ub'] + ) return transformed_constraints diff --git a/pyomo/gdp/tests/test_bigm.py b/pyomo/gdp/tests/test_bigm.py index 00efcb46485..ec281218786 100644 --- a/pyomo/gdp/tests/test_bigm.py +++ b/pyomo/gdp/tests/test_bigm.py @@ -1316,18 +1316,11 @@ def test_do_not_transform_deactivated_constraintDatas(self): bigm.apply_to(m) # the real test: This wasn't transformed - log = StringIO() - with LoggingIntercept(log, 'pyomo.gdp', logging.ERROR): - self.assertRaisesRegex( - KeyError, - r".*b.simpledisj1.c\[1\]", - bigm.get_transformed_constraints, - m.b.simpledisj1.c[1], - ) - self.assertRegex( - log.getvalue(), - r".*Constraint 'b.simpledisj1.c\[1\]' has not been transformed.", - ) + with self.assertRaisesRegex( + GDP_Error, + r"Constraint 'b.simpledisj1.c\[1\]' has not been transformed." + ): + bigm.get_transformed_constraints(m.b.simpledisj1.c[1]) # and the rest of the container was transformed cons_list = bigm.get_transformed_constraints(m.b.simpledisj1.c[2]) @@ -2272,18 +2265,13 @@ def check_all_but_evil1_b_anotherblock_constraint_transformed(self, m): self.assertEqual(len(evil1), 2) self.assertIs(evil1[0].parent_block(), disjBlock[1]) self.assertIs(evil1[1].parent_block(), disjBlock[1]) - out = StringIO() - with LoggingIntercept(out, 'pyomo.gdp', logging.ERROR): - self.assertRaisesRegex( - KeyError, - r".*.evil\[1\].b.anotherblock.c", - bigm.get_transformed_constraints, - m.evil[1].b.anotherblock.c, - ) - self.assertRegex( - out.getvalue(), - r".*Constraint 'evil\[1\].b.anotherblock.c' has not been transformed.", - ) + with self.assertRaisesRegex( + GDP_Error, + r"Constraint 'evil\[1\].b.anotherblock.c' has not been " + r"transformed.", + ): + bigm.get_transformed_constraints(m.evil[1].b.anotherblock.c) + evil1 = bigm.get_transformed_constraints(m.evil[1].bb[1].c) self.assertEqual(len(evil1), 2) self.assertIs(evil1[0].parent_block(), disjBlock[1]) diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index 98322d4888d..02b3e0152b4 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -897,18 +897,11 @@ def test_do_not_transform_deactivated_constraintDatas(self): hull = TransformationFactory('gdp.hull') hull.apply_to(m) # can't ask for simpledisj1.c[1]: it wasn't transformed - log = StringIO() - with LoggingIntercept(log, 'pyomo.gdp', logging.ERROR): - self.assertRaisesRegex( - KeyError, - r".*b.simpledisj1.c\[1\]", - hull.get_transformed_constraints, - m.b.simpledisj1.c[1], - ) - self.assertRegex( - log.getvalue(), - r".*Constraint 'b.simpledisj1.c\[1\]' has not been transformed.", - ) + with self.assertRaisesRegex( + GDP_Error, + r"Constraint 'b.simpledisj1.c\[1\]' has not been transformed." + ): + hull.get_transformed_constraints(m.b.simpledisj1.c[1]) # this fixes a[2] to 0, so we should get the disggregated var transformed = hull.get_transformed_constraints(m.b.simpledisj1.c[2]) diff --git a/pyomo/gdp/util.py b/pyomo/gdp/util.py index b3e7de8a7cd..e7da03c7f41 100644 --- a/pyomo/gdp/util.py +++ b/pyomo/gdp/util.py @@ -538,13 +538,13 @@ def get_transformed_constraints(srcConstraint): "from any of its _ComponentDatas.)" ) transBlock = _get_constraint_transBlock(srcConstraint) - try: - return transBlock.private_data('pyomo.gdp').transformed_constraint[ - srcConstraint - ] - except: - logger.error("Constraint '%s' has not been transformed." % srcConstraint.name) - raise + transformed_constraints = transBlock.private_data( + 'pyomo.gdp').transformed_constraints + if srcConstraint in transformed_constraints: + return transformed_constraints[srcConstraint] + else: + raise GDP_Error("Constraint '%s' has not been transformed." % + srcConstraint.name) def _warn_for_active_disjunct(innerdisjunct, outerdisjunct): From 1405731cfa5547d478c3cc9a52e1a60dd61bfa27 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 23 Feb 2024 13:21:50 -0700 Subject: [PATCH 1140/1204] black --- pyomo/gdp/plugins/bigm_mixin.py | 3 ++- pyomo/gdp/plugins/hull.py | 3 ++- pyomo/gdp/tests/test_bigm.py | 6 ++---- pyomo/gdp/tests/test_hull.py | 3 +-- pyomo/gdp/util.py | 8 +++++--- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/pyomo/gdp/plugins/bigm_mixin.py b/pyomo/gdp/plugins/bigm_mixin.py index b76c8d43279..510b36b5102 100644 --- a/pyomo/gdp/plugins/bigm_mixin.py +++ b/pyomo/gdp/plugins/bigm_mixin.py @@ -254,7 +254,8 @@ def _add_constraint_expressions( M_expr = M[0] * (1 - indicator_var) newConstraint.add((name, i, 'lb'), c.lower <= c.body - M_expr) constraint_map.transformed_constraints[c].append( - newConstraint[name, i, 'lb']) + newConstraint[name, i, 'lb'] + ) constraint_map.src_constraint[newConstraint[name, i, 'lb']] = c if c.upper is not None: if M[1] is None: diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 1dc6b76e6a6..5b9d2ad08a9 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -752,7 +752,8 @@ def _transform_constraint( newConstraint.add((name, i, 'eq'), newConsExpr) # map the _ConstraintDatas (we mapped the container above) constraint_map.transformed_constraints[c].append( - newConstraint[name, i, 'eq']) + newConstraint[name, i, 'eq'] + ) constraint_map.src_constraint[newConstraint[name, i, 'eq']] = c else: newConstraint.add((name, 'eq'), newConsExpr) diff --git a/pyomo/gdp/tests/test_bigm.py b/pyomo/gdp/tests/test_bigm.py index ec281218786..2383d4587f5 100644 --- a/pyomo/gdp/tests/test_bigm.py +++ b/pyomo/gdp/tests/test_bigm.py @@ -1317,8 +1317,7 @@ def test_do_not_transform_deactivated_constraintDatas(self): # the real test: This wasn't transformed with self.assertRaisesRegex( - GDP_Error, - r"Constraint 'b.simpledisj1.c\[1\]' has not been transformed." + GDP_Error, r"Constraint 'b.simpledisj1.c\[1\]' has not been transformed." ): bigm.get_transformed_constraints(m.b.simpledisj1.c[1]) @@ -2267,8 +2266,7 @@ def check_all_but_evil1_b_anotherblock_constraint_transformed(self, m): self.assertIs(evil1[1].parent_block(), disjBlock[1]) with self.assertRaisesRegex( GDP_Error, - r"Constraint 'evil\[1\].b.anotherblock.c' has not been " - r"transformed.", + r"Constraint 'evil\[1\].b.anotherblock.c' has not been transformed.", ): bigm.get_transformed_constraints(m.evil[1].b.anotherblock.c) diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index 02b3e0152b4..55edf244731 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -898,8 +898,7 @@ def test_do_not_transform_deactivated_constraintDatas(self): hull.apply_to(m) # can't ask for simpledisj1.c[1]: it wasn't transformed with self.assertRaisesRegex( - GDP_Error, - r"Constraint 'b.simpledisj1.c\[1\]' has not been transformed." + GDP_Error, r"Constraint 'b.simpledisj1.c\[1\]' has not been transformed." ): hull.get_transformed_constraints(m.b.simpledisj1.c[1]) diff --git a/pyomo/gdp/util.py b/pyomo/gdp/util.py index e7da03c7f41..fe11975954d 100644 --- a/pyomo/gdp/util.py +++ b/pyomo/gdp/util.py @@ -539,12 +539,14 @@ def get_transformed_constraints(srcConstraint): ) transBlock = _get_constraint_transBlock(srcConstraint) transformed_constraints = transBlock.private_data( - 'pyomo.gdp').transformed_constraints + 'pyomo.gdp' + ).transformed_constraints if srcConstraint in transformed_constraints: return transformed_constraints[srcConstraint] else: - raise GDP_Error("Constraint '%s' has not been transformed." % - srcConstraint.name) + raise GDP_Error( + "Constraint '%s' has not been transformed." % srcConstraint.name + ) def _warn_for_active_disjunct(innerdisjunct, outerdisjunct): From 58996fa1e4da2df12a5e0d6290ff95ed178d2603 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Sat, 24 Feb 2024 13:12:58 -0700 Subject: [PATCH 1141/1204] fix division by zero error in linear presolve --- pyomo/repn/plugins/nl_writer.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 3fd97ac06d0..a256cd1b900 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1829,15 +1829,6 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): if expr_info.linear[x] == 0: nnz -= 1 coef = expr_info.linear.pop(x) - if not nnz: - if abs(expr_info.const) > TOL: - # constraint is trivially infeasible - raise InfeasibleConstraintException( - "model contains a trivially infeasible constraint " - f"{expr_info.const} == {coef}*{var_map[x]}" - ) - # constraint is trivially feasible - eliminated_cons.add(con_id) elif a: expr_info.linear[x] = c * a # replacing _id with x... NNZ is not changing, @@ -1847,6 +1838,15 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): continue _old = lcon_by_linear_nnz[old_nnz] if con_id in _old: + if not nnz: + if abs(expr_info.const) > TOL: + # constraint is trivially infeasible + raise InfeasibleConstraintException( + "model contains a trivially infeasible constraint " + f"{expr_info.const} == {coef}*{var_map[x]}" + ) + # constraint is trivially feasible + eliminated_cons.add(con_id) lcon_by_linear_nnz[nnz][con_id] = _old.pop(con_id) # If variables were replaced by the variable that # we are currently eliminating, then we need to update From 9bf36c7f81367449ba0441a0840487bb122320bd Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 25 Feb 2024 13:26:51 -0700 Subject: [PATCH 1142/1204] Adding tests to NLv2 motivated by 58996fa --- pyomo/repn/tests/ampl/test_nlv2.py | 125 +++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 8b95fc03bdb..215715dba10 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1683,6 +1683,131 @@ def test_presolve_named_expressions(self): G0 2 #obj 0 0 1 0 +""", + OUT.getvalue(), + ) + ) + + def test_presolve_zero_coef(self): + m = ConcreteModel() + m.x = Var() + m.y = Var() + m.z = Var() + m.obj = Objective(expr=m.x**2 + m.y**2 + m.z**2) + m.c1 = Constraint(expr=m.x == m.y + m.z + 1.5) + m.c2 = Constraint(expr=m.z == -m.y) + + OUT = io.StringIO() + with LoggingIntercept() as LOG: + nlinfo = nl_writer.NLWriter().write( + m, OUT, symbolic_solver_labels=True, linear_presolve=True + ) + self.assertEqual(LOG.getvalue(), "") + + self.assertEqual(nlinfo.eliminated_vars[0], (m.x, 1.5)) + self.assertIs(nlinfo.eliminated_vars[1][0], m.y) + self.assertExpressionsEqual( + nlinfo.eliminated_vars[1][1], LinearExpression([-1.0 * m.z]) + ) + + self.assertEqual( + *nl_diff( + """g3 1 1 0 # problem unknown + 1 0 1 0 0 #vars, constraints, objectives, ranges, eqns + 0 1 0 0 0 0 #nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb + 0 0 #network constraints: nonlinear, linear + 0 1 0 #nonlinear vars in constraints, objectives, both + 0 0 0 1 #linear network variables; functions; arith, flags + 0 0 0 0 0 #discrete variables: binary, integer, nonlinear (b,c,o) + 0 1 #nonzeros in Jacobian, obj. gradient + 3 1 #max name lengths: constraints, variables + 0 0 0 0 0 #common exprs: b,c,o,c1,o1 +O0 0 #obj +o54 #sumlist +3 #(n) +o5 #^ +n1.5 +n2 +o5 #^ +o16 #- +v0 #z +n2 +o5 #^ +v0 #z +n2 +x0 #initial guess +r #0 ranges (rhs's) +b #1 bounds (on variables) +3 #z +k0 #intermediate Jacobian column lengths +G0 1 #obj +0 0 +""", + OUT.getvalue(), + ) + ) + + m.c3 = Constraint(expr=m.x == 2) + OUT = io.StringIO() + with LoggingIntercept() as LOG: + with self.assertRaisesRegex( + nl_writer.InfeasibleConstraintException, + r"model contains a trivially infeasible constraint 0.5 == 0.0\*y", + ): + nlinfo = nl_writer.NLWriter().write( + m, OUT, symbolic_solver_labels=True, linear_presolve=True + ) + self.assertEqual(LOG.getvalue(), "") + + m.c1.set_value(m.x >= m.y + m.z + 1.5) + OUT = io.StringIO() + with LoggingIntercept() as LOG: + nlinfo = nl_writer.NLWriter().write( + m, OUT, symbolic_solver_labels=True, linear_presolve=True + ) + self.assertEqual(LOG.getvalue(), "") + + self.assertIs(nlinfo.eliminated_vars[0][0], m.y) + self.assertExpressionsEqual( + nlinfo.eliminated_vars[0][1], LinearExpression([-1.0 * m.z]) + ) + self.assertEqual(nlinfo.eliminated_vars[1], (m.x, 2)) + + self.assertEqual( + *nl_diff( + """g3 1 1 0 # problem unknown + 1 1 1 0 0 #vars, constraints, objectives, ranges, eqns + 0 1 0 0 0 0 #nonlinear constrs, objs; ccons: lin, nonlin, nd, nzlb + 0 0 #network constraints: nonlinear, linear + 0 1 0 #nonlinear vars in constraints, objectives, both + 0 0 0 1 #linear network variables; functions; arith, flags + 0 0 0 0 0 #discrete variables: binary, integer, nonlinear (b,c,o) + 0 1 #nonzeros in Jacobian, obj. gradient + 3 1 #max name lengths: constraints, variables + 0 0 0 0 0 #common exprs: b,c,o,c1,o1 +C0 #c1 +n0 +O0 0 #obj +o54 #sumlist +3 #(n) +o5 #^ +n2 +n2 +o5 #^ +o16 #- +v0 #z +n2 +o5 #^ +v0 #z +n2 +x0 #initial guess +r #1 ranges (rhs's) +1 0.5 #c1 +b #1 bounds (on variables) +3 #z +k0 #intermediate Jacobian column lengths +G0 1 #obj +0 0 """, OUT.getvalue(), ) From fca1035351fec7f189c07557705f340a00d71dae Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Sun, 25 Feb 2024 16:01:49 -0700 Subject: [PATCH 1143/1204] allow cyipopt to solve problems without objectives --- .../algorithms/solvers/cyipopt_solver.py | 23 +++++++++++++++---- .../solvers/tests/test_cyipopt_solver.py | 10 ++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py b/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py index 9d24c0dd562..cdea542295b 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py @@ -24,6 +24,8 @@ from pyomo.common.deprecation import relocated_module_attribute from pyomo.common.dependencies import attempt_import, numpy as np, numpy_available from pyomo.common.tee import redirect_fd, TeeStream +from pyomo.common.modeling import unique_component_name +from pyomo.core.base.objective import Objective # Because pynumero.interfaces requires numpy, we will leverage deferred # imports here so that the solver can be registered even when numpy is @@ -332,11 +334,22 @@ def solve(self, model, **kwds): grey_box_blocks = list( model.component_data_objects(egb.ExternalGreyBoxBlock, active=True) ) - if grey_box_blocks: - # nlp = pyomo_nlp.PyomoGreyBoxNLP(model) - nlp = pyomo_grey_box.PyomoNLPWithGreyBoxBlocks(model) - else: - nlp = pyomo_nlp.PyomoNLP(model) + # if there is no objective, add one temporarily so we can construct an NLP + objectives = list(model.component_data_objects(Objective, active=True)) + if not objectives: + objname = unique_component_name(model, "_obj") + objective = model.add_component(objname, Objective(expr=0.0)) + try: + if grey_box_blocks: + # nlp = pyomo_nlp.PyomoGreyBoxNLP(model) + nlp = pyomo_grey_box.PyomoNLPWithGreyBoxBlocks(model) + else: + nlp = pyomo_nlp.PyomoNLP(model) + finally: + # We only need the objective to construct the NLP, so we delete + # it from the model ASAP + if not objectives: + model.del_component(objective) problem = cyipopt_interface.CyIpoptNLP( nlp, diff --git a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py index e9da31097a0..0af5a772c98 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/tests/test_cyipopt_solver.py @@ -316,3 +316,13 @@ def test_hs071_evalerror_old_cyipopt(self): msg = "Error in AMPL evaluation" with self.assertRaisesRegex(PyNumeroEvaluationError, msg): res = solver.solve(m, tee=True) + + def test_solve_without_objective(self): + m = create_model1() + m.o.deactivate() + m.x[2].fix(0.0) + m.x[3].fix(4.0) + solver = pyo.SolverFactory("cyipopt") + res = solver.solve(m, tee=True) + pyo.assert_optimal_termination(res) + self.assertAlmostEqual(m.x[1].value, 9.0) From 242ba7f424eb351ad250c48963627b30e43d2a3c Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 25 Feb 2024 17:15:39 -0700 Subject: [PATCH 1144/1204] Updating the TPL package list due to contrib.solver --- pyomo/environ/tests/test_environ.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/environ/tests/test_environ.py b/pyomo/environ/tests/test_environ.py index 9c89fd135d5..6121a310024 100644 --- a/pyomo/environ/tests/test_environ.py +++ b/pyomo/environ/tests/test_environ.py @@ -140,6 +140,7 @@ def test_tpl_import_time(self): 'cPickle', 'csv', 'ctypes', # mandatory import in core/base/external.py; TODO: fix this + 'datetime', # imported by contrib.solver 'decimal', 'gc', # Imported on MacOS, Windows; Linux in 3.10 'glob', From 2edbf24a6cde120889cc7a1d32bda814f3d9379a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 25 Feb 2024 17:21:30 -0700 Subject: [PATCH 1145/1204] Apply black --- pyomo/environ/tests/test_environ.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/environ/tests/test_environ.py b/pyomo/environ/tests/test_environ.py index 6121a310024..9811b412af7 100644 --- a/pyomo/environ/tests/test_environ.py +++ b/pyomo/environ/tests/test_environ.py @@ -140,7 +140,7 @@ def test_tpl_import_time(self): 'cPickle', 'csv', 'ctypes', # mandatory import in core/base/external.py; TODO: fix this - 'datetime', # imported by contrib.solver + 'datetime', # imported by contrib.solver 'decimal', 'gc', # Imported on MacOS, Windows; Linux in 3.10 'glob', From 0ea6a293df516acb7a02ba4ec56a51b51009cbc6 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 26 Feb 2024 10:25:06 -0700 Subject: [PATCH 1146/1204] Improve registration of new native types encountered by ExternalFunction --- pyomo/common/numeric_types.py | 108 +++++++++++++++++++++++++++------- pyomo/core/base/external.py | 14 +++-- 2 files changed, 95 insertions(+), 27 deletions(-) diff --git a/pyomo/common/numeric_types.py b/pyomo/common/numeric_types.py index ba104203667..f24b007b096 100644 --- a/pyomo/common/numeric_types.py +++ b/pyomo/common/numeric_types.py @@ -194,6 +194,53 @@ def RegisterLogicalType(new_type: type): nonpyomo_leaf_types.add(new_type) +def check_if_native_type(obj): + if isinstance(obj, (str, bytes)): + native_types.add(obj.__class__) + return True + if check_if_logical_type(obj): + return True + if check_if_numeric_type(obj): + return True + return False + + +def check_if_logical_type(obj): + """Test if the argument behaves like a logical type. + + We check for "numeric types" by checking if we can add zero to it + without changing the object's type, and that the object compares to + 0 in a meaningful way. If that works, then we register the type in + :py:attr:`native_numeric_types`. + + """ + obj_class = obj.__class__ + # Do not re-evaluate known native types + if obj_class in native_types: + return obj_class in native_logical_types + + if 'numpy' in obj_class.__module__: + # trigger the resolution of numpy_available and check if this + # type was automatically registered + bool(numpy_available) + if obj_class in native_types: + return obj_class in native_logical_types + + try: + if all(( + obj_class(1) == obj_class(2), + obj_class(False) != obj_class(True), + obj_class(False) ^ obj_class(True) == obj_class(True), + obj_class(False) | obj_class(True) == obj_class(True), + obj_class(False) & obj_class(True) == obj_class(False), + )): + RegisterLogicalType(obj_class) + return True + except: + pass + return False + + def check_if_numeric_type(obj): """Test if the argument behaves like a numeric type. @@ -218,36 +265,53 @@ def check_if_numeric_type(obj): try: obj_plus_0 = obj + 0 obj_p0_class = obj_plus_0.__class__ - # ensure that the object is comparable to 0 in a meaningful way - # (among other things, this prevents numpy.ndarray objects from - # being added to native_numeric_types) + # Native numeric types *must* be hashable + hash(obj) + except: + return False + if obj_p0_class is not obj_class and obj_p0_class not in native_numeric_types: + return False + # + # Check if the numeric type behaves like a complex type + # + try: + if 1.41 < abs(obj_class(1j+1)) < 1.42: + RegisterComplexType(obj_class) + return False + except: + pass + # + # ensure that the object is comparable to 0 in a meaningful way + # (among other things, this prevents numpy.ndarray objects from + # being added to native_numeric_types) + try: if not ((obj < 0) ^ (obj >= 0)): return False - # Native types *must* be hashable - hash(obj) except: return False - if obj_p0_class is obj_class or obj_p0_class in native_numeric_types: - # - # If we get here, this is a reasonably well-behaving - # numeric type: add it to the native numeric types - # so that future lookups will be faster. - # - RegisterNumericType(obj_class) - # - # Generate a warning, since Pyomo's management of third-party - # numeric types is more robust when registering explicitly. - # - logger.warning( - f"""Dynamically registering the following numeric type: + # + # If we get here, this is a reasonably well-behaving + # numeric type: add it to the native numeric types + # so that future lookups will be faster. + # + RegisterNumericType(obj_class) + try: + if obj_class(0.4) == obj_class(0): + RegisterIntegerType(obj_class) + except: + pass + # + # Generate a warning, since Pyomo's management of third-party + # numeric types is more robust when registering explicitly. + # + logger.warning( + f"""Dynamically registering the following numeric type: {obj_class.__module__}.{obj_class.__name__} Dynamic registration is supported for convenience, but there are known limitations to this approach. We recommend explicitly registering numeric types using RegisterNumericType() or RegisterIntegerType().""" - ) - return True - else: - return False + ) + return True def value(obj, exception=True): diff --git a/pyomo/core/base/external.py b/pyomo/core/base/external.py index 3c0038d745d..cae62d31941 100644 --- a/pyomo/core/base/external.py +++ b/pyomo/core/base/external.py @@ -31,13 +31,16 @@ from pyomo.common.autoslots import AutoSlots from pyomo.common.fileutils import find_library -from pyomo.core.expr.numvalue import ( +from pyomo.common.numeric_types import ( + check_if_native_type, native_types, native_numeric_types, pyomo_constant_types, + value, +) +from pyomo.core.expr.numvalue import ( NonNumericValue, NumericConstant, - value, ) import pyomo.core.expr as EXPR from pyomo.core.base.component import Component @@ -197,14 +200,15 @@ def __call__(self, *args): pv = False for i, arg in enumerate(args_): try: - # Q: Is there a better way to test if a value is an object - # not in native_types and not a standard expression type? if arg.__class__ in native_types: continue if arg.is_potentially_variable(): pv = True + continue except AttributeError: - args_[i] = NonNumericValue(arg) + if check_if_native_type(arg): + continue + args_[i] = NonNumericValue(arg) # if pv: return EXPR.ExternalFunctionExpression(args_, self) From bf15d23e3c46c1735d9ff4b3050e85947a9760a0 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 26 Feb 2024 10:26:06 -0700 Subject: [PATCH 1147/1204] Deprecate the pyomo_constant_types set --- pyomo/common/numeric_types.py | 21 ++++++++++----------- pyomo/core/base/external.py | 4 ++-- pyomo/core/base/units_container.py | 5 ++--- pyomo/core/expr/numvalue.py | 4 ++-- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/pyomo/common/numeric_types.py b/pyomo/common/numeric_types.py index f24b007b096..0cfdd347484 100644 --- a/pyomo/common/numeric_types.py +++ b/pyomo/common/numeric_types.py @@ -50,7 +50,6 @@ native_integer_types = {int} native_logical_types = {bool} native_complex_types = {complex} -pyomo_constant_types = set() # includes NumericConstant _native_boolean_types = {int, bool, str, bytes} relocated_module_attribute( @@ -62,6 +61,16 @@ "be treated as if they were bool (as was the case for the other " "native_*_types sets). Users likely should use native_logical_types.", ) +_pyomo_constant_types = set() # includes NumericConstant, _PythonCallbackFunctionID +relocated_module_attribute( + 'pyomo_constant_types', + 'pyomo.common.numeric_types._pyomo_constant_types', + version='6.7.2.dev0', + msg="The pyomo_constant_types set will be removed in the future: the set " + "contained only NumericConstant and _PythonCallbackFunctionID, and provided " + "no meaningful value to clients or walkers. Users should likely handle " + "these types in the same manner as immutable Params.", +) #: Python set used to identify numeric constants and related native @@ -338,16 +347,6 @@ def value(obj, exception=True): """ if obj.__class__ in native_types: return obj - if obj.__class__ in pyomo_constant_types: - # - # I'm commenting this out for now, but I think we should never expect - # to see a numeric constant with value None. - # - # if exception and obj.value is None: - # raise ValueError( - # "No value for uninitialized NumericConstant object %s" - # % (obj.name,)) - return obj.value # # Test if we have a duck typed Pyomo expression # diff --git a/pyomo/core/base/external.py b/pyomo/core/base/external.py index cae62d31941..92e7286f3d4 100644 --- a/pyomo/core/base/external.py +++ b/pyomo/core/base/external.py @@ -35,8 +35,8 @@ check_if_native_type, native_types, native_numeric_types, - pyomo_constant_types, value, + _pyomo_constant_types, ) from pyomo.core.expr.numvalue import ( NonNumericValue, @@ -495,7 +495,7 @@ def is_constant(self): return False -pyomo_constant_types.add(_PythonCallbackFunctionID) +_pyomo_constant_types.add(_PythonCallbackFunctionID) class PythonCallbackFunction(ExternalFunction): diff --git a/pyomo/core/base/units_container.py b/pyomo/core/base/units_container.py index 1bf25ffdead..af8c77b25aa 100644 --- a/pyomo/core/base/units_container.py +++ b/pyomo/core/base/units_container.py @@ -119,7 +119,6 @@ value, native_types, native_numeric_types, - pyomo_constant_types, ) from pyomo.core.expr.template_expr import IndexTemplate from pyomo.core.expr.visitor import ExpressionValueVisitor @@ -902,7 +901,7 @@ def initializeWalker(self, expr): def beforeChild(self, node, child, child_idx): ctype = child.__class__ - if ctype in native_types or ctype in pyomo_constant_types: + if ctype in native_types: return False, self._pint_dimensionless if child.is_expression_type(): @@ -917,7 +916,7 @@ def beforeChild(self, node, child, child_idx): pint_unit = self._pyomo_units_container._get_pint_units(pyomo_unit) return False, pint_unit - return True, None + return False, self._pint_dimensionless def exitNode(self, node, data): """Visitor callback when moving up the expression tree. diff --git a/pyomo/core/expr/numvalue.py b/pyomo/core/expr/numvalue.py index 3a4359af2f9..95914248bc7 100644 --- a/pyomo/core/expr/numvalue.py +++ b/pyomo/core/expr/numvalue.py @@ -28,7 +28,7 @@ native_numeric_types, native_integer_types, native_logical_types, - pyomo_constant_types, + _pyomo_constant_types, check_if_numeric_type, value, ) @@ -410,7 +410,7 @@ def pprint(self, ostream=None, verbose=False): ostream.write(str(self)) -pyomo_constant_types.add(NumericConstant) +_pyomo_constant_types.add(NumericConstant) # We use as_numeric() so that the constant is also in the cache ZeroConstant = as_numeric(0) From 5373c333746e219c0fdc3b202d73bac55f449da3 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 26 Feb 2024 10:27:31 -0700 Subject: [PATCH 1148/1204] Improve efficiency of value() --- pyomo/common/numeric_types.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pyomo/common/numeric_types.py b/pyomo/common/numeric_types.py index 0cfdd347484..a234bb4df05 100644 --- a/pyomo/common/numeric_types.py +++ b/pyomo/common/numeric_types.py @@ -350,9 +350,7 @@ def value(obj, exception=True): # # Test if we have a duck typed Pyomo expression # - try: - obj.is_numeric_type() - except AttributeError: + if not hasattr(obj, 'is_numeric_type'): # # TODO: Historically we checked for new *numeric* types and # raised exceptions for anything else. That is inconsistent @@ -367,7 +365,7 @@ def value(obj, exception=True): return None raise TypeError( "Cannot evaluate object with unknown type: %s" % obj.__class__.__name__ - ) from None + ) # # Evaluate the expression object # From 6830e2787aa49fd48b29a0cb7316a4f7f2aa1841 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 26 Feb 2024 10:28:00 -0700 Subject: [PATCH 1149/1204] Add NonNumericValue to teh PyomoObject hierarchy, define __call__ --- pyomo/core/expr/numvalue.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyomo/core/expr/numvalue.py b/pyomo/core/expr/numvalue.py index 95914248bc7..64ef0a6cca2 100644 --- a/pyomo/core/expr/numvalue.py +++ b/pyomo/core/expr/numvalue.py @@ -85,7 +85,7 @@ ##------------------------------------------------------------------------ -class NonNumericValue(object): +class NonNumericValue(PyomoObject): """An object that contains a non-numeric value Constructor Arguments: @@ -100,6 +100,8 @@ def __init__(self, value): def __str__(self): return str(self.value) + def __call__(self, exception=None): + return self.value nonpyomo_leaf_types.add(NonNumericValue) From 10281708b1efef82751d1f53b8a6ae645321a0ac Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 26 Feb 2024 10:28:48 -0700 Subject: [PATCH 1150/1204] NFC: apply black --- pyomo/common/numeric_types.py | 18 ++++++++++-------- pyomo/core/base/external.py | 5 +---- pyomo/core/expr/numvalue.py | 1 + 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pyomo/common/numeric_types.py b/pyomo/common/numeric_types.py index a234bb4df05..412a1bbeade 100644 --- a/pyomo/common/numeric_types.py +++ b/pyomo/common/numeric_types.py @@ -236,13 +236,15 @@ def check_if_logical_type(obj): return obj_class in native_logical_types try: - if all(( - obj_class(1) == obj_class(2), - obj_class(False) != obj_class(True), - obj_class(False) ^ obj_class(True) == obj_class(True), - obj_class(False) | obj_class(True) == obj_class(True), - obj_class(False) & obj_class(True) == obj_class(False), - )): + if all( + ( + obj_class(1) == obj_class(2), + obj_class(False) != obj_class(True), + obj_class(False) ^ obj_class(True) == obj_class(True), + obj_class(False) | obj_class(True) == obj_class(True), + obj_class(False) & obj_class(True) == obj_class(False), + ) + ): RegisterLogicalType(obj_class) return True except: @@ -284,7 +286,7 @@ def check_if_numeric_type(obj): # Check if the numeric type behaves like a complex type # try: - if 1.41 < abs(obj_class(1j+1)) < 1.42: + if 1.41 < abs(obj_class(1j + 1)) < 1.42: RegisterComplexType(obj_class) return False except: diff --git a/pyomo/core/base/external.py b/pyomo/core/base/external.py index 92e7286f3d4..0fda004b664 100644 --- a/pyomo/core/base/external.py +++ b/pyomo/core/base/external.py @@ -38,10 +38,7 @@ value, _pyomo_constant_types, ) -from pyomo.core.expr.numvalue import ( - NonNumericValue, - NumericConstant, -) +from pyomo.core.expr.numvalue import NonNumericValue, NumericConstant import pyomo.core.expr as EXPR from pyomo.core.base.component import Component from pyomo.core.base.units_container import units diff --git a/pyomo/core/expr/numvalue.py b/pyomo/core/expr/numvalue.py index 64ef0a6cca2..b656eea1bcd 100644 --- a/pyomo/core/expr/numvalue.py +++ b/pyomo/core/expr/numvalue.py @@ -103,6 +103,7 @@ def __str__(self): def __call__(self, exception=None): return self.value + nonpyomo_leaf_types.add(NonNumericValue) From e0312525ed13dfd1e437bf65d9892f2975f3bf03 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 26 Feb 2024 15:22:42 -0700 Subject: [PATCH 1151/1204] Add hooks for automatic registration on external TPL module import --- pyomo/common/dependencies.py | 87 ++++++++++++++++++++++++++++++++++- pyomo/common/numeric_types.py | 8 ---- 2 files changed, 86 insertions(+), 9 deletions(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 9e96fdd5860..9aa6c4c4f7a 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -9,13 +9,16 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from collections.abc import Mapping import inspect import importlib import logging import sys import warnings +from collections.abc import Mapping +from types import ModuleType +from typing import List + from .deprecation import deprecated, deprecation_warning, in_testing_environment from .errors import DeferredImportError @@ -312,6 +315,12 @@ def __init__( self._module = None self._available = None self._deferred_submodules = deferred_submodules + # If this import has a callback, then record this deferred + # import so that any direct imports of this module also trigger + # the resolution of this DeferredImportIndicator (and the + # corresponding callback) + if callback is not None: + DeferredImportCallbackFinder._callbacks.setdefault(name, []).append(self) def __bool__(self): self.resolve() @@ -433,6 +442,82 @@ def check_min_version(module, min_version): check_min_version._parser = None +# +# Note that we are duck-typing the Loader and MetaPathFinder base +# classes from importlib.abc. This avoids a (surprisingly costly) +# import of importlib.abc +# +class DeferredImportCallbackLoader: + """Custom Loader to resolve registered :py:class:`DeferredImportIndicator` objects + + This :py:class:`importlib.abc.Loader` loader wraps a regular loader + and automatically resolves the registered + :py:class:`DeferredImportIndicator` objects after the module is + loaded. + + """ + + def __init__(self, loader, deferred_indicators: List[DeferredImportIndicator]): + self._loader = loader + self._deferred_indicators = deferred_indicators + + def module_repr(self, module: ModuleType) -> str: + return self._loader.module_repr(module) + + def create_module(self, spec) -> ModuleType: + return self._loader.create_module(spec) + + def exec_module(self, module: ModuleType) -> None: + self._loader.exec_module(module) + # Now that the module has been loaded, trigger the resolution of + # the deferred indicators (and their associated callbacks) + for deferred in self._deferred_indicators: + deferred.resolve() + + def load_module(self, fullname) -> ModuleType: + return self._loader.load_module(fullname) + + +class DeferredImportCallbackFinder: + """Custom Finder that will wrap the normal loader to trigger callbacks + + This :py:class:`importlib.abc.MetaPathFinder` finder will wrap the + normal loader returned by ``PathFinder`` with a loader that will + trigger custom callbacks after the module is loaded. We use this to + trigger the post import callbacks registered through + :py:fcn:`attempt_import` even when a user imports the target library + directly (and not through attribute access on the + :py:class:`DeferredImportModule`. + + """ + _callbacks = {} + + def find_spec(self, fullname, path, target=None): + if fullname not in self._callbacks: + return None + + spec = importlib.machinery.PathFinder.find_spec(fullname, path, target) + if spec is None: + # Module not found. Returning None will proceed to the next + # finder (which is likely to raise a ModuleNotFoundError) + return None + spec.loader = DeferredImportCallbackLoader( + spec.loader, self._callbacks[fullname] + ) + return spec + + def invalidate_caches(self): + pass + + +_DeferredImportCallbackFinder = DeferredImportCallbackFinder() +# Insert the DeferredImportCallbackFinder at the beginning of the +# mata_path to that it is found before the standard finders (so that we +# can correctly inject the resolution of the DeferredImportIndicators -- +# which triggers the needed callbacks) +sys.meta_path.insert(0, _DeferredImportCallbackFinder) + + def attempt_import( name, error_message=None, diff --git a/pyomo/common/numeric_types.py b/pyomo/common/numeric_types.py index ba104203667..ca2ce0f9c6c 100644 --- a/pyomo/common/numeric_types.py +++ b/pyomo/common/numeric_types.py @@ -12,7 +12,6 @@ import logging import sys -from pyomo.common.dependencies import numpy_available from pyomo.common.deprecation import deprecated, relocated_module_attribute from pyomo.common.errors import TemplateExpressionError @@ -208,13 +207,6 @@ def check_if_numeric_type(obj): if obj_class in native_types: return obj_class in native_numeric_types - if 'numpy' in obj_class.__module__: - # trigger the resolution of numpy_available and check if this - # type was automatically registered - bool(numpy_available) - if obj_class in native_types: - return obj_class in native_numeric_types - try: obj_plus_0 = obj + 0 obj_p0_class = obj_plus_0.__class__ From 9205b81d6a3f39e2d1fc5c730deac932716b6a3a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 26 Feb 2024 15:34:28 -0700 Subject: [PATCH 1152/1204] rename 'defer_check' to 'defer_import' --- doc/OnlineDocs/conf.py | 2 +- pyomo/common/dependencies.py | 31 ++++++++---- pyomo/common/tests/dep_mod.py | 4 +- pyomo/common/tests/deps.py | 5 +- pyomo/common/tests/test_dependencies.py | 48 +++++++++---------- pyomo/common/unittest.py | 2 +- pyomo/contrib/pynumero/dependencies.py | 2 +- .../examples/tests/test_cyipopt_examples.py | 2 +- pyomo/contrib/pynumero/intrinsic.py | 4 +- pyomo/core/base/units_container.py | 1 - pyomo/solvers/plugins/solvers/GAMS.py | 2 +- 11 files changed, 58 insertions(+), 45 deletions(-) diff --git a/doc/OnlineDocs/conf.py b/doc/OnlineDocs/conf.py index 04fe458407b..1aab4cd76c2 100644 --- a/doc/OnlineDocs/conf.py +++ b/doc/OnlineDocs/conf.py @@ -271,7 +271,7 @@ def check_output(self, want, got, optionflags): yaml_available, networkx_available, matplotlib_available, pympler_available, dill_available, ) -pint_available = attempt_import('pint', defer_check=False)[1] +pint_available = attempt_import('pint', defer_import=False)[1] from pyomo.contrib.parmest.parmest import parmest_available import pyomo.environ as _pe # (trigger all plugin registrations) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 9aa6c4c4f7a..12ca6bd4ce3 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -130,7 +130,7 @@ class DeferredImportModule(object): This object is returned by :py:func:`attempt_import()` in lieu of the module when :py:func:`attempt_import()` is called with - ``defer_check=True``. Any attempts to access attributes on this + ``defer_import=True``. Any attempts to access attributes on this object will trigger the actual module import and return either the appropriate module attribute or else if the module import fails, raise a :py:class:`.DeferredImportError` exception. @@ -526,7 +526,8 @@ def attempt_import( alt_names=None, callback=None, importer=None, - defer_check=True, + defer_check=None, + defer_import=None, deferred_submodules=None, catch_exceptions=None, ): @@ -607,10 +608,16 @@ def attempt_import( want to import/return the first one that is available. defer_check: bool, optional - If True (the default), then the attempted import is deferred - until the first use of either the module or the availability - flag. The method will return instances of :py:class:`DeferredImportModule` - and :py:class:`DeferredImportIndicator`. + DEPRECATED: renamed to ``defer_import`` + + defer_import: bool, optional + If True, then the attempted import is deferred until the first + use of either the module or the availability flag. The method + will return instances of :py:class:`DeferredImportModule` and + :py:class:`DeferredImportIndicator`. If False, the import will + be attempted immediately. If not set, then the import will be + deferred unless the ``name`` is already present in + ``sys.modules``. deferred_submodules: Iterable[str], optional If provided, an iterable of submodule names within this module @@ -661,9 +668,17 @@ def attempt_import( if catch_exceptions is None: catch_exceptions = (ImportError,) + if defer_check is not None: + deprecation_warning( + 'defer_check=%s is deprecated. Please use defer_import' % (defer_check,), + version='6.7.2.dev0', + ) + assert defer_import is None + defer_import = defer_check + # If we are going to defer the check until later, return the # deferred import module object - if defer_check: + if defer_import: if deferred_submodules: if isinstance(deferred_submodules, Mapping): deprecation_warning( @@ -706,7 +721,7 @@ def attempt_import( return DeferredImportModule(indicator, deferred, None), indicator if deferred_submodules: - raise ValueError("deferred_submodules is only valid if defer_check==True") + raise ValueError("deferred_submodules is only valid if defer_import==True") return _perform_import( name=name, diff --git a/pyomo/common/tests/dep_mod.py b/pyomo/common/tests/dep_mod.py index f6add596ed4..34c7219c6eb 100644 --- a/pyomo/common/tests/dep_mod.py +++ b/pyomo/common/tests/dep_mod.py @@ -13,8 +13,8 @@ __version__ = '1.5' -numpy, numpy_available = attempt_import('numpy', defer_check=True) +numpy, numpy_available = attempt_import('numpy', defer_import=True) bogus_nonexisting_module, bogus_nonexisting_module_available = attempt_import( - 'bogus_nonexisting_module', alt_names=['bogus_nem'], defer_check=True + 'bogus_nonexisting_module', alt_names=['bogus_nem'], defer_import=True ) diff --git a/pyomo/common/tests/deps.py b/pyomo/common/tests/deps.py index d00281553f4..5f8c1fffdf8 100644 --- a/pyomo/common/tests/deps.py +++ b/pyomo/common/tests/deps.py @@ -23,15 +23,16 @@ bogus_nonexisting_module_available as has_bogus_nem, ) -bogus, bogus_available = attempt_import('nonexisting.module.bogus', defer_check=True) +bogus, bogus_available = attempt_import('nonexisting.module.bogus', defer_import=True) pkl_test, pkl_available = attempt_import( - 'nonexisting.module.pickle_test', deferred_submodules=['submod'], defer_check=True + 'nonexisting.module.pickle_test', deferred_submodules=['submod'], defer_import=True ) pyo, pyo_available = attempt_import( 'pyomo', alt_names=['pyo'], + defer_import=True, deferred_submodules={'version': None, 'common.tests.dep_mod': ['dm']}, ) diff --git a/pyomo/common/tests/test_dependencies.py b/pyomo/common/tests/test_dependencies.py index 30822a4f81f..31f9520b613 100644 --- a/pyomo/common/tests/test_dependencies.py +++ b/pyomo/common/tests/test_dependencies.py @@ -45,7 +45,7 @@ def test_import_error(self): module_obj, module_available = attempt_import( '__there_is_no_module_named_this__', 'Testing import of a non-existent module', - defer_check=False, + defer_import=False, ) self.assertFalse(module_available) with self.assertRaisesRegex( @@ -85,7 +85,7 @@ def test_pickle(self): def test_import_success(self): module_obj, module_available = attempt_import( - 'ply', 'Testing import of ply', defer_check=False + 'ply', 'Testing import of ply', defer_import=False ) self.assertTrue(module_available) import ply @@ -123,7 +123,7 @@ def test_imported_deferred_import(self): def test_min_version(self): mod, avail = attempt_import( - 'pyomo.common.tests.dep_mod', minimum_version='1.0', defer_check=False + 'pyomo.common.tests.dep_mod', minimum_version='1.0', defer_import=False ) self.assertTrue(avail) self.assertTrue(inspect.ismodule(mod)) @@ -131,7 +131,7 @@ def test_min_version(self): self.assertFalse(check_min_version(mod, '2.0')) mod, avail = attempt_import( - 'pyomo.common.tests.dep_mod', minimum_version='2.0', defer_check=False + 'pyomo.common.tests.dep_mod', minimum_version='2.0', defer_import=False ) self.assertFalse(avail) self.assertIs(type(mod), ModuleUnavailable) @@ -146,7 +146,7 @@ def test_min_version(self): 'pyomo.common.tests.dep_mod', error_message="Failed import", minimum_version='2.0', - defer_check=False, + defer_import=False, ) self.assertFalse(avail) self.assertIs(type(mod), ModuleUnavailable) @@ -159,10 +159,10 @@ def test_min_version(self): # Verify check_min_version works with deferred imports - mod, avail = attempt_import('pyomo.common.tests.dep_mod', defer_check=True) + mod, avail = attempt_import('pyomo.common.tests.dep_mod', defer_import=True) self.assertTrue(check_min_version(mod, '1.0')) - mod, avail = attempt_import('pyomo.common.tests.dep_mod', defer_check=True) + mod, avail = attempt_import('pyomo.common.tests.dep_mod', defer_import=True) self.assertFalse(check_min_version(mod, '2.0')) # Verify check_min_version works when called directly @@ -174,10 +174,10 @@ def test_min_version(self): self.assertFalse(check_min_version(mod, '1.0')) def test_and_or(self): - mod0, avail0 = attempt_import('ply', defer_check=True) - mod1, avail1 = attempt_import('pyomo.common.tests.dep_mod', defer_check=True) + mod0, avail0 = attempt_import('ply', defer_import=True) + mod1, avail1 = attempt_import('pyomo.common.tests.dep_mod', defer_import=True) mod2, avail2 = attempt_import( - 'pyomo.common.tests.dep_mod', minimum_version='2.0', defer_check=True + 'pyomo.common.tests.dep_mod', minimum_version='2.0', defer_import=True ) _and = avail0 & avail1 @@ -233,11 +233,11 @@ def test_callbacks(self): def _record_avail(module, avail): ans.append(avail) - mod0, avail0 = attempt_import('ply', defer_check=True, callback=_record_avail) + mod0, avail0 = attempt_import('ply', defer_import=True, callback=_record_avail) mod1, avail1 = attempt_import( 'pyomo.common.tests.dep_mod', minimum_version='2.0', - defer_check=True, + defer_import=True, callback=_record_avail, ) @@ -250,7 +250,7 @@ def _record_avail(module, avail): def test_import_exceptions(self): mod, avail = attempt_import( 'pyomo.common.tests.dep_mod_except', - defer_check=True, + defer_import=True, only_catch_importerror=True, ) with self.assertRaisesRegex(ValueError, "cannot import module"): @@ -260,7 +260,7 @@ def test_import_exceptions(self): mod, avail = attempt_import( 'pyomo.common.tests.dep_mod_except', - defer_check=True, + defer_import=True, only_catch_importerror=False, ) self.assertFalse(avail) @@ -268,7 +268,7 @@ def test_import_exceptions(self): mod, avail = attempt_import( 'pyomo.common.tests.dep_mod_except', - defer_check=True, + defer_import=True, catch_exceptions=(ImportError, ValueError), ) self.assertFalse(avail) @@ -280,7 +280,7 @@ def test_import_exceptions(self): ): mod, avail = attempt_import( 'pyomo.common.tests.dep_mod_except', - defer_check=True, + defer_import=True, only_catch_importerror=True, catch_exceptions=(ImportError,), ) @@ -288,7 +288,7 @@ def test_import_exceptions(self): def test_generate_warning(self): mod, avail = attempt_import( 'pyomo.common.tests.dep_mod_except', - defer_check=True, + defer_import=True, only_catch_importerror=False, ) @@ -324,7 +324,7 @@ def test_generate_warning(self): def test_log_warning(self): mod, avail = attempt_import( 'pyomo.common.tests.dep_mod_except', - defer_check=True, + defer_import=True, only_catch_importerror=False, ) log = StringIO() @@ -366,9 +366,9 @@ def test_importer(self): def _importer(): attempted_import.append(True) - return attempt_import('pyomo.common.tests.dep_mod', defer_check=False)[0] + return attempt_import('pyomo.common.tests.dep_mod', defer_import=False)[0] - mod, avail = attempt_import('foo', importer=_importer, defer_check=True) + mod, avail = attempt_import('foo', importer=_importer, defer_import=True) self.assertEqual(attempted_import, []) self.assertIsInstance(mod, DeferredImportModule) @@ -401,17 +401,17 @@ def test_deferred_submodules(self): self.assertTrue(inspect.ismodule(deps.dm)) with self.assertRaisesRegex( - ValueError, "deferred_submodules is only valid if defer_check==True" + ValueError, "deferred_submodules is only valid if defer_import==True" ): mod, mod_available = attempt_import( 'nonexisting.module', - defer_check=False, + defer_import=False, deferred_submodules={'submod': None}, ) mod, mod_available = attempt_import( 'nonexisting.module', - defer_check=True, + defer_import=True, deferred_submodules={'submod.subsubmod': None}, ) self.assertIs(type(mod), DeferredImportModule) @@ -427,7 +427,7 @@ def test_UnavailableClass(self): module_obj, module_available = attempt_import( '__there_is_no_module_named_this__', 'Testing import of a non-existent module', - defer_check=False, + defer_import=False, ) class A_Class(UnavailableClass(module_obj)): diff --git a/pyomo/common/unittest.py b/pyomo/common/unittest.py index 9a21b35faa8..84b44775c1b 100644 --- a/pyomo/common/unittest.py +++ b/pyomo/common/unittest.py @@ -631,7 +631,7 @@ def initialize_dependencies(self): cls.package_modules = {} packages_used = set(sum(list(cls.package_dependencies.values()), [])) for package_ in packages_used: - pack, pack_avail = attempt_import(package_, defer_check=False) + pack, pack_avail = attempt_import(package_, defer_import=False) cls.package_available[package_] = pack_avail cls.package_modules[package_] = pack diff --git a/pyomo/contrib/pynumero/dependencies.py b/pyomo/contrib/pynumero/dependencies.py index 9e2088ffa0a..d323bd43e84 100644 --- a/pyomo/contrib/pynumero/dependencies.py +++ b/pyomo/contrib/pynumero/dependencies.py @@ -17,7 +17,7 @@ 'numpy', 'Pynumero requires the optional Pyomo dependency "numpy"', minimum_version='1.13.0', - defer_check=False, + defer_import=False, ) if not numpy_available: diff --git a/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py b/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py index 1f45f26d43b..408a0197382 100644 --- a/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py +++ b/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py @@ -35,7 +35,7 @@ 'One of the tests below requires a recent version of pandas for' ' comparing with a tolerance.', minimum_version='1.1.0', - defer_check=False, + defer_import=False, ) from pyomo.contrib.pynumero.asl import AmplInterface diff --git a/pyomo/contrib/pynumero/intrinsic.py b/pyomo/contrib/pynumero/intrinsic.py index 84675cc4c02..34054e7ffa2 100644 --- a/pyomo/contrib/pynumero/intrinsic.py +++ b/pyomo/contrib/pynumero/intrinsic.py @@ -11,9 +11,7 @@ from pyomo.common.dependencies import numpy as np, attempt_import -block_vector = attempt_import( - 'pyomo.contrib.pynumero.sparse.block_vector', defer_check=True -)[0] +block_vector = attempt_import('pyomo.contrib.pynumero.sparse.block_vector')[0] def norm(x, ord=None): diff --git a/pyomo/core/base/units_container.py b/pyomo/core/base/units_container.py index 1bf25ffdead..fb3d28385d0 100644 --- a/pyomo/core/base/units_container.py +++ b/pyomo/core/base/units_container.py @@ -127,7 +127,6 @@ pint_module, pint_available = attempt_import( 'pint', - defer_check=True, error_message=( 'The "pint" package failed to import. ' 'This package is necessary to use Pyomo units.' diff --git a/pyomo/solvers/plugins/solvers/GAMS.py b/pyomo/solvers/plugins/solvers/GAMS.py index e84cbdb441d..be3499a2f6b 100644 --- a/pyomo/solvers/plugins/solvers/GAMS.py +++ b/pyomo/solvers/plugins/solvers/GAMS.py @@ -41,7 +41,7 @@ from pyomo.common.dependencies import attempt_import -gdxcc, gdxcc_available = attempt_import('gdxcc', defer_check=True) +gdxcc, gdxcc_available = attempt_import('gdxcc') logger = logging.getLogger('pyomo.solvers') From 4f1b93c0f08b5819b2f54c565b2ebc8c96061887 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 26 Feb 2024 15:34:44 -0700 Subject: [PATCH 1153/1204] NFC: update comment --- pyomo/common/dependencies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 12ca6bd4ce3..8246cbb0776 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -597,7 +597,7 @@ def attempt_import( module in the ``globals()`` namespaces. For example, the alt_names for NumPy would be ``['np']``. (deprecated in version 6.0) - callback: function, optional + callback: Callable[[ModuleType, bool], None], optional A function with the signature "``fcn(module, available)``" that will be called after the import is first attempted. From d80cde1728883cb11e193be4dbb1109e68833665 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 26 Feb 2024 15:35:22 -0700 Subject: [PATCH 1154/1204] Do not defer import if module is already imported --- pyomo/common/dependencies.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 8246cbb0776..9b2f5ab5767 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -676,6 +676,15 @@ def attempt_import( assert defer_import is None defer_import = defer_check + # If the module has already been imported, there is no reason to + # further defer things: just import it. + if defer_import is None: + if name in sys.modules: + defer_import = False + deferred_submodules = None + else: + defer_import = True + # If we are going to defer the check until later, return the # deferred import module object if defer_import: From 6e2db622df1e3e556be9aee6523d269dd3bf95df Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 26 Feb 2024 15:37:48 -0700 Subject: [PATCH 1155/1204] NFC: apply black --- pyomo/common/dependencies.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 9b2f5ab5767..5bc752deb53 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -490,6 +490,7 @@ class DeferredImportCallbackFinder: :py:class:`DeferredImportModule`. """ + _callbacks = {} def find_spec(self, fullname, path, target=None): From a10d36405c28cabb180dae9c852335f7dd967e95 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 26 Feb 2024 15:41:11 -0700 Subject: [PATCH 1156/1204] NFC: fix typo --- pyomo/common/dependencies.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 5bc752deb53..9034342b5a1 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -513,9 +513,9 @@ def invalidate_caches(self): _DeferredImportCallbackFinder = DeferredImportCallbackFinder() # Insert the DeferredImportCallbackFinder at the beginning of the -# mata_path to that it is found before the standard finders (so that we -# can correctly inject the resolution of the DeferredImportIndicators -- -# which triggers the needed callbacks) +# sys.meta_path to that it is found before the standard finders (so that +# we can correctly inject the resolution of the DeferredImportIndicators +# -- which triggers the needed callbacks) sys.meta_path.insert(0, _DeferredImportCallbackFinder) From 648aae7392ec98bc33debe94aaa530b7f872417a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 27 Feb 2024 00:00:10 -0700 Subject: [PATCH 1157/1204] Update type registration tests to reflect automatic numpy callback --- pyomo/core/tests/unit/test_numvalue.py | 90 ++++++++++++++++++++------ 1 file changed, 72 insertions(+), 18 deletions(-) diff --git a/pyomo/core/tests/unit/test_numvalue.py b/pyomo/core/tests/unit/test_numvalue.py index bd784d655e8..2dca2df56a6 100644 --- a/pyomo/core/tests/unit/test_numvalue.py +++ b/pyomo/core/tests/unit/test_numvalue.py @@ -50,7 +50,16 @@ def __init__(self, val=0): class MyBogusNumericType(MyBogusType): def __add__(self, other): - return MyBogusNumericType(self.val + float(other)) + if other.__class__ in native_numeric_types: + return MyBogusNumericType(self.val + float(other)) + else: + return NotImplemented + + def __le__(self, other): + if other.__class__ in native_numeric_types: + return self.val <= float(other) + else: + return NotImplemented def __lt__(self, other): return self.val < float(other) @@ -534,6 +543,8 @@ def test_unknownNumericType(self): try: val = as_numeric(ref) self.assertEqual(val().val, 42.0) + self.assertIn(MyBogusNumericType, native_numeric_types) + self.assertIn(MyBogusNumericType, native_types) finally: native_numeric_types.remove(MyBogusNumericType) native_types.remove(MyBogusNumericType) @@ -562,10 +573,43 @@ def test_numpy_basic_bool_registration(self): @unittest.skipUnless(numpy_available, "This test requires NumPy") def test_automatic_numpy_registration(self): cmd = ( - 'import pyomo; from pyomo.core.base import Var, Param; ' - 'from pyomo.core.base.units_container import units; import numpy as np; ' - 'print(np.float64 in pyomo.common.numeric_types.native_numeric_types); ' - '%s; print(np.float64 in pyomo.common.numeric_types.native_numeric_types)' + 'from pyomo.common.numeric_types import native_numeric_types as nnt; ' + 'print("float64" in [_.__name__ for _ in nnt]); ' + 'import numpy; ' + 'print("float64" in [_.__name__ for _ in nnt])' + ) + + rc = subprocess.run( + [sys.executable, '-c', cmd], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + self.assertEqual((rc.returncode, rc.stdout), (0, "False\nTrue\n")) + + cmd = ( + 'import numpy; ' + 'from pyomo.common.numeric_types import native_numeric_types as nnt; ' + 'print("float64" in [_.__name__ for _ in nnt])' + ) + + rc = subprocess.run( + [sys.executable, '-c', cmd], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + self.assertEqual((rc.returncode, rc.stdout), (0, "True\n")) + + def test_unknownNumericType_expr_registration(self): + cmd = ( + 'import pyomo; ' + 'from pyomo.core.base import Var, Param; ' + 'from pyomo.core.base.units_container import units; ' + 'from pyomo.common.numeric_types import native_numeric_types as nnt; ' + f'from {__name__} import MyBogusNumericType; ' + 'ref = MyBogusNumericType(42); ' + 'print(MyBogusNumericType in nnt); %s; print(MyBogusNumericType in nnt); ' ) def _tester(expr): @@ -575,19 +619,29 @@ def _tester(expr): stderr=subprocess.STDOUT, text=True, ) - self.assertEqual((rc.returncode, rc.stdout), (0, "False\nTrue\n")) - - _tester('Var() <= np.float64(5)') - _tester('np.float64(5) <= Var()') - _tester('np.float64(5) + Var()') - _tester('Var() + np.float64(5)') - _tester('v = Var(); v.construct(); v.value = np.float64(5)') - _tester('p = Param(mutable=True); p.construct(); p.value = np.float64(5)') - _tester('v = Var(units=units.m); v.construct(); v.value = np.float64(5)') - _tester( - 'p = Param(mutable=True, units=units.m); p.construct(); ' - 'p.value = np.float64(5)' - ) + self.assertEqual( + (rc.returncode, rc.stdout), + ( + 0, + '''False +WARNING: Dynamically registering the following numeric type: + pyomo.core.tests.unit.test_numvalue.MyBogusNumericType + Dynamic registration is supported for convenience, but there are known + limitations to this approach. We recommend explicitly registering numeric + types using RegisterNumericType() or RegisterIntegerType(). +True +''', + ), + ) + + _tester('Var() <= ref') + _tester('ref <= Var()') + _tester('ref + Var()') + _tester('Var() + ref') + _tester('v = Var(); v.construct(); v.value = ref') + _tester('p = Param(mutable=True); p.construct(); p.value = ref') + _tester('v = Var(units=units.m); v.construct(); v.value = ref') + _tester('p = Param(mutable=True, units=units.m); p.construct(); p.value = ref') if __name__ == "__main__": From adbf1de2e3b6ca8c16eb6b81a9ae4966ea30fd94 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 27 Feb 2024 00:09:19 -0700 Subject: [PATCH 1158/1204] Remove numpy reference/check --- pyomo/common/numeric_types.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pyomo/common/numeric_types.py b/pyomo/common/numeric_types.py index 412a1bbeade..616d4c4bae4 100644 --- a/pyomo/common/numeric_types.py +++ b/pyomo/common/numeric_types.py @@ -228,13 +228,6 @@ def check_if_logical_type(obj): if obj_class in native_types: return obj_class in native_logical_types - if 'numpy' in obj_class.__module__: - # trigger the resolution of numpy_available and check if this - # type was automatically registered - bool(numpy_available) - if obj_class in native_types: - return obj_class in native_logical_types - try: if all( ( From ea77dca9ecf302e3757fbc916ce515562e16473e Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 27 Feb 2024 12:36:36 -0700 Subject: [PATCH 1159/1204] Resolve registration for modules that were already inmported --- pyomo/common/dependencies.py | 117 +++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 47 deletions(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 9034342b5a1..505211aeb56 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -822,20 +822,36 @@ def declare_deferred_modules_as_importable(globals_dict): :py:class:`ModuleUnavailable` instance. """ - _global_name = globals_dict['__name__'] + '.' - deferred = list( - (k, v) for k, v in globals_dict.items() if type(v) is DeferredImportModule - ) - while deferred: - name, mod = deferred.pop(0) - mod.__path__ = None - mod.__spec__ = None - sys.modules[_global_name + name] = mod - deferred.extend( - (name + '.' + k, v) - for k, v in mod.__dict__.items() - if type(v) is DeferredImportModule - ) + return declare_modules_as_importable(globals_dict).__exit__(None, None, None) + + +class declare_modules_as_importable(object): + def __init__(self, globals_dict): + self.globals_dict = globals_dict + self.init_dict = {} + + def __enter__(self): + self.init_dict.update(self.globals_dict) + + def __exit__(self, exc_type, exc_value, traceback): + _global_name = self.globals_dict['__name__'] + '.' + deferred = [ + (k, v) + for k, v in self.globals_dict.items() + if k not in self.init_dict + and isinstance(v, (ModuleType, DeferredImportModule)) + ] + while deferred: + name, mod = deferred.pop(0) + mod.__path__ = None + mod.__spec__ = None + sys.modules[_global_name + name] = mod + if isinstance(mod, DeferredImportModule): + deferred.extend( + (name + '.' + k, v) + for k, v in mod.__dict__.items() + if type(v) is DeferredImportModule + ) # @@ -952,41 +968,48 @@ def _pyutilib_importer(): return importlib.import_module('pyutilib') -# Standard libraries that are slower to import and not strictly required -# on all platforms / situations. -ctypes, _ = attempt_import( - 'ctypes', deferred_submodules=['util'], callback=_finalize_ctypes -) -random, _ = attempt_import('random') - -# Commonly-used optional dependencies -dill, dill_available = attempt_import('dill') -mpi4py, mpi4py_available = attempt_import('mpi4py') -networkx, networkx_available = attempt_import('networkx') -numpy, numpy_available = attempt_import('numpy', callback=_finalize_numpy) -pandas, pandas_available = attempt_import('pandas') -plotly, plotly_available = attempt_import('plotly') -pympler, pympler_available = attempt_import('pympler', callback=_finalize_pympler) -pyutilib, pyutilib_available = attempt_import('pyutilib', importer=_pyutilib_importer) -scipy, scipy_available = attempt_import( - 'scipy', - callback=_finalize_scipy, - deferred_submodules=['stats', 'sparse', 'spatial', 'integrate'], -) -yaml, yaml_available = attempt_import('yaml', callback=_finalize_yaml) - -# Note that matplotlib.pyplot can generate a runtime error on OSX when -# not installed as a Framework (as is the case in the CI systems) -matplotlib, matplotlib_available = attempt_import( - 'matplotlib', - callback=_finalize_matplotlib, - deferred_submodules=['pyplot', 'pylab'], - catch_exceptions=(ImportError, RuntimeError), -) +# +# Note: because we will be calling +# declare_deferred_modules_as_importable, it is important that the +# following declarations explicitly defer_import (even if the target +# module has already been imported) +# +with declare_modules_as_importable(globals()): + # Standard libraries that are slower to import and not strictly required + # on all platforms / situations. + ctypes, _ = attempt_import( + 'ctypes', deferred_submodules=['util'], callback=_finalize_ctypes + ) + random, _ = attempt_import('random') + + # Commonly-used optional dependencies + dill, dill_available = attempt_import('dill') + mpi4py, mpi4py_available = attempt_import('mpi4py') + networkx, networkx_available = attempt_import('networkx') + numpy, numpy_available = attempt_import('numpy', callback=_finalize_numpy) + pandas, pandas_available = attempt_import('pandas') + plotly, plotly_available = attempt_import('plotly') + pympler, pympler_available = attempt_import('pympler', callback=_finalize_pympler) + pyutilib, pyutilib_available = attempt_import( + 'pyutilib', importer=_pyutilib_importer + ) + scipy, scipy_available = attempt_import( + 'scipy', + callback=_finalize_scipy, + deferred_submodules=['stats', 'sparse', 'spatial', 'integrate'], + ) + yaml, yaml_available = attempt_import('yaml', callback=_finalize_yaml) + + # Note that matplotlib.pyplot can generate a runtime error on OSX when + # not installed as a Framework (as is the case in the CI systems) + matplotlib, matplotlib_available = attempt_import( + 'matplotlib', + callback=_finalize_matplotlib, + deferred_submodules=['pyplot', 'pylab'], + catch_exceptions=(ImportError, RuntimeError), + ) try: import cPickle as pickle except ImportError: import pickle - -declare_deferred_modules_as_importable(globals()) From 6699fde39928e0dc9eba0482bc018a8d1a540101 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 27 Feb 2024 16:07:43 -0700 Subject: [PATCH 1160/1204] declare_modules_as_importable will also detect imported submodules --- pyomo/common/dependencies.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 505211aeb56..2954a8bff83 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -829,25 +829,31 @@ class declare_modules_as_importable(object): def __init__(self, globals_dict): self.globals_dict = globals_dict self.init_dict = {} + self.init_modules = None def __enter__(self): self.init_dict.update(self.globals_dict) + self.init_modules = set(sys.modules) def __exit__(self, exc_type, exc_value, traceback): _global_name = self.globals_dict['__name__'] + '.' - deferred = [ - (k, v) + deferred = { + k: v for k, v in self.globals_dict.items() if k not in self.init_dict and isinstance(v, (ModuleType, DeferredImportModule)) - ] + } + if self.init_modules: + for name in set(sys.modules) - self.init_modules: + if '.' in name and name.split('.', 1)[0] in deferred: + sys.modules[_global_name + name] = sys.modules[name] while deferred: - name, mod = deferred.pop(0) + name, mod = deferred.popitem() mod.__path__ = None mod.__spec__ = None sys.modules[_global_name + name] = mod if isinstance(mod, DeferredImportModule): - deferred.extend( + deferred.update( (name + '.' + k, v) for k, v in mod.__dict__.items() if type(v) is DeferredImportModule From a95af93eb62be5ea0d4137457f904d109de00363 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 27 Feb 2024 16:08:05 -0700 Subject: [PATCH 1161/1204] Update docs, deprecate declare_deferred_modules_as_importable --- pyomo/common/dependencies.py | 49 ++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 2954a8bff83..7d5437f6da9 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -782,6 +782,11 @@ def _perform_import( return module, False +@deprecated( + "declare_deferred_modules_as_importable() is dperecated. " + "Use the declare_modules_as_importable() context manager." + version='6.7.2.dev0' +) def declare_deferred_modules_as_importable(globals_dict): """Make all :py:class:`DeferredImportModules` in ``globals_dict`` importable @@ -826,6 +831,50 @@ def declare_deferred_modules_as_importable(globals_dict): class declare_modules_as_importable(object): + """Make all :py:class:`ModuleType` and :py:class:`DeferredImportModules` + importable through the ``globals_dict`` context. + + This context manager will detect all modules imported into the + specified ``globals_dict`` environment (either directly or through + :py:fcn:`attempt_import`) and will make those modules importable + from the specified ``globals_dict`` context. It works by detecting + changes in the specified ``globals_dict`` dictionary and adding any new + modules or instances of :py:class:`DeferredImportModule` that it + finds (and any of their deferred submodules) to ``sys.modules`` so + that the modules can be imported through the ``globals_dict`` + namespace. + + For example, ``pyomo/common/dependencies.py`` declares: + + .. doctest:: + :hide: + + >>> from pyomo.common.dependencies import ( + ... attempt_import, _finalize_scipy, __dict__ as dep_globals, + ... declare_deferred_modules_as_importable, ) + >>> # Sphinx does not provide a proper globals() + >>> def globals(): return dep_globals + + .. doctest:: + + >>> with declare_modules_as_importable(globals()): + ... scipy, scipy_available = attempt_import( + ... 'scipy', callback=_finalize_scipy, + ... deferred_submodules=['stats', 'sparse', 'spatial', 'integrate']) + + Which enables users to use: + + .. doctest:: + + >>> import pyomo.common.dependencies.scipy.sparse as spa + + If the deferred import has not yet been triggered, then the + :py:class:`DeferredImportModule` is returned and named ``spa``. + However, if the import has already been triggered, then ``spa`` will + either be the ``scipy.sparse`` module, or a + :py:class:`ModuleUnavailable` instance. + + """ def __init__(self, globals_dict): self.globals_dict = globals_dict self.init_dict = {} From 076dabe721876e931cb488e84f9b546e4a9baa2f Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 27 Feb 2024 16:08:27 -0700 Subject: [PATCH 1162/1204] Add deep import for numpy to resolve scipy import error --- pyomo/common/dependencies.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 7d5437f6da9..aab0d55d9b4 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -964,6 +964,11 @@ def _finalize_matplotlib(module, available): def _finalize_numpy(np, available): if not available: return + # scipy has a dependence on numpy.testing, and if we don't import it + # as part of resolving numpy, then certain deferred scipy imports + # fail when run under pytest. + import numpy.testing + from . import numeric_types # Register ndarray as a native type to prevent 1-element ndarrays From dc19d4e333f6c714deab75a06e9602c90ea97b5a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 27 Feb 2024 16:09:49 -0700 Subject: [PATCH 1163/1204] Fix typo --- pyomo/common/dependencies.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index aab0d55d9b4..900618b696a 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -784,8 +784,8 @@ def _perform_import( @deprecated( "declare_deferred_modules_as_importable() is dperecated. " - "Use the declare_modules_as_importable() context manager." - version='6.7.2.dev0' + "Use the declare_modules_as_importable() context manager.", + version='6.7.2.dev0', ) def declare_deferred_modules_as_importable(globals_dict): """Make all :py:class:`DeferredImportModules` in ``globals_dict`` importable @@ -875,6 +875,7 @@ class declare_modules_as_importable(object): :py:class:`ModuleUnavailable` instance. """ + def __init__(self, globals_dict): self.globals_dict = globals_dict self.init_dict = {} From 6efece0f3385b5e782525d0d7a1aeee45f824039 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 27 Feb 2024 16:30:21 -0700 Subject: [PATCH 1164/1204] Resolve doctest failures --- doc/OnlineDocs/contributed_packages/pyros.rst | 2 +- pyomo/common/dependencies.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/OnlineDocs/contributed_packages/pyros.rst b/doc/OnlineDocs/contributed_packages/pyros.rst index aad37a9685a..76a751dd994 100644 --- a/doc/OnlineDocs/contributed_packages/pyros.rst +++ b/doc/OnlineDocs/contributed_packages/pyros.rst @@ -689,7 +689,7 @@ could have been equivalently written as: ... }, ... ) ============================================================================== - PyROS: The Pyomo Robust Optimization Solver. + PyROS: The Pyomo Robust Optimization Solver... ... ------------------------------------------------------------------------------ Robust optimal solution identified. diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 900618b696a..c09594b6e12 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -813,6 +813,7 @@ def declare_deferred_modules_as_importable(globals_dict): ... 'scipy', callback=_finalize_scipy, ... deferred_submodules=['stats', 'sparse', 'spatial', 'integrate']) >>> declare_deferred_modules_as_importable(globals()) + WARNING: DEPRECATED: ... Which enables users to use: @@ -851,7 +852,7 @@ class declare_modules_as_importable(object): >>> from pyomo.common.dependencies import ( ... attempt_import, _finalize_scipy, __dict__ as dep_globals, - ... declare_deferred_modules_as_importable, ) + ... declare_modules_as_importable, ) >>> # Sphinx does not provide a proper globals() >>> def globals(): return dep_globals From 754de4114ac58381089c21fdffa0ee3345b8d21a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 27 Feb 2024 16:30:48 -0700 Subject: [PATCH 1165/1204] NFC: doc updates --- pyomo/common/dependencies.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index c09594b6e12..f0713a53cbf 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -485,7 +485,7 @@ class DeferredImportCallbackFinder: normal loader returned by ``PathFinder`` with a loader that will trigger custom callbacks after the module is loaded. We use this to trigger the post import callbacks registered through - :py:fcn:`attempt_import` even when a user imports the target library + :py:func:`attempt_import` even when a user imports the target library directly (and not through attribute access on the :py:class:`DeferredImportModule`. @@ -582,7 +582,8 @@ def attempt_import( The message for the exception raised by :py:class:`ModuleUnavailable` only_catch_importerror: bool, optional - DEPRECATED: use catch_exceptions instead or only_catch_importerror. + DEPRECATED: use ``catch_exceptions`` instead of ``only_catch_importerror``. + If True (the default), exceptions other than ``ImportError`` raised during module import will be reraised. If False, any exception will result in returning a :py:class:`ModuleUnavailable` object. @@ -593,13 +594,14 @@ def attempt_import( ``module.__version__``) alt_names: list, optional - DEPRECATED: alt_names no longer needs to be specified and is ignored. + DEPRECATED: ``alt_names`` no longer needs to be specified and is ignored. + A list of common alternate names by which to look for this module in the ``globals()`` namespaces. For example, the alt_names for NumPy would be ``['np']``. (deprecated in version 6.0) callback: Callable[[ModuleType, bool], None], optional - A function with the signature "``fcn(module, available)``" that + A function with the signature ``fcn(module, available)`` that will be called after the import is first attempted. importer: function, optional @@ -609,7 +611,7 @@ def attempt_import( want to import/return the first one that is available. defer_check: bool, optional - DEPRECATED: renamed to ``defer_import`` + DEPRECATED: renamed to ``defer_import`` (deprecated in version 6.7.2.dev0) defer_import: bool, optional If True, then the attempted import is deferred until the first @@ -783,8 +785,8 @@ def _perform_import( @deprecated( - "declare_deferred_modules_as_importable() is dperecated. " - "Use the declare_modules_as_importable() context manager.", + "``declare_deferred_modules_as_importable()`` is deprecated. " + "Use the :py:class:`declare_modules_as_importable` context manager.", version='6.7.2.dev0', ) def declare_deferred_modules_as_importable(globals_dict): @@ -837,7 +839,7 @@ class declare_modules_as_importable(object): This context manager will detect all modules imported into the specified ``globals_dict`` environment (either directly or through - :py:fcn:`attempt_import`) and will make those modules importable + :py:func:`attempt_import`) and will make those modules importable from the specified ``globals_dict`` context. It works by detecting changes in the specified ``globals_dict`` dictionary and adding any new modules or instances of :py:class:`DeferredImportModule` that it From 96658623579fb767c4a6347c01c6392e17ebf774 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 27 Feb 2024 16:31:07 -0700 Subject: [PATCH 1166/1204] Add backends tot eh matplotlib deferred imports --- pyomo/common/dependencies.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index f0713a53cbf..edf32baa6d6 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -963,6 +963,8 @@ def _finalize_matplotlib(module, available): if in_testing_environment(): module.use('Agg') import matplotlib.pyplot + import matplotlib.pylab + import matplotlib.backends def _finalize_numpy(np, available): @@ -1069,7 +1071,7 @@ def _pyutilib_importer(): matplotlib, matplotlib_available = attempt_import( 'matplotlib', callback=_finalize_matplotlib, - deferred_submodules=['pyplot', 'pylab'], + deferred_submodules=['pyplot', 'pylab', 'backends'], catch_exceptions=(ImportError, RuntimeError), ) From 0938052a92a147e0f450e8744fb400eacae89f5e Mon Sep 17 00:00:00 2001 From: jasherma Date: Tue, 27 Feb 2024 19:14:46 -0500 Subject: [PATCH 1167/1204] Simplify a few config domain validators --- pyomo/contrib/pyros/config.py | 77 ++++++------------------ pyomo/contrib/pyros/tests/test_config.py | 26 +++++--- 2 files changed, 36 insertions(+), 67 deletions(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index a7ca41d095f..bc2bfd591e6 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -26,71 +26,34 @@ default_pyros_solver_logger = setup_pyros_logger() -class LoggerType: +def logger_domain(obj): """ - Domain validator for objects castable to logging.Logger. - """ - - def __call__(self, obj): - """ - Cast object to logger. + Domain validator for logger-type arguments. - Parameters - ---------- - obj : object - Object to be cast. + This admits any object of type ``logging.Logger``, + or which can be cast to ``logging.Logger``. + """ + if isinstance(obj, logging.Logger): + return obj + else: + return logging.getLogger(obj) - Returns - ------- - logging.Logger - If `str_or_logger` is of type `logging.Logger`,then - `str_or_logger` is returned. - Otherwise, ``logging.getLogger(str_or_logger)`` - is returned. - """ - if isinstance(obj, logging.Logger): - return obj - else: - return logging.getLogger(obj) - def domain_name(self): - """Return str briefly describing domain encompassed by self.""" - return "None, str or logging.Logger" +logger_domain.domain_name = "None, str or logging.Logger" -class PositiveIntOrMinusOne: +def positive_int_or_minus_one(obj): """ - Domain validator for objects castable to a - strictly positive int or -1. + Domain validator for objects castable to a strictly + positive int or -1. """ + ans = int(obj) + if ans != float(obj) or (ans <= 0 and ans != -1): + raise ValueError(f"Expected positive int or -1, but received value {obj!r}") + return ans - def __call__(self, obj): - """ - Cast object to positive int or -1. - Parameters - ---------- - obj : object - Object of interest. - - Returns - ------- - int - Positive int, or -1. - - Raises - ------ - ValueError - If object not castable to positive int, or -1. - """ - ans = int(obj) - if ans != float(obj) or (ans <= 0 and ans != -1): - raise ValueError(f"Expected positive int or -1, but received value {obj!r}") - return ans - - def domain_name(self): - """Return str briefly describing domain encompassed by self.""" - return "positive int or -1" +positive_int_or_minus_one.domain_name = "positive int or -1" def mutable_param_validator(param_obj): @@ -721,7 +684,7 @@ def pyros_config(): "max_iter", ConfigValue( default=-1, - domain=PositiveIntOrMinusOne(), + domain=positive_int_or_minus_one, description=( """ Iteration limit. If -1 is provided, then no iteration @@ -766,7 +729,7 @@ def pyros_config(): "progress_logger", ConfigValue( default=default_pyros_solver_logger, - domain=LoggerType(), + domain=logger_domain, doc=( """ Logger (or name thereof) used for reporting PyROS solver diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index 76b9114b9e6..3555391fd95 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -12,9 +12,9 @@ from pyomo.contrib.pyros.config import ( InputDataStandardizer, mutable_param_validator, - LoggerType, + logger_domain, SolverNotResolvable, - PositiveIntOrMinusOne, + positive_int_or_minus_one, pyros_config, SolverIterable, SolverResolvable, @@ -557,16 +557,22 @@ def test_positive_int_or_minus_one(self): """ Test positive int or -1 validator works as expected. """ - standardizer_func = PositiveIntOrMinusOne() + standardizer_func = positive_int_or_minus_one self.assertIs( standardizer_func(1.0), 1, - msg=(f"{PositiveIntOrMinusOne.__name__} does not standardize as expected."), + msg=( + f"{positive_int_or_minus_one.__name__} " + "does not standardize as expected." + ), ) self.assertEqual( standardizer_func(-1.00), -1, - msg=(f"{PositiveIntOrMinusOne.__name__} does not standardize as expected."), + msg=( + f"{positive_int_or_minus_one.__name__} " + "does not standardize as expected." + ), ) exc_str = r"Expected positive int or -1, but received value.*" @@ -576,26 +582,26 @@ def test_positive_int_or_minus_one(self): standardizer_func(0) -class TestLoggerType(unittest.TestCase): +class TestLoggerDomain(unittest.TestCase): """ - Test logger type validator. + Test logger type domain validator. """ def test_logger_type(self): """ Test logger type validator. """ - standardizer_func = LoggerType() + standardizer_func = logger_domain mylogger = logging.getLogger("example") self.assertIs( standardizer_func(mylogger), mylogger, - msg=f"{LoggerType.__name__} output not as expected", + msg=f"{standardizer_func.__name__} output not as expected", ) self.assertIs( standardizer_func(mylogger.name), mylogger, - msg=f"{LoggerType.__name__} output not as expected", + msg=f"{standardizer_func.__name__} output not as expected", ) exc_str = r"A logger name must be a string" From 3d5cb6c8fc7eae46f91d85bc4bf2cc71aaac9dc9 Mon Sep 17 00:00:00 2001 From: jasherma Date: Tue, 27 Feb 2024 20:22:14 -0500 Subject: [PATCH 1168/1204] Fix PyROS discrete separation iteration log --- .../contrib/pyros/pyros_algorithm_methods.py | 2 +- .../pyros/separation_problem_methods.py | 1 + pyomo/contrib/pyros/solve_data.py | 29 +++++++++++++++++-- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index 45b652447ff..f0e32a284bb 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -805,7 +805,7 @@ def ROSolver_iterative_solve(model_data, config): len(scaled_violations) == len(separation_model.util.performance_constraints) and not separation_results.subsolver_error and not separation_results.time_out - ) + ) or separation_results.all_discrete_scenarios_exhausted iter_log_record = IterationLogRecord( iteration=k, diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index 084b0442ae6..b5939ff5b19 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -649,6 +649,7 @@ def perform_separation_loop(model_data, config, solve_globally): solver_call_results=ComponentMap(), solved_globally=solve_globally, worst_case_perf_con=None, + all_discrete_scenarios_exhausted=True, ) perf_con_to_maximize = sorted_priority_groups[ diff --git a/pyomo/contrib/pyros/solve_data.py b/pyomo/contrib/pyros/solve_data.py index bc6c071c9a3..c31eb8e5d3f 100644 --- a/pyomo/contrib/pyros/solve_data.py +++ b/pyomo/contrib/pyros/solve_data.py @@ -347,16 +347,23 @@ class SeparationLoopResults: solver_call_results : ComponentMap Mapping from performance constraints to corresponding ``SeparationSolveCallResults`` objects. - worst_case_perf_con : None or int, optional + worst_case_perf_con : None or Constraint Performance constraint mapped to ``SeparationSolveCallResults`` object in `self` corresponding to maximally violating separation problem solution. + all_discrete_scenarios_exhausted : bool, optional + For problems with discrete uncertainty sets, + True if all scenarios were explicitly accounted for in master + (which occurs if there have been + as many PyROS iterations as there are scenarios in the set) + False otherwise. Attributes ---------- solver_call_results solved_globally worst_case_perf_con + all_discrete_scenarios_exhausted found_violation violating_param_realization scaled_violations @@ -365,11 +372,18 @@ class SeparationLoopResults: time_out """ - def __init__(self, solved_globally, solver_call_results, worst_case_perf_con): + def __init__( + self, + solved_globally, + solver_call_results, + worst_case_perf_con, + all_discrete_scenarios_exhausted=False, + ): """Initialize self (see class docstring).""" self.solver_call_results = solver_call_results self.solved_globally = solved_globally self.worst_case_perf_con = worst_case_perf_con + self.all_discrete_scenarios_exhausted = all_discrete_scenarios_exhausted @property def found_violation(self): @@ -599,6 +613,17 @@ def get_violating_attr(self, attr_name): """ return getattr(self.main_loop_results, attr_name, None) + @property + def all_discrete_scenarios_exhausted(self): + """ + bool : For problems where the uncertainty set is of type + DiscreteScenarioSet, + True if last master problem solved explicitly + accounts for all scenarios in the uncertainty set, + False otherwise. + """ + return self.get_violating_attr("all_discrete_scenarios_exhausted") + @property def worst_case_perf_con(self): """ From 782c4ec093e95b14cdf4ca4aaa2a95ca5a0302b5 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 27 Feb 2024 23:45:04 -0700 Subject: [PATCH 1169/1204] Add test guards for pint availability --- pyomo/core/tests/unit/test_numvalue.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyomo/core/tests/unit/test_numvalue.py b/pyomo/core/tests/unit/test_numvalue.py index 2dca2df56a6..442d5bc1a6c 100644 --- a/pyomo/core/tests/unit/test_numvalue.py +++ b/pyomo/core/tests/unit/test_numvalue.py @@ -18,6 +18,7 @@ import pyomo.common.unittest as unittest from pyomo.common.dependencies import numpy, numpy_available +from pyomo.core.base.units_container import pint_available from pyomo.environ import ( value, @@ -640,8 +641,9 @@ def _tester(expr): _tester('Var() + ref') _tester('v = Var(); v.construct(); v.value = ref') _tester('p = Param(mutable=True); p.construct(); p.value = ref') - _tester('v = Var(units=units.m); v.construct(); v.value = ref') - _tester('p = Param(mutable=True, units=units.m); p.construct(); p.value = ref') + if pint_available: + _tester('v = Var(units=units.m); v.construct(); v.value = ref') + _tester('p = Param(mutable=True, units=units.m); p.construct(); p.value = ref') if __name__ == "__main__": From e46d2b193a25c321d118ae1474788f5119a155e1 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 27 Feb 2024 23:56:19 -0700 Subject: [PATCH 1170/1204] Set maxDiff=None on the base TestCase class --- pyomo/common/tests/test_config.py | 6 ------ pyomo/common/tests/test_log.py | 1 - pyomo/common/tests/test_timing.py | 4 ---- pyomo/common/unittest.py | 4 ++++ pyomo/core/tests/unit/test_block.py | 1 - pyomo/core/tests/unit/test_numeric_expr.py | 1 - pyomo/core/tests/unit/test_reference.py | 2 -- pyomo/core/tests/unit/test_set.py | 1 - pyomo/repn/tests/ampl/test_nlv2.py | 1 - 9 files changed, 4 insertions(+), 17 deletions(-) diff --git a/pyomo/common/tests/test_config.py b/pyomo/common/tests/test_config.py index 12657481764..a47f5e0d8af 100644 --- a/pyomo/common/tests/test_config.py +++ b/pyomo/common/tests/test_config.py @@ -2098,7 +2098,6 @@ def test_generate_custom_documentation(self): "generate_documentation is deprecated.", LOG, ) - self.maxDiff = None # print(test) self.assertEqual(test, reference) @@ -2113,7 +2112,6 @@ def test_generate_custom_documentation(self): ) ) self.assertEqual(LOG.getvalue(), "") - self.maxDiff = None # print(test) self.assertEqual(test, reference) @@ -2159,7 +2157,6 @@ def test_generate_custom_documentation(self): "generate_documentation is deprecated.", LOG, ) - self.maxDiff = None # print(test) self.assertEqual(test, reference) @@ -2577,7 +2574,6 @@ def test_argparse_help_implicit_disable(self): parser = argparse.ArgumentParser(prog='tester') self.config.initialize_argparse(parser) help = parser.format_help() - self.maxDiff = None self.assertIn( """ -h, --help show this help message and exit @@ -3106,8 +3102,6 @@ def test_declare_from(self): cfg2.declare_from({}) def test_docstring_decorator(self): - self.maxDiff = None - @document_kwargs_from_configdict('CONFIG') class ExampleClass(object): CONFIG = ExampleConfig() diff --git a/pyomo/common/tests/test_log.py b/pyomo/common/tests/test_log.py index 64691c0015a..166e1e44cdb 100644 --- a/pyomo/common/tests/test_log.py +++ b/pyomo/common/tests/test_log.py @@ -511,7 +511,6 @@ def test_verbatim(self): "\n" " quote block\n" ) - self.maxDiff = None self.assertEqual(self.stream.getvalue(), ans) diff --git a/pyomo/common/tests/test_timing.py b/pyomo/common/tests/test_timing.py index 0a4224c5476..48288746882 100644 --- a/pyomo/common/tests/test_timing.py +++ b/pyomo/common/tests/test_timing.py @@ -107,7 +107,6 @@ def test_report_timing(self): m.y = Var(Any, dense=False) xfrm.apply_to(m) result = out.getvalue().strip() - self.maxDiff = None for l, r in zip(result.splitlines(), ref.splitlines()): self.assertRegex(str(l.strip()), str(r.strip())) finally: @@ -122,7 +121,6 @@ def test_report_timing(self): m.y = Var(Any, dense=False) xfrm.apply_to(m) result = os.getvalue().strip() - self.maxDiff = None for l, r in zip(result.splitlines(), ref.splitlines()): self.assertRegex(str(l.strip()), str(r.strip())) finally: @@ -135,7 +133,6 @@ def test_report_timing(self): m.y = Var(Any, dense=False) xfrm.apply_to(m) result = os.getvalue().strip() - self.maxDiff = None for l, r in zip(result.splitlines(), ref.splitlines()): self.assertRegex(str(l.strip()), str(r.strip())) self.assertEqual(buf.getvalue().strip(), "") @@ -172,7 +169,6 @@ def test_report_timing_context_manager(self): xfrm.apply_to(m) self.assertEqual(OUT.getvalue(), "") result = OS.getvalue().strip() - self.maxDiff = None for l, r in zip_longest(result.splitlines(), ref.splitlines()): self.assertRegex(str(l.strip()), str(r.strip())) # Active reporting is False: the previous log should not have changed diff --git a/pyomo/common/unittest.py b/pyomo/common/unittest.py index 9a21b35faa8..9ee7731bda4 100644 --- a/pyomo/common/unittest.py +++ b/pyomo/common/unittest.py @@ -498,6 +498,10 @@ class TestCase(_unittest.TestCase): __doc__ += _unittest.TestCase.__doc__ + # By default, we always want to spend the time to create the full + # diff of the test reault and the baseline + maxDiff = None + def assertStructuredAlmostEqual( self, first, diff --git a/pyomo/core/tests/unit/test_block.py b/pyomo/core/tests/unit/test_block.py index 88646643703..71e80d90a73 100644 --- a/pyomo/core/tests/unit/test_block.py +++ b/pyomo/core/tests/unit/test_block.py @@ -2667,7 +2667,6 @@ def test_pprint(self): 5 Declarations: a1_IDX a3_IDX c a b """ - self.maxDiff = None self.assertEqual(ref, buf.getvalue()) @unittest.skipIf(not 'glpk' in solvers, "glpk solver is not available") diff --git a/pyomo/core/tests/unit/test_numeric_expr.py b/pyomo/core/tests/unit/test_numeric_expr.py index c073ee0f726..c1066c292d7 100644 --- a/pyomo/core/tests/unit/test_numeric_expr.py +++ b/pyomo/core/tests/unit/test_numeric_expr.py @@ -1424,7 +1424,6 @@ def test_sumOf_nestedTrivialProduct2(self): e1 = m.a * m.p e2 = m.b - m.c e = e2 - e1 - self.maxDiff = None self.assertExpressionsEqual( e, LinearExpression( diff --git a/pyomo/core/tests/unit/test_reference.py b/pyomo/core/tests/unit/test_reference.py index 287ff204f9e..cfd9b99f945 100644 --- a/pyomo/core/tests/unit/test_reference.py +++ b/pyomo/core/tests/unit/test_reference.py @@ -1280,7 +1280,6 @@ def test_contains_with_nonflattened(self): normalize_index.flatten = _old_flatten def test_pprint_nonfinite_sets(self): - self.maxDiff = None m = ConcreteModel() m.v = Var(NonNegativeIntegers, dense=False) m.ref = Reference(m.v) @@ -1322,7 +1321,6 @@ def test_pprint_nonfinite_sets(self): def test_pprint_nonfinite_sets_ctypeNone(self): # test issue #2039 - self.maxDiff = None m = ConcreteModel() m.v = Var(NonNegativeIntegers, dense=False) m.ref = Reference(m.v, ctype=None) diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index 1ad08ba025c..4bbac6ecaa0 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -6267,7 +6267,6 @@ def test_issue_835(self): @unittest.skipIf(NamedTuple is None, "typing module not available") def test_issue_938(self): - self.maxDiff = None NodeKey = NamedTuple('NodeKey', [('id', int)]) ArcKey = NamedTuple('ArcKey', [('node_from', NodeKey), ('node_to', NodeKey)]) diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 215715dba10..86eb43d9a37 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1096,7 +1096,6 @@ def test_log_timing(self): m.c1 = Constraint([1, 2], rule=lambda m, i: sum(m.x.values()) == 1) m.c2 = Constraint(expr=m.p * m.x[1] ** 2 + m.x[2] ** 3 <= 100) - self.maxDiff = None OUT = io.StringIO() with capture_output() as LOG: with report_timing(level=logging.DEBUG): From 66696b33dd17ae61b02b729af24da7ee0cc0164a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 28 Feb 2024 00:40:15 -0700 Subject: [PATCH 1171/1204] Add tests for native type set registration --- pyomo/common/tests/test_numeric_types.py | 219 +++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 pyomo/common/tests/test_numeric_types.py diff --git a/pyomo/common/tests/test_numeric_types.py b/pyomo/common/tests/test_numeric_types.py new file mode 100644 index 00000000000..a6570b7440e --- /dev/null +++ b/pyomo/common/tests/test_numeric_types.py @@ -0,0 +1,219 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + +import pyomo.common.numeric_types as nt +import pyomo.common.unittest as unittest + +from pyomo.common.dependencies import numpy, numpy_available +from pyomo.core.expr import LinearExpression +from pyomo.environ import Var + +_type_sets = ( + 'native_types', + 'native_numeric_types', + 'native_logical_types', + 'native_integer_types', + 'native_complex_types', +) + + +class TestNativeTypes(unittest.TestCase): + def setUp(self): + bool(numpy_available) + for s in _type_sets: + setattr(self, s, set(getattr(nt, s))) + getattr(nt, s).clear() + + def tearDown(self): + for s in _type_sets: + getattr(nt, s).clear() + getattr(nt, s).update(getattr(nt, s)) + + def test_check_if_native_type(self): + self.assertEqual(nt.native_types, set()) + self.assertEqual(nt.native_logical_types, set()) + self.assertEqual(nt.native_numeric_types, set()) + self.assertEqual(nt.native_integer_types, set()) + self.assertEqual(nt.native_complex_types, set()) + + self.assertTrue(nt.check_if_native_type("a")) + self.assertIn(str, nt.native_types) + self.assertNotIn(str, nt.native_logical_types) + self.assertNotIn(str, nt.native_numeric_types) + self.assertNotIn(str, nt.native_integer_types) + self.assertNotIn(str, nt.native_complex_types) + + self.assertTrue(nt.check_if_native_type(1)) + self.assertIn(int, nt.native_types) + self.assertNotIn(int, nt.native_logical_types) + self.assertIn(int, nt.native_numeric_types) + self.assertIn(int, nt.native_integer_types) + self.assertNotIn(int, nt.native_complex_types) + + self.assertTrue(nt.check_if_native_type(1.5)) + self.assertIn(float, nt.native_types) + self.assertNotIn(float, nt.native_logical_types) + self.assertIn(float, nt.native_numeric_types) + self.assertNotIn(float, nt.native_integer_types) + self.assertNotIn(float, nt.native_complex_types) + + self.assertTrue(nt.check_if_native_type(True)) + self.assertIn(bool, nt.native_types) + self.assertIn(bool, nt.native_logical_types) + self.assertNotIn(bool, nt.native_numeric_types) + self.assertNotIn(bool, nt.native_integer_types) + self.assertNotIn(bool, nt.native_complex_types) + + self.assertFalse(nt.check_if_native_type(slice(None, None, None))) + self.assertNotIn(slice, nt.native_types) + self.assertNotIn(slice, nt.native_logical_types) + self.assertNotIn(slice, nt.native_numeric_types) + self.assertNotIn(slice, nt.native_integer_types) + self.assertNotIn(slice, nt.native_complex_types) + + def test_check_if_logical_type(self): + self.assertEqual(nt.native_types, set()) + self.assertEqual(nt.native_logical_types, set()) + self.assertEqual(nt.native_numeric_types, set()) + self.assertEqual(nt.native_integer_types, set()) + self.assertEqual(nt.native_complex_types, set()) + + self.assertFalse(nt.check_if_logical_type("a")) + self.assertNotIn(str, nt.native_types) + self.assertNotIn(str, nt.native_logical_types) + self.assertNotIn(str, nt.native_numeric_types) + self.assertNotIn(str, nt.native_integer_types) + self.assertNotIn(str, nt.native_complex_types) + + self.assertFalse(nt.check_if_logical_type("a")) + + self.assertTrue(nt.check_if_logical_type(True)) + self.assertIn(bool, nt.native_types) + self.assertIn(bool, nt.native_logical_types) + self.assertNotIn(bool, nt.native_numeric_types) + self.assertNotIn(bool, nt.native_integer_types) + self.assertNotIn(bool, nt.native_complex_types) + + self.assertTrue(nt.check_if_logical_type(True)) + + self.assertFalse(nt.check_if_logical_type(1)) + self.assertNotIn(int, nt.native_types) + self.assertNotIn(int, nt.native_logical_types) + self.assertNotIn(int, nt.native_numeric_types) + self.assertNotIn(int, nt.native_integer_types) + self.assertNotIn(int, nt.native_complex_types) + + if numpy_available: + self.assertTrue(nt.check_if_logical_type(numpy.bool_(1))) + self.assertIn(numpy.bool_, nt.native_types) + self.assertIn(numpy.bool_, nt.native_logical_types) + self.assertNotIn(numpy.bool_, nt.native_numeric_types) + self.assertNotIn(numpy.bool_, nt.native_integer_types) + self.assertNotIn(numpy.bool_, nt.native_complex_types) + + def test_check_if_numeric_type(self): + self.assertEqual(nt.native_types, set()) + self.assertEqual(nt.native_logical_types, set()) + self.assertEqual(nt.native_numeric_types, set()) + self.assertEqual(nt.native_integer_types, set()) + self.assertEqual(nt.native_complex_types, set()) + + self.assertFalse(nt.check_if_numeric_type("a")) + self.assertFalse(nt.check_if_numeric_type("a")) + self.assertNotIn(str, nt.native_types) + self.assertNotIn(str, nt.native_logical_types) + self.assertNotIn(str, nt.native_numeric_types) + self.assertNotIn(str, nt.native_integer_types) + self.assertNotIn(str, nt.native_complex_types) + + self.assertFalse(nt.check_if_numeric_type(True)) + self.assertFalse(nt.check_if_numeric_type(True)) + self.assertNotIn(bool, nt.native_types) + self.assertNotIn(bool, nt.native_logical_types) + self.assertNotIn(bool, nt.native_numeric_types) + self.assertNotIn(bool, nt.native_integer_types) + self.assertNotIn(bool, nt.native_complex_types) + + self.assertTrue(nt.check_if_numeric_type(1)) + self.assertTrue(nt.check_if_numeric_type(1)) + self.assertIn(int, nt.native_types) + self.assertNotIn(int, nt.native_logical_types) + self.assertIn(int, nt.native_numeric_types) + self.assertIn(int, nt.native_integer_types) + self.assertNotIn(int, nt.native_complex_types) + + self.assertTrue(nt.check_if_numeric_type(1.5)) + self.assertTrue(nt.check_if_numeric_type(1.5)) + self.assertIn(float, nt.native_types) + self.assertNotIn(float, nt.native_logical_types) + self.assertIn(float, nt.native_numeric_types) + self.assertNotIn(float, nt.native_integer_types) + self.assertNotIn(float, nt.native_complex_types) + + self.assertFalse(nt.check_if_numeric_type(1j)) + self.assertIn(complex, nt.native_types) + self.assertNotIn(complex, nt.native_logical_types) + self.assertNotIn(complex, nt.native_numeric_types) + self.assertNotIn(complex, nt.native_integer_types) + self.assertIn(complex, nt.native_complex_types) + + v = Var() + v.construct() + self.assertFalse(nt.check_if_numeric_type(v)) + self.assertNotIn(type(v), nt.native_types) + self.assertNotIn(type(v), nt.native_logical_types) + self.assertNotIn(type(v), nt.native_numeric_types) + self.assertNotIn(type(v), nt.native_integer_types) + self.assertNotIn(type(v), nt.native_complex_types) + + e = LinearExpression([1]) + self.assertFalse(nt.check_if_numeric_type(e)) + self.assertNotIn(type(e), nt.native_types) + self.assertNotIn(type(e), nt.native_logical_types) + self.assertNotIn(type(e), nt.native_numeric_types) + self.assertNotIn(type(e), nt.native_integer_types) + self.assertNotIn(type(e), nt.native_complex_types) + + if numpy_available: + self.assertFalse(nt.check_if_numeric_type(numpy.bool_(1))) + self.assertNotIn(numpy.bool_, nt.native_types) + self.assertNotIn(numpy.bool_, nt.native_logical_types) + self.assertNotIn(numpy.bool_, nt.native_numeric_types) + self.assertNotIn(numpy.bool_, nt.native_integer_types) + self.assertNotIn(numpy.bool_, nt.native_complex_types) + + self.assertFalse(nt.check_if_numeric_type(numpy.array([1]))) + self.assertNotIn(numpy.ndarray, nt.native_types) + self.assertNotIn(numpy.ndarray, nt.native_logical_types) + self.assertNotIn(numpy.ndarray, nt.native_numeric_types) + self.assertNotIn(numpy.ndarray, nt.native_integer_types) + self.assertNotIn(numpy.ndarray, nt.native_complex_types) + + self.assertTrue(nt.check_if_numeric_type(numpy.float64(1))) + self.assertIn(numpy.float64, nt.native_types) + self.assertNotIn(numpy.float64, nt.native_logical_types) + self.assertIn(numpy.float64, nt.native_numeric_types) + self.assertNotIn(numpy.float64, nt.native_integer_types) + self.assertNotIn(numpy.float64, nt.native_complex_types) + + self.assertTrue(nt.check_if_numeric_type(numpy.int64(1))) + self.assertIn(numpy.int64, nt.native_types) + self.assertNotIn(numpy.int64, nt.native_logical_types) + self.assertIn(numpy.int64, nt.native_numeric_types) + self.assertIn(numpy.int64, nt.native_integer_types) + self.assertNotIn(numpy.int64, nt.native_complex_types) + + self.assertFalse(nt.check_if_numeric_type(numpy.complex128(1))) + self.assertIn(numpy.complex128, nt.native_types) + self.assertNotIn(numpy.complex128, nt.native_logical_types) + self.assertNotIn(numpy.complex128, nt.native_numeric_types) + self.assertNotIn(numpy.complex128, nt.native_integer_types) + self.assertIn(numpy.complex128, nt.native_complex_types) From 5b6cf69c862e8a97605eb272561e60b73ae640f1 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 28 Feb 2024 00:43:22 -0700 Subject: [PATCH 1172/1204] NFC: apply black --- pyomo/core/tests/unit/test_numvalue.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyomo/core/tests/unit/test_numvalue.py b/pyomo/core/tests/unit/test_numvalue.py index 442d5bc1a6c..1cccd3863ea 100644 --- a/pyomo/core/tests/unit/test_numvalue.py +++ b/pyomo/core/tests/unit/test_numvalue.py @@ -643,7 +643,9 @@ def _tester(expr): _tester('p = Param(mutable=True); p.construct(); p.value = ref') if pint_available: _tester('v = Var(units=units.m); v.construct(); v.value = ref') - _tester('p = Param(mutable=True, units=units.m); p.construct(); p.value = ref') + _tester( + 'p = Param(mutable=True, units=units.m); p.construct(); p.value = ref' + ) if __name__ == "__main__": From 1a347bf7fea5430cd1041e408f4b66cdcc874e68 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 28 Feb 2024 00:52:46 -0700 Subject: [PATCH 1173/1204] Fix typo restoring state after test --- pyomo/common/tests/test_numeric_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/common/tests/test_numeric_types.py b/pyomo/common/tests/test_numeric_types.py index a6570b7440e..b7ffb5fb255 100644 --- a/pyomo/common/tests/test_numeric_types.py +++ b/pyomo/common/tests/test_numeric_types.py @@ -35,7 +35,7 @@ def setUp(self): def tearDown(self): for s in _type_sets: getattr(nt, s).clear() - getattr(nt, s).update(getattr(nt, s)) + getattr(nt, s).update(getattr(self, s)) def test_check_if_native_type(self): self.assertEqual(nt.native_types, set()) From 84ca52464286faeb95fd5edda052b4d3459f021c Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 28 Feb 2024 08:35:37 -0700 Subject: [PATCH 1174/1204] NFC: fix comment typo --- pyomo/common/dependencies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index edf32baa6d6..895759a8a2c 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -513,7 +513,7 @@ def invalidate_caches(self): _DeferredImportCallbackFinder = DeferredImportCallbackFinder() # Insert the DeferredImportCallbackFinder at the beginning of the -# sys.meta_path to that it is found before the standard finders (so that +# sys.meta_path so that it is found before the standard finders (so that # we can correctly inject the resolution of the DeferredImportIndicators # -- which triggers the needed callbacks) sys.meta_path.insert(0, _DeferredImportCallbackFinder) From de86a6aa93374baf6e0f6147a13421c9ef45c49e Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 28 Feb 2024 09:03:49 -0700 Subject: [PATCH 1175/1204] check_if_logical_type(): expand Boolean tests, relax cast-from-int requirement --- pyomo/common/numeric_types.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/pyomo/common/numeric_types.py b/pyomo/common/numeric_types.py index 616d4c4bae4..9d4adc12e22 100644 --- a/pyomo/common/numeric_types.py +++ b/pyomo/common/numeric_types.py @@ -229,13 +229,32 @@ def check_if_logical_type(obj): return obj_class in native_logical_types try: + # It is not an error if you can't initialize the type from an + # int, but if you can, it should map !0 to True + if obj_class(1) != obj_class(2): + return False + except: + pass + + try: + # Native logical types *must* be hashable + hash(obj) + # Native logical types must honor standard Boolean operators if all( ( - obj_class(1) == obj_class(2), obj_class(False) != obj_class(True), + obj_class(False) ^ obj_class(False) == obj_class(False), obj_class(False) ^ obj_class(True) == obj_class(True), + obj_class(True) ^ obj_class(False) == obj_class(True), + obj_class(True) ^ obj_class(True) == obj_class(False), + obj_class(False) | obj_class(False) == obj_class(False), obj_class(False) | obj_class(True) == obj_class(True), + obj_class(True) | obj_class(False) == obj_class(True), + obj_class(True) | obj_class(True) == obj_class(True), + obj_class(False) & obj_class(False) == obj_class(False), obj_class(False) & obj_class(True) == obj_class(False), + obj_class(True) & obj_class(False) == obj_class(False), + obj_class(True) & obj_class(True) == obj_class(True), ) ): RegisterLogicalType(obj_class) From e617a6e773c16ff840a4f22cd9751eaadf1ed132 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 28 Feb 2024 09:04:16 -0700 Subject: [PATCH 1176/1204] NFC: update comments/docstrings --- pyomo/common/numeric_types.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pyomo/common/numeric_types.py b/pyomo/common/numeric_types.py index 9d4adc12e22..a1fe1e7514e 100644 --- a/pyomo/common/numeric_types.py +++ b/pyomo/common/numeric_types.py @@ -217,10 +217,10 @@ def check_if_native_type(obj): def check_if_logical_type(obj): """Test if the argument behaves like a logical type. - We check for "numeric types" by checking if we can add zero to it - without changing the object's type, and that the object compares to - 0 in a meaningful way. If that works, then we register the type in - :py:attr:`native_numeric_types`. + We check for "logical types" by checking if the type returns sane + results for Boolean operators (``^``, ``|``, ``&``) and if it maps + ``1`` and ``2`` both to the same equivalent instance. If that + works, then we register the type in :py:attr:`native_logical_types`. """ obj_class = obj.__class__ @@ -304,9 +304,8 @@ def check_if_numeric_type(obj): except: pass # - # ensure that the object is comparable to 0 in a meaningful way - # (among other things, this prevents numpy.ndarray objects from - # being added to native_numeric_types) + # Ensure that the object is comparable to 0 in a meaningful way + # try: if not ((obj < 0) ^ (obj >= 0)): return False From 46bfee38dbdcc11c76e8279207b7ac2d0dfeafb7 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 28 Feb 2024 11:02:12 -0700 Subject: [PATCH 1177/1204] NFC: removing a comment that is no longer relevant --- pyomo/common/dependencies.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 895759a8a2c..472b0011edb 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -1034,12 +1034,6 @@ def _pyutilib_importer(): return importlib.import_module('pyutilib') -# -# Note: because we will be calling -# declare_deferred_modules_as_importable, it is important that the -# following declarations explicitly defer_import (even if the target -# module has already been imported) -# with declare_modules_as_importable(globals()): # Standard libraries that are slower to import and not strictly required # on all platforms / situations. From 5b5f0046ab59accedee601deb51cfe14939298ec Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 28 Feb 2024 15:24:45 -0500 Subject: [PATCH 1178/1204] Fix indentation typo --- pyomo/contrib/pyros/solve_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/solve_data.py b/pyomo/contrib/pyros/solve_data.py index c31eb8e5d3f..73eee5202aa 100644 --- a/pyomo/contrib/pyros/solve_data.py +++ b/pyomo/contrib/pyros/solve_data.py @@ -347,7 +347,7 @@ class SeparationLoopResults: solver_call_results : ComponentMap Mapping from performance constraints to corresponding ``SeparationSolveCallResults`` objects. - worst_case_perf_con : None or Constraint + worst_case_perf_con : None or Constraint Performance constraint mapped to ``SeparationSolveCallResults`` object in `self` corresponding to maximally violating separation problem solution. From 20a63602692ee8c9e8ee2bfa9efa95af392f5a6e Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 28 Feb 2024 15:52:14 -0500 Subject: [PATCH 1179/1204] Update `positive_int_or_minus_1` tests --- pyomo/contrib/pyros/tests/test_config.py | 29 +++++++++++++++--------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index 3555391fd95..0f52d04135d 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -558,21 +558,28 @@ def test_positive_int_or_minus_one(self): Test positive int or -1 validator works as expected. """ standardizer_func = positive_int_or_minus_one - self.assertIs( - standardizer_func(1.0), + ans = standardizer_func(1.0) + self.assertEqual( + ans, 1, - msg=( - f"{positive_int_or_minus_one.__name__} " - "does not standardize as expected." - ), + msg=f"{positive_int_or_minus_one.__name__} output value not as expected.", + ) + self.assertIs( + type(ans), + int, + msg=f"{positive_int_or_minus_one.__name__} output type not as expected.", ) + + ans = standardizer_func(-1.0) self.assertEqual( - standardizer_func(-1.00), + ans, -1, - msg=( - f"{positive_int_or_minus_one.__name__} " - "does not standardize as expected." - ), + msg=f"{positive_int_or_minus_one.__name__} output value not as expected.", + ) + self.assertIs( + type(ans), + int, + msg=f"{positive_int_or_minus_one.__name__} output type not as expected.", ) exc_str = r"Expected positive int or -1, but received value.*" From 4e5bbbf2f073911ed89d39002ea96b652e807e16 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 2 Mar 2024 22:58:51 -0700 Subject: [PATCH 1180/1204] NFC: fix copyright header --- pyomo/contrib/latex_printer/latex_printer.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index 110df7cd5ca..a986f5d6b81 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -9,17 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -# ___________________________________________________________________________ -# -# Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2023 -# 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 math import copy import re From 0e673b663ad339e00ca93a65cf414bd0f79ca012 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 2 Mar 2024 23:01:38 -0700 Subject: [PATCH 1181/1204] performance: avoid duplication, linear searches --- pyomo/contrib/latex_printer/latex_printer.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index a986f5d6b81..41cff29ad80 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -275,11 +275,11 @@ def handle_functionID_node(visitor, node, *args): def handle_indexTemplate_node(visitor, node, *args): - if node._set in ComponentSet(visitor.setMap.keys()): + if node._set in visitor.setMap: # already detected set, do nothing pass else: - visitor.setMap[node._set] = 'SET%d' % (len(visitor.setMap.keys()) + 1) + visitor.setMap[node._set] = 'SET%d' % (len(visitor.setMap) + 1) return '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( node._group, @@ -616,15 +616,15 @@ def latex_printer( # Cody's backdoor because he got outvoted if latex_component_map is not None: - if 'use_short_descriptors' in list(latex_component_map.keys()): + if 'use_short_descriptors' in latex_component_map: if latex_component_map['use_short_descriptors'] == False: use_short_descriptors = False if latex_component_map is None: latex_component_map = ComponentMap() - existing_components = ComponentSet([]) + existing_components = ComponentSet() else: - existing_components = ComponentSet(list(latex_component_map.keys())) + existing_components = ComponentSet(latex_component_map) isSingle = False @@ -1225,14 +1225,14 @@ def latex_printer( ) for ky, vl in new_variableMap.items(): - if ky not in ComponentSet(latex_component_map.keys()): + if ky not in latex_component_map: latex_component_map[ky] = vl for ky, vl in new_parameterMap.items(): - if ky not in ComponentSet(latex_component_map.keys()): + if ky not in latex_component_map: latex_component_map[ky] = vl rep_dict = {} - for ky in ComponentSet(list(reversed(list(latex_component_map.keys())))): + for ky in reversed(list(latex_component_map)): if isinstance(ky, (pyo.Var, _GeneralVarData)): overwrite_value = latex_component_map[ky] if ky not in existing_components: From ff111df42ac8aa4ff87377ab3fd16612a91b0eda Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 2 Mar 2024 23:04:18 -0700 Subject: [PATCH 1182/1204] resolve indextemplate naming for multidimensional sets --- pyomo/contrib/latex_printer/latex_printer.py | 153 +++++++++++-------- 1 file changed, 88 insertions(+), 65 deletions(-) diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index 41cff29ad80..90a5da0d9c1 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -281,8 +281,9 @@ def handle_indexTemplate_node(visitor, node, *args): else: visitor.setMap[node._set] = 'SET%d' % (len(visitor.setMap) + 1) - return '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( + return '__I_PLACEHOLDER_8675309_GROUP_%s_%s_%s__' % ( node._group, + node._id, visitor.setMap[node._set], ) @@ -304,8 +305,9 @@ def handle_numericGetItemExpression_node(visitor, node, *args): def handle_templateSumExpression_node(visitor, node, *args): pstr = '' for i in range(0, len(node._iters)): - pstr += '\\sum_{__S_PLACEHOLDER_8675309_GROUP_%s_%s__} ' % ( + pstr += '\\sum_{__S_PLACEHOLDER_8675309_GROUP_%s_%s_%s__} ' % ( node._iters[i][0]._group, + ','.join(str(it._id) for it in node._iters[i]), visitor.setMap[node._iters[i][0]._set], ) @@ -904,24 +906,33 @@ def latex_printer( # setMap = visitor.setMap # Multiple constraints are generated using a set if len(indices) > 0: - if indices[0]._set in ComponentSet(visitor.setMap.keys()): - # already detected set, do nothing - pass - else: - visitor.setMap[indices[0]._set] = 'SET%d' % ( - len(visitor.setMap.keys()) + 1 + conLine += ' \\qquad \\forall' + + _bygroups = {} + for idx in indices: + _bygroups.setdefault(idx._group, []).append(idx) + for _group, idxs in _bygroups.items(): + if idxs[0]._set in visitor.setMap: + # already detected set, do nothing + pass + else: + visitor.setMap[idxs[0]._set] = 'SET%d' % ( + len(visitor.setMap) + 1 + ) + + idxTag = ','.join( + '__I_PLACEHOLDER_8675309_GROUP_%s_%s_%s__' + % (idx._group, idx._id, visitor.setMap[idx._set]) + for idx in idxs ) - idxTag = '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( - indices[0]._group, - visitor.setMap[indices[0]._set], - ) - setTag = '__S_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( - indices[0]._group, - visitor.setMap[indices[0]._set], - ) + setTag = '__S_PLACEHOLDER_8675309_GROUP_%s_%s_%s__' % ( + indices[0]._group, + ','.join(str(it._id) for it in idxs), + visitor.setMap[indices[0]._set], + ) - conLine += ' \\qquad \\forall %s \\in %s ' % (idxTag, setTag) + conLine += ' %s \\in %s ' % (idxTag, setTag) pstr += conLine # Add labels as needed @@ -1070,8 +1081,8 @@ def latex_printer( for word in splitLatex: if "PLACEHOLDER_8675309_GROUP_" in word: ifo = word.split("PLACEHOLDER_8675309_GROUP_")[1] - gpNum, stName = ifo.split('_') - if gpNum not in groupMap.keys(): + gpNum, idNum, stName = ifo.split('_') + if gpNum not in groupMap: groupMap[gpNum] = [stName] if stName not in ComponentSet(uniqueSets): uniqueSets.append(stName) @@ -1088,10 +1099,7 @@ def latex_printer( ix = int(ky[3:]) - 1 setInfo[ky]['setObject'] = setMap_inverse[ky] # setList[ix] setInfo[ky]['setRegEx'] = ( - r'__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__' % (ky) - ) - setInfo[ky]['sumSetRegEx'] = ( - r'sum_{__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__}' % (ky) + r'__S_PLACEHOLDER_8675309_GROUP_([0-9]+)_([0-9,]+)_%s__' % (ky,) ) # setInfo[ky]['idxRegEx'] = r'__I_PLACEHOLDER_8675309_GROUP_[0-9*]_%s__'%(ky) @@ -1116,27 +1124,41 @@ def latex_printer( ed = stData[-1] replacement = ( - r'sum_{ __I_PLACEHOLDER_8675309_GROUP_\1_%s__ = %d }^{%d}' + r'sum_{ __I_PLACEHOLDER_8675309_GROUP_\1_\2_%s__ = %d }^{%d}' % (ky, bgn, ed) ) - ln = re.sub(setInfo[ky]['sumSetRegEx'], replacement, ln) + ln = re.sub( + 'sum_{' + setInfo[ky]['setRegEx'] + '}', replacement, ln + ) else: # if the set is not continuous or the flag has not been set - replacement = ( - r'sum_{ __I_PLACEHOLDER_8675309_GROUP_\1_%s__ \\in __S_PLACEHOLDER_8675309_GROUP_\1_%s__ }' - % (ky, ky) - ) - ln = re.sub(setInfo[ky]['sumSetRegEx'], replacement, ln) + for _grp, _id in re.findall( + 'sum_{' + setInfo[ky]['setRegEx'] + '}', ln + ): + set_placeholder = '__S_PLACEHOLDER_8675309_GROUP_%s_%s_%s__' % ( + _grp, + _id, + ky, + ) + i_placeholder = ','.join( + '__I_PLACEHOLDER_8675309_GROUP_%s_%s_%s__' % (_grp, _, ky) + for _ in _id.split(',') + ) + replacement = r'sum_{ %s \in %s }' % ( + i_placeholder, + set_placeholder, + ) + ln = ln.replace('sum_{' + set_placeholder + '}', replacement) replacement = repr(defaultSetLatexNames[setInfo[ky]['setObject']])[1:-1] ln = re.sub(setInfo[ky]['setRegEx'], replacement, ln) # groupNumbers = re.findall(r'__I_PLACEHOLDER_8675309_GROUP_([0-9*])_SET[0-9]*__',ln) setNumbers = re.findall( - r'__I_PLACEHOLDER_8675309_GROUP_[0-9*]_SET([0-9]*)__', ln + r'__I_PLACEHOLDER_8675309_GROUP_[0-9]+_[0-9]+_SET([0-9]+)__', ln ) - groupSetPairs = re.findall( - r'__I_PLACEHOLDER_8675309_GROUP_([0-9*])_SET([0-9]*)__', ln + groupIdSetTuples = re.findall( + r'__I_PLACEHOLDER_8675309_GROUP_([0-9]+)_([0-9]+)_SET([0-9]+)__', ln ) groupInfo = {} @@ -1146,43 +1168,44 @@ def latex_printer( 'indices': [], } - for gp in groupSetPairs: - if gp[0] not in groupInfo['SET' + gp[1]]['indices']: - groupInfo['SET' + gp[1]]['indices'].append(gp[0]) + for _gp, _id, _set in groupIdSetTuples: + if (_gp, _id) not in groupInfo['SET' + _set]['indices']: + groupInfo['SET' + _set]['indices'].append((_gp, _id)) + + def get_index_names(st, lcm): + if st in lcm: + return lcm[st][1] + elif isinstance(st, SetOperator): + return sum( + (get_index_names(s, lcm) for s in st.subsets(False)), start=[] + ) + elif st.dimen is not None: + return [None] * st.dimen + else: + return [Ellipsis] indexCounter = 0 for ky, vl in groupInfo.items(): - if vl['setObject'] in ComponentSet(latex_component_map.keys()): - indexNames = latex_component_map[vl['setObject']][1] - if len(indexNames) != 0: - if len(indexNames) < len(vl['indices']): - raise ValueError( - 'Insufficient number of indices provided to the overwrite dictionary for set %s' - % (vl['setObject'].name) - ) - for i in range(0, len(vl['indices'])): - ln = ln.replace( - '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' - % (vl['indices'][i], ky), - indexNames[i], - ) - else: - for i in range(0, len(vl['indices'])): - ln = ln.replace( - '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' - % (vl['indices'][i], ky), - alphabetStringGenerator(indexCounter), - ) - indexCounter += 1 - else: - for i in range(0, len(vl['indices'])): - ln = ln.replace( - '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' - % (vl['indices'][i], ky), - alphabetStringGenerator(indexCounter), + indexNames = get_index_names(vl['setObject'], latex_component_map) + nonNone = list(filter(None, indexNames)) + if nonNone: + if len(nonNone) < len(vl['indices']): + raise ValueError( + 'Insufficient number of indices provided to the ' + 'overwrite dictionary for set %s (expected %s, but got %s)' + % (vl['setObject'].name, len(vl['indices']), indexNames) ) + else: + indexNames = [] + for i in vl['indices']: + indexNames.append(alphabetStringGenerator(indexCounter)) indexCounter += 1 - + for i in range(0, len(vl['indices'])): + ln = ln.replace( + '__I_PLACEHOLDER_8675309_GROUP_%s_%s_%s__' + % (*vl['indices'][i], ky), + indexNames[i], + ) latexLines[jj] = ln pstr = '\n'.join(latexLines) From e2e8165b731f24564a689a0f035797b621e78942 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 2 Mar 2024 23:04:53 -0700 Subject: [PATCH 1183/1204] make it easier to switch mathds/mathbb --- pyomo/contrib/latex_printer/latex_printer.py | 27 +++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index 90a5da0d9c1..f3ffe2e5982 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -406,25 +406,28 @@ def exitNode(self, node, data): ) +mathbb = r'\mathbb' + + def analyze_variable(vr): domainMap = { - 'Reals': '\\mathds{R}', - 'PositiveReals': '\\mathds{R}_{> 0}', - 'NonPositiveReals': '\\mathds{R}_{\\leq 0}', - 'NegativeReals': '\\mathds{R}_{< 0}', - 'NonNegativeReals': '\\mathds{R}_{\\geq 0}', - 'Integers': '\\mathds{Z}', - 'PositiveIntegers': '\\mathds{Z}_{> 0}', - 'NonPositiveIntegers': '\\mathds{Z}_{\\leq 0}', - 'NegativeIntegers': '\\mathds{Z}_{< 0}', - 'NonNegativeIntegers': '\\mathds{Z}_{\\geq 0}', + 'Reals': mathbb + '{R}', + 'PositiveReals': mathbb + '{R}_{> 0}', + 'NonPositiveReals': mathbb + '{R}_{\\leq 0}', + 'NegativeReals': mathbb + '{R}_{< 0}', + 'NonNegativeReals': mathbb + '{R}_{\\geq 0}', + 'Integers': mathbb + '{Z}', + 'PositiveIntegers': mathbb + '{Z}_{> 0}', + 'NonPositiveIntegers': mathbb + '{Z}_{\\leq 0}', + 'NegativeIntegers': mathbb + '{Z}_{< 0}', + 'NonNegativeIntegers': mathbb + '{Z}_{\\geq 0}', 'Boolean': '\\left\\{ \\text{True} , \\text{False} \\right \\}', 'Binary': '\\left\\{ 0 , 1 \\right \\}', # 'Any': None, # 'AnyWithNone': None, 'EmptySet': '\\varnothing', - 'UnitInterval': '\\mathds{R}', - 'PercentFraction': '\\mathds{R}', + 'UnitInterval': mathbb + '{R}', + 'PercentFraction': mathbb + '{R}', # 'RealInterval' : None , # 'IntegerInterval' : None , } From 99c1bc319f281215fe42260af3da0296719dc650 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 2 Mar 2024 23:05:59 -0700 Subject: [PATCH 1184/1204] Resolve issue with ambiguous field codes (when >10 vars or params) --- pyomo/contrib/latex_printer/latex_printer.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index f3ffe2e5982..77afeb8f849 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -776,12 +776,12 @@ def latex_printer( for vr in variableList: vrIdx += 1 if isinstance(vr, ScalarVar): - variableMap[vr] = 'x_' + str(vrIdx) + variableMap[vr] = 'x_' + str(vrIdx) + '_' elif isinstance(vr, IndexedVar): - variableMap[vr] = 'x_' + str(vrIdx) + variableMap[vr] = 'x_' + str(vrIdx) + '_' for sd in vr.index_set().data(): vrIdx += 1 - variableMap[vr[sd]] = 'x_' + str(vrIdx) + variableMap[vr[sd]] = 'x_' + str(vrIdx) + '_' else: raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers' @@ -793,12 +793,12 @@ def latex_printer( for vr in parameterList: pmIdx += 1 if isinstance(vr, ScalarParam): - parameterMap[vr] = 'p_' + str(pmIdx) + parameterMap[vr] = 'p_' + str(pmIdx) + '_' elif isinstance(vr, IndexedParam): - parameterMap[vr] = 'p_' + str(pmIdx) + parameterMap[vr] = 'p_' + str(pmIdx) + '_' for sd in vr.index_set().data(): pmIdx += 1 - parameterMap[vr[sd]] = 'p_' + str(pmIdx) + parameterMap[vr[sd]] = 'p_' + str(pmIdx) + '_' else: raise DeveloperError( 'Parameter is not a parameter. Should not happen. Contact developers' From 28db0387d398c96af331741820d1715f210d0cdc Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 2 Mar 2024 23:25:07 -0700 Subject: [PATCH 1185/1204] Support name generation for set expressions --- pyomo/contrib/latex_printer/latex_printer.py | 81 ++++++++++++-------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index 77afeb8f849..e41cbeac51e 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -49,7 +49,7 @@ ) from pyomo.core.base.var import ScalarVar, _GeneralVarData, IndexedVar from pyomo.core.base.param import _ParamData, ScalarParam, IndexedParam -from pyomo.core.base.set import _SetData +from pyomo.core.base.set import _SetData, SetOperator from pyomo.core.base.constraint import ScalarConstraint, IndexedConstraint from pyomo.common.collections.component_map import ComponentMap from pyomo.common.collections.component_set import ComponentSet @@ -79,6 +79,39 @@ from pyomo.common.dependencies import numpy as np, numpy_available +set_operator_map = { + '|': r' \cup ', + '&': r' \cap ', + '*': r' \times ', + '-': r' \setminus ', + '^': r' \triangle ', +} + +latex_reals = r'\mathds{R}' +latex_integers = r'\mathds{Z}' + +domainMap = { + 'Reals': latex_reals, + 'PositiveReals': latex_reals + '_{> 0}', + 'NonPositiveReals': latex_reals + '_{\\leq 0}', + 'NegativeReals': latex_reals + '_{< 0}', + 'NonNegativeReals': latex_reals + '_{\\geq 0}', + 'Integers': latex_integers, + 'PositiveIntegers': latex_integers + '_{> 0}', + 'NonPositiveIntegers': latex_integers + '_{\\leq 0}', + 'NegativeIntegers': latex_integers + '_{< 0}', + 'NonNegativeIntegers': latex_integers + '_{\\geq 0}', + 'Boolean': '\\left\\{ \\text{True} , \\text{False} \\right \\}', + 'Binary': '\\left\\{ 0 , 1 \\right \\}', + # 'Any': None, + # 'AnyWithNone': None, + 'EmptySet': '\\varnothing', + 'UnitInterval': latex_reals, + 'PercentFraction': latex_reals, + # 'RealInterval' : None , + # 'IntegerInterval' : None , +} + def decoder(num, base): if int(num) != abs(num): # Requiring an integer is nice, but not strictly necessary; @@ -406,32 +439,7 @@ def exitNode(self, node, data): ) -mathbb = r'\mathbb' - - def analyze_variable(vr): - domainMap = { - 'Reals': mathbb + '{R}', - 'PositiveReals': mathbb + '{R}_{> 0}', - 'NonPositiveReals': mathbb + '{R}_{\\leq 0}', - 'NegativeReals': mathbb + '{R}_{< 0}', - 'NonNegativeReals': mathbb + '{R}_{\\geq 0}', - 'Integers': mathbb + '{Z}', - 'PositiveIntegers': mathbb + '{Z}_{> 0}', - 'NonPositiveIntegers': mathbb + '{Z}_{\\leq 0}', - 'NegativeIntegers': mathbb + '{Z}_{< 0}', - 'NonNegativeIntegers': mathbb + '{Z}_{\\geq 0}', - 'Boolean': '\\left\\{ \\text{True} , \\text{False} \\right \\}', - 'Binary': '\\left\\{ 0 , 1 \\right \\}', - # 'Any': None, - # 'AnyWithNone': None, - 'EmptySet': '\\varnothing', - 'UnitInterval': mathbb + '{R}', - 'PercentFraction': mathbb + '{R}', - # 'RealInterval' : None , - # 'IntegerInterval' : None , - } - domainName = vr.domain.name varBounds = vr.bounds lowerBoundValue = varBounds[0] @@ -1062,15 +1070,22 @@ def latex_printer( setMap = visitor.setMap setMap_inverse = {vl: ky for ky, vl in setMap.items()} + def generate_set_name(st, lcm): + if st in lcm: + return lcm[st][0] + if st.parent_block().component(st.name) is st: + return st.name.replace('_', r'\_') + if isinstance(st, SetOperator): + return _set_op_map[st._operator.strip()].join( + generate_set_name(s, lcm) for s in st.subsets(False) + ) + else: + return str(st).replace('_', r'\_').replace('{', '\{').replace('}', '\}') + # Handling the iterator indices defaultSetLatexNames = ComponentMap() - for ky, vl in setMap.items(): - st = ky - defaultSetLatexNames[st] = st.name.replace('_', '\\_') - if st in ComponentSet(latex_component_map.keys()): - defaultSetLatexNames[st] = latex_component_map[st][ - 0 - ] # .replace('_', '\\_') + for ky in setMap: + defaultSetLatexNames[ky] = generate_set_name(ky, latex_component_map) latexLines = pstr.split('\n') for jj in range(0, len(latexLines)): From b1444017c3fd536b0630dda91f9290b6e3043798 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 3 Mar 2024 09:07:15 -0700 Subject: [PATCH 1186/1204] NFC: apply black --- pyomo/contrib/latex_printer/latex_printer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index e41cbeac51e..c2cbfd6b2e1 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -112,6 +112,7 @@ # 'IntegerInterval' : None , } + def decoder(num, base): if int(num) != abs(num): # Requiring an integer is nice, but not strictly necessary; From 2273282c76d8cba84767ece983153445fa795ade Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Mar 2024 08:17:07 -0700 Subject: [PATCH 1187/1204] Try some different stuff to get more printouts --- .github/workflows/test_branches.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 77f47b505ff..661d86ef890 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -86,7 +86,7 @@ jobs: PACKAGES: pytest-qt - os: ubuntu-latest - python: 3.9 + python: '3.10' other: /mpi mpi: 3 skip_doctest: 1 @@ -333,10 +333,11 @@ jobs: CONDA_DEPENDENCIES="$CONDA_DEPENDENCIES $PKG" fi done + echo "" echo "*** Install Pyomo dependencies ***" # Note: this will fail the build if any installation fails (or # possibly if it outputs messages to stderr) - conda install --update-deps -q -y $CONDA_DEPENDENCIES + conda install --update-deps -y $CONDA_DEPENDENCIES if test -z "${{matrix.slim}}"; then PYVER=$(echo "py${{matrix.python}}" | sed 's/\.//g') echo "Installing for $PYVER" From a32c0040b5c66ed524966a072d2ab22d5f6d9745 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Mar 2024 09:00:06 -0700 Subject: [PATCH 1188/1204] Revert to 3.9 --- .github/workflows/test_branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 661d86ef890..5dca79f294e 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -86,7 +86,7 @@ jobs: PACKAGES: pytest-qt - os: ubuntu-latest - python: '3.10' + python: 3.9 other: /mpi mpi: 3 skip_doctest: 1 From ffa594cdf1e4bc7f621e73ae5a8bb94595cf6399 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Mar 2024 09:08:04 -0700 Subject: [PATCH 1189/1204] Back to 3.10 --- .github/workflows/test_branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 5dca79f294e..661d86ef890 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -86,7 +86,7 @@ jobs: PACKAGES: pytest-qt - os: ubuntu-latest - python: 3.9 + python: '3.10' other: /mpi mpi: 3 skip_doctest: 1 From e446941c9d2d965b6b2cb4744eb01f03de93f67c Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Mar 2024 09:11:00 -0700 Subject: [PATCH 1190/1204] Upgrade to macos-13 --- .github/workflows/test_branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 661d86ef890..89c1fbeb7e4 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -66,7 +66,7 @@ jobs: TARGET: linux PYENV: pip - - os: macos-latest + - os: macos-13 python: '3.10' TARGET: osx PYENV: pip From a4a1fd41a70708eb7179b421e67d9c739a016793 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Mar 2024 09:23:16 -0700 Subject: [PATCH 1191/1204] Lower to 2 --- .github/workflows/test_branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 89c1fbeb7e4..6867043f67e 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -88,7 +88,7 @@ jobs: - os: ubuntu-latest python: '3.10' other: /mpi - mpi: 3 + mpi: 2 skip_doctest: 1 TARGET: linux PYENV: conda From 4ae6a70a72b07b0f811ea8e72035937b7a111efb Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Mar 2024 12:15:57 -0700 Subject: [PATCH 1192/1204] Add oversubscribe --- .github/workflows/test_branches.yml | 4 ++-- .github/workflows/test_pr_and_main.yml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 6867043f67e..1cb6fd0926d 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -88,7 +88,7 @@ jobs: - os: ubuntu-latest python: '3.10' other: /mpi - mpi: 2 + mpi: 3 skip_doctest: 1 TARGET: linux PYENV: conda @@ -632,7 +632,7 @@ jobs: $PYTHON_EXE -c "from pyomo.dataportal.parse_datacmds import \ parse_data_commands; parse_data_commands(data='')" # Note: if we are testing with openmpi, add '--oversubscribe' - mpirun -np ${{matrix.mpi}} pytest -v \ + mpirun -np ${{matrix.mpi}} -oversubscribe pytest -v \ --junit-xml=TEST-pyomo-mpi.xml \ -m "mpi" -W ignore::Warning \ pyomo `pwd`/pyomo-model-libraries diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 87d6aa4d7a8..e3e08847aa9 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -59,7 +59,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-13, windows-latest] python: [ 3.8, 3.9, '3.10', '3.11', '3.12' ] other: [""] category: [""] @@ -69,7 +69,7 @@ jobs: TARGET: linux PYENV: pip - - os: macos-latest + - os: macos-13 TARGET: osx PYENV: pip @@ -87,7 +87,7 @@ jobs: PACKAGES: pytest-qt - os: ubuntu-latest - python: 3.9 + python: '3.10' other: /mpi mpi: 3 skip_doctest: 1 @@ -661,7 +661,7 @@ jobs: $PYTHON_EXE -c "from pyomo.dataportal.parse_datacmds import \ parse_data_commands; parse_data_commands(data='')" # Note: if we are testing with openmpi, add '--oversubscribe' - mpirun -np ${{matrix.mpi}} pytest -v \ + mpirun -np ${{matrix.mpi}} -oversubscribe pytest -v \ --junit-xml=TEST-pyomo-mpi.xml \ -m "mpi" -W ignore::Warning \ pyomo `pwd`/pyomo-model-libraries From 42fd802267b5f4e25606f844a966cceb569cbdd7 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Mar 2024 14:20:57 -0700 Subject: [PATCH 1193/1204] Change macos for coverage upload as well --- .github/workflows/test_branches.yml | 4 ++-- .github/workflows/test_pr_and_main.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 1cb6fd0926d..55f903a37f9 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -709,12 +709,12 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-13, windows-latest] include: - os: ubuntu-latest TARGET: linux - - os: macos-latest + - os: macos-13 TARGET: osx - os: windows-latest TARGET: win diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index e3e08847aa9..76ec6de951a 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -739,12 +739,12 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-13, windows-latest] include: - os: ubuntu-latest TARGET: linux - - os: macos-latest + - os: macos-13 TARGET: osx - os: windows-latest TARGET: win From ada10bd444d93aad724d665cbaf890a8badbd6cd Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 4 Mar 2024 16:31:20 -0700 Subject: [PATCH 1194/1204] only modify module __path__ and __spec__ for deferred import modules --- pyomo/common/dependencies.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 472b0011edb..22d15749879 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -902,10 +902,10 @@ def __exit__(self, exc_type, exc_value, traceback): sys.modules[_global_name + name] = sys.modules[name] while deferred: name, mod = deferred.popitem() - mod.__path__ = None - mod.__spec__ = None - sys.modules[_global_name + name] = mod if isinstance(mod, DeferredImportModule): + mod.__path__ = None + mod.__spec__ = None + sys.modules[_global_name + name] = mod deferred.update( (name + '.' + k, v) for k, v in mod.__dict__.items() From df94ec7786894a38e528d4802b5ff6ecf23ca1dc Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 4 Mar 2024 17:06:08 -0700 Subject: [PATCH 1195/1204] deferred import fix --- pyomo/common/dependencies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index 22d15749879..ea9efe370f7 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -902,10 +902,10 @@ def __exit__(self, exc_type, exc_value, traceback): sys.modules[_global_name + name] = sys.modules[name] while deferred: name, mod = deferred.popitem() + sys.modules[_global_name + name] = mod if isinstance(mod, DeferredImportModule): mod.__path__ = None mod.__spec__ = None - sys.modules[_global_name + name] = mod deferred.update( (name + '.' + k, v) for k, v in mod.__dict__.items() From 8ee01941a433eab987e6fa11ffa74a6b7ea5f2bb Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 5 Mar 2024 12:38:32 -0700 Subject: [PATCH 1196/1204] Add tests for set products --- pyomo/contrib/latex_printer/latex_printer.py | 2 +- .../latex_printer/tests/test_latex_printer.py | 63 +++++++++++++---- pyomo/core/tests/examples/pmedian_concrete.py | 70 +++++++++++++++++++ 3 files changed, 120 insertions(+), 15 deletions(-) create mode 100644 pyomo/core/tests/examples/pmedian_concrete.py diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index c2cbfd6b2e1..1d5279e984a 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -1077,7 +1077,7 @@ def generate_set_name(st, lcm): if st.parent_block().component(st.name) is st: return st.name.replace('_', r'\_') if isinstance(st, SetOperator): - return _set_op_map[st._operator.strip()].join( + return set_operator_map[st._operator.strip()].join( generate_set_name(s, lcm) for s in st.subsets(False) ) else: diff --git a/pyomo/contrib/latex_printer/tests/test_latex_printer.py b/pyomo/contrib/latex_printer/tests/test_latex_printer.py index 2d7dd69dba8..f09a14b8b00 100644 --- a/pyomo/contrib/latex_printer/tests/test_latex_printer.py +++ b/pyomo/contrib/latex_printer/tests/test_latex_printer.py @@ -9,25 +9,16 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -# ___________________________________________________________________________ -# -# Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2023 -# 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 io +from textwrap import dedent + import pyomo.common.unittest as unittest -from pyomo.contrib.latex_printer import latex_printer +import pyomo.core.tests.examples.pmedian_concrete as pmedian_concrete import pyomo.environ as pyo -from textwrap import dedent + +from pyomo.contrib.latex_printer import latex_printer from pyomo.common.tempfiles import TempfileManager from pyomo.common.collections.component_map import ComponentMap - from pyomo.environ import ( Reals, PositiveReals, @@ -797,6 +788,50 @@ def ruleMaker_2(m, i): self.assertEqual('\n' + pstr + '\n', bstr) + def test_latexPrinter_pmedian_verbose(self): + m = pmedian_concrete.create_model() + self.assertEqual( + latex_printer(m).strip(), + r""" +\begin{align} + & \min + & & \sum_{ i \in Locations } \sum_{ j \in Customers } cost_{i,j} serve\_customer\_from\_location_{i,j} & \label{obj:M1_obj} \\ + & \text{s.t.} + & & \sum_{ i \in Locations } serve\_customer\_from\_location_{i,j} = 1 & \qquad \forall j \in Customers \label{con:M1_single_x} \\ + &&& serve\_customer\_from\_location_{i,j} \leq select\_location_{i} & \qquad \forall i,j \in Locations \times Customers \label{con:M1_bound_y} \\ + &&& \sum_{ i \in Locations } select\_location_{i} = P & \label{con:M1_num_facilities} \\ + & \text{w.b.} + & & 0.0 \leq serve\_customer\_from\_location \leq 1.0 & \qquad \in \mathds{R} \label{con:M1_serve_customer_from_location_bound} \\ + &&& select\_location & \qquad \in \left\{ 0 , 1 \right \} \label{con:M1_select_location_bound} +\end{align} + """.strip() + ) + + def test_latexPrinter_pmedian_concise(self): + m = pmedian_concrete.create_model() + lcm = ComponentMap() + lcm[m.Locations] = ['L', ['n']] + lcm[m.Customers] = ['C', ['m']] + lcm[m.cost] = 'd' + lcm[m.serve_customer_from_location] = 'x' + lcm[m.select_location] = 'y' + self.assertEqual( + latex_printer(m, latex_component_map=lcm).strip(), + r""" +\begin{align} + & \min + & & \sum_{ n \in L } \sum_{ m \in C } d_{n,m} x_{n,m} & \label{obj:M1_obj} \\ + & \text{s.t.} + & & \sum_{ n \in L } x_{n,m} = 1 & \qquad \forall m \in C \label{con:M1_single_x} \\ + &&& x_{n,m} \leq y_{n} & \qquad \forall n,m \in L \times C \label{con:M1_bound_y} \\ + &&& \sum_{ n \in L } y_{n} = P & \label{con:M1_num_facilities} \\ + & \text{w.b.} + & & 0.0 \leq x \leq 1.0 & \qquad \in \mathds{R} \label{con:M1_x_bound} \\ + &&& y & \qquad \in \left\{ 0 , 1 \right \} \label{con:M1_y_bound} +\end{align} + """.strip() + ) + if __name__ == '__main__': unittest.main() diff --git a/pyomo/core/tests/examples/pmedian_concrete.py b/pyomo/core/tests/examples/pmedian_concrete.py new file mode 100644 index 00000000000..a6a1859df23 --- /dev/null +++ b/pyomo/core/tests/examples/pmedian_concrete.py @@ -0,0 +1,70 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + +import math +from pyomo.environ import ( + ConcreteModel, + Param, + RangeSet, + Var, + Reals, + Binary, + PositiveIntegers, +) + + +def _cost_rule(model, n, m): + # We will assume costs are an arbitrary function of the indices + return math.sin(n * 2.33333 + m * 7.99999) + + +def create_model(n=3, m=3, p=2): + model = ConcreteModel(name="M1") + + model.N = Param(initialize=n, within=PositiveIntegers) + model.M = Param(initialize=m, within=PositiveIntegers) + model.P = Param(initialize=p, within=RangeSet(1, model.N), mutable=True) + + model.Locations = RangeSet(1, model.N) + model.Customers = RangeSet(1, model.M) + + model.cost = Param( + model.Locations, model.Customers, initialize=_cost_rule, within=Reals + ) + model.serve_customer_from_location = Var( + model.Locations, model.Customers, bounds=(0.0, 1.0) + ) + model.select_location = Var(model.Locations, within=Binary) + + @model.Objective() + def obj(model): + return sum( + model.cost[n, m] * model.serve_customer_from_location[n, m] + for n in model.Locations + for m in model.Customers + ) + + @model.Constraint(model.Customers) + def single_x(model, m): + return ( + sum(model.serve_customer_from_location[n, m] for n in model.Locations) + == 1.0 + ) + + @model.Constraint(model.Locations, model.Customers) + def bound_y(model, n, m): + return model.serve_customer_from_location[n, m] <= model.select_location[n] + + @model.Constraint() + def num_facilities(model): + return sum(model.select_location[n] for n in model.Locations) == model.P + + return model From 30cb7e4b6daa82430eab415ae5cb603cd849ccf1 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 5 Mar 2024 12:49:29 -0700 Subject: [PATCH 1197/1204] NFC: apply black --- pyomo/contrib/latex_printer/tests/test_latex_printer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/latex_printer/tests/test_latex_printer.py b/pyomo/contrib/latex_printer/tests/test_latex_printer.py index f09a14b8b00..b0ada97a5fe 100644 --- a/pyomo/contrib/latex_printer/tests/test_latex_printer.py +++ b/pyomo/contrib/latex_printer/tests/test_latex_printer.py @@ -13,7 +13,7 @@ from textwrap import dedent import pyomo.common.unittest as unittest -import pyomo.core.tests.examples.pmedian_concrete as pmedian_concrete +import pyomo.core.tests.examples.pmedian_concrete as pmedian_concrete import pyomo.environ as pyo from pyomo.contrib.latex_printer import latex_printer @@ -804,7 +804,7 @@ def test_latexPrinter_pmedian_verbose(self): & & 0.0 \leq serve\_customer\_from\_location \leq 1.0 & \qquad \in \mathds{R} \label{con:M1_serve_customer_from_location_bound} \\ &&& select\_location & \qquad \in \left\{ 0 , 1 \right \} \label{con:M1_select_location_bound} \end{align} - """.strip() + """.strip(), ) def test_latexPrinter_pmedian_concise(self): @@ -829,7 +829,7 @@ def test_latexPrinter_pmedian_concise(self): & & 0.0 \leq x \leq 1.0 & \qquad \in \mathds{R} \label{con:M1_x_bound} \\ &&& y & \qquad \in \left\{ 0 , 1 \right \} \label{con:M1_y_bound} \end{align} - """.strip() + """.strip(), ) From 44b7ef2fca90843d807ff818ce36efea78a09713 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 5 Mar 2024 21:32:05 -0700 Subject: [PATCH 1198/1204] Fix raw string escaping --- pyomo/contrib/latex_printer/latex_printer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index 1d5279e984a..0a595dd8e1b 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -1081,7 +1081,7 @@ def generate_set_name(st, lcm): generate_set_name(s, lcm) for s in st.subsets(False) ) else: - return str(st).replace('_', r'\_').replace('{', '\{').replace('}', '\}') + return str(st).replace('_', r'\_').replace('{', r'\{').replace('}', r'\}') # Handling the iterator indices defaultSetLatexNames = ComponentMap() From db4062419b56d810f30c05a96f72cca5eefdfd1a Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Thu, 7 Mar 2024 11:07:40 -0700 Subject: [PATCH 1199/1204] add failing test --- .../solvers/tests/test_persistent_solvers.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index af615d1ed8b..ae189aca701 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -918,6 +918,27 @@ def test_bounds_with_params( res = opt.solve(m) self.assertAlmostEqual(m.y.value, 3) + @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) + def test_bounds_with_immutable_params( + self, name: str, opt_class: Type[PersistentSolver], only_child_vars + ): + # this test is for issue #2574 + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + if not opt.available(): + raise unittest.SkipTest + m = pe.ConcreteModel() + m.p = pe.Param(mutable=False, initialize=1) + m.q = pe.Param([1, 2], mutable=False, initialize=10) + m.y = pe.Var() + m.y.setlb(m.p) + m.y.setub(m.q[1]) + m.obj = pe.Objective(expr=m.y) + res = opt.solve(m) + self.assertAlmostEqual(m.y.value, 1) + m.y.setlb(m.q[2]) + res = opt.solve(m) + self.assertAlmostEqual(m.y.value, 10) + @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_solution_loader( self, name: str, opt_class: Type[PersistentSolver], only_child_vars From b09b3077c10be1431452c877e86593476f267a1b Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Thu, 7 Mar 2024 11:10:05 -0700 Subject: [PATCH 1200/1204] apply patch --- pyomo/contrib/appsi/cmodel/src/expression.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/cmodel/src/expression.cpp b/pyomo/contrib/appsi/cmodel/src/expression.cpp index 234ef47e86f..8079de42b21 100644 --- a/pyomo/contrib/appsi/cmodel/src/expression.cpp +++ b/pyomo/contrib/appsi/cmodel/src/expression.cpp @@ -1548,7 +1548,10 @@ appsi_operator_from_pyomo_expr(py::handle expr, py::handle var_map, break; } case param: { - res = param_map[expr_types.id(expr)].cast>(); + if (expr.attr("parent_component")().attr("mutable").cast()) + res = param_map[expr_types.id(expr)].cast>(); + else + res = std::make_shared(expr.attr("value").cast()); break; } case product: { From 9bbb8871d7407574bf22d25e52f4553d3f9da53e Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 7 Mar 2024 11:23:25 -0700 Subject: [PATCH 1201/1204] Standardize subprocess_timeout import to 2; move to a central location --- pyomo/contrib/appsi/solvers/ipopt.py | 3 ++- pyomo/contrib/solver/ipopt.py | 6 +++--- pyomo/opt/base/__init__.py | 2 ++ pyomo/solvers/plugins/solvers/CONOPT.py | 4 ++-- pyomo/solvers/plugins/solvers/CPLEX.py | 10 ++++++++-- pyomo/solvers/plugins/solvers/GLPK.py | 3 ++- pyomo/solvers/plugins/solvers/IPOPT.py | 4 ++-- pyomo/solvers/plugins/solvers/SCIPAMPL.py | 4 ++-- 8 files changed, 23 insertions(+), 13 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index 29e74f81c98..82f851ce02c 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -42,6 +42,7 @@ import os from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager +from pyomo.opt.base import subprocess_timeout logger = logging.getLogger(__name__) @@ -158,7 +159,7 @@ def available(self): def version(self): results = subprocess.run( [str(self.config.executable), '--version'], - timeout=1, + timeout=subprocess_timeout, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index dc632adb184..8c5e13a534e 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -9,6 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +import logging import os import subprocess import datetime @@ -38,8 +39,7 @@ from pyomo.core.expr.numvalue import value from pyomo.core.base.suffix import Suffix from pyomo.common.collections import ComponentMap - -import logging +from pyomo.opt.base import subprocess_timeout logger = logging.getLogger(__name__) @@ -229,7 +229,7 @@ def version(self, config=None): else: results = subprocess.run( [str(pth), '--version'], - timeout=1, + timeout=subprocess_timeout, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, diff --git a/pyomo/opt/base/__init__.py b/pyomo/opt/base/__init__.py index 8d11114dd09..c625c09d1c0 100644 --- a/pyomo/opt/base/__init__.py +++ b/pyomo/opt/base/__init__.py @@ -22,3 +22,5 @@ from pyomo.opt.base.results import ReaderFactory, AbstractResultsReader from pyomo.opt.base.problem import AbstractProblemWriter, BranchDirection, WriterFactory from pyomo.opt.base.formats import ProblemFormat, ResultsFormat, guess_format + +subprocess_timeout = 2 diff --git a/pyomo/solvers/plugins/solvers/CONOPT.py b/pyomo/solvers/plugins/solvers/CONOPT.py index 89ee3848805..bde68d32c55 100644 --- a/pyomo/solvers/plugins/solvers/CONOPT.py +++ b/pyomo/solvers/plugins/solvers/CONOPT.py @@ -16,7 +16,7 @@ from pyomo.common.collections import Bunch from pyomo.common.tempfiles import TempfileManager -from pyomo.opt.base import ProblemFormat, ResultsFormat +from pyomo.opt.base import ProblemFormat, ResultsFormat, subprocess_timeout from pyomo.opt.base.solvers import _extract_version, SolverFactory from pyomo.opt.results import SolverStatus from pyomo.opt.solver import SystemCallSolver @@ -79,7 +79,7 @@ def _get_version(self): return _extract_version('') results = subprocess.run( [solver_exec], - timeout=1, + timeout=subprocess_timeout, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, diff --git a/pyomo/solvers/plugins/solvers/CPLEX.py b/pyomo/solvers/plugins/solvers/CPLEX.py index b2b8c5e988d..f7a4774b073 100644 --- a/pyomo/solvers/plugins/solvers/CPLEX.py +++ b/pyomo/solvers/plugins/solvers/CPLEX.py @@ -21,7 +21,13 @@ from pyomo.common.tempfiles import TempfileManager from pyomo.common.collections import ComponentMap, Bunch -from pyomo.opt.base import ProblemFormat, ResultsFormat, OptSolver, BranchDirection +from pyomo.opt.base import ( + ProblemFormat, + ResultsFormat, + OptSolver, + BranchDirection, + subprocess_timeout, +) from pyomo.opt.base.solvers import _extract_version, SolverFactory from pyomo.opt.results import ( SolverResults, @@ -404,7 +410,7 @@ def _get_version(self): return _extract_version('') results = subprocess.run( [solver_exec, '-c', 'quit'], - timeout=1, + timeout=subprocess_timeout, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, diff --git a/pyomo/solvers/plugins/solvers/GLPK.py b/pyomo/solvers/plugins/solvers/GLPK.py index 39948d465f4..2e09aae1668 100644 --- a/pyomo/solvers/plugins/solvers/GLPK.py +++ b/pyomo/solvers/plugins/solvers/GLPK.py @@ -29,6 +29,7 @@ SolutionStatus, ProblemSense, ) +from pyomo.opt.base import subprocess_timeout from pyomo.opt.base.solvers import _extract_version from pyomo.opt.solver import SystemCallSolver from pyomo.solvers.mockmip import MockMIP @@ -137,7 +138,7 @@ def _get_version(self, executable=None): [executable, "--version"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - timeout=1, + timeout=subprocess_timeout, universal_newlines=True, ) return _extract_version(result.stdout) diff --git a/pyomo/solvers/plugins/solvers/IPOPT.py b/pyomo/solvers/plugins/solvers/IPOPT.py index deda4314a52..84017a7596e 100644 --- a/pyomo/solvers/plugins/solvers/IPOPT.py +++ b/pyomo/solvers/plugins/solvers/IPOPT.py @@ -16,7 +16,7 @@ from pyomo.common.collections import Bunch from pyomo.common.tempfiles import TempfileManager -from pyomo.opt.base import ProblemFormat, ResultsFormat +from pyomo.opt.base import ProblemFormat, ResultsFormat, subprocess_timeout from pyomo.opt.base.solvers import _extract_version, SolverFactory from pyomo.opt.results import SolverStatus, SolverResults, TerminationCondition from pyomo.opt.solver import SystemCallSolver @@ -79,7 +79,7 @@ def _get_version(self): return _extract_version('') results = subprocess.run( [solver_exec, "-v"], - timeout=1, + timeout=subprocess_timeout, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, diff --git a/pyomo/solvers/plugins/solvers/SCIPAMPL.py b/pyomo/solvers/plugins/solvers/SCIPAMPL.py index be7415a19ef..50191d82e5e 100644 --- a/pyomo/solvers/plugins/solvers/SCIPAMPL.py +++ b/pyomo/solvers/plugins/solvers/SCIPAMPL.py @@ -18,7 +18,7 @@ from pyomo.common.collections import Bunch from pyomo.common.tempfiles import TempfileManager -from pyomo.opt.base import ProblemFormat, ResultsFormat +from pyomo.opt.base import ProblemFormat, ResultsFormat, subprocess_timeout from pyomo.opt.base.solvers import _extract_version, SolverFactory from pyomo.opt.results import ( SolverStatus, @@ -103,7 +103,7 @@ def _get_version(self, solver_exec=None): return _extract_version('') results = subprocess.run( [solver_exec, "--version"], - timeout=1, + timeout=subprocess_timeout, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, From 78431b71f895aa87c875d811b5e05cd933ba4f8a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 7 Mar 2024 12:01:07 -0700 Subject: [PATCH 1202/1204] Change implementation: make private-esque attribute that user can alter --- pyomo/contrib/appsi/solvers/ipopt.py | 4 ++-- pyomo/contrib/solver/ipopt.py | 4 ++-- pyomo/opt/base/__init__.py | 2 -- pyomo/opt/solver/shellcmd.py | 1 + pyomo/solvers/plugins/solvers/CONOPT.py | 4 ++-- pyomo/solvers/plugins/solvers/CPLEX.py | 10 ++-------- pyomo/solvers/plugins/solvers/GLPK.py | 4 ++-- pyomo/solvers/plugins/solvers/IPOPT.py | 4 ++-- pyomo/solvers/plugins/solvers/SCIPAMPL.py | 4 ++-- 9 files changed, 15 insertions(+), 22 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index 82f851ce02c..54e21d333e5 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -42,7 +42,6 @@ import os from pyomo.contrib.appsi.cmodel import cmodel_available from pyomo.core.staleflag import StaleFlagManager -from pyomo.opt.base import subprocess_timeout logger = logging.getLogger(__name__) @@ -148,6 +147,7 @@ def __init__(self, only_child_vars=False): self._primal_sol = ComponentMap() self._reduced_costs = ComponentMap() self._last_results_object: Optional[Results] = None + self._version_timeout = 2 def available(self): if self.config.executable.path() is None: @@ -159,7 +159,7 @@ def available(self): def version(self): results = subprocess.run( [str(self.config.executable), '--version'], - timeout=subprocess_timeout, + timeout=self._version_timeout, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 8c5e13a534e..edc5799ae20 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -39,7 +39,6 @@ from pyomo.core.expr.numvalue import value from pyomo.core.base.suffix import Suffix from pyomo.common.collections import ComponentMap -from pyomo.opt.base import subprocess_timeout logger = logging.getLogger(__name__) @@ -207,6 +206,7 @@ def __init__(self, **kwds): self._writer = NLWriter() self._available_cache = None self._version_cache = None + self._version_timeout = 2 def available(self, config=None): if config is None: @@ -229,7 +229,7 @@ def version(self, config=None): else: results = subprocess.run( [str(pth), '--version'], - timeout=subprocess_timeout, + timeout=self._version_timeout, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, diff --git a/pyomo/opt/base/__init__.py b/pyomo/opt/base/__init__.py index c625c09d1c0..8d11114dd09 100644 --- a/pyomo/opt/base/__init__.py +++ b/pyomo/opt/base/__init__.py @@ -22,5 +22,3 @@ from pyomo.opt.base.results import ReaderFactory, AbstractResultsReader from pyomo.opt.base.problem import AbstractProblemWriter, BranchDirection, WriterFactory from pyomo.opt.base.formats import ProblemFormat, ResultsFormat, guess_format - -subprocess_timeout = 2 diff --git a/pyomo/opt/solver/shellcmd.py b/pyomo/opt/solver/shellcmd.py index 94117779237..baa0369e1d6 100644 --- a/pyomo/opt/solver/shellcmd.py +++ b/pyomo/opt/solver/shellcmd.py @@ -60,6 +60,7 @@ def __init__(self, **kwargs): # a solver plugin may not report execution time. self._last_solve_time = None self._define_signal_handlers = None + self._version_timeout = 2 if executable is not None: self.set_executable(name=executable, validate=validate) diff --git a/pyomo/solvers/plugins/solvers/CONOPT.py b/pyomo/solvers/plugins/solvers/CONOPT.py index bde68d32c55..3455eede67b 100644 --- a/pyomo/solvers/plugins/solvers/CONOPT.py +++ b/pyomo/solvers/plugins/solvers/CONOPT.py @@ -16,7 +16,7 @@ from pyomo.common.collections import Bunch from pyomo.common.tempfiles import TempfileManager -from pyomo.opt.base import ProblemFormat, ResultsFormat, subprocess_timeout +from pyomo.opt.base import ProblemFormat, ResultsFormat from pyomo.opt.base.solvers import _extract_version, SolverFactory from pyomo.opt.results import SolverStatus from pyomo.opt.solver import SystemCallSolver @@ -79,7 +79,7 @@ def _get_version(self): return _extract_version('') results = subprocess.run( [solver_exec], - timeout=subprocess_timeout, + timeout=self._version_timeout, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, diff --git a/pyomo/solvers/plugins/solvers/CPLEX.py b/pyomo/solvers/plugins/solvers/CPLEX.py index f7a4774b073..9f876b2d0f8 100644 --- a/pyomo/solvers/plugins/solvers/CPLEX.py +++ b/pyomo/solvers/plugins/solvers/CPLEX.py @@ -21,13 +21,7 @@ from pyomo.common.tempfiles import TempfileManager from pyomo.common.collections import ComponentMap, Bunch -from pyomo.opt.base import ( - ProblemFormat, - ResultsFormat, - OptSolver, - BranchDirection, - subprocess_timeout, -) +from pyomo.opt.base import ProblemFormat, ResultsFormat, OptSolver, BranchDirection from pyomo.opt.base.solvers import _extract_version, SolverFactory from pyomo.opt.results import ( SolverResults, @@ -410,7 +404,7 @@ def _get_version(self): return _extract_version('') results = subprocess.run( [solver_exec, '-c', 'quit'], - timeout=subprocess_timeout, + timeout=self._version_timeout, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, diff --git a/pyomo/solvers/plugins/solvers/GLPK.py b/pyomo/solvers/plugins/solvers/GLPK.py index 2e09aae1668..e6d8576489d 100644 --- a/pyomo/solvers/plugins/solvers/GLPK.py +++ b/pyomo/solvers/plugins/solvers/GLPK.py @@ -19,6 +19,7 @@ from pyomo.common import Executable from pyomo.common.collections import Bunch +from pyomo.common.errors import ApplicationError from pyomo.opt import ( SolverFactory, OptSolver, @@ -29,7 +30,6 @@ SolutionStatus, ProblemSense, ) -from pyomo.opt.base import subprocess_timeout from pyomo.opt.base.solvers import _extract_version from pyomo.opt.solver import SystemCallSolver from pyomo.solvers.mockmip import MockMIP @@ -138,7 +138,7 @@ def _get_version(self, executable=None): [executable, "--version"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - timeout=subprocess_timeout, + timeout=self._version_timeout, universal_newlines=True, ) return _extract_version(result.stdout) diff --git a/pyomo/solvers/plugins/solvers/IPOPT.py b/pyomo/solvers/plugins/solvers/IPOPT.py index 84017a7596e..4ebbbc07d3b 100644 --- a/pyomo/solvers/plugins/solvers/IPOPT.py +++ b/pyomo/solvers/plugins/solvers/IPOPT.py @@ -16,7 +16,7 @@ from pyomo.common.collections import Bunch from pyomo.common.tempfiles import TempfileManager -from pyomo.opt.base import ProblemFormat, ResultsFormat, subprocess_timeout +from pyomo.opt.base import ProblemFormat, ResultsFormat from pyomo.opt.base.solvers import _extract_version, SolverFactory from pyomo.opt.results import SolverStatus, SolverResults, TerminationCondition from pyomo.opt.solver import SystemCallSolver @@ -79,7 +79,7 @@ def _get_version(self): return _extract_version('') results = subprocess.run( [solver_exec, "-v"], - timeout=subprocess_timeout, + timeout=self._version_timeout, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, diff --git a/pyomo/solvers/plugins/solvers/SCIPAMPL.py b/pyomo/solvers/plugins/solvers/SCIPAMPL.py index 50191d82e5e..fd69954b428 100644 --- a/pyomo/solvers/plugins/solvers/SCIPAMPL.py +++ b/pyomo/solvers/plugins/solvers/SCIPAMPL.py @@ -18,7 +18,7 @@ from pyomo.common.collections import Bunch from pyomo.common.tempfiles import TempfileManager -from pyomo.opt.base import ProblemFormat, ResultsFormat, subprocess_timeout +from pyomo.opt.base import ProblemFormat, ResultsFormat from pyomo.opt.base.solvers import _extract_version, SolverFactory from pyomo.opt.results import ( SolverStatus, @@ -103,7 +103,7 @@ def _get_version(self, solver_exec=None): return _extract_version('') results = subprocess.run( [solver_exec, "--version"], - timeout=subprocess_timeout, + timeout=self._version_timeout, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, From e7a4c948e7e05455f44745c472b9af4f5265edd2 Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Thu, 7 Mar 2024 16:09:42 -0700 Subject: [PATCH 1203/1204] add failing test --- pyomo/contrib/appsi/tests/test_fbbt.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pyomo/contrib/appsi/tests/test_fbbt.py b/pyomo/contrib/appsi/tests/test_fbbt.py index a3f520e7bd6..97af611c572 100644 --- a/pyomo/contrib/appsi/tests/test_fbbt.py +++ b/pyomo/contrib/appsi/tests/test_fbbt.py @@ -151,3 +151,16 @@ def test_named_exprs(self): for x in m.x.values(): self.assertAlmostEqual(x.lb, 0) self.assertAlmostEqual(x.ub, 0) + + def test_named_exprs_nest(self): + # test for issue #3184 + m = pe.ConcreteModel() + m.x = pe.Var() + m.e = pe.Expression(expr=m.x + 1) + m.f = pe.Expression(expr=m.e) + m.c = pe.Constraint(expr=(0, m.f, 0)) + it = appsi.fbbt.IntervalTightener() + it.perform_fbbt(m) + for x in m.x.values(): + self.assertAlmostEqual(x.lb, -1) + self.assertAlmostEqual(x.ub, -1) From cd8c6ae6a9e30c4ce8f998252a8b82e4b80bfc93 Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Thu, 7 Mar 2024 16:12:08 -0700 Subject: [PATCH 1204/1204] apply patch --- pyomo/contrib/appsi/cmodel/src/expression.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/cmodel/src/expression.cpp b/pyomo/contrib/appsi/cmodel/src/expression.cpp index 234ef47e86f..f1446c6a21b 100644 --- a/pyomo/contrib/appsi/cmodel/src/expression.cpp +++ b/pyomo/contrib/appsi/cmodel/src/expression.cpp @@ -1789,7 +1789,8 @@ int build_expression_tree(py::handle pyomo_expr, if (expr_types.expr_type_map[py::type::of(pyomo_expr)].cast() == named_expr) - pyomo_expr = pyomo_expr.attr("expr"); + return build_expression_tree(pyomo_expr.attr("expr"), appsi_expr, var_map, + param_map, expr_types); if (appsi_expr->is_leaf()) { ;